Kubernetes特集
夜の港の管制室から、コンテナヤードとクレーンを見下ろしている

Kubernetes特集 | 第2章(Day 3 / 6)

Kubernetesアーキテクチャ
YAMLがコンテナになるまでを追う

API Server・etcd・Scheduler・Controller・kubelet。5つの部品が何を頼み合っているかがわかると、
「どの層で止まっているか」を自分で切り分けられるようになります。

📅 Day 3 | 10/21(水)公開

3日目です。昨日の第1章では、Podを直接作ると誰も見張らず、Deploymentという見張り役を付けると消しても戻ってくることを確かめました。今日は、その「見張り役」と「裏側の部品たち」を分解します。kubectl apply と打ってからコンテナが動き出すまでに、誰が何をしているのか。それが見えると、症状を見た瞬間に「止まっているのはどの層か」を言えるようになります。

昨日の演習の続きになっているので、第1章を読んでいない方は、先に「最初のPodを動かす」と「kubectlで状態を読む」だけ目を通しておいてください。

この章の結論

💡 Kubernetesは「YAMLを実行する」のではなく、APIに保存された望ましい状態(spec)と、いま観測されている状態(status)を、複数の制御ループが比べて差を埋め続けている仕組みです。API Serverは唯一の窓口、etcdは保存庫、SchedulerはNodeを選ぶ係、Controllerは差を埋める係、kubeletはNodeの上で実際に動かす係。障害調査は、この5つのどこまで進んだかを切り分けることから始まります。

この章では部品の名前を暗記してもらうつもりはありません。代わりに、1本のDeploymentを作って、イベントの1行1行が「誰の仕事か」を読めるようにします。読めるようになると、Pending と ImagePullBackOff が別の層の話だと、見た瞬間にわかります。

✅ この章を読み終えるとできること

  • kubectl apply からコンテナ起動までの流れを、図2-1を指さしながら6ステップで説明できる
  • kubectl get events の1行ずつが、Controller・Scheduler・kubeletのどの仕事か読める
  • Deployment → ReplicaSet → Podの親子関係を、ownerReferences で確かめられる
  • Pending・イメージ取得失敗・Forbidden を「止まっている層」で切り分けられる
  • Control Planeが止まったとき、続くことと止まることを分けて説明できる

5つの部品と、それぞれの仕事

特集トップの図1で「司令塔」とまとめたControl Planeの中身と、Node側の部品を、仕事で分けると次の表になります。

部品いる場所仕事かなり雑に言うと
API ServerControl Planeすべての読み書きを受け付ける。認証・認可・検証をして、etcdに保存する港の受付窓口。誰もここを通らずには何も頼めない
etcdControl Plane望ましい状態と観測した状態を保存する分散データベース台帳。ここが消えると、何を頼まれていたか誰もわからなくなる
SchedulerControl PlaneまだNodeが決まっていないPodを見つけて、置き場所を1つ選ぶ配置係。「どの岸壁に下ろすか」を決めるだけで、自分では運ばない
Controller ManagerControl PlaneDeployment、ReplicaSet、Node、Jobなど、種類ごとの見張り役をまとめて動かす見張り役の詰め所。台帳と現実を見比べて、足りなければ作る
kubelet各Node自分に割り当てられたPodを、コンテナランタイムに起動させ、結果を報告する現場の作業者。実際にコンテナを動かすのはここだけ

ほかにも、Serviceの通信を各Nodeで振り分けるkube-proxyや、クラウドと連携するCloud Controller Managerがいますが、第2章ではこの5つに絞ります。kube-proxyは第4章で扱います。

YAMLがコンテナになるまでの6ステップ

第1章でDeploymentを作ったとき、裏では次の順番で仕事が回っていました。図2-1の番号を追ってください。

Control Plane(司令塔) kubectl apply -f API Server 認証・認可・検証 唯一の窓口 etcd 「apiを2個」と保存 Controller Manager Deployment → ReplicaSet ReplicaSet → Pod ×2 Scheduler Nodeが未定のPodに Nodeを1つ書き込む Node(作業者のマシン) kubelet 自分宛のPodを 見つけて起動を頼む コンテナランタイム containerd など イメージ取得・起動 コンテナ nginx が 動き出す ⑤ 自分宛のPodを読む ⑥ 起動結果を報告する 1 2 3 4 5

図2-1: kubectl applyからコンテナ起動まで。1〜4はControl Plane、5はNode、6は報告。

  1. kubectlがAPI Serverに送る。 YAMLはHTTPのリクエストになってAPI Serverへ届きます。あなたが話す相手は、いつもAPI Serverだけです。
  2. API Serverがetcdに保存する。 認証・認可・検証を通ったDeploymentが、望ましい状態として台帳に書かれます。apply が「成功」と返すのはこの時点で、まだコンテナは1つも動いていません。
  3. Controllerが子を作る。 Deploymentの見張り役が「このDeploymentにはReplicaSetが要る」と気づいてReplicaSetを作り、ReplicaSetの見張り役が「Podが2個要る」と気づいてPodを2個作ります。この時点のPodには、まだNodeが書かれていません。
  4. SchedulerがNodeを決める。 Nodeが未定のPodを見つけ、条件に合うNodeを1つ選んで、Podの nodeName に書き込みます。運ぶのではなく、台帳に「置き場所」を書くだけです。
  5. kubeletがランタイムに頼む。 各Nodeのkubeletは、自分の名前が書かれたPodを見つけると、containerdなどのコンテナランタイムに、イメージの取得とコンテナの起動を頼みます。
  6. kubeletが報告する。 起動できたか、Readyか、何回再起動したかを、kubeletがAPI Serverへ書き戻します。あなたが kubectl get pod で見ている STATUS は、この報告です。

📌 部品どうしは直接話しません。 ControllerもSchedulerもkubeletも、API Serverの台帳を読んで、自分の担当の差を見つけて、結果を台帳に書き戻すだけです。この「全員が台帳を見て動く」形のおかげで、どれか1つが止まっても、ほかの部品は台帳を読み続けられます。

イベントで6ステップを追う

ここから手を動かします。昨日と同じく、まずContextを確かめて lab を作ってください。

kubectl config current-context      # kind-xxx / minikube / docker-desktop などが出ればOK
kubectl create namespace lab

次のDeploymentを deploy.yaml として保存します。第1章の create deployment と同じ内容を、今回はYAMLで書きます。

# deploy.yaml ― 「nginxのPodを2個、いつもあるように」という望ましい状態
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app.kubernetes.io/name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: api      # このラベルのPodを「自分の子」とみなす
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api    # selectorと一致していないと適用できない
    spec:
      containers:
        - name: api
          image: nginx:stable
          ports:
            - containerPort: 80
          resources:
            requests:                  # Schedulerが置き場所を選ぶときの「必要量」
              cpu: 100m
              memory: 64Mi

このYAMLを適用すると、裏で起きること

  1. API Serverが selector と template のラベルが一致しているかなどを検証し、etcdに保存する
  2. Deployment Controllerが、ReplicaSet api-<ハッシュ> を1つ作る
  3. ReplicaSet Controllerが、Pod api-<ハッシュ>-<ランダム> を2個作る
  4. Schedulerが、requests の分だけ空きがあるNodeを選んで2個のPodに書き込む
  5. 各Nodeのkubeletが nginx:stable を取得して起動し、Readyになったと報告する
kubectl -n lab apply -f deploy.yaml

# 親子3世代を一度に見る
kubectl -n lab get deploy,rs,pod -o wide

# 出来事を古い順に並べる(新しいものが下に来る)
kubectl -n lab get events --sort-by=.lastTimestamp

イベントは、次のような順番で並ぶはずです(名前やNodeは環境によって違います)。

# 出力の例(列を一部省略)
REASON              OBJECT                      MESSAGE
ScalingReplicaSet   deployment/api              Scaled up replica set api-7d9c6b5f4 to 2
SuccessfulCreate    replicaset/api-7d9c6b5f4    Created pod: api-7d9c6b5f4-x8k2q
SuccessfulCreate    replicaset/api-7d9c6b5f4    Created pod: api-7d9c6b5f4-m4n7z
Scheduled           pod/api-7d9c6b5f4-x8k2q     Successfully assigned lab/api-7d9c6b5f4-x8k2q to kind-worker
Pulling             pod/api-7d9c6b5f4-x8k2q     Pulling image "nginx:stable"
Pulled              pod/api-7d9c6b5f4-x8k2q     Successfully pulled image "nginx:stable"
Created             pod/api-7d9c6b5f4-x8k2q     Created container api
Started             pod/api-7d9c6b5f4-x8k2q     Started container api

この1行1行に、担当者の名前を付けてみます。

REASON誰の仕事か図2-1のステップ
ScalingReplicaSetDeployment Controller(Controller Managerの中)3
SuccessfulCreateReplicaSet Controller(同上)3
ScheduledScheduler4
Pulling / Pulledkubelet(コンテナランタイムに取得を頼んだ)5
Created / Startedkubelet(コンテナランタイムに起動を頼んだ)5

障害のときは、この表のどこで行が途切れているかを見ます。Scheduled が出ていなければSchedulerの手前、Scheduled はあるのに Pulled がなければkubeletかレジストリ、という読み方です。第1章の演習1で「どこまでは成功したか」と書いたのは、このことでした。

API Serverは唯一の窓口

kubectlも、Controllerも、kubeletも、etcdに直接書きには行きません。全員がAPI Serverを通ります。そのおかげで、認証、権限、検証、監査ログを1か所でそろえられます。API Serverの中では、1つのリクエストが次の関所を順番に通ります。

API Server の中 ① 認証 あなたは誰か 証明書・トークン ② 認可 それをしてよいか RBAC ③ Admission ルールに合うか 書き換え・拒否 ④ 検証 項目名や型が 正しいか ⑤ 保存 etcd に書く ここで apply 成功 止まると Unauthorized 止まると Forbidden 止まると admission webhook ... denied the request 止まると error validating unknown field ... 通ると deployment.apps/api created

図2-2: API Serverの関所。どこで止まったかは、kubectlのエラー文でほぼ特定できる。

関所の順番を知っていると、エラー文を見ただけで切り分けが進みます。Unauthorized ならkubeconfigや証明書の期限、Forbidden なら権限、error validating ならYAMLの項目名です。Admissionは、クラスター管理者が「このラベルがないPodは拒否する」「このレジストリ以外のイメージは拒否する」といったルールを差し込む場所で、ここで止まると、YAMLは正しいのに適用できない、という症状になります。

# 自分がその操作をしてよいか、適用する前に聞ける(②認可の確認)
kubectl auth can-i create deployments -n lab
kubectl auth can-i delete nodes

# YAMLの項目名が正しいか、クラスターに何も作らずに確かめる(④検証の確認)
kubectl -n lab apply --dry-run=server -f deploy.yaml

--dry-run=server は、昨日使った --dry-run=client と違って、API Serverまでリクエストを送り、関所をすべて通してから「保存だけしない」で返してくれます。Admissionで拒否されるかどうかも、これで事前にわかります。

⚠️ 環境依存: Admission Webhookは、外部のプログラムに判断を委ねる仕組みです。そのプログラムが落ちているときに「拒否する(Fail)」か「通す(Ignore)」かは、Webhookごとに failurePolicy で決まっています。本番で「Webhookが落ちたせいで、全Namespaceで何も適用できなくなった」という障害は珍しくありません。導入するときは、落ちたときの挙動を先に決めてください。

etcdは台帳

etcdは、Kubernetesのすべての状態を保存する分散キーバリューストアです。DeploymentやServiceの定義だけでなく、Secretの値、Podにどのnodeが割り当てられたか、Controllerが管理している途中経過まで、全部ここにあります。

第1章で「Kubernetesは状態を頼む仕組み」と書きましたが、その状態が置かれている場所がetcdです。Nodeの上でコンテナが動いていても、etcdが失われると、「何個あるべきだったか」「どのServiceがどのPodを指していたか」を誰も思い出せなくなり、クラスターとして組み直せなくなります。

環境etcdを守るのは誰かあなたが確認すること
kind / minikube誰も守らない(学習用)消えても作り直せばよい。だから壊す練習ができる
自前で構築したクラスターあなたetcdctl snapshot save の定期実行、復元テスト、暗号化、保管期間
EKS / GKE / AKS などマネージドクラウド事業者事業者が守るのはControl Planeの中だけ。アプリのデータ(PVCの中身)は別

⚠️ etcdのバックアップと、データのバックアップは別物です。 マネージドKubernetesで「バックアップは事業者がやってくれる」と聞いたとき、それはetcd、つまり「何を頼んでいたか」の台帳の話です。データベースのPodが使っているディスク(PVC)の中身は含まれません。第4章で、PVCのスナップショットを別に設計します。

SchedulerはNodeを選ぶだけ

Podが作られた直後は、まだどのNodeにも置かれていません。Schedulerは、この「Node未定」のPodを見つけると、全Nodeを2段階でふるいにかけます。

Pod(Node未定) requests: cpu 100m memory 64Mi 1段目: フィルタ(除外) Node A ✗ CPUの空きが requests に足りない Node B ✗ Taint があり、Podに Toleration がない Node C ○ 空きあり、Taint なし、ポート競合なし 2段目: スコア(点数) 残ったNodeを、空き具合や 同じアプリの分散度で採点し いちばん高いNodeを1つ選ぶ Node C に決定 Pod の nodeName に書き込む 残りが 0 なら Pending のまま。理由は Events に書かれる

図2-3: Schedulerはまず「置けないNode」を除外し、残りを採点して1つ選ぶ。

フィルタで見るのは、requests に対するCPUとメモリの空き、NodeのTaint(「特別な許可がないPodは乗せない」という印)とPodのToleration(その許可)、Nodeを指名するAffinity、ホストのポート競合、ボリュームの制約などです。ここで注意してほしいのは、Schedulerが見るのは「実際の使用量」ではなく「requestsの合計」だということです。Nodeのメモリが実際には空いていても、すでに置かれたPodのrequestsの合計で埋まっていれば、置けません。

だから Pending のときに「Nodeを増やす」の前に、まず requests の値が妥当かを見ます。開発者が「念のため」と大きめに書いたrequestsが、クラスター全体の空きを食いつぶしていることはよくあります。

# 各NodeのCPU・メモリの「requestsの合計」と「上限」を見る(Allocated resources の欄)
kubectl describe node <Node名>

# NodeにTaintが付いているか
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

⚠️ Tolerationを安易に足さないでください。 Taintの付いたNodeに乗せたいとき、Tolerationを書けば乗ります。でもTaintは「ここは特別なNodeだ」という管理者の意思表示です。意味を確かめずにTolerationを付けると、重要なアプリがControl Planeと同じNodeに載ってしまったり、GPU専用Nodeを普通のアプリが埋めてしまったりします。

Controllerは差を埋め続ける

第1章で「見張り役」と呼んでいたのがControllerです。Deployment、ReplicaSet、Node、Jobなど、リソースの種類ごとに専用の見張り役がいて、Controller Managerという1つのプロセスの中でまとめて動いています。それぞれの仕事は同じ形です。台帳の「望ましい状態」と「観測した状態」を比べて、差があれば1つ埋める。これをずっと繰り返す。

Deployment「api」 spec.replicas: 2 / image: nginx:stable 作る(owner) ReplicaSet「api-7d9c…」 replicas: 2 / selector: name=api Pod …x8k2q Running Pod …m4n7z 落ちた(消えた) 子は metadata.ownerReferences に親を持つ ReplicaSet Controller の制御ループ(ずっと回っている) ① 台帳を読む 望ましい数: 2 ② 現実を数える selectorに合うPod: 1 ③ 差を計算する 2 − 1 = 1個足りない ④ 1個だけ作る 新しい名前のPodを台帳に書く 作ったPodにNodeを決めるのはScheduler、起動するのはkubelet。 Controllerは「台帳に1個書く」ところまでで、あとは待つ。 差が0になるまで、何度でも①に戻る

図2-4: 親子関係と制御ループ。Podが1個消えると、差の1個だけを作り直す。

親子関係は、台帳の上で確かめられます。子のリソースには metadata.ownerReferences に親の名前が書かれていて、ReplicaSetはこれと selector を使って「自分の子」を数えます。

# ReplicaSetの親はDeployment、Podの親はReplicaSet
kubectl -n lab get rs,pod -o custom-columns=KIND:.kind,NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,OWNER_NAME:.metadata.ownerReferences[0].name
# 出力の例
KIND         NAME                    OWNER        OWNER_NAME
ReplicaSet   api-7d9c6b5f4           Deployment   api
Pod          api-7d9c6b5f4-x8k2q     ReplicaSet   api-7d9c6b5f4
Pod          api-7d9c6b5f4-m4n7z     ReplicaSet   api-7d9c6b5f4

Deploymentを更新すると、Deployment Controllerは既存のReplicaSetを書き換えるのではなく、新しいReplicaSetをもう1つ作り、古い方の数を減らしながら新しい方の数を増やします。これが「ローリングアップデート」で、古いReplicaSetは数が0になっても残るので、戻せます。使い方は明日の第3章で扱います。

📌 同じYAMLを何度applyしても、結果は同じになります。 Controllerは「差があれば埋める」だけなので、差が0なら何もしません。これが宣言的な運用の安心感です。逆に言うと、台帳の外で現実を変えると、Controllerはそれを「差」とみなして元に戻そうとします。Podを手で書き換えると数が合わなくなるのは、このためです(演習3で試します)。

kubeletとコンテナランタイム

kubeletは各Nodeで動くエージェントです。API Serverの台帳を見て、自分のNode名が書かれたPodを見つけると、CRI(Container Runtime Interface)という共通の取り決めを通して、containerdなどのコンテナランタイムに頼みます。イメージを取得して、コンテナを作って、起動して、止めて、ログを管理するのはランタイムの仕事です。

切り分けの観点では、ここが大事です。Podに Scheduled のイベントがあるのに起動しないなら、API ServerやSchedulerの話はもう終わっていて、Node側の話に入っています。 見る場所は、kubelet、コンテナランタイム、レジストリへの通信、Nodeのディスク容量、CNI(ネットワーク)、CSI(ストレージ)です。

Node側の症状疑う場所見るコマンド
ImagePullBackOffレジストリの認証、Nodeからの外向き通信、イメージ名、CPUアーキテクチャdescribe pod のEvents
ContainerCreating が長いボリュームのマウント(CSI)、ネットワークの用意(CNI)describe pod のEvents、get pvc
CrashLoopBackOffアプリ自身(設定が読めない、依存先に届かない)logs --previous、Last State の Exit Code
Nodeが NotReadykubeletの停止、ディスク枯渇、ランタイムの異常get nodes、describe node のConditions
止めるのに30秒かかるアプリがPID 1でSIGTERMを受け取っていないDockerfileのENTRYPOINT、アプリの終了処理

最後の行は、Kubernetesの問題に見えて実際はコンテナの中身の問題、という典型です。kubeletはコンテナを止めるときにまずSIGTERMを送り、既定で30秒待ってから強制終了します。アプリがシェル経由で起動されていてシグナルを受け取れないと、毎回30秒待たされ、ローリングアップデートがやたら遅くなります。Dockerfileの書き方を直せば解決する話で、Kubernetesの設定をいじっても直りません。

Manifestを「構造」として読む

ここまでの部品がやり取りしているのは、全部APIオブジェクトです。YAMLを「設定ファイル」ではなく、台帳の1ページとして読み直してみます。どのリソースも、次の4〜5つの区画でできています。

区画誰が書くか何が入るか
apiVersion / kindあなたどの種類のリソースか。apps/v1 の Deployment など
metadataあなた(+Kubernetes)name(識別子)、namespace(範囲)、labels(選び出す用)、annotations(メモ用)。uid や ownerReferences はKubernetesが足す
specあなた望ましい状態。Controllerが実現しようとする目標
statusControllerとkubelet観測した状態。何個Readyか、どのNodeにいるか、条件(Conditions)

spec はあなたの注文、status は現場からの報告です。status をYAMLに手で書いて apply しても、Controllerが報告で上書きするので意味がありません。逆に、kubectl get -o yaml で取り出したものには status も、Kubernetesが補った既定値も入っているので、それをそのままGitに戻すとファイルが膨らみ、差分が読めなくなります。

# 台帳の実体を読む(statusと既定値が足されている。手元のYAMLとは別物)
kubectl -n lab get deployment api -o yaml

# 手元のYAMLと台帳の差だけを見る(Gitのdiffと同じ感覚)
kubectl -n lab diff -f deploy.yaml

# どの項目を誰が書いたかを見る(通常は省略されている)
kubectl -n lab get deployment api -o yaml --show-managed-fields | less

status の中でも conditions は読む価値があります。Deploymentなら Available(必要数がReadyか)と Progressing(更新が進んでいるか)の2つがあり、「applyは成功したのに使えない」ときの最初の手がかりになります。

kubectl -n lab get deployment api -o jsonpath='{range .status.conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'

⚠️ 「applyが成功した」と「アプリが使える」は別です。 applyの成功は、図2-2の⑤「台帳に書けた」という意味しかありません。コンテナが動き、Readyになり、ServiceのEndpointに入り、ヘルスチェックに応えて、初めて「使える」です。確認の順番は第1章と同じで、存在 → Ready → エンドポイント → イベントです。

Namespace・Label・Annotationの使い分け

項目何のため気をつけること
Namespace名前の衝突を避け、権限や上限(ResourceQuota)の範囲を区切るNode、StorageClass、ClusterRoleはNamespaceの外にある。Namespaceを分けただけでは通信も権限も隔離されない(第4章・第5章)
Label選び出すためのタグ。ServiceやReplicaSetのselectorが見る後から変えると「誰の子か」が変わる。命名規則を先に決める(app.kubernetes.io/name など公式の推奨ラベルがある)
Annotation選ぶ条件ではない情報。ツールの設定、所有者、チケット番号など巨大な値や秘密情報を入れない。get -o yaml で誰でも読める

わざと壊す演習

ここからは、さっきの deploy.yaml を使って、層ごとに1つずつ壊します。壊すたびに「どの部品の仕事で止まったか」を言ってから直してください。

演習1

Schedulerの層で止める(Pending)

CPUのrequestsを、どのNodeにもないほど大きくします。

# requests を cpu 64コア に変える(学習環境にそんなNodeはない)
kubectl -n lab set resources deployment/api --requests=cpu=64
kubectl -n lab get pods -w     # 新しいPodが Pending のまま止まる(Ctrl+Cで終了)

注目してほしいのは、古い2個のPodはRunningのまま残っていることです。Deploymentは「古いのを消してから新しいのを作る」のではなく、「新しいのが使えるようになってから古いのを減らす」ので、置けない変更を入れても利用者には影響が出ていません。

kubectl -n lab describe pod <Pending の Pod名>    # 一番下の Events
# Events の例
Warning  FailedScheduling  default-scheduler  0/1 nodes are available: 1 Insufficient cpu.
                                              preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.

発行者が default-scheduler、理由が Insufficient cpu。図2-1でいう4番目のステップで止まっていて、kubeletもレジストリも、まだ何もしていません。ここでNodeを増やすのは1つの手ですが、今回はrequestsの方が間違っています。前の版に戻します。

kubectl -n lab rollout undo deployment/api
kubectl -n lab get pods            # Pending だったPodが消え、2個に戻る
演習2

kubeletの層で止める(イメージ取得の失敗)

今度は、置き場所は決まるのに、Nodeの上で起動できない変更を入れます。

kubectl -n lab set image deployment/api api=nginx:this-tag-does-not-exist
kubectl -n lab rollout status deployment/api --timeout=60s

rollout status は「1 out of 2 new replicas have been updated...」のまま進まず、60秒でタイムアウトします。イベントを読みましょう。

kubectl -n lab get rs                                   # ReplicaSetが2つになっている
kubectl -n lab get events --sort-by=.lastTimestamp | tail -8

新しいReplicaSetが作られ(Controllerの仕事は成功)、Podに Scheduled が付き(Schedulerの仕事も成功)、そのあと Failed / BackOff の行が、kubeletの名前で並んでいるはずです。止まっているのは図2-1の5番目、Node側です。そして演習1と同じく、古いReplicaSetの2個はRunningのままです。

この状態を放っておくと、Deploymentは既定の10分(progressDeadlineSeconds: 600)後に、Progressing の条件を ProgressDeadlineExceeded という理由でFalseにします。ただし、それは「あきらめた」という意味ではなく、Controllerは引き続き新しいPodの起動を試み続けます。原因を直すまで、この状態は終わりません。

kubectl -n lab rollout history deployment/api          # 戻る前に、履歴を見ておく
kubectl -n lab rollout undo deployment/api
kubectl -n lab rollout status deployment/api            # successfully rolled out

戻した理由をメモに残してください。「イメージのタグが存在しなかった。Schedulerではなくkubeletの段階で失敗。古いPodが残っていたので利用者への影響はなし」。この3点が書ければ、今日の章は身についています。

演習3

台帳の外で現実を変える(ラベルを書き換える)

最後に、Controllerの「差を埋める」動きを逆方向から見ます。ReplicaSetの子であるPodのラベルを、selectorに合わないものに書き換えます。

POD=$(kubectl -n lab get pod -l app.kubernetes.io/name=api -o jsonpath='{.items[0].metadata.name}')
kubectl -n lab label pod "$POD" app.kubernetes.io/name=rogue --overwrite
kubectl -n lab get pods --show-labels

Podが3個になります。ラベルを変えたPodは消えずにRunningのまま残り、新しいPodが1個増えています。ReplicaSet Controllerから見ると、「selectorに合うPodが1個になった」ので差の1個を作っただけです。書き換えたPodは、もう誰の子でもありません。

kubectl -n lab get pod -o custom-columns=NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,LABEL:.metadata.labels.app\.kubernetes\.io/name

OWNER が <none> の行が、さっき書き換えたPodです。ServiceもラベルでPodを探すので、このPodには通信も届かなくなっています(第1章の演習3と同じ症状です)。本番でこれが起きると、「Podは動いているのに数が合わない」「削除しても別のが増える」という混乱になります。Podを直接いじる前に、そのPodの所有者が誰かを見る癖をつけてください。

kubectl -n lab delete pod "$POD"   # 親のいないPodは、消しても誰も作り直さない
kubectl -n lab get pods            # 2個に戻る

壊したら、メモを残す

第1章と同じ6項目です。今日は「原因」の欄に、止まった層(部品の名前)を必ず入れてください。

# 演習メモの例
日時/Context : 2026-10-21 21:30 / kind-kind(Namespace: lab)
症状         : set image 後、rollout status が60秒で timed out
最初に見たもの: get events → 新Podに Scheduled あり、その後 Failed(kubelet)
原因         : イメージタグが存在しない。止まった層は kubelet/レジストリ
修正         : rollout undo で前の ReplicaSet に戻した
再発防止     : タグは --dry-run=server では検出できない。CIでイメージの存在を確認する

どの層で止まっているか、の早見表

今日の内容を、障害のときにすぐ引ける形にまとめます。上から順に見ていけば、層を飛ばして「とりあえず再起動」をせずに済みます。

症状止まっている層最初に見る場所
connection refused、Unable to connect to the serverAPI Serverに届いていないkubectl config current-context、VPN、エンドポイントの到達性
Unauthorized認証(図2-2の①)kubeconfigの証明書・トークンの期限、クラウドのログイン状態
Forbidden認可(②)kubectl auth can-i、RoleBinding
admission webhook ... deniedAdmission(③)エラー文に書かれたルール名。管理者に確認
error validating、unknown field検証(④)kubectl explain、インデント、apiVersion
applyは成功したのにPodが出ないController(図2-1の3)get rs、describe deployment のConditionsとEvents
PendingScheduler(4)describe pod の FailedScheduling の理由
ContainerCreating、ImagePullBackOff、CrashLoopBackOffNode側(5)describe pod のEvents、logs --previous、get nodes
Runningなのに届かないPod以外(Service、ラベル、ネットワーク)get endpointslice、--show-labels(第1章・第4章)

Control Planeが止まると、何が止まるか

「API Serverが落ちたらサービスが全部止まる」と思っている人が多いのですが、正確ではありません。図2-1で、kubeletはAPI Serverの台帳を読んで動いていました。台帳が読めなくなっても、kubeletは最後に読んだPodを維持し続けます。

続くこと止まること
すでに動いているPodは動き続ける新しいPodの作成、Deploymentの更新
Serviceの振り分けは、各Nodeに配られた設定で続く(kube-proxy)Pod入れ替わりに伴うServiceの宛先更新
Nodeの上での再起動(restartPolicy)はkubeletが行う別のNodeへの作り直し、Pendingの解消、オートスケール
利用者から見た画面は、しばらく正常に見えるkubectl のすべて。状態を読むことも、直すこともできない

だから「画面が見えているから正常」とは判断できません。逆に、Control Planeの障害中でも、慌ててNodeを再起動するとPodが戻ってこなくなります。マネージドKubernetesでは、Control PlaneのログやetcdにはあなたからはOSごと触れないので、クラウドのヘルスイベント、監査ログ、サポート窓口を使う前提で、障害対応の手順書を作ります。

# Control Planeの各部品の健康状態(権限がないと Forbidden になる環境もある)
kubectl get --raw='/readyz?verbose'

# Nodeの条件(Ready / MemoryPressure / DiskPressure など)
kubectl get nodes
kubectl describe node <Node名> | sed -n '/Conditions:/,/Addresses:/p'

⚠️ 環境依存: kubectl get --raw がマネージド環境で Forbidden になっても、それはクラスター障害ではなく権限の話です。「自分が実行できる確認」と「事業者に問い合わせる項目」を、障害が起きる前に分けておいてください。責任分界を知らないまま障害に入ると、見られないログを探して時間が溶けます。

片付け

kubectl delete namespace lab
kubectl get namespaces          # lab が消えたことを確認

今日もロードバランサーやディスクは作っていないので、これで終わりです。演習2のイメージタグ違いは、CIでイメージの存在を確認すれば防げる類のものです。明日の第3章では、更新とロールバックをDeploymentの機能として、もう少し丁寧に扱います。

まとめ

  • 部品どうしは直接話さない。全員がAPI Serverの台帳を読み、自分の担当の差を埋めて、書き戻す
  • applyの成功は「台帳に書けた」だけ。コンテナが動くのは、Controller → Scheduler → kubelet の後
  • Eventsの1行ずつに担当者がいる。行が途切れた場所が、止まっている層
  • Pending はSchedulerの層、ImagePullBackOff はNodeの層。古いPodが残っていれば利用者への影響はない
  • Podを直接いじる前に、所有者(ownerReferences)を見る。台帳の外で変えた現実は、Controllerが差として扱う
  • Control Planeが止まっても、動いているPodは動き続ける。止まるのは「変えること」と「読むこと」

🔖 明日10月22日(木)、第3章「ワークロード」を公開します。 今日作ったDeploymentに、Service、ConfigMapとSecret、Probe、requests/limitsを足して、Webアプリを複数台で安定して動かします。ローリングアップデートの「同時に止めてよい数」や、Jobで一度だけ動かす処理も扱います。

🔖 特集トップをブックマークしておいてください

この特集は10月24日まで、毎日1ページずつ増えていきます。新しい章へのリンクは、すべて特集トップに追加されます。章ごとにブックマークしなくても、特集トップを1つ保存しておけば迷いません。

Windows Ctrl+DMac ⌘+Dスマホ 共有 → ブックマーク
𝕏 更新のお知らせを受け取る

毎日の公開は、X(@BusinessHub413)でもお知らせします。

公式資料

※本章の記述は2026年10月時点の公式ドキュメントに基づきます。イベントの文言、Schedulerのメッセージ、kubectl の出力形式は、Kubernetesのバージョンや学習環境によって少し異なることがあります。

Kubernetesの導入判断や設計についてご相談ください

「うちはKubernetesが要るのか」「ECSやCloud Runで足りるのか」から、
クラスター設計・運用体制づくりまで、30分の無料相談で承ります。

無料相談を予約する →