Certified Kubernetes Administrator (CKA) #5 HA クラスター: 複数 control plane、外部 etcd cluster
#4 kubeadm クラスターのインストール で単一 control plane クラスターをブートストラップしました。そのクラスターには弱点が 1 つあります。control plane ノードが 1 台しかないため、そのノードが死ぬと apiserver も etcd も一緒に消えてしまいます。ワーカーノードの Pod はしばらく動き続けますが、新しいデプロイもスケーリングも復旧も止まります。今回の記事では、この単一障害点 (single point of failure) をなくす 高可用性 (High Availability, HA) クラスター を扱います。
HA クラスターの核心は control plane を複数台に増やすことです。ただし control plane は apiserver だけでなく etcd という状態ストアも一緒に持つので、etcd を control plane ノードの中に置くか外に置くかから決める必要があります。この選択が stacked etcd と external etcd という 2 つのトポロジーを分けます。ここに apiserver の前段ロードバランサーと etcd クォーラムという 2 つの概念が加わると、HA クラスターの全体像が完成します。
単一 control plane の単一障害点 #
#2 で見たように control plane は apiserver、etcd、scheduler、controller-manager で構成されます。単一ノードクラスターでは、この 4 つが 1 つのノードの上に static Pod として立っています。そのノードが止まると何が起こるかを整理します。
- apiserver 停止。
kubectlがクラスターに届かなくなります。新しいデプロイ、スケーリング、削除がすべて塞がれます。 - etcd 停止。クラスターのすべての状態が 1 つのノードだけにあったので、そのノードのディスクが壊れるとクラスター状態そのものを失います。
- scheduler 停止。新しい Pod がノードに配置されません。Pending 状態で積み上がります。
- controller-manager 停止。reconciliation loop が止まり、Deployment の望ましい状態を保てなくなります。
すでに立っていたワーカーノードの Pod は kubelet がローカルで動かし続けるので、当面は生きています。しかしクラスターを 運用 する手段が消えた状態なので、事実上の運用停止です。HA はこの control plane を複数台に複製し、1 台が死んでも残りがクラスターを運用し続けるようにします。
2 つのトポロジー: stacked etcd と external etcd #
control plane を複数台に増やすとき、まず決めるべきは etcd をどこに置くか です。kubeadm は 2 つのトポロジーをサポートします。
stacked etcd トポロジー #
各 control plane ノードの中に etcd メンバーを一緒に乗せる方式です。control plane ノード 3 台を立てると、その中で etcd も 3 メンバークラスターを構成します。apiserver は同じノードのローカル etcd メンバーと通信します。
[control plane 1] apiserver + etcd
[control plane 2] apiserver + etcd
[control plane 3] apiserver + etcd
|
(前段ロードバランサー)
|
[ワーカーノード]この方式の利点は ノード数が少なく構成が単純 なことです。control plane ノードを増やすだけで control plane と etcd が同時に HA になります。kubeadm のデフォルトトポロジーでもあります。欠点は control plane と etcd の運命が 結び付く ことです。1 つのノードが死ぬと、そのノードの apiserver と etcd メンバーが一緒に消えます。
external etcd トポロジー #
etcd を control plane ノードの外に、別の etcd クラスター として分離する方式です。control plane ノードは apiserver/scheduler/controller-manager だけを乗せ、etcd は別に置いた 3 台 (あるいは 5 台) のノードが担当します。
[control plane 1] apiserver [etcd 1]
[control plane 2] apiserver [etcd 2]
[control plane 3] apiserver [etcd 3]
| |
(前段ロードバランサー) (apiserver が外部 etcd へ接続)
|
[ワーカーノード]この方式の利点は control plane と etcd の 障害が分離される ことです。control plane ノードが死んでも etcd クラスターは無事で、その逆も成り立ちます。欠点は ノードがより多く必要で構成が複雑 なことです。etcd クラスターを別に立て、証明書で control plane と接続する追加作業が伴います。
トレードオフの整理 #
| 項目 | stacked etcd | external etcd |
|---|---|---|
| ノード数 | 少ない (control plane = etcd) | 多い (control plane と etcd を分離) |
| 構成の複雑さ | 低い (kubeadm デフォルト) | 高い (etcd を別途構築) |
| 障害分離 | 弱い (1 ノードに apiserver+etcd) | 強い (control plane と etcd を分離) |
| ノード喪失の影響 | apiserver と etcd メンバーを同時喪失 | apiserver のみ喪失、etcd は無関係 |
| 推奨される状況 | 小規模、素早い構築 | 大規模、高い信頼性が求められる |
CKA の観点では、stacked がデフォルトで単純、external は障害分離が強い代わりに複雑 というトレードオフを理解することが核心です。実際の試験で両方を最初から最後まで構築せよという問題が出るより、この違いと etcd メンバーシップを理解しているかを見ます。
前段ロードバランサー #
control plane を複数台に増やすと、すぐに付いてくる疑問があります。ワーカーノードと kubectl は どの apiserver に接続すべきか です。apiserver が 3 つあるのに、そのうち 1 つの IP を直接書いておくと、そのノードが死んだ瞬間にまた単一障害点になります。
そこで HA クラスターは複数の apiserver の 前段にロードバランサー (LB) を置きます。ワーカーノードとクライアントは LB の単一アドレス (仮想 IP またはドメイン) に接続し、LB が生きている apiserver にリクエストを分配します。1 つの apiserver が死ぬと、LB はそのノードを外して残りに送ります。
- HAProxy。apiserver の前に置く L4 ロードバランサーとしてよく使われます。6443 ポートに入ってきたリクエストを各 control plane の 6443 に分配します。
- keepalived。VRRP で仮想 IP (VIP) をノードの間で浮かせ、LB 自体の単一障害点も減らします。HAProxy とよく組み合わされます。
- クラウドロードバランサー。AWS NLB、GCP TCP LB のようにクラウドが提供するマネージド L4 LB も同じ役割を果たします。
--control-plane-endpoint で init
#
この LB アドレスをクラスターに 固定された control plane エンドポイント として打ち込むのが核心です。最初の control plane をブートストラップするとき --control-plane-endpoint で LB アドレスを指定します。
kubeadm init \
--control-plane-endpoint "LOAD_BALANCER_DNS:6443" \
--upload-certs \
--pod-network-cidr=10.244.0.0/16--control-plane-endpoint を LB の DNS や VIP に指定すると、以降に生成される kubeconfig とクラスター内部設定がすべてこの単一エンドポイントを見ます。そのため control plane をさらに追加してもクライアント設定を変える必要がありません。逆に単一ノードで kubeadm init をした後に control plane を追加しようとすると、エンドポイントが 1 つのノードの IP で打ち込まれているため HA 転換が難しくなります。HA を念頭に置くなら最初から --control-plane-endpoint を指定する必要があります。
--upload-certs は control plane 用の証明書をクラスターに暗号化してアップロードし、後から合流する control plane ノードがその証明書をダウンロードして使えるようにするオプションです。次の節のジョインで使います。
etcd クォーラムと耐障害性 #
HA において control plane と同じく重要なのが etcd の動作方式です。etcd は分散キー・バリューストアで、複数のメンバーが同じデータを複製して持ちます。ただしメンバー同士でデータが食い違わないように、過半数 (majority) のメンバーが同意しないと書き込みを確定しません。この過半数を クォーラム (quorum) といいます。
クォーラムは (N / 2) + 1 で計算します。メンバーが N 個のとき、その過半数が生きていればクラスターが書き込みを受け付けます。生きているメンバーが過半数に届かないと、etcd はデータ一貫性を守るために書き込みを拒否し、その上の apiserver も事実上止まります。
| メンバー数 (N) | クォーラム | 耐えられる障害数 |
|---|---|---|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
表のとおりメンバーを 奇数 にするのが効率的です。メンバーが 2 個だとクォーラムが 2 なので 1 台死んだだけでクラスターが止まります。1 個のときより耐障害性が増えないままコストだけ増えます。3 個なら 1 台が死んでも残りの 2 個が過半数を成して正常に運用されます。4 個は 3 個と同じく 1 台しか耐えないので、1 台多く使っても得がありません。そのため etcd は 3 個または 5 個 のように奇数で構成するのが定石です。ほとんどのクラスターには 3 メンバーで十分で、より高い耐障害性が必要なら 5 メンバーを使います。
control plane ノードの追加ジョイン #
最初の control plane を LB エンドポイントでブートストラップしたら、2 番目と 3 番目の control plane を合流させます。ワーカーノードを合流させるときに使った kubeadm join に --control-plane フラグと証明書キー を加えます。
kubeadm join LOAD_BALANCER_DNS:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key>各引数の役割を整理すると次のとおりです。
LOAD_BALANCER_DNS:6443。合流先は特定の control plane ではなく LB エンドポイントです。このアドレスでクラスターに入ります。--token/--discovery-token-ca-cert-hash。ワーカージョインと同じ認証情報で、合流するノードが正しいクラスターかどうかを確認します。--control-plane。このノードをワーカーではなく control plane として合流 させる印です。apiserver/scheduler/controller-manager (そして stacked なら etcd メンバー) がこのノードに立ち上がります。--certificate-key。先に--upload-certsでアップロードしておいた証明書をダウンロードして復号するキーです。このキーがあって初めて control plane 用の証明書を共有してもらって合流できます。
ジョインコマンドに必要なトークンと証明書キーは最初のノードで再取得できます。
# control plane ジョイン用の証明書キーを再アップロード
kubeadm init phase upload-certs --upload-certs
# ジョインコマンド全体を出力 (control plane 用)
kubeadm token create --print-join-command証明書キーはセキュリティ上、一定時間が過ぎると失効するので、新しい control plane を合流させるときに上のコマンドで再発行するケースが多いです。
検証: ノードと etcd メンバーシップの確認 #
control plane をすべて合流させたら 2 つを確認します。ノード一覧に control plane ロールが複数ある か、そして etcd メンバーがその数だけある かです。
control plane ノードの確認 #
k get nodesNAME STATUS ROLES AGE VERSION
cp-1 Ready control-plane 30m v1.31.0
cp-2 Ready control-plane 12m v1.31.0
cp-3 Ready control-plane 8m v1.31.0
worker-1 Ready <none> 25m v1.31.0
worker-2 Ready <none> 25m v1.31.0ROLES 列に control-plane が 3 つ見えれば control plane HA が構成されています。すべて Ready かどうかも一緒に確認します。
etcd メンバーの確認 #
stacked トポロジーなら etcd メンバーは control plane ノードの中の static Pod として立っています。etcdctl member list でメンバー数と状態を確認します。etcd は mTLS で保護されているので、証明書のパスを一緒に渡す必要があります。
ETCDCTL_API=3 etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key<id1>, started, cp-1, https://10.0.0.11:2380, https://10.0.0.11:2379, false
<id2>, started, cp-2, https://10.0.0.12:2380, https://10.0.0.12:2379, false
<id3>, started, cp-3, https://10.0.0.13:2380, https://10.0.0.13:2379, falseメンバーが 3 個ですべて started なら etcd クラスターが正常にクォーラムを成しています。メンバー状態をより詳しく見るには endpoint health と endpoint status も役立ちます。
ETCDCTL_API=3 etcdctl endpoint health \
--cluster \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.keyこれらのコマンドは #7 etcd バックアップとリストア と #24 Control plane トラブルシューティング で再び使うので、証明書のパスと一緒に手に馴染ませておくとよいです。
試験の観点 #
CKA 実技で HA クラスターを最初から最後まで構築せよという問題はまれです。2 時間以内に LB を立てて control plane 3 台をジョインするのは時間がかかりすぎるためです。試験がより頻繁に問うのは 概念と etcd メンバーシップを理解しているか です。
- 単一 control plane の単一障害点が何で、HA がそれをどうなくすか
- stacked etcd と external etcd の違いとトレードオフ
- なぜ etcd メンバーを奇数にするか、3 個のとき耐えられる障害が何個か
--control-plane-endpointを最初から指定すべき理由etcdctl member listでメンバーを確認し、メンバーを追加/削除する流れ
特に etcd メンバーを etcdctl member add / etcdctl member remove で扱う作業は #7 と #24 で再び出会うので、今回の記事のメンバーシップ概念をその土台にします。
試験ポイント #
- HA の目的は control plane の単一障害点の除去 です。control plane を複数台に複製し、1 台が死んでもクラスターを運用し続けます。
- stacked etcd は control plane ノードの中に etcd を一緒に置く kubeadm デフォルトトポロジーで、単純ですが障害分離が弱いです。
- external etcd は etcd を別クラスターに分離して障害を分離しますが、ノードがより多く必要で構成が複雑です。
- 前段ロードバランサー は複数の apiserver を単一エンドポイントに束ねます。HAProxy/keepalived またはクラウド LB を使います。
--control-plane-endpointは LB アドレスをクラスターの固定エンドポイントとして打ち込みます。HA なら最初のkubeadm initから指定します。- etcd クォーラムは
(N/2)+1です。メンバーを奇数 (3 または 5) にするのが定石で、3 メンバーは 1 台、5 メンバーは 2 台の障害まで耐えます。 - control plane ジョイン は
kubeadm joinに--control-planeと--certificate-keyを加えます。証明書は--upload-certsで共有します。 - 検証 は
k get nodesでcontrol-planeロールが複数あるか、etcdctl member listで etcd メンバーがすべてstartedかを確認します。
まとめ #
今回の記事で押さえたことを整理します。
- 単一 control plane はそのノードが死ぬとクラスター運用が止まる単一障害点です。HA は control plane を複数台に複製してこれをなくします。
- HA トポロジーは 2 つです。control plane の中に etcd を置く stacked etcd と、etcd を別クラスターに分離する external etcd です。単純さと障害分離の間のトレードオフで選びます。
- 複数の apiserver の前にロードバランサーを置き、
--control-plane-endpointでそのアドレスをクラスターの固定エンドポイントに指定します。 - etcd はクォーラム (過半数) で一貫性を守るのでメンバーを奇数にします。3 メンバーは 1 台の障害に耐えます。
- control plane ノードは
kubeadm join --control-planeに証明書キーを加えて合流させ、k get nodesとetcdctl member listで検証します。
次へ: クラスターアップグレード #
HA で control plane を複数台に増やしたので、次はこのクラスターを バージョンアップグレード する番です。複数の control plane があるとき、アップグレードは 1 台ずつ順番に進めてこそ無停止で終わります。
#6 クラスターアップグレード では、kubeadm upgrade plan でアップグレード経路を確認し、kubeadm upgrade apply で control plane をマイナーバージョン 1 つ上げた後、ノード別に kubectl drain と kubectl uncordon を使ってワークロードを空けてアップグレードする流れを整理します。