コントロールプレーンはなぜ四つに割れたのか
一言でいうと
Kubernetesのコントロールプレーンは、etcd、kube-apiserver、kube-controller-manager、kube-schedulerの4つのプロセスに分かれており、そのうちetcdと話すのはapiserverだけです。この一文が、CKAのトラブルシューティングの半分を説明します。
なぜ必要なのか
想像してみましょう。スケジューラーがetcdに直接つながり、Podオブジェクトを読んでnodeNameを書き込むとしたら、どうなるでしょうか。次のようなことが起こります。
- スケジューラーが、etcdの保存形式(protobufエンコーディング、
/registry/pods/<ns>/<name>というキー構造)に結合されます。保存バージョンを変えると、スケジューラーも一緒に直さなければなりません。 - 認可(RBAC)、検証(validation)、アドミッションWebhook、監査ログがすべてバイパスされます。誰が何を変更したのかが残りません。
- フィールドのデフォルト値の補完や、バージョンの変換(v1beta1 to v1)を、コンポーネントごとに別々に実装しなければなりません。
そのためKubernetesは、apiserverを唯一の関門としました。apiserverは状態を作りません。受け取り、検証し、認可し、保存し、見守っている者たちに知らせるだけです。実際の判断は、すべて外側のコントローラーたちが行います。
どう動くのか
コントローラーのreconcileループは、次のような形をしています。
- watchで、宣言された状態(spec)を読みます。
- 実際の状態(status)を観測します。
- 差を計算し、その分だけAPIを呼び出します。
- 再び1に戻ります。
命令ではなく差を縮めるという点が重要です。そのため、イベントを1つ取りこぼしても、次のリシンクで収束し、コントローラーを再起動しても、最初から合わせ直していきます。DeploymentコントローラーはReplicaSetを作り、ReplicaSetコントローラーはPodを作ります。それぞれが自分の層だけを見ます。
スケジューラーは2段階で動作します。
| 段階 | 行うこと | 結果 |
|---|---|---|
| filter (predicate) | リソース不足、テイントの非許容、nodeSelectorの不一致、ボリュームのゾーンの不一致のノードを除外します | 配置可能なノードの一覧 |
| score (priority) | 残ったノードに点数を付けます (リソースのバランス、イメージの局所性、トポロジーの分散) | 最高点のノード1つ |
0/3 nodes are available: 3 Insufficient cpuのようなメッセージは、filter段階の脱落理由の集計表です。この文を読めるようになれば、PendingのPodの90%は、その場で片が付きます。
現場での姿
事例1: DHCPがクラスターを殺しました。ホームラボのコントロールプレーンのIPが、10.0.0.111から10.0.0.120に変わったことがあります。DHCPのリース更新が原因でした。症状はdial tcp 10.0.0.111:6443: connect: no route to hostで、本当の原因は証明書にありました。apiserverの証明書のSANにIP Address:10.0.0.111しかなく、.120がなかったのです。ここで面白かったのは、コンポーネントごとの反応の違いでした。etcdとkube-apiserverは、存在しない.111にバインドしようとしてCrashLoopBackOffに陥り、kube-schedulerとcontroller-managerは127.0.0.1にバインドするため、プロセスは生きているのに何もできない状態でした。4つのプロセスがそれぞれ別のアドレスにつながるという設計を知っていてはじめて、この図が読み解けます。
事例2: etcdのクォーラムとAPIの可用性は別物です。同じホームラボを3ノードから7ノードに増やす際に、コントロールプレーンを3台にしました。etcdctl member listにはメンバー3つが正確に表示され、各ノードでapiserver、scheduler、controller-managerが1つずつ動いていました。HAが完成したように見えました。いいえ、違います。
controlPlaneEndpoint: 10.0.0.120:6443 # cp-1 의 물리 IP
この値が、VIPやDNSではなく、最初のノードの実際のIPでした。そのため、cp-1が落ちると、etcdのクォーラムは2/3で問題なく、cp-2とcp-3のapiserverも正常に動作するのに、kubectlと7つのノードのkubeletがすべて接続不能になります。さらに、証明書のSANにほかのコントロールプレーンのIPがないため、cp-2に直接つないでもTLS検証が失敗します。データの可用性とアクセスの可用性は、別の問題です。
付け加えると、コントロールプレーンを2台までしか増やさないのは、1台よりももっと危険です。メンバー2の過半数は2なので、どれか1台が落ちただけでクォーラムを失います。奇数(1、3、5)が推奨される理由であり、だからこの拡張も、必ず3台まで進めました。
次のラボですること
最初のラボでは、クラスターを調査し、ネームスペース・ラベル・アノテーションを扱い、kubeconfigのコンテキストを自分で作って切り替え、kubectl explainと--dry-run=client -o yamlでマニフェストを取り出します。2つ目のラボでは、CustomResourceDefinitionを自分で書いて、APIを拡張してみます。スキーマ検証が実際にリクエストを拒否する瞬間を、目で確認することが目標です。