Skip to content

1. Stranded etcd member#

Symptom. VerifyEtcdState reports the verdict hazard: EtcdMember CR missing (VerdictHazardCRMissing) — etcd counts a member that no EtcdMember CR owns. The same section applies when a clean host in scope of an add already has its peer address in the raw member list, and when VerifyEtcdState (after a removal) reports a target still present in the raw member list.

What it means. etcd counts a member toward quorum that nothing owns: a replaced or reset host, a join that never finished, or a leave that did not complete. The quorum bar is raised by a member that can never vote.

This symptom covers two different situations with opposite repairs, and the phase does not tell them apart — you have to. The two signals that would distinguish them are an empty etcd member name and empty clientURLs. mkectl does fetch both — it reads etcd's own member/list API rather than k0s etcd member-list — but its verdict logic keys only on "is there a raw member" and "is there a CR", so the finding is identical in both situations. The k0s etcd member-list output you run by hand does not carry clientURLs at all. Classify by hand, on the host that owns the peer address:

sudo ls <dataDir>/etcd/member/snap/db
result situation repair
does not exist the member was added to etcd but its etcd never started — nothing to lose k0s etcd leave below is safe
exists the host is a real member whose etcd has data; only its EtcdMember CR is missing (its k0s could not reach the kube-apiserver when it tried to register) do not k0s etcd leave — go to the recovery paragraph below instead

If the host is unreachable or already gone (replaced, reimaged, wiped), it holds no data by definition and the first row applies.

Repair — first row only. From a healthy controller:

sudo k0s etcd leave --peer-address <peer-address>
sudo k0s etcd member-list --data-dir=<dataDir>   # confirm the address is gone

This is destructive under the second row. k0s skips the join entirely when <dataDir>/etcd/member/snap/db exists, so removing the member of a host that still holds etcd data strands that host permanently — it will never rejoin, and its data is not recoverable through the normal flow. Re-check before running it, not after.

Repair — second row. The host is a live member; the missing CR is the only defect, and it is self-healing. Restart k0s on that host once its kube-apiserver access is working (sudo systemctl restart k0scontroller); EtcdMemberReconciler recreates the CR as soon as its local etcd answers and the API is reachable. If the host is unrecoverable, treat it as a removal — take a backup, then mkectl node remove --scope=controller+worker — rather than leaving it.

Then re-run the original mkectl node add / mkectl node remove. Both are idempotent.