<p>When I add node in master of etcd cluster using this command:</p>

<pre class="lang-sh prettyprint-override"><code>curl http://127.0.0.1:2379/v3beta/members \
-XPOST -H "Content-Type: application/json" \
-d '{"peerURLs": ["http://172.19.104.230:2380"]}'
</code></pre>

<p>It shows <code>{"error":"etcdserver: unhealthy cluster","code":14}</code>. </p>

<p>And I check the cluster status:</p>

<pre><code>[root@iZuf63refzweg1d9dh94t8Z ~]# etcdctl member list
55a782166ce91d01, started, infra3, https://172.19.150.82:2380, https://172.19.150.82:2379
696a771758a889c4, started, infra1, https://172.19.104.231:2380, https://172.19.104.231:2379
</code></pre>

<p>It is fine. What should I do to make it works?</p>

Dolphin
<p>According to <a href="https://github.com/etcd-io/etcd/blob/master/etcdserver/errors.go" rel="nofollow noreferrer"><code>etcd</code> source code</a>, it <a href="https://github.com/etcd-io/etcd/blob/c55410ccea53bf613f10f6376ef870d3c81e2eec/etcdserver/server.go#L1474" rel="nofollow noreferrer">returns</a> <code>ErrUnhealthy</code> error code if <a href="https://github.com/etcd-io/etcd/blob/9c5426830b1b8728af14e069acfdc2a64dea768c/etcdserver/util.go#L65" rel="nofollow noreferrer"><code>longestConnected</code></a> method failed.</p>

<pre class="lang-golang prettyprint-override"><code>// longestConnected chooses the member with longest active-since-time.
// It returns false, if nothing is active.
func longestConnected(tp rafthttp.Transporter, membs []types.ID) (types.ID, bool) {
    var longest types.ID
    var oldest time.Time
    for _, id := range membs {
        tm := tp.ActiveSince(id)
        if tm.IsZero() { // inactive
            continue
        }

        if oldest.IsZero() { // first longest candidate
            oldest = tm
            longest = id
        }

        if tm.Before(oldest) {
            oldest = tm
            longest = id
        }
    }
    if uint64(longest) == 0 {
        return longest, false
    }
    return longest, true
}
</code></pre>

<p>So, <code>ectd</code> can't find appropriate member to connect.</p>

<p>Cluster's method <a href="https://github.com/etcd-io/etcd/blob/77e1c37787fb08d8faf29ecc4a3114f62e2fff68/etcdserver/api/membership/cluster.go#L824" rel="nofollow noreferrer"><code>VotingMemberIDs</code></a> returns list of <em>voting</em> members:</p>

<pre><code>transferee, ok := longestConnected(s.r.transport, s.cluster.VotingMemberIDs())
if !ok {
    return ErrUnhealthy
}
</code></pre>

<pre class="lang-golang prettyprint-override"><code>// VotingMemberIDs returns the ID of voting members in cluster.
func (c *RaftCluster) VotingMemberIDs() []types.ID {
    c.Lock()
    defer c.Unlock()
    var ids []types.ID
    for _, m := range c.members {
        if !m.IsLearner {
            ids = append(ids, m.ID)
        }
    }
    sort.Sort(types.IDSlice(ids))
    return ids
}
</code></pre>

<p>As we can see from you report, there <em>are</em> members in your cluster. </p>

<blockquote>
  <pre class="lang-sh prettyprint-override"><code>$ etcdctl member list
&gt; 55a782166ce91d01, started, infra3, https://172.19.150.82:2380, https://172.19.150.82:2379
&gt; 696a771758a889c4, started, infra1, https://172.19.104.231:2380, https://172.19.104.231:2379
</code></pre>
</blockquote>

<p>So we should check members - are they voiting members, not <a href="https://github.com/etcd-io/etcd/issues/10537" rel="nofollow noreferrer"><code>learners</code></a>, see <a href="https://etcd.io/docs/v3.3.12/learning/learner/" rel="nofollow noreferrer">etcd docs | Learner</a></p>

<p><img src="https://etcd.io/img/server-learner-figure-10.png" alt="Raft learner"></p>

<pre class="lang-golang prettyprint-override"><code>// RaftAttributes represents the raft related attributes of an etcd member.
type RaftAttributes struct {
    // PeerURLs is the list of peers in the raft cluster.
    // TODO(philips): ensure these are URLs
    PeerURLs []string `json:"peerURLs"`
    // IsLearner indicates if the member is raft learner.
    IsLearner bool `json:"isLearner,omitempty"`
}
</code></pre>

<p>So, try to increase members count to provide a <a href="https://etcd.io/docs/v3.4.0/learning/design-learner/" rel="nofollow noreferrer">quorum</a>
<img src="https://etcd.io/img/server-learner-figure-02.png" alt="etcd quorum"></p>

<p>To force creating members <a href="https://github.com/etcd-io/etcd/issues/7171#issuecomment-404736553" rel="nofollow noreferrer">try this</a> <code>ETCD_FORCE_NEW_CLUSTER=“true"</code></p>

<h1>Quorum</h1>

<p>See also this post: <a href="https://learn.microsoft.com/en-us/windows-server/storage/storage-spaces/understand-quorum" rel="nofollow noreferrer">Understanding cluster and pool quorum</a></p>
