Kubernetes とはなにか
Kubernetes(k8s)は、宣言的な API と 継続的な収束(reconciliation) を使って、システム全体を望ましい状態に保ち続けるための計算基盤。
個別の機能を追い始めると、Deployment、Service、Ingress、CNI、CSI など用語が多く、全体像を見失いやすい。そのため、この記事では、Kubernetes の全体や歴史をまとめていく。
- Kubernetes は、API を通じて状態を宣言し、controller 群がそれを収束させるシステム
- 中核は
kube-apiserver、etcd (storage)、controller、scheduler、kubeletの連携にある - 実行・通信・永続化はそれぞれ CRI / CNI / CSI などのインタフェースで抽象化されている
- 設計思想の背景には Borg / Omega のような大規模クラスタ管理の知見がある
主要コンポーネントの役割分担は次のように整理できる。
つまり Kubernetes の本質は「コンテナ実行」そのものより、状態管理と収束の仕組み にある。
全体の処理の流れ
まずは、典型的なリクエストの流れを 1 本で見る。
- ユーザー が
kubectl/ API client で kube-apiserver にリクエスト - kube-apiserver が認証/認可/Admission を通し、etcd に永続化
- Controller が Watch で差分を検知し、足りないものを作る/直す(収束)
- Scheduler が未割当Podに Node を割り当てる(Binding)
- kubelet が PodSpec を見てコンテナを作り、CNI/CSI を呼び出して
実行可能な状態にする - kube-proxy(または eBPF 実装)が Service の経路制御を構成し、名前解決(CoreDNS)などのアドオンが補助
ここで重要なのは、Kubernetes が何かを 1 回だけ実行して終わるシステムではないことだ。状態を書き込み、その後も controller や kubelet が継続的に監視し、ズレがあれば直し続ける。
この継続的な制御があるから、Pod が落ちても再作成され、ノード障害が起きても別ノードに再配置される。
結果整合性 (Eventual Consistency)
Kubernetes は、システム全体の状態を即座に一致させるのではなく、最終的に一致することを目指す 結果整合性 のモデルを採用している。これにより、部分的な障害や遅延があっても、最終的には望ましい状態に収束する。
ただし、これは Kubernetes の設計原則であって、すべての controller 実装が自動的にその性質を満たすわけではない。特に custom controller / operator では、外部 API への副作用、冪等性のない更新、再試行時の重複実行を無造作に入れると、結果整合的に収束しない実装も作れてしまう。そのため、controller を作るときは「何度 reconcile されても同じ望ましい状態に近づくか」を意識する必要がある。
コンポーネント一覧
「どのプロセスが、どの責務を持つか」を粗く把握しておくと、障害時の切り分けがかなり速くなる。
Control Plane(コントロールプレーン)
| コンポーネント | 主な責務 | 典型的な依存 | よくある論点 |
|---|---|---|---|
| kube-apiserver | Kubernetes API の提供、認証/認可/Admission、永続化の入口 | etcd | QPS/Watch負荷、Admission遅延、オブジェクト肥大化 |
| etcd | クラスタ状態の永続ストア(強整合) | Raft | ストレージ、スナップショット、断片化、バックアップ設計 |
| kube-scheduler | Pod を Node に割り当て | apiserver | スケジューリング戦略、拡張(Extender/Plugins)、分散配置 |
| kube-controller-manager | 各種 controller の実行(Deployment/ReplicaSet/Node/Endpoint等) | apiserver | 再試行/収束、レート制限、ワークキュー設計 |
| cloud-controller-manager | クラウド依存の制御(LB/Route/Node等) | クラウドAPI | API制限/遅延、権限、クラウド差分 |
Control Plane の高可用性を考えるときは、結局 apiserver の冗長化 と etcd のクォーラム維持 が中核になる。
etcd 以外の選択肢
原理的には、Kubernetes が必要としているのは「etcd そのもの」ではなく、強整合な永続ストア と Watch / transaction / revision 管理 のような API Machinery が前提にしている性質だ。そのため、概念的には同等の性質を持つ分散 KVS やデータストアで置き換える余地はある。
ただし、実際の upstream Kubernetes では、apiserver の永続化先として etcd が事実上の標準であり、素の構成で現実的に差し替える前提にはなっていない。別のストアで実現しようとすると、storage layer の互換実装やフォーク、あるいはマネージドサービス側の独自実装が必要になることが多い。
Node(ワーカーノード側)
| コンポーネント | 主な責務 | 典型的な依存 | よくある論点 |
|---|---|---|---|
| kubelet | Pod の実体化(CRI 経由でコンテナ起動)、Probe/Status更新 | CRI runtime, apiserver | cgroup/リソース隔離、Eviction、証明書/認証 |
| Container Runtime | コンテナの実行(containerd/CRI-O 等) | OCI | 画像配布、pull latency、runtime差分 |
| kube-proxy | Service の L4 経路制御(iptables/ipvs等) | OS networking | ルール肥大化、SNAT、IPVS調整 |
| CNI plugin | Pod ネットワークの構成 | Linux netns | NetworkPolicy、MTU、VXLAN等のオーバレイ |
| CSI node plugin | ボリュームの attach/mount | ストレージ | マウント失敗、デバイス枯渇、再接続 |
ノード側は「Pod をちゃんと動かし続けられるか」が責務で、ここには Linux カーネル機能やランタイム差分が強く影響する。コントロールプレーンだけ見ていても解けない障害は多い。
Container Runtime の選択肢
CRI を満たしていても、実装ごとに運用上の性質はかなり異なる。
containerd: 現在もっとも一般的。Kubernetes との組み合わせ実績が多く、機能とエコシステムのバランスがよいCRI-O: Kubernetes 専用寄りに絞った実装。構成が比較的シンプルで、OpenShift 系の文脈でもよく使われるcri-dockerd経由の Docker Engine: Docker の操作性や既存資産は活かしやすいが、Kubernetes の標準経路からは少し遠く、いまは第一選択になりにくいgVisorやKata Containersのような隔離強化系: 通常の runtime より分離性を高めやすい一方、性能や運用複雑性とのトレードオフがある
実務上は、次のような観点で差が出る。
- 起動オーバーヘッドや image pull 時間
- cgroup / namespace / seccomp まわりの挙動差
- ログ、デバッグ、トラブルシュートの容易性
- GPU や特殊デバイス、sandbox 実行への対応
- 既存ディストリビューションやマネージド Kubernetes との相性
そのため、「どの runtime が最適か」よりも、「標準的なワークロードを安定して回したいのか」「分離性を優先したいのか」「既存運用資産を活かしたいのか」で選ぶのが実態に近い。
Add-ons(クラスタによく入る追加要素)
- CoreDNS: Service 名解決(kube-dns の後継)
- Ingress Controller / Gateway API 実装: L7 入口(nginx/Envoy 等)
- metrics-server: HPA のためのリソースメトリクス
- CNI/CSI コントローラ側コンポーネント(プロバイダ実装次第)
- クラスタ監視(Prometheus)、ログ収集(Fluent Bit 等)、トレース(OTel)
Kubernetes は素の状態では「クラスタ運用の核」だけを提供し、監視、L7 入口、詳細なポリシー、ストレージ機能は周辺コンポーネントで補うことが多い。
素の Kubernetes はなにもできない
少し極端に言えば、素の Kubernetes は Pod を配置して維持する仕組み までは持っているが、アプリケーション基盤として期待されがちな機能はほとんど揃っていない。
たとえば、Service という抽象はあっても、名前解決をまともに使うには CoreDNS のような DNS add-on がほぼ前提になる。HTTP / HTTPS の入口も同様で、Ingress や Gateway API のリソース定義だけでは実際のトラフィック処理はできず、nginx や Envoy などの controller 実装が別途必要になる。
つまり、素的にKurnetes だけでは「名前でつながる」「HTTP を受ける」「メトリクスを集める」「証明書を配る」「ログを集約する」といった、実運用で当然ほしくなる機能は自然には揃わない。Kubernetes は万能な PaaS というより、そうした機能を後から組み合わせるための基盤と見たほうが実態に近い。
設計思想
1) 宣言的APIと「望ましい状態」
Kubernetes の基本単位は「実行手順」ではなく、望ましい状態(desired state) を表すオブジェクトだ。
- YAML を書く = 手順を書く、ではなく「状態の宣言」をする
- 望ましい状態と現実のズレを埋めるのは controller(人間ではない)
ここを理解しないまま使うと、「apply したのになぜ勝手に戻るのか」「なぜ Pod を消しても復活するのか」が直感に反して見える。逆にここを理解すると、Kubernetes の挙動はかなり素直になる。
宣言的 API について
宣言的 API とは、「どう実行するか」ではなく「最終的にどうなっていてほしいか」を表現する API だ。Kubernetes では、この考え方がほぼすべての主要リソースに貫かれている。
たとえば Deployment では、「nginx の Pod を 3 個動かしたい」と宣言する。ここで書くのは手順ではなく、目標状態だ。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
この YAML から分かるのは、「Pod を 3 個にしたい」「この image を使いたい」という要求だけであり、「まず Pod を 1 個作って、そのあと 2 個増やして、失敗したら再試行する」といった手順は書かない。手順の責務は Deployment controller や ReplicaSet controller 側にある。
このとき Kubernetes API では、ざっくり言うと spec が望ましい状態、status が現在観測されている状態に相当する。たとえば spec.replicas: 3 なのに、実際には Pod が 2 個しか動いていなければ、その差分を controller が埋めにいく。逆に Pod を 1 個手で消しても、API 上の望ましい状態は変わっていないため、最終的には 3 個へ戻ろうとする。
Service や Job、StatefulSet、HorizontalPodAutoscaler なども同じ発想で理解できる。Kubernetes を使うとは、個々のコマンド実行を積み上げることではなく、API に対して望ましい状態を宣言し、その収束をシステムに任せることだ。この理解を持つと全体像を掴みやすい。
補足すると、Kubernetes にも命令的に見える操作はある。たとえば kubectl scale deployment web --replicas=5 は一見すると「5 個に増やす操作」に見えるが、実際には Deployment の spec.replicas を 5 に更新しているだけで、やっていること自体は望ましい状態の書き換えだ。
2) Controller パターン(reconciliation loop)
controller は概ね次の擬似コードで表せる。
while true:
desired = read_from_apiserver()
observed = observe_world()
diff = desired - observed
apply(diff)
重要なのは、「1 回で成功しなくてもよい」ことと、「何度でも再実行される」ことが前提になっている点だ。だからこそ、エラー処理、再試行、冪等性、レート制限が設計の中心に来る。
exponential backoff について
再試行が前提の controller では、失敗した瞬間から即座に無限リトライすると、API server や外部依存先へ過剰な負荷をかけてしまう。そこで使われるのが exponential backoff で、失敗回数に応じて再試行間隔を徐々に伸ばしていく。
たとえば 1 秒後、2 秒後、4 秒後、8 秒後というように待ち時間を増やせば、一時的な障害があるときに無駄な再試行を連打せずに済む。Kubernetes の controller が workqueue や rate limiter を重視するのはこのためで、単に「いつか成功するまで回す」のではなく、「壊れている間はシステム全体を悪化させない」ことも設計の一部になっている。
これは custom controller / operator を書くときにも重要で、外部 API 呼び出しや重い処理を失敗のたびに即時再実行すると、自分の controller が障害増幅器になりやすい。収束を急ぐことより、失敗時に穏やかに振る舞うことのほうが、結果として安定して収束しやすい。
3) API Machinery(型・バージョン・拡張)
- Group/Version/Kind(GVK)と、内部表現(Internal)
- CRD による拡張(ただし、API を増やすは管理/処理コストも増える)
- admission(mutating/validating)でポリシーやデフォルトを注入
ここが分かっていると、「どこでデフォルトが入ったのか」「どこで拒否されたのか」「なぜ v1beta1 ではなく v1 で見えるのか」といった変換点を追いやすい。
4) Interface 分離(CRI/CNI/CSI)
Kubernetes 自体は、実行・通信・永続化を実装は知らない形で差し替えられるようになっている。
- CRI(Container Runtime Interface): kubelet ↔ runtime
- CNI(Container Network Interface): Pod ネットワーク構成
- CSI(Container Storage Interface): ストレージ
ポイントは、Kubernetes が抽象と接続面を定義し、実装の多くをエコシステム側に委ねていることだ。便利さの源泉でもあるが、同時に「実装差分で挙動が変わる」わかりづらさの原因でもある。
背景として読むと理解が深まる資料
Kubernetes の思想や基盤技術を理解するうえで有用な資料をテーマ別にまとめる。
クラスタマネジメント(Google由来)
- Borg: Large-scale cluster management at Google with Borg(EuroSys 2015)
- 大規模クラスタ運用の要件や設計感覚を掴みやすい
- Omega: Omega: flexible, scalable schedulers for large compute clusters(2013)
- 複数スケジューラや楽観的並行制御の背景理解に役立つ
分散合意 / 永続ストア(etcdの前提知識)
- Raft: In Search of an Understandable Consensus Algorithm (Raft)(Ongaro & Ousterhout, 2014)
- etcd を理解するうえで必須
コンテナ(隔離と実行単位)
- Linux の namespaces / cgroups のドキュメント(論文というより一次情報)
- kubelet や runtime が前提にしている隔離の原理を確認できる