TT Lab
はじめる
学ぶ 学習パス コース

KCNA — Kubernetes・クラウドネイティブ入門

コントロールプレーンはなぜこう割れたのか

TT Labで続きを見る

一言でいうと

Kubernetesは「命令を実行するシステム」ではなく、「望ましい状態を書いておくと、コントローラーたちが現実をそれに合わせていくシステム」です。コントロールプレーンが複数の部品に分かれているのは、このループを回す主体を役割ごとに分けたからです。

なぜ必要なのか

命令型のシステムは「Podを3つ起動せよ」を実行して終わりです。では、ノードが1つ落ちたらどうなるでしょうか。誰も気にしません。命令はもう終わっているからです。

宣言型のシステムは「Podが3つある状態が望ましい」を保存します。そして誰かが現在の状態を読み続けて望ましい状態と比較し、違っていれば差を縮めます。このループをreconciliation loopと呼び、これがKubernetesのすべてです。残りは、このループを安全で拡張可能にするための仕掛けです。

こう見ると、コンポーネントの分離が自然に読めてきます。状態をどこに書くのか(etcd)、誰にその状態を読み書きさせるのか(kube-apiserver)、誰がループを回すのか(kube-controller-manager)、Podをどのノードに置くか決める特別なループは誰が回すのか(kube-scheduler)、ノード上で実際にコンテナを起動するのは誰か(kubelet)。

どう動くのか

コントロールプレーン

コンポーネント 役割 なくなると
etcd クラスターのすべての状態を保持する唯一のストア クラスターの記憶が消えます
kube-apiserver etcdへの唯一の入口。認証・認可・アドミッション・検証を行います 誰も何も読み書きできません
kube-controller-manager Deployment/ReplicaSet/Node/Endpointなど数十個のコントローラーループ 宣言は保存されますが、何も起きません
kube-scheduler PendingのPodにノードを割り当てます Podは作られても永遠にPendingのままです

どのコンポーネントもetcdを直接触りません。スケジューラーも、コントローラーマネージャーも、kubeletも、すべてapiserverを経由します。そうすることで、認証・認可・アドミッション・監査ログが1か所で効きます。これがKCSAにつながるセキュリティ設計の出発点です。

etcd内のキー構造も知っておくと役立ちます。すべてのリソースは/registry/の下にあり、ネームスペースリソースは/registry/<종류>/<네임스페이스>/<이름>、クラスターリソースは/registry/<종류>/<이름>の形です(プレースホルダーは順に種類、ネームスペース、名前です)。値はデフォルトでprotobufにシリアライズされます。

ノード

APIはリソースでできている

Kubernetesと対話する方法は1つだけです。RESTリソースに対するCRUDです。Deploymentを作るのも、Podを削除するのも、すべて同じ文法です。kubectl api-resourcesでこのクラスターが知っているリソースの全一覧を、kubectl explainで各フィールドの意味を見られます。ドキュメントサイトを開かずにクラスターへ直接尋ねる習慣が、試験会場でも現場でも最も速い方法です。

ラベルセレクター: 疎結合の接着剤

Kubernetesには「このServiceはあの3つのPodを指す」といった直接参照がほとんどありません。代わりにラベルを付けて、セレクターで選びます。Serviceも、ReplicaSetも、NetworkPolicyも、すべてセレクターで対象を見つけます。そのため、セレクターのタイプミス1つが静かな障害を生みます。オブジェクトは正常に作成されますが、何も捕捉されないのです。

現場での姿

筆者のホームラボで実際にあった出来事です。ある日クラスターが応答しなくなり、dial tcp 10.0.0.111:6443: connect: no route to hostが表示されました。原因はDHCPでした。コントロールプレーンノードのIPが.111から.120へ更新されてしまったのです。

ここでコンポーネントの分離が目に見える形で現れました。etcdとkube-apiserverは、存在しないアドレス.111にバインドしようとして失敗し、CrashLoopBackOffに陥りましたが、kube-schedulerとcontroller-managerは127.0.0.1にバインドするため、プロセスは無事に生きていました。生きてはいても、何もできない状態でした。apiserverがなければ、コントローラーには読むものも書くものもないからです。

決定的な証拠は証明書にありました。apiserver.crtのSubject Alternative Nameに.120がなかったのです。そのため、IPを変えるだけでは復旧できません。SANにないアドレスで接続すると、TLS検証が失敗するからです。

同じクラスターをのちに7ノード(コントロールプレーン3台 + GPUワーカー4台)に拡張した際には、さらに痛い教訓が得られました。etcdメンバーを3つにしてクォーラムを確保したのに、controlPlaneEndpointがVIPやDNSではなくcp-1の物理IP(10.0.0.120:6443)で埋め込まれていました。cp-1が落ちても、etcdのクォーラムは2/3で無事、ほかの2ノードのapiserverプロセスも正常なのに、kubectlと7台のkubeletがすべて接続不能になります。「コントロールプレーンは生きているのに、誰も入口を見つけられない」状態です。 データの可用性とアクセスの可用性はまったく別の問題であることが、この事故の要約です。

次のラボですること

すぐ次のラボで、kcna-archネームスペースを作り、ノード一覧を取り出し、kubectl api-resourcesとkubectl explainでAPIをクラスターに直接尋ねます。ラベルセレクターでPodを選び出し、最後に同じDeploymentを命令型と宣言型の2つの方法で作って、何が違うかを確認します。