本記事には広告(アフィリエイトリンク)が含まれます。

kubeadm HA設計+HAProxy LB【CKA第3回】

広告

新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第3回です。動作確認バージョン: AlmaLinux 10.1 / HAProxy 3.0.5 / K8s v1.35.5(2026-05-22 時点・k8s-lb / k8s-cp-01 実機検証済)。第2回では k8s-cp-01 に kubeadm v1.35 をインストールし、シングルノードクラスタを起動しました。k8s-lb には最小限の HAProxy パススルー設定(CP-01 のみ)を入れました。第3回では、その「最小」設定を HA 本格設計に作り込みます。CP×3 を backend に登録し、stats ページとヘルスチェックを有効にします。あわせて HA クラスタの根幹となる etcd quorum と Raft コンセンサスアルゴリズムを学びます。第3回が終わると、第4回の kubeadm join(CP-02/03 + WL-01/02 参加)に向けた準備が整います。

広告

今ここマップ(第3回 / 全16回 / 第1部)

今ここマップ(第2巻 16 回中の現在位置):

今ここ: 第3回 / 全16回(第1部:クラスタ構築)
▓▓▓░░░░░░░░░░░░░  19%

第1部(クラスタ構築):       ■■■□□ 3/5 回(進行中)
第2部(ワークロード管理):   □□□   0/3 回
第3部(ネットワーク):       □□□   0/3 回
第4部(ストレージ):         □     0/1 回
第5部(監視・運用):         □□    0/2 回
第6部(トラブルシュート):   □□    0/2 回

第3回で学ぶことは次の 5 点です。これがそのまま今回の学習目標になります。

  • kubeadm HA クラスタのトポロジ(stacked etcd)と controlPlaneEndpoint の役割を理解する
  • etcd quorum と Raft の仕組みを理解し、N ノードの quorum 計算・障害耐性・偶数ノードが非推奨な理由を説明できる
  • k8s-lb の haproxy.cfg を HA 本格設計版(CP×3 backend・stats・tcp-check・roundrobin)に更新し、haproxy -c で検証後に reload できる
  • HAProxy stats ページ(http://192.168.1.124:9000/stats)で CP-01 UP / CP-02/03 DOWN の状態を確認し、第4回 join 後に UP になる理由を説明できる
  • 第4回の kubeadm join --control-plane に向けて、kubeadm-config.yaml の controlPlaneEndpoint 設定と --upload-certs の有効期限を理解する

第3回終了時の達成状態を整理します。第4回の HA クラスタ構築の前提になる状態です。

  • k8s-lb の haproxy.cfg が HA 本格設計版に更新されている(CP×3 backend・stats・tcp-check)
  • HAProxy stats ページ(http://192.168.1.124:9000/stats)で CP-01 UP / CP-02/03 DOWN の状態を確認済み
  • etcd quorum の計算式(⌊N/2⌋ + 1)と偶数ノード非推奨の理由を説明できる
  • 第4回の kubeadm join --control-plane に向けた kubeadm-config.yaml の設計を把握している

第3回のスコープ — 第2回「最小パススルー」から HA 設計へ

具体的な設定に入る前に、第2回・第3回・第4回の役割分担を整理します。今回「何をどこまでやるか」を把握してから手を動かすと、作業の目的が明確になります。

第2回〜第4回の役割分担

第2回から第4回は「HA クラスタを作り上げる 3 回連続のシリーズ」です。第2回でシングル CP クラスタを起動し、第3回で LB を HA 設計に育て、第4回で全ノードを参加させて HA クラスタを完成させます。3 回を通じた役割分担を次の表に整理します。

k8s-lb での作業k8s-cp での作業この回が終わると
第2回(完了)最小パススルー(:6443 → k8s-cp-01 のみ)k8s-cp-01: kubeadm init + Calico CNIシングルノードクラスタ稼働
第3回(本回)HA 本格設計(CP×3 backend・stats・tcp-check)haproxy.cfg 更新のみ(CP-02/03 は未 join)第4回 join の準備完了
第4回(次回)stats ページで全 backend UP 確認k8s-cp-02/03: kubeadm join –control-plane / k8s-wl-01/02: kubeadm join5 ノード HA クラスタ完成

第3回で「やること」と「やらないこと」

設計解説と実機作業が混在する回のため、スコープを明確にします。

やることやらないこと
haproxy.cfg を HA 版に書き換える(CP×3 backend・stats 追加)k8s-cp-02/03 の kubeadm join(→ 第4回)
stats ページで UP/DOWN 状態を確認するk8s-wl-01/02 の構築(→ 第4回)
etcd quorum の計算・Raft の仕組みを学ぶCalico CNI の追加設定(→ 第4回)
kubeadm-config.yaml の設計を再確認するgateway-https frontend の設定(→ 第11回)

第3回終了時点での各 VM の状態

第3回が終わったとき、各 VM がどういう状態になっているかを先に確認します。この表が「ゴール」です。

VM第3回終了時の状態
k8s-lb(192.168.1.124)HAProxy HA 設計版 haproxy.cfg 稼働中
k8s-cp-01(192.168.1.125)稼働中(kubeadm init 済み・シングル CP)
k8s-cp-02(192.168.1.126)VM 稼働中だが未 join(HAProxy stats で DOWN 表示)
k8s-cp-03(192.168.1.127)VM 稼働中だが未 join(HAProxy stats で DOWN 表示)
k8s-wl-01/02VM 稼働中だが未 join(第4回で join)

CP-02/03 が HAProxy stats で DOWN 表示になるのは想定どおりです。haproxy.cfg に backend として記載されていますが、6443 ポートを LISTEN しているプロセス(kube-apiserver)が存在しないため、tcp-check が失敗して DOWN になります。第4回で kubeadm join --control-plane を実行すると kube-apiserver が起動し、stats で UP に変わります。DOWN 表示を見て「設定が間違っている」と焦る必要はありません。

なぜ HA が必要か — SPOF の脅威と可用性の考え方

HAProxy の設定を変える前に、「なぜ HA が必要なのか」を理解します。HA(High Availability・高可用性)構成の必要性は、SPOF という概念から説明できます。

SPOF とは何か

SPOF(Single Point of Failure)は「単一障害点」と訳します。そのコンポーネントが停止すると、それだけでシステム全体が停止するポイントのことです。

第2回が終わった時点のクラスタは、k8s-cp-01 が SPOF です。k8s-cp-01 が OS 障害・ハードウェア障害・メンテナンス停止によって停止すると、クラスタ全体が停止します。k8s-lb が 6443 に受け取った接続を k8s-cp-01 の 6443 にしか転送しないからです。

kube-apiserver が停止すると何が起きるか

kube-apiserver は Kubernetes クラスタの中枢です。apiserver が停止すると次のことが起きます。これは CKA 試験で問われる頻出の挙動です。

  • kubectl コマンドがすべて失敗する(kubectl は API Server 経由でクラスタを操作するため)
  • 新規 Pod のスケジューリングが停止する(kube-scheduler が API Server に依存するため)
  • 既存 Pod・コンテナは引き続き動作する(kubelet は API Server なしでも既存コンテナを維持する)
  • etcd への書き込みが停止する(kube-apiserver が唯一の etcd クライアント)

特に「既存 Pod は止まらない」という点は試験でよく問われます。API Server が落ちてもすでに動いているアプリは止まりません。止まるのは「新しい操作・新しい Pod の作成」です。本番環境で API Server 障害が発生したとき、「アプリは動いているのに kubectl が通らない」という状況がこれです。

HA 構成で SPOF を解消する

HA 構成では Control Plane Node を 3 台に増やし、HAProxy がラウンドロビンで接続を振り分けます。1 台の CP ノードが停止しても残り 2 台でクラスタが継続動作します。

シングル CP と HA CP の比較 — 単一障害点(SPOF)の解消
シングル CP と HA CP の比較 — 単一障害点(SPOF)の解消

可用性の定量化 — 99.9% と 99.99% の違い

HA 構成が「必要かどうか」を判断する基準の一つが可用性の目標値(SLA/SLO)です。可用性は年間の稼働率として表現されます。

可用性許容停止時間(年間)シングル CP の典型的リスク
99%87.6 時間
99.9%8.76 時間単一 CP でも頑張れる上限
99.99%52.6 分HA 構成が事実上必須
99.999%5.26 分HA + 追加自動化が必要

シングル CP クラスタで年間 8 時間以内の停止に収めることは難しくありません。しかし 99.99%(年間 52 分以内)を目標とするサービスでは、CP ノードの計画メンテナンスだけでも停止時間が積み上がります。HA 構成は「高可用性の第一歩」であり、「完全無停止」を保証するものではありません。CP が 3 台になっても、同時に 2 台が障害を起こせばクラスタは停止します。HA はリスクを下げる構成であり、ゼロリスクではないことを理解しておきます。

Control Plane HA アーキテクチャ(stacked etcd・CP×3)

HA クラスタの全体像を理解します。kubeadm HA には 2 つのトポロジがあります。本シリーズで採用する stacked etcd と、対比として知っておくべき external etcd です。

2 つのトポロジ比較

トポロジ構成最小ノード数本シリーズの扱い
stacked etcd(本シリーズ採用)etcd が各 CP ノード上で同居3 CP ノード第3〜4回で構築
external etcdCP ノードと etcd ノードを分離3 CP + 3 etcd = 6 ノード解説のみ(本シリーズ対象外)

stacked etcd を選ぶ理由は 3 つあります。セットアップが簡素・ノード数が少ない・CKA 試験で問われる標準的なトポロジである、という点です。external etcd は etcd クラスタを独立してスケールしたい大規模環境向けです。etcd の書き込みが多く CP ノードとリソースを分離したい場合に有効ですが、必要なノード数が倍増します。本シリーズの学習環境(9 VM 構成)では stacked etcd が現実的です。

Stacked etcd の構成図

stacked etcd では etcd が各 CP ノードに「同居(stacked)」します。以下の構成図で関係を確認します。

Stacked etcd HA 構成 — HAProxy ラウンドロビンと CP×3 同居 etcd の Raft 同期
Stacked etcd HA 構成 — HAProxy ラウンドロビンと CP×3 同居 etcd の Raft 同期

各 CP ノードには 4 つの Control Plane コンポーネントが Static Pod として稼働します。Static Pod は /etc/kubernetes/manifests/ に配置された YAML ファイルを kubelet が直接起動・管理するものです。etcd もこの仕組みで動作します。kubeadm はこれらの Static Pod マニフェストを自動生成します。

controlPlaneEndpoint の役割(第2回設定済み・再確認)

controlPlaneEndpoint は、クラスタの全コンポーネントが API Server に接続する際の「共通エントリポイント」です。本シリーズでは k8s-lb:6443 を指定しています。

この設定があることで、kubelet・kube-controller-manager・kube-scheduler・kubeadm join のいずれも k8s-lb:6443 を経由して API Server に接続します。HAProxy がラウンドロビンで CP ノードに転送するため、どの CP ノードが応答しても同じ結果が得られます。

変更時のコストを覚えておきます。controlPlaneEndpoint を init 後に変更すると、API Server の TLS 証明書に含まれる SAN(Subject Alternative Name)の再生成が必要になります。これは本番稼働中クラスタでは大掛かりな作業です。第2回でこの値を最初から k8s-lb:6443 に設定したのはこのためです。

controlPlaneEndpoint: "k8s-lb:6443"

第2回の kubeadm-config.yaml にすでにこの値が設定されているため、第3回以降は確認するだけです。第4回で k8s-cp-02/03 が kubeadm join k8s-lb:6443 を実行するとき、この controlPlaneEndpoint が join 先として機能します。

CP 障害時の挙動

HA クラスタ完成後(第4回以降)に k8s-cp-01 が停止した場合の動作を整理します。

  • HAProxy の tcp-check が k8s-cp-01:6443 への接続失敗を検出し、k8s-cp-01 を DOWN マークする
  • 以降の接続は k8s-cp-02・k8s-cp-03 にのみ転送される
  • etcd: 3 ノードのうち 2 ノード稼働 = quorum(2)を満たす → etcd 継続動作
  • 既存 Pod は kubelet が維持する(kubelet は API Server なしでもコンテナを維持する)
  • CP-01 復旧後: HAProxy が tcp-check で UP 検出 → ラウンドロビンに自動復帰

この挙動のポイントは「自動検出・自動復帰」です。HAProxy の tcp-check が定期的にヘルスチェックを行うため、管理者が手動で設定変更する必要がありません。障害から復旧した CP ノードは自動でラウンドロビンに戻ります。

etcd quorum と Raft — 障害耐性の仕組みと偶数ノード非推奨の理由

HA クラスタで最も重要な概念の一つが etcd quorum です。「なぜ CP は 3 台なのか」「なぜ偶数ではいけないのか」という問いに答えられるようになることが、この節の目標です。

etcd と Raft の概要

etcd は分散 key-value store です。Kubernetes のすべての「状態」(Node / Pod / ConfigMap / Secret 等)をここに保存します。kubectl で見えるすべての情報は、最終的には etcd に格納されたデータから読み取られます。

etcd は Raft コンセンサスアルゴリズムを採用しています。複数の etcd インスタンスが同じデータを持つようにするための合意形成の仕組みです。Raft では各メンバーが次の 3 つのいずれかの役割を持ちます。

  • Leader: 書き込みリクエストを受け付け、Follower にログエントリを複製する
  • Follower: Leader からエントリを受け取り、自身のログに追記する
  • Candidate: Leader が不在の場合に立候補し、過半数の票で Leader になる

書き込みが確定するには、quorum(多数決成立数)以上のメンバーから応答が必要です。quorum を維持できない状況(例: 過半数のメンバーが停止)では、書き込みが停止します。読み取りは継続できますが、etcd クラスタとしては「書き込み停止 = 機能停止」と同義です。

Quorum の計算式と障害耐性の表

quorum の計算式は次のとおりです。

quorum(多数決成立数)= ⌊N/2⌋ + 1
障害耐性              = ⌊(N-1)/2⌋

この式をさまざまなメンバー数に適用した結果を表にします。

メンバー数(N)quorum障害耐性備考
110障害耐性なし
220障害耐性なし(1 台でも落ちると停止)
321本シリーズ採用(1 台障害まで許容)
4313 ノードと耐性同じ(コスト増のみ)
532大規模本番向け(2 台障害まで許容)
743最大規模構成

本シリーズは 3 CP ノード(stacked etcd 3 ノード)構成です。quorum=2・障害耐性=1 ノードです。3 台のうち 1 台が停止しても etcd クラスタは継続動作します。2 台が同時に停止すると etcd が停止し、kube-apiserver も停止します。

偶数ノードが非推奨な理由

「可用性を上げるために etcd を 4 ノードに増やす」という提案が現場でたまに出ます。これは誤った判断です。理由は 2 つあります。

1. コスト増に見合わない: N=4 の quorum=3、障害耐性=1。N=3 の障害耐性と同じです。ノードを 1 台増やしてもリソースコストが増えるだけで、障害耐性は向上しません。3 ノードから 5 ノードに増やすと障害耐性が 1 → 2 に向上しますが、4 ノードへの変更は全く意味がありません。

2. Split-brain リスクが増加する: ネットワーク分断(Network Partition)が発生した場合、偶数ノードでは両側の partition がそれぞれ「同数」のノードを持つ可能性があります。たとえば 4 ノードが 2:2 に分断された場合、どちらの partition も多数決(quorum=3)を満たせません。etcd クラスタ全体が書き込みを停止する「split-brain」状態になります。

奇数ノードでは分断が発生しても「多い方」の partition が必ず多数決を取れます。3 ノードが 2:1 に分断されると、2 ノード側が quorum(2)を満たし継続動作できます。1 ノード側は quorum を取れないため書き込みを停止しますが、これはデータの整合性を守るための正しい挙動です。

実践的な結論: etcd クラスタは 3 または 5(奇数)で構成します。「可用性向上」を理由に 4 ノードにする提案は断ります。5 ノードが必要な場合(2 台同時障害まで許容したい場合)は 5 ノードにします。

CKA 試験での出題傾向

CKA の D1 ドメイン(Cluster Architecture, Installation and Configuration・配点 25%)では、etcd quorum 計算と障害耐性の概念が出題されます。「3 ノード etcd でのノード障害数の上限は何か」という問題形式が頻出です。計算式(⌊N/2⌋ + 1)と N=3 の場合の値(quorum=2・耐性=1)を確実に答えられるようにしておきます。etcd メンバーの追加・削除操作(etcdctl member add/remove)は第5回で扱います。

HAProxy HA 設計 — frontend stats + k8s-api・backend CP×3・roundrobin・tcp-check

etcd quorum の概念を理解したところで、HAProxy の HA 設計に入ります。第2回で入れた最小パススルー設定と、第3回で作る HA 設計版の差分を確認してから、設定の詳細を解説します。

第2回の最小設定と第3回の HA 設計の差分

設定項目第2回(最小)第3回(HA 設計版)
frontend statsなしbind *:9000 / mode http / stats enable / stats uri /stats
frontend k8s-apibind *:6443 / mode tcp同じ(変更なし)
backend k8s-cp-apiserver k8s-cp-01 192.168.1.125:6443 check のみCP-01〜03 の 3 サーバ + balance roundrobin + option tcp-check
frontend gateway-httpsなしなし(第11回で追加)
firewalld 9000/tcp未開放開放(本回で追加)

haproxy.cfg 全量

第3回で設定する haproxy.cfg の全量です。frontend gateway-https(:443 → Traefik)は第11回(Gateway API + Traefik 設置)で追加します。第3回には含めません。

global
  log /dev/log local0
  maxconn 4000

defaults
  log global
  timeout connect 5s
  timeout client  10m
  timeout server  10m

frontend stats
  bind *:9000
  mode http
  stats enable
  stats uri /stats

frontend k8s-api
  bind *:6443
  mode tcp
  default_backend k8s-cp-api

backend k8s-cp-api
  mode tcp
  option tcp-check
  balance roundrobin
  server k8s-cp-01 192.168.1.125:6443 check
  server k8s-cp-02 192.168.1.126:6443 check
  server k8s-cp-03 192.168.1.127:6443 check

第11回で frontend gateway-https(:443 → traefik-https)を追加します。現時点では含めません。

各設定の解説

global / defaults セクション: log /dev/log local0 は rsyslog 経由のログ出力設定です。maxconn 4000 は最大同時接続数の制限です。timeout connect 5s はバックエンドサーバへの接続確立タイムアウト、timeout client/server 10m はクライアント・サーバ側のアイドルタイムアウトです。API Server の watch ストリーム(kubectl の -w・controller-manager の reconcile)は数分〜長時間 keep-alive するため、ここを短くすると watch が再接続を繰り返してクラスタ運用に悪影響を与えます。kubeadm 公式の HAProxy リファレンス設定でも 10m 程度が採用されています。

frontend stats セクション: bind *:9000 で 9000 番ポートをすべてのインタフェースで受け付けます。mode http は HTTP モードです(stats ページは HTTP で提供するため)。stats enable で統計機能を有効化し、stats uri /stats でアクセスパスを /stats に設定します。ブラウザから http://192.168.1.124:9000/stats でアクセスすると、backend サーバの UP/DOWN・セッション数・応答時間を確認できる Web UI が表示されます。本番環境では stats auth user:password を追加して認証を設けますが、本シリーズは演習環境のため認証なしで設定します。

frontend k8s-api セクション: bind *:6443 で 6443 番ポートを受け付けます。mode tcp は TCP レイヤパススルーです。HAProxy は TLS を復号せず透過転送します。kube-apiserver の TLS 証明書の検証はクライアント(kubectl 等)が行います。API Server LB では mode tcp が唯一の正しい選択肢です。mode http を使うと HAProxy が TLS を終端しようとし、証明書設定が複雑になるため推奨されません。

backend k8s-cp-api セクション: balance roundrobin は接続ごとに CP-01 → CP-02 → CP-03 → CP-01 と順番に振り分けるアルゴリズムです。kubeadm HA では全 CP ノードが同じ etcd クラスタを参照するため、どのノードに転送しても結果は同一です。セッション維持(sticky session)は不要です。

option tcp-check は TCP 接続確立でヘルスチェックを行う設定です。6443 ポートに対して TCP 接続が成立すれば UP、接続拒否・タイムアウトなら DOWN と判定します。CP-02/03 は未 join(kube-apiserver 未起動)のため、第3回段階では DOWN 表示になります。これは想定内の状態です。第4回で kubeadm join --control-plane を実行して kube-apiserver が起動すると、次のヘルスチェックで UP になります。

server 行の IP アドレス指定について: server k8s-cp-01 192.168.1.125:6443 check のように IP アドレスで指定します。ホスト名(k8s-cp-01)を使わない理由は、HAProxy がホスト名解決に失敗する場合があるためです。HAProxy は backend サーバのホスト名解決を起動時に一度だけ行います。DNS が不安定な場合や起動タイミングによっては解決に失敗し、backend が UNAVAIL 状態になります。IP アドレスを直接指定すれば DNS への依存がなくなり、この問題を回避できます。詳細はヒヤリハットで後述します。

firewalld 9000/tcp 開放の必要性

第2回で 6443/tcp は開放済みです。stats ページ(9000/tcp)は第3回で新規に開放します。SELinux については、9000 番ポートは http_port_t に既定で含まれるため semanage port の追加設定は不要です。haproxy_connect_any ブール値も第2回で設定済みのため、第3回では追加設定は不要です。

やってみよう① — k8s-lb HAProxy を HA 設定に更新 + stats ページ確認

演習①です。k8s-lb の /etc/haproxy/haproxy.cfg を HA 設計版に書き換え、設定検証・reload・stats ページ確認を行います。作業場所は k8s-lb(192.168.1.124)です。k8s-ops から ssh developer@192.168.1.124 で接続し、root 作業を行います。

現在の haproxy.cfg を確認する

まず第2回の最小設定(SP_vol2-pre-03 の状態)を確認します。

実行コマンド:

# cat /etc/haproxy/haproxy.cfg

実行結果(第2回最小設定・backend は k8s-cp-01 のみ):

global
  log /dev/log local0
  maxconn 4000

defaults
  log global
  timeout connect 5s
  timeout client  10m
  timeout server  10m

frontend k8s-api
  bind *:6443
  mode tcp
  default_backend k8s-cp-api

backend k8s-cp-api
  mode tcp
  server k8s-cp-01 192.168.1.125:6443 check

frontend stats がなく、backend に k8s-cp-01 しかないのが第2回最小設定です。これをこれから HA 設計版に書き換えます。

HA 設計版の haproxy.cfg に書き換える

ヒアドキュメントで全量を書き換えます。

実行コマンド:

# cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
  log /dev/log local0
  maxconn 4000

defaults
  log global
  timeout connect 5s
  timeout client  10m
  timeout server  10m

frontend stats
  bind *:9000
  mode http
  stats enable
  stats uri /stats

frontend k8s-api
  bind *:6443
  mode tcp
  default_backend k8s-cp-api

backend k8s-cp-api
  mode tcp
  option tcp-check
  balance roundrobin
  server k8s-cp-01 192.168.1.125:6443 check
  server k8s-cp-02 192.168.1.126:6443 check
  server k8s-cp-03 192.168.1.127:6443 check
EOF

ヒアドキュメント(<< 'EOF')を使うと、シングルクォートで囲んだ終端文字列(EOF)によりシェル変数の展開が抑制されます。$ を含む文字列を安全に書き込めます。frontend gateway-https(:443 → Traefik)は第11回で追加します。この時点では含めません。

設定ファイルの構文検証

reload の前に必ず構文検証を行います。設定に誤りがあっても reload がエラーになるだけで既存設定は継続しますが、事前に検証する習慣をつけます。

実行コマンド:

# haproxy -c -f /etc/haproxy/haproxy.cfg
# echo exit:$?

実行結果(構文エラーなしの場合):

exit:0

haproxy -c は設定ファイルの構文チェックのみを行い、実際には起動しません。HAProxy 3.0 系では構文が正しい場合は何も出力せず終了コード 0 を返すため、echo exit:$?exit:0 を確認します。構文エラーがある場合は該当行とエラー内容が出力され、終了コードが非ゼロになります。よくある間違いは IP アドレスや行のインデント(スペース vs タブ)の誤りです。

HAProxy のサービス reload

構文検証が通ったら HAProxy を reload します。restart ではなく reload を使う理由は、reload がグレースフル(既存接続を切断せずに設定を反映する)だからです。restart は既存の TCP 接続をすべて切断します。本番稼働中の API Server LB を restart すると、kubectl の watch 接続や kube-controller-manager の通信が切れる可能性があります。

実行コマンド:

# systemctl reload haproxy

実行コマンド(reload 後の状態確認):

# systemctl status haproxy

実行結果:

● haproxy.service - HAProxy Load Balancer
     Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; ...)
     Active: active (running) since ...

Active が active (running) であれば reload 成功です。reload 後に Active: failed になった場合は設定ファイルに問題があります。journalctl -u haproxy --no-pager | tail -20 でエラーメッセージを確認します。

firewalld で 9000/tcp を開放する

stats ページのポート(9000/tcp)を firewalld で開放します。

実行コマンド:

# firewall-cmd --permanent --add-port=9000/tcp
# firewall-cmd --reload

実行コマンド(開放確認):

# firewall-cmd --list-ports

実行結果:

6443/tcp 9000/tcp

6443/tcp9000/tcp の両方が表示されれば開放済みです。--permanent フラグを付けないと、OS 再起動で設定が消えます。--permanent + firewall-cmd --reload をセットで実行する習慣をつけます。

stats ページをブラウザまたは curl で確認する

k8s-ops から curl で stats ページに接続できるかを確認します。

実行コマンド(k8s-ops で実行):

$ curl -s "http://192.168.1.124:9000/stats;csv" | grep k8s-cp | cut -d, -f1,2,18

実行結果(CP-01 が UP・CP-02/03 が DOWN であることを確認):

k8s-cp-api,k8s-cp-01,UP
k8s-cp-api,k8s-cp-02,DOWN
k8s-cp-api,k8s-cp-03,DOWN
k8s-cp-api,BACKEND,UP

HAProxy の stats ページは URL に ;csv を付けると CSV 形式で出力されます。上記コマンドは CSV の 1 列目(pxname)・2 列目(svname)・18 列目(status)だけを抜き出しています。k8s-cp-01UPk8s-cp-02k8s-cp-03DOWN、backend 全体(BACKEND)は 1 台でも UP があれば UP です。ホスト OS のブラウザから http://192.168.1.124:9000/stats;csv なし)を開くと、カラフルな HAProxy 統計 UI で同じ状態を視覚的に確認できます。

DOWN 表示は k8s-cp-02/03 に kube-apiserver が未起動のため正常な状態です。「DOWN が出ているから設定が間違っている」という判断は誤りです。第4回で kubeadm join --control-plane を実行して kube-apiserver が起動すると、stats ページで UP に変わります。この変化がそのまま「HA クラスタが完成した」証拠になります。

k8s-ops からの kubectl 疎通確認

HAProxy HA 設定後も k8s-ops から kubectl が通ることを確認します。CP-02/03 が DOWN のため、HAProxy は全接続を CP-01 に転送します。

実行コマンド(k8s-ops で実行):

$ k get nodes

実行結果(CP-01 のみ Ready の状態):

NAME        STATUS   ROLES           AGE   VERSION
k8s-cp-01   Ready    control-plane   5h    v1.35.5

HAProxy が CP-01 に正しく転送できていることを確認できます。CP-02/03 は DOWN のためラウンドロビンから除外され、全接続が CP-01 に転送されます。これは option tcp-check によるヘルスチェックが正しく機能している結果です。

以上で演習①は完了です。k8s-lb の HAProxy が HA 設計版に更新され、stats ページで CP-01 UP / CP-02/03 DOWN(第4回 join まで正常な状態)が確認できました。

やってみよう② — etcd quorum 計算演習

演習②です。quorum の計算式を使い、さまざまな etcd クラスタ構成で障害耐性を求めます。CKA 試験で出題される典型的なパターンを練習します。計算自体は簡単ですが、「なぜその数になるか」まで答えられることが目標です。

問題(4 問)

次の 4 問を解きます。回答を見る前に自分で計算してみてください。計算式は quorum = ⌊N/2⌋ + 1障害耐性 = ⌊(N-1)/2⌋ です。

問 1: etcd クラスタが 3 ノード構成のとき、クラスタを継続動作させるための最少稼働ノード数(quorum)はいくつか。また、同時に障害を許容できるノード数は最大何台か。

問 2: 5 ノード etcd クラスタで 2 台が同時に障害を起こした。クラスタは稼働を継続できるか。その理由を答えよ。

問 3: etcd クラスタを「より高い可用性のために」4 ノードに増やす提案がある。3 ノード構成と比べて障害耐性はどう変わるか。偶数ノードにすることの問題点を答えよ。

問 4: 本シリーズの 3 CP ノード(stacked etcd)構成で、k8s-cp-01 が停止した場合に etcd クラスタはどうなるか。k8s-cp-02 も追加で停止した場合はどうか。

回答と解説

問 1 回答: quorum = ⌊3/2⌋ + 1 = 1 + 1 = 2。最少稼働ノード数は 2。障害耐性 = ⌊(3-1)/2⌋ = ⌊1⌋ = 1。同時に障害を許容できるのは最大 1 台です。3 台のうち 1 台が停止しても残り 2 台が quorum を満たし、クラスタは継続動作します。2 台が同時に停止すると稼働は 1 台となり、quorum(2)を満たせないためクラスタは書き込みを停止します。

問 2 回答: 継続可能です。5 ノードのうち 2 台が停止すると稼働ノードは 3 台。5 ノードの quorum = ⌊5/2⌋ + 1 = 2 + 1 = 3。稼働 3 台 = quorum(3)を満たすため、クラスタは動作を継続します。5 ノード構成の障害耐性 = ⌊(5-1)/2⌋ = 2。2 台まで同時障害を許容できます。

問 3 回答: 4 ノードの quorum = ⌊4/2⌋ + 1 = 2 + 1 = 3。障害耐性 = ⌊(4-1)/2⌋ = ⌊1.5⌋ = 1。3 ノード構成と障害耐性は同じ(1 台)です。コストが増えるだけで可用性向上はありません。加えて、偶数ノードではネットワーク分断時に両 partition が同数のノードを持つ(たとえば 2:2 に分断)と、双方が quorum(3)を取れず全停止(split-brain)になるリスクがあります。3 ノードから 4 ノードへの変更は「コスト増・リスク増・耐性向上なし」という三重に意味のない変更です。

問 4 回答:

  • k8s-cp-01 停止: etcd は 2/3 で稼働。quorum(2)を満たす。etcd 継続動作・kube-apiserver も CP-02/03 で継続動作。
  • k8s-cp-02 も追加停止: etcd は 1/3 で稼働。quorum(2)を満たせない。etcd 停止 → kube-apiserver も停止(API Server は etcd への書き込みが必要なため)。ただし既存 Pod は kubelet が維持するため、動いているアプリは停止しません。

計算演習まとめ

quorum 計算は 1 分以内で解けます。CKA 試験では etcd バックアップ・リストア(第5回)と組み合わせた形式で出題されることが多いです。「3 ノード構成を前提として、同時障害許容は 1 台まで」という数字を目安として押さえておきます。本シリーズの 3 CP ノード構成でこの数字が意味することは、「1 台の CP ノードを止めてメンテナンスしても、残り 2 台でクラスタが動き続ける」ということです。

HA 用 kubeadm-config.yaml の設計確認(第4回 join への布石)

HAProxy の設定が完了したところで、第4回の kubeadm join --control-plane に向けた kubeadm-config.yaml の設計を確認します。第2回で作成済みのファイルを読み取り、HA 設計の観点で各フィールドを理解します。

第2回で作成済みの kubeadm-config.yaml を確認する

k8s-cp-01 に ssh して確認します。

実行コマンド(k8s-cp-01 で実行):

# cat /root/kubeadm-config.yaml

実行結果(第2回で作成済み・全量):

apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.35.5
controlPlaneEndpoint: "k8s-lb:6443"
networking:
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/12"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "192.168.1.125"
  bindPort: 6443

2 つのドキュメントが --- で区切られた YAML 形式です。ClusterConfiguration がクラスタ全体の設定、InitConfiguration がこのノード固有の設定です。

HA 設計の観点で各フィールドを再確認する

フィールドHA 設計での意味
controlPlaneEndpointk8s-lb:6443全 CP ノードが共有するエンドポイント。CP-02/03 の join 時も kubeadm join k8s-lb:6443 と指定する
kubernetesVersionv1.35.5バージョンピン留め済み。CP-02/03 も同じバージョンで join する
podSubnet10.244.0.0/16Calico が使用する Pod CIDR。全 CP・WL ノードで同じ CIDR を使用する
localAPIEndpoint.advertiseAddress192.168.1.125CP-01 の実 IP。CP-02/03 の join 時は各ノードの IP に変わる(CP-02 は 192.168.1.126、CP-03 は 192.168.1.127)

apiVersion: kubeadm.k8s.io/v1beta4 は K8s v1.35 に対応した API バージョンです。v1beta3 は Deprecated になっているため、v1beta4 を使います。

–upload-certs の有効期限と再アップロード方法

第2回の kubeadm init --upload-certs で、CP-02/03 が証明書を自動取得できるように証明書を etcd にアップロードしました。この証明書の有効期限は 2 時間です。

第3回から第4回の間隔が 2 時間を超える場合(複数日に分けて作業する場合など)、第4回の kubeadm join --control-plane 前に証明書の再アップロードが必要です。再アップロードのコマンドを以下に示します。

実行コマンド(k8s-cp-01 で実行・第4回 join 直前に実行する):

# kubeadm init phase upload-certs --upload-certs

実行結果(新しい certificate-key が発行される):

[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

実際の certificate-key 値は第4回の join 直前にこのコマンドを実行して確定します。出力された新しい certificate-key を使って第4回の join コマンドを実行します。有効期限切れのまま join を試みると、証明書取得が失敗して join が途中で止まります。

第4回で実行する join コマンドのプレビュー

第4回では k8s-cp-02/03 でそれぞれ次のコマンドを実行します。第3回では実行しません。「こういうコマンドを次回実行する」という布石として確認します。

# kubeadm join k8s-lb:6443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --control-plane \
  --certificate-key <cert-key> \
  --apiserver-advertise-address 192.168.1.126

--apiserver-advertise-address に各 CP ノードの実 IP を指定します。CP-02 は 192.168.1.126、CP-03 は 192.168.1.127 です。<token><hash> は第2回の kubeadm init 出力か、kubeadm token create --print-join-command で再取得できます。<cert-key> は上記の --upload-certs 再アップロードで得た値を使います。

まとめ・現場ヒヤリハット・理解度チェック

まとめと次回予告

第3回で学んだことをまとめます。

  • kubeadm HA クラスタの stacked etcd トポロジでは、etcd が各 CP ノードに同居する。3 CP ノード構成が最小推奨であり、本シリーズで採用するトポロジである
  • etcd quorum = ⌊N/2⌋ + 1。3 ノード構成では quorum=2・障害耐性=1 ノード。1 台の CP ノードを止めてメンテナンスしても、残り 2 台でクラスタが継続動作する
  • 偶数ノードは 1 つ下の奇数ノードと障害耐性が同じうえ split-brain リスクもあるため非推奨。etcd クラスタは 3 または 5(奇数)で構成する
  • HAProxy の TCP モード(mode tcp)+ option tcp-check + balance roundrobin が API Server LB の標準構成。mode http は TLS パススルーができないため NG
  • frontend stats(:9000)で HAProxy の backend UP/DOWN をリアルタイム確認できる。CP-02/03 未 join 段階では DOWN が正常。第4回 join 後に UP になる
  • controlPlaneEndpoint: k8s-lb:6443 は kubeadm init 時から設定済み。変更には証明書 SAN 再生成が必要なため、最初から LB アドレスを指定することがベストプラクティス
  • --upload-certs の有効期限は 2 時間。複数日に分けて作業する場合は第4回 join 前に kubeadm init phase upload-certs --upload-certs で再アップロードが必要

第3回で押さえた HA 設計のポイントを表で整理します。

設計ポイント理由
etcd は 3 または 5(奇数)ノード偶数は耐性向上なし・split-brain リスク増加
HAProxy mode tcp(API Server LB)TLS パススルーのため。mode http は TLS 終端が必要になり複雑
backend に IP アドレスを使用ホスト名は DNS 依存で起動時に失敗リスク。IP 直指定で回避
設定反映は reload を使う(restart を避ける)restart は既存接続を切断。reload はグレースフルに設定を反映
controlPlaneEndpoint を最初から設定後から変更すると証明書 SAN の再生成が必要な大掛かりな作業

次回(第4回)は本シリーズの最大の山場「kubeadm HA クラスタ構築」です。k8s-cp-02/03 に kubeadm join --control-plane を実行して Control Plane ×3 を完成させ、k8s-wl-01/02 を Workload Node として参加させます。HAProxy stats ページで全 backend が UP になることを確認し、第1巻で構築した fanclub-api を kind クラスタから kubeadm HA クラスタへ移行します。

現場ヒヤリハット

ヒヤリハット①(必須): backend の設定にホスト名を使ったら名前解決失敗で HAProxy が起動しなかった

状況: haproxy.cfg の server 行にホスト名(server k8s-cp-01 k8s-cp-01:6443 check)を記述した。haproxy -c の構文チェックは通過したが、systemctl start haproxy の直後に backend が全部 DOWN になり、しばらくすると UNAVAIL 状態になった。

原因: HAProxy は backend サーバのホスト名解決を起動時に一度だけ行います。本環境では alma-proxy の dnsmasq が DNS を提供していますが、HAProxy の起動タイミングで dnsmasq が未応答だった(または /etc/hosts に登録されていなかった)ため、名前解決に失敗して backend が UNAVAIL になりました。haproxy -c は設定ファイルの構文を検査するだけで、DNS 解決は行いません。このため構文チェックは通っても実際の起動で失敗します。

対処: server 行には必ず IP アドレスを使用します(server k8s-cp-01 192.168.1.125:6443 check)。HAProxy の DNS 動的解決は追加設定が必要で、基本設定では初回起動時の静的解決のみです。IP アドレスを使えば DNS への依存をなくせます。ホスト名を使いたい場合は /etc/hosts に登録する方法もありますが、IP アドレス直指定の方が確実です。

本記事の haproxy.cfg はすべて IP アドレスで記述しています(192.168.1.125:6443 等)。この理由がこのヒヤリハットです。設定ファイルを手書きで書く場合も、コピーペーストで書き換える場合も、server 行のホスト名・IP を間違えやすいです。設定後に必ず haproxy -c と stats ページの確認を行います。

ヒヤリハット②: 偶数ノード構成にして「可用性が上がった」と誤解した

状況: 「etcd ノードを 3 から 4 に増やすと可用性が上がる」という判断で、本番環境の etcd クラスタを 4 ノードに変更した。

実態: 障害耐性は 3 ノードと同じ「1 台まで」のままです。コストが増えただけです。さらにネットワーク分断が発生したとき、各側が 2 ノードずつ持つと双方が quorum(3)を取れず全体停止(split-brain)するリスクが増加しました。

対処: etcd は奇数(3 / 5 / 7)で構成します。可用性を上げるには 5 ノード構成にします(障害耐性 2 ノード)。4 ノードへの増設は「耐性は増えないのに管理対象が増え、split-brain リスクも増える」という三重の負担です。本シリーズの演習環境で使う 3 CP ノード構成でも、この判断基準を覚えておきます。

理解度チェック(○×形式・7 問)

以下の 7 問に○か×で答えてください。答えと解説は下に記載しています。

問 1: kubeadm HA クラスタの stacked etcd トポロジでは、etcd と Control Plane コンポーネントが同一ノード上で稼働する

問 2: 3 ノード etcd クラスタで 2 台が同時に停止した場合、クラスタは書き込みを継続できる

問 3: 偶数ノード(4 台)の etcd クラスタは、奇数ノード(3 台)よりも障害耐性が高い

問 4: HAProxy の mode http を API Server LB に使用すると、TLS の復号が必要になるため推奨されない

問 5: HAProxy stats ページで backend が DOWN と表示された場合、必ずその backend のサーバに障害が発生している

問 6: controlPlaneEndpoint は kubeadm init 後でも簡単に変更できる

問 7: kubeadm の --upload-certs オプションで etcd にアップロードされた証明書の有効期限は 2 時間である

— 答えと解説 —

問 1: ○ — stacked etcd では etcd インスタンスが各 Control Plane Node 上で Static Pod として稼働します。etcd と kube-apiserver・kube-controller-manager・kube-scheduler が同じノードに同居するのが stacked etcd の特徴です。

問 2: × — 3 ノード etcd の quorum = ⌊3/2⌋ + 1 = 2 です。2 台が停止すると稼働は 1 台となり、quorum(2)を満たせません。etcd クラスタは書き込みを停止します(読み取りは一部可能な場合もありますが、実質的に機能停止です)。

問 3: × — 4 ノードの quorum = ⌊4/2⌋ + 1 = 3、障害耐性 = ⌊(4-1)/2⌋ = 1 です。3 ノード構成と障害耐性は同じ(1 台)です。コストが増えるだけで可用性は向上しません。偶数ノードは split-brain リスクも増加します。

問 4: ○ — API Server は TLS を自身で終端するため、HAProxy では TCP レイヤパススルー(mode tcp)を使います。mode http を使うと HAProxy が TLS 終端を行う必要があり、証明書の設定が複雑になります。CKA 試験・本番環境ともに mode tcp が標準です。

問 5: × — 今回のように kube-apiserver が未起動(未 join)の場合も DOWN になります。DOWN はヘルスチェックが失敗している状態を示すものであり、原因はさまざまです。第4回で kubeadm join --control-plane 実行後に kube-apiserver が起動すると UP に変わります。DOWN がすべて障害を意味するわけではありません。

問 6: × — controlPlaneEndpoint を init 後に変更すると、API Server の TLS 証明書に含まれる SAN の再生成が必要です。本番稼働中クラスタではすべての CP ノードでの証明書更新・コンポーネント再起動が必要な大掛かりな作業になります。これを避けるために kubeadm init 時に最初から LB のエンドポイントを指定するのがベストプラクティスです。

問 7: ○ — --upload-certs で etcd にアップロードされた証明書は 2 時間で期限切れになります。複数日に分けて作業する場合は、第4回 join の直前に kubeadm init phase upload-certs --upload-certs を実行して新しい certificate-key を取得します。

シリーズ一覧

第1部:クラスタ構築

第2部:ワークロード管理

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

第6部:トラブルシュート

広告
kubernetes
スポンサーリンク