Skip to content
Published on

Kubernetes アーキテクチャを可視化する — コントロールプレーンからポッドまで

Authors

はじめに

Kubernetes はコンテナ化されたアプリケーションのデプロイ・スケール・運用を自動化するオーケストレーションプラットフォームです。初めて触れると数多くの構成要素やオブジェクトに圧倒されがちですが、中核を貫く一つの原理があります。それは 宣言的な desired state と、それに絶え間なく収束していくコントローラループです。

本記事は「何を命令するか」よりも「クラスタ内部でその命令がどう流れ、コントロールプレーンとノードがどう協力して desired state を実現するか」を図で解きほぐします。具体的な挙動はバージョンやディストリビューションによって変わり得るため、正確な動作は公式ドキュメント(kubernetes.io)で確認することをおすすめします。


1. 全体アーキテクチャを一目で

クラスタは大きく **コントロールプレーン(頭脳)**と **ワーカーノード(働き手)**に分かれます。

┌──────────────────────── コントロールプレーン ──────────────────┐
│                                                               │
│   ┌─────────────┐    ┌──────────┐    ┌──────────────────┐    │
│   │ kube-api    │◀──▶│  etcd     │    │ controller-      │    │
│   │  server     │    │ (状態保存) │    │  manager         │    │
│   └──────┬──────┘    └──────────┘    └──────────────────┘    │
│          │            ┌──────────────┐                        │
│          │            │  scheduler    │                        │
│          │            └──────────────┘                        │
└──────────┼────────────────────────────────────────────────────┘
           │ (すべての通信は api-server 経由)
   ┌───────┴───────────────────┬───────────────────────┐
   ▼                           ▼                       ▼
┌─────────────────┐   ┌─────────────────┐    ┌─────────────────┐
│  ワーカーノード 1 │   │  ワーカーノード 2 │    │  ワーカーノード N │
│ ┌─────────────┐ │   │ ┌─────────────┐ │    │ ┌─────────────┐ │
│ │  kubelet     │ │   │ │  kubelet     │ │    │ │  kubelet     │ │
│ ├─────────────┤ │   │ ├─────────────┤ │    │ ├─────────────┤ │
│ │ kube-proxy   │ │   │ │ kube-proxy   │ │    │ │ kube-proxy   │ │
│ ├─────────────┤ │   │ ├─────────────┤ │    │ ├─────────────┤ │
│ │ コンテナ      │ │   │ │ コンテナ      │ │    │ │ コンテナ      │ │
│ │ ランタイム(CRI)│ │   │ │ ランタイム(CRI)│ │    │ │ ランタイム(CRI)│ │
│ ├─────────────┤ │   │ ├─────────────┤ │    │ ├─────────────┤ │
│ │ Pod Pod Pod  │ │   │ │ Pod Pod      │ │    │ │ Pod          │ │
│ └─────────────┘ │   │ └─────────────┘ │    │ └─────────────┘ │
└─────────────────┘   └─────────────────┘    └─────────────────┘

最も重要なルールは すべての通信が api-server を経由することです。コンポーネント同士は直接対話せず、api-server を中央ハブとして状態を読み書きします。


2. コントロールプレーンの構成要素

kube-apiserver — クラスタの正面玄関

api-server はすべてのリクエストの入口です。認証・認可・検証を経たうえで状態を etcd に保存し、他のコンポーネントに変更を知らせます。

   kubectl / コントローラ / kubelet
            │  REST リクエスト
   ┌──────────────────────────────────┐
   │           kube-apiserver          │
   │  1) 認証(誰か?)                 │
   │  2) 認可(権限があるか? RBAC)     │
   │  3) Admission(ポリシー/変形/検証) │
   │  4) etcd に保存                    │
   └──────────────────────────────────┘
            ▼   (保存された状態を watch で購読)

etcd — 単一の真実の源

etcd は分散キーバリューストアで、クラスタのすべての状態(オブジェクトの仕様、現在の状態)を保持します。クラスタの記憶と言え、etcd がなければクラスタは自分が何を望んだのか分かりません。

kube-scheduler — ポッドをノードに配置

スケジューラは「まだノードが決まっていないポッド」を見つけ、適切なノードを選びます。

   待機中のポッドを発見
   ┌─────────────────────────────────────┐
   │ 第1段階: フィルタリング              │
   │  - リソースは十分か?(CPU/メモリ)   │
   │  - nodeSelector/affinity を満たすか? │
   │  - taint/toleration を通過するか?    │
   │  ──▶ 候補ノードの集合に絞る           │
   ├─────────────────────────────────────┤
   │ 第2段階: スコアリング                │
   │  - 候補に点数付け(分散、親和性など) │
   │  ──▶ 最高点のノードを選択            │
   └─────────────────────────────────────┘
   ポッドにノードをバインド(api-server に記録)

kube-controller-manager — コントローラたちの家

複数のコントローラを一つのプロセスにまとめて実行します。各コントローラは特定リソースの desired state と現在の状態を比較し、差を埋めます(例: Deployment、ReplicaSet、Node、Job のコントローラ)。


3. 中核の原理 — コントローラループと desired state

Kubernetes を理解するただ一つの鍵は **調整ループ(reconciliation loop)**です。ユーザーは「望む状態」を宣言し、コントローラはその状態になるまで現実を絶え間なく調整します。

        ┌──────────────────────────────────────────┐
        │           調整ループ(無限反復)            │
        │                                            │
        │   1) desired state を読む(例: replicas=3)│
        │              │                              │
        │              ▼                              │
        │   2) current state を観察(実ポッド 2 個)  │
        │              │                              │
        │              ▼                              │
        │   3) 差を計算(3 - 2 = 1 不足)             │
        │              │                              │
        │              ▼                              │
        │   4) アクション(ポッドを 1 個追加生成)    │
        │              │                              │
        │              └────────▶ (再び 1 へ)        │
        └──────────────────────────────────────────┘

このループのおかげで Kubernetes は **自己修復(self-healing)**します。ポッドが死ぬと current state が desired state より不足し、コントローラが即座に新しいポッドを作って埋めます。

オブジェクト階層

   Deployment
      │ 管理
   ReplicaSet(replicas=3)
      │ 管理
   Pod  Pod  Pod   ◀── 実際の実行単位(コンテナの束)

Deployment はローリングアップデートとロールバックを担い、ReplicaSet はポッド数の維持を担い、Pod は実際にコンテナを収める最小デプロイ単位です。


4. ノードの構成要素

kubelet — ノードの代理人

kubelet は各ノードで動作し、api-server から「このノードにどのポッドを立てるべきか」を受け取り、コンテナランタイムに実際の生成を指示します。そしてポッドの状態を定期的に api-server へ報告します。

   api-server ──「このポッドを立てよ」──▶ kubelet
                              コンテナランタイム(CRI)に生成要求
                              コンテナ実行 + ヘルスチェック
                              状態報告 ──▶ api-server

kube-proxy — サービスネットワーキング

kube-proxy は各ノードで Service の抽象を実際のネットワークルールとして実装します。クライアントが Service の仮想 IP に送ったトラフィックを実際のポッド群へ分配します。

コンテナランタイム(CRI)

kubelet は標準インターフェースである **CRI(Container Runtime Interface)**を通じてランタイムと対話します。おかげで特定のランタイムに縛られず、さまざまな実装を差し替えられます。

   kubelet ──CRI(標準インターフェース)──▶ コンテナランタイム
                              イメージ取得、コンテナ生成/起動/停止

5. ポッド生成のシーケンス図

kubectl apply で Deployment を作るとき何が起きるかを段階的に追ってみます。

ユーザー       api-server     etcd      controller    scheduler    kubelet
  │              │            │            │            │            │
  │ apply        │            │            │            │            │
  ├─────────────▶│            │            │            │            │
  │              │ 認証/認可/検証            │            │            │
  │              ├───────────▶│ 保存        │            │            │
  │              │            │            │            │            │
  │              │   watch: 新 Deployment を検知          │            │
  │              │◀───────────────────────┤            │            │
  │              │     ReplicaSet を生成    │            │            │
  │              │◀───────────────────────┤            │            │
  │              │       Pod(ノード未指定)を生成        │            │
  │              │◀───────────────────────┤            │            │
  │              │                         │            │            │
  │              │   watch: 未配置ポッドを検知            │            │
  │              │◀───────────────────────────────────┤            │
  │              │     ノードのバインドを記録 │            │            │
  │              │◀───────────────────────────────────┤            │
  │              │                         │            │            │
  │              │   watch: 自ノードのポッドを検知                    │
  │              │◀───────────────────────────────────────────────┤
  │              │     ランタイムに生成指示 → 実行                    │
  │              │     状態報告(Running)  │            │            │
  │              │◀───────────────────────────────────────────────┤
  ▼              ▼            ▼            ▼            ▼            ▼

要点はどのコンポーネントも互いを直接呼び出さないことです。みな api-server の状態を watch し、自分に関係する変化が見えれば行動します。この疎結合が Kubernetes の拡張性と堅牢さの秘訣です。


6. ネットワーキング

Kubernetes のネットワーキングはいくつかの階層で理解すると明快です。

┌──────────────────────────────────────────────────────────┐
│ 1) ポッドネットワーク(CNI)                               │
│    - すべてのポッドは固有 IP を持ち NAT なしで通信          │
│    - CNI プラグインがこの規約を実装                        │
├──────────────────────────────────────────────────────────┤
│ 2) Service(安定した仮想 IP + ロードバランシング)         │
│    - ポッドは消えて再生成され IP が変わる                   │
│    - Service は変わらない名前/IP でポッド集合を抽象化       │
├──────────────────────────────────────────────────────────┤
│ 3) Ingress / Gateway API(外部 → 内部ルーティング)        │
│    - HTTP のパス/ホストで外部トラフィックを Service へ分配  │
└──────────────────────────────────────────────────────────┘

Service がトラフィックを送る仕組み

   クライアント ──▶ Service(仮想 IP、変わらない)
                        │  エンドポイント集合を参照
        ┌───────────────┼───────────────┐
        ▼               ▼               ▼
      Pod A           Pod B           Pod C
   (ポッド IP が変わっても Service 名はそのまま)

Gateway API

従来の Ingress の限界を補うため、Gateway API が標準として定着しつつあります。役割を分離(インフラ担当者 vs アプリ開発者)し、より表現力のあるルーティングを提供します。

   GatewayClass(インフラの種類を定義)
   Gateway(リスナー: ポート/プロトコル)
   HTTPRoute(パス/ホスト → Service マッピング)
   Service ──▶ Pod

7. ストレージ(CSI)

コンテナは本質的に揮発性です。状態を保つには外部ボリュームが必要で、Kubernetes は **CSI(Container Storage Interface)**という標準でさまざまなストレージを接続します。

   PersistentVolumeClaim (PVC)   ◀── アプリが「これだけの容量が欲しい」と要求
            │  バインド
   PersistentVolume (PV)         ◀── 実際の保存資源(動的/静的プロビジョニング)
            │  CSI ドライバ経由
   外部ストレージ(ブロック/ファイル/オブジェクト)
   StorageClass(プロビジョニング方針を定義)
        │  PVC が参照
   動的プロビジョニング: PVC 生成時に PV を自動で作りバインド

この抽象のおかげで、アプリは「ストレージが必要」とだけ宣言し、実際にどのストレージかは運用者が StorageClass で決めます。


8. 拡張 — CRD とオペレーター

Kubernetes の真の力は 拡張性です。CRD(Custom Resource Definition)で新しいオブジェクト種を定義し、オペレーターでそのオブジェクトを調整するコントローラを自作できます。

   CRD を定義 ──▶ 新リソース種を登録(例: Database)
   ユーザーが Database オブジェクトを生成(desired state を宣言)
   オペレーター(カスタムコントローラ)が watch
        │  調整ループを実行
   実際の DB ポッド/ストレージ/バックアップを desired state に合わせて生成・運用

オペレーターパターンは「Kubernetes の調整ループの原理を任意のドメインに適用する」ものです。データベース、メッセージキュー、ML ワークロードなど複雑な運用知識をコードに収めて自動化します。


9. 運用でよく出会う落とし穴

[ ] リソース requests/limits 未設定 ─▶ ノード過負荷、予測不能な退避(eviction)
[ ] liveness/readiness プローブ不在 ─▶ 死んだポッドにトラフィック流入
[ ] etcd バックアップ未実施 ─▶ コントロールプレーン障害時に復旧不可
[ ] 単一ノード/単一コントロールプレーン ─▶ 可用性が脆弱(多重化を推奨)
[ ] 無分別な権限(RBAC) ─▶ セキュリティリスク、最小権限の原則を適用
[ ] イメージタグ latest の乱用 ─▶ 再現不可、明示的なタグ/ダイジェストを推奨

特に requests/limits の設定は運用安定性の基礎です。requests はスケジューラが配置を決める根拠になり、limits は一つのポッドがノードを食い潰さないよう防ぎます。


10. ヘルスプローブとポッドのライフサイクル

自己修復が実際に機能するには、クラスタが「ポッドは生きているか、トラフィックを受ける準備ができたか」を知る必要があります。これを判定するのが三種類のプローブです。

┌────────────────────────────────────────────────────────────┐
│ Liveness Probe(生きているか?)                            │
│   失敗 ─▶ kubelet がコンテナを再起動                         │
│   用途: デッドロック/ハングの検知                            │
├────────────────────────────────────────────────────────────┤
│ Readiness Probe(受ける準備ができたか?)                   │
│   失敗 ─▶ Service エンドポイントから除外(トラフィック遮断) │
│   用途: ウォームアップ/一時的過負荷の間トラフィックを保護    │
├────────────────────────────────────────────────────────────┤
│ Startup Probe(起動し切ったか?)                           │
│   成功するまで liveness/readiness を保留                    │
│   用途: 起動の遅いレガシーアプリを保護                       │
└────────────────────────────────────────────────────────────┘

ポッドの状態も定められたライフサイクルに従います。各フェーズの意味を知れば kubectl get pods の出力がずっと読みやすくなります。

   Pending ──▶ スケジュール/イメージ取得を待機中
   Running ──▶ コンテナがノードで実行中
      ├──▶ Succeeded ──▶ (Job のように)正常終了
      └──▶ Failed    ──▶ 異常終了
   (Unknown) ──▶ ノードと通信できない

終了フローと graceful shutdown

ポッドを削除しても即座には死なず、定められた順序で片付けられます。この流れを理解すれば無停止デプロイの秘訣が見えてきます。

   削除要求
   1) Service エンドポイントから除外(新規トラフィック遮断)
   2) preStop フック実行(あれば)+ SIGTERM 送信
   3) grace period の間、処理中リクエストの完了を待つ
   4) 期限超過なら SIGKILL で強制終了

readiness プローブで先にトラフィックを切り、graceful 終了で処理中リクエストを仕上げる組み合わせが、ローリングアップデート中でもユーザー体験を滑らかに保つ鍵です。


おわりに

Kubernetes の複雑に見える構成要素は、実は一つの単純な原理に収束します。ユーザーは 望む状態を宣言し、すべてのコンポーネントは api-server を通じて状態を watch し、コントローラは現実をその状態に合わせるよう 調整します。この宣言的モデルと調整ループが、自己修復、スケーラビリティ、そして CRD/オペレーターによる無限の拡張性の基礎です。

コンポーネントの名前を暗記するより、「この変化が誰の watch を刺激し、どんな調整を引き起こすか」を追う習慣をつければ、クラスタの動作はずっと明快になります。細かな挙動と機能はバージョンやディストリビューションによって変わり得るため、実運用では公式ドキュメントを併せて参照することをおすすめします。


参考資料