Kubernetes特集
夜の港で、クレーンがコンテナを1つずつ積み替えている

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

Kubernetesのワークロード
アプリを安全に更新・復旧する

Deployment・Service・ConfigMap・Probe・requests/limits。昨日のDeploymentに部品を足していくと、
「Runningになった」ではなく「止めずに更新して、戻せる」状態をつくれます。

📅 Day 4 | 10/22(木)公開

4日目です。昨日の第2章では、API Serverの台帳を、Controller・Scheduler・kubeletが読んで差を埋めていく流れを追いました。今日は、その上で動かす"中身"の話です。昨日と同じDeployment api を作り直し、Service、ConfigMapとSecret、Probe、requests/limitsを1つずつ足して、複数台のWebアプリを止めずに更新し、戻せるところまで進めます。

昨日の最後に lab は片付けたので、今日も作るところから始めます。第2章を読んでいない方は、「Controllerは差を埋め続ける」だけ先に読んでおくと、ローリングアップデートの動きが追いやすくなります。

この章の結論

💡 通常のステートレスなWebアプリは、Podを直接作らず Deployment で管理します。DeploymentはReplicaSetを通じてPod数と更新履歴を持ち、Service は入れ替わるPodに安定した名前と仮想IPを渡します。設定はConfigMap、秘密情報はSecret、終わる処理はJob/CronJob、固有の名前とディスクが要る処理はStatefulSet、Nodeごとのエージェントは DaemonSet。運用の要点は、「起動したか」ではなく 「トラフィックを受けられるか」をProbeで表す こと、requests/limitsを測った値で決めること、更新とロールバックを事前に試すことです。

この章で手に入れてほしいのは、Running という表示を信用しすぎない目です。Podが動いていても、通信を受けられるとは限りません。Ready、EndpointSliceの中身、更新の進み具合を順に見て、「使える」を自分で確かめます。

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

  • Deployment・ReplicaSet・Podの関係と、ローリングアップデートの進み方を図3-5で説明できる
  • Serviceのselectorと、EndpointSliceに載っているPodを突き合わせられる
  • ConfigMapとSecretを環境変数で渡し、変更を反映させる手順を言える
  • readiness・liveness・startupの3つのProbeの違いと、失敗したときに起きることを説明できる
  • kubectl diff → apply → rollout status → rollout undo の順で、更新して戻せる

今日つくる構成

今日は3つのYAMLファイルを作ります。中心の deploy.yaml は、章の途中で3回に分けて書き足します。最終的に図3-1の形になります。

Namespace: lab curl Pod クライアント Service api ClusterIP port 80 selector app.kubernetes.io/name=api ConfigMap api-config Secret api-secret envFromで注入 Deployment api(replicas: 3) ReplicaSetが3つのPodを維持する Pod nginx:stable Probe 2種 100m・64Mi Pod nginx:stable Probe 2種 100m・64Mi Pod nginx:stable Probe 2種 100m・64Mi Probe 2種 = readiness と liveness。数値は requests(CPU・メモリ)

図3-1: 今日つくる構成。Deploymentを中心に、Service・ConfigMap/Secret・Probe・リソース値を足していく。

ファイル中身作る節
deploy.yamlDeployment。3段階で書き足すDeployment/ConfigMapとSecret/Probe
svc.yamlServiceServiceとラベル選択
config.yamlConfigMapとSecretConfigMapとSecret
fail-job.yaml・cron.yamlJob・CronJob(後半)JobとCronJob/わざと壊す演習

昨日と同じく、まずContextを確かめて lab を作ります。

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

Pod・ReplicaSet・Deployment

Podは、作り直されることが前提の一時的な実行単位です。ReplicaSetは指定した数のPodを維持しますが、通常は直接触りません。DeploymentがReplicaSetを作り、更新の履歴を持ちます。新しいイメージを適用すると、新しいReplicaSetを少しずつ増やし、古いReplicaSetを減らしていきます。第2章で見た親子関係(Deployment → ReplicaSet → Pod)の続きです。

まず、昨日のDeploymentを3台にして、更新のしかたを書き足した版を作ります。

# deploy.yaml(第1版)― 3台で動かし、更新中も3台を下回らせない
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app.kubernetes.io/name: api
spec:
  replicas: 3
  minReadySeconds: 5                 # Readyになってから5秒続いたら「使える」とみなす
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0              # 更新中に減ってよい数(0 = 常に3台を確保)
      maxSurge: 1                    # 更新中に一時的に増やしてよい数
  selector:
    matchLabels:
      app.kubernetes.io/name: api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api
    spec:
      containers:
        - name: api
          image: nginx:stable
          ports:
            - name: http             # 名前付きポート。Serviceから名前で指せる
              containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 128Mi

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

  1. API Serverが内容を検証し、etcdに保存する
  2. Deployment Controllerが、ReplicaSet api-<ハッシュ> を1つ作る
  3. ReplicaSet Controllerが、Podを3個作る
  4. Schedulerが requests の空きを見てNodeを決め、kubeletが起動する(第2章の6ステップと同じ)
kubectl -n lab apply -f deploy.yaml
kubectl -n lab rollout status deployment/api     # deployment "api" successfully rolled out
kubectl -n lab get deploy,rs,pod

maxUnavailable: 0 は、全レプリカを常に使える状態にしておく前提です。Nodeの空きが少ないと、増やす1台が置けず、更新が進まないことがあります。何も書かなければ、maxSurge と maxUnavailable はどちらも既定で25%です。

⚠️ YAMLの中身は、本番ではそのまま使えません。 レジストリ、タグ、CPU、メモリの値はすべて例です。タグが存在し、Nodeがそのレジストリからイメージを取得できるかを、先に確認してください。nginx:stable のような動き続けるタグは学習用です。本番では、日付やコミットIDなど、中身が変わらないタグを使います。

Serviceとラベル選択

PodのIPは、作り直すと変わります。だからクライアントは、PodではなくServiceへ接続します。Serviceはselectorに一致するPodをEndpointSliceにまとめ、ClusterIPとDNS名を提供します。selectorがPodのラベルと一致しなければ、Serviceは存在しても転送先がありません。第1章の演習3と同じ症状です。

curl http://api Service api ClusterIP 10.96.x.x port 80 → targetPort http selectorでPodを選ぶ EndpointSlice 10.244.0.5 ready=true 10.244.0.6 ready=true 10.244.0.7 ready=false ✗ Pod 1 Ready Pod 2 Ready Pod 3 readiness失敗中 通信が届くのは ready=true のPodだけ Probeに失敗したPodはServiceの転送先から外れる(再起動はしない)

図3-2: Serviceはselectorに合うPodをEndpointSliceに集め、Readyなものにだけ通信を届ける。

# svc.yaml ― 入れ替わるPodの前に、変わらない名前を置く
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: api        # Podのラベルと1文字でも違うと転送先が0になる
  ports:
    - name: http
      port: 80                         # Serviceが受ける番号
      targetPort: http                 # Pod側。名前付きポートで指す
kubectl -n lab apply -f svc.yaml

# 転送先(EndpointSlice)を見る。Serviceが繋がらないときは、DNSより先にここを見る
kubectl -n lab get endpointslice -l kubernetes.io/service-name=api

# 1台ずつ ready かどうかまで見る
kubectl -n lab get endpointslice -l kubernetes.io/service-name=api \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}  ready={.conditions.ready}{"\n"}{end}'

# クラスターの中から名前で呼ぶ(使い捨てのPodを作って、終わったら消す)
kubectl -n lab run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
  curl -s -o /dev/null -w '%{http_code}\n' http://api

出力の例です。名前やIPは環境によって違います。

# 出力の例
NAME        ADDRESSTYPE   PORTS   ENDPOINTS                            AGE
api-7xk2p   IPv4          80      10.244.0.5,10.244.0.6,10.244.0.7     12s

10.244.0.5  ready=true
10.244.0.6  ready=true
10.244.0.7  ready=true

200
pod "curl" deleted

port はServiceが受ける番号、targetPort はPodのポートです。名前付きポートを使うと、あとで番号を変えるときも意図が読み取れます。http://api と名前だけで呼べたのは、同じNamespaceの中にいるからです。別のNamespaceからは api.lab のように名前を足します。DNSの詳しい話は、明日の第4章で扱います。

ConfigMapとSecret

ConfigMapは、環境ごとの設定や機能フラグをイメージの外に出すためのオブジェクトです。Secretはパスワードやトークン用ですが、Base64で書けば安全になるわけではありません。保存時の暗号化、RBAC、クラウドのKMS連携、ログへの露出防止は、別に設定します。

# config.yaml ― 設定と秘密情報を、イメージの外に出す
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
data:
  LOG_LEVEL: info
  API_TIMEOUT: 2s
---
apiVersion: v1
kind: Secret
metadata:
  name: api-secret
type: Opaque
stringData:                            # 保存するときにAPIがBase64へ変換する
  DATABASE_URL: "REPLACE_IN_SECRET_STORE"

deploy.yaml の ports: の下に、envFrom を書き足します(第2版)。順番は configMapRef → secretRef にしてください。演習2で使います。

          envFrom:                       # ← 第2版で追加(ports と resources の間)
            - configMapRef:
                name: api-config
            - secretRef:
                name: api-secret
kubectl -n lab apply -f config.yaml       # 先に作る。参照先がないとPodが起動できない(演習2)
kubectl -n lab apply -f deploy.yaml       # Podの中身が変わるので、ローリングアップデートが走る
kubectl -n lab rollout status deployment/api
kubectl -n lab exec deploy/api -- printenv LOG_LEVEL API_TIMEOUT

出力の例は info と 2s です。

Secretが「隠されていない」ことも確かめておきます。

kubectl -n lab get secret api-secret -o jsonpath='{.data.DATABASE_URL}' | base64 -d; echo
# REPLACE_IN_SECRET_STORE   ← get secret の権限がある人は誰でも読める

もう1つ、変更は自動では届きません。環境変数で注入した値は、起動済みのプロセスへ自動では反映されないからです。

# config.yaml の LOG_LEVEL を debug に変えて適用
kubectl -n lab apply -f config.yaml
kubectl -n lab exec deploy/api -- printenv LOG_LEVEL      # まだ info のまま

kubectl -n lab rollout restart deployment/api             # Podを順番に作り直す
kubectl -n lab rollout status deployment/api
kubectl -n lab exec deploy/api -- printenv LOG_LEVEL      # debug になる

⚠️ Secretの扱いは、環境で変わります。 実運用では、SecretをGitに平文で置かないよう、External Secrets、Sealed Secrets、SOPSなどを検討します。etcdでの保存時暗号化が有効かどうかは、クラスターの設定やマネージドかどうかで違います。ConfigMapをファイルとしてマウントした場合は、時間差で中身が更新されますが、アプリが読み直さなければ同じことです。

Probeで「受けられるか」を表す

Probeは、コンテナの状態をkubeletが定期的に確かめる仕組みです。3種類あり、失敗したときに起きることがまったく違います。

Probe何を表すか失敗したらよくある誤り
startupProbe起動処理が終わったか終わるまで他の2つを始めない。上限まで失敗すると再起動起動の遅いアプリに付けず、livenessが初期化中に殺す
readinessProbe今トラフィックを受けてよいかEndpointSliceで ready=false になり、Serviceから外れる(再起動はしない)付けないと、起動直後の準備中Podに通信が届く
livenessProbe再起動が必要なほど壊れているかkubeletがコンテナを再起動するDBの疎通まで含め、DBの一時障害で全Podが同時に再起動する
startupProbe readinessProbe livenessProbe 起動 → 初期化 稼働 startup成功 起動の完了を確認中 startupの成功を待つ startupの成功を待つ 受付中 失敗中 受付中 Serviceから外す(Podはそのまま) 生きている ✗✗✗ 3回続けて失敗 → kubeletが再起動 readiness = 入口から外す / liveness = 作り直す

図3-3: 3つのProbeの役割。readinessは入口から外すだけ、livenessは再起動する。

deploy.yaml の resources: の下に、readinessとlivenessを書き足します(第3版、これが完成形)。nginxはすぐ起動するので、startupProbeは付けません。

          readinessProbe:                # ← 第3版で追加
            httpGet:
              path: /
              port: http
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /
              port: http
            periodSeconds: 10
            failureThreshold: 3

書き足したあとの完成形です。以降の演習は、この deploy.yaml を使います。

# deploy.yaml(完成形)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app.kubernetes.io/name: api
spec:
  replicas: 3
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api
    spec:
      containers:
        - name: api
          image: nginx:stable
          ports:
            - name: http
              containerPort: 80
          envFrom:
            - configMapRef:
                name: api-config
            - secretRef:
                name: api-secret
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: http
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /
              port: http
            periodSeconds: 10
            failureThreshold: 3
kubectl -n lab apply -f deploy.yaml
kubectl -n lab rollout status deployment/api
kubectl -n lab get pods                  # READY 1/1 が3つ
kubectl -n lab describe pod -l app.kubernetes.io/name=api | grep -E 'Readiness|Liveness'

アプリ側に用途別のエンドポイントを用意できるなら、本番では次のような形になります。

startupProbe:
  httpGet:
    path: /startup
    port: http
  failureThreshold: 30
  periodSeconds: 5
readinessProbe:
  httpGet:
    path: /ready
    port: http
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 3
livenessProbe:
  httpGet:
    path: /health
    port: http
  periodSeconds: 10
  failureThreshold: 3

📌 エンドポイントは、依存先全体の疎通を単純に返さず、readinessとlivenessの意味を分けます。DBが一時的に遅いだけでlivenessが失敗し、全Podが同時に再起動する設計は危険です。初期遅延・タイムアウト・成功回数・失敗回数は、負荷試験と障害訓練で調整します。

requestsとlimits

requests はSchedulerが配置を決めるときに使う予約量、limits はコンテナが使える上限です。第2章で見たとおり、Schedulerが見るのは実際の使用量ではなく、requestsの合計でした。requestsが小さすぎるとNodeに詰め込みすぎ、limitsが小さすぎるとCPUを絞られたり、OOMKilledで落ちたりします。値は希望ではなく、平常時とピーク時のメトリクスから決めます。

Node(allocatable) 空き Pod C requests Pod B requests Pod A requests Schedulerは requestsの合計で 置けるか決める 1コンテナの使用量 limits(上限) requests(予約) limitsに到達 CPUが超えると 速度を絞られる(throttling) メモリが超えると 強制終了(OOMKilled)

図3-4: requestsは置き場所を決めるための予約、limitsは超えたときの扱いが決まる上限。

症状原因の候補見る場所
Pending(Insufficient cpu/memory)requestsが大きすぎる、Nodeの空き不足describe pod のEvents、describe node の Allocated resources
遅いのに落ちないCPU limitsに当たって絞られているメトリクス(kubectl top pod など)
再起動が増える・OOMKilledメモリlimitsを超えたdescribe pod の Last State: Terminated、Reason: OOMKilled、Exit Code: 137

作ったPodが、どのQoSクラスになっているかも見られます。

kubectl -n lab get pod -l app.kubernetes.io/name=api \
  -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass
# requests < limits なので Burstable。requests と limits を全部同じにすると Guaranteed になる
kubectl -n lab top pod        # metrics-server がないと error になる(下の注記)

⚠️ JavaやNode.jsは、コンテナのメモリ制限を認識するか確認してください。 limitを超えると、アプリが自分でエラーを返す前に終了することがあります。NamespaceにResourceQuotaやLimitRangeを設定する場合は、開発用のデフォルト値が本番の性能を歪めないよう、適用範囲を明記します。なお kubectl top には metrics-server が必要です。kind には既定で入っていません(minikube は minikube addons enable metrics-server)。

更新・履歴・ロールバック

更新の前に、kubectl diff で差分を見て、対象のContextをもう一度確かめます。適用したら rollout status とアプリのメトリクスを見ます。失敗したら rollout history で履歴を確認し、原因を記録してから戻します。

maxSurge: 1(最大4台)/ maxUnavailable: 0(Readyは3を下回らない) ① 開始 Ready 3 ② 新を1台追加 Ready 3 ③ 旧を1台減らす Ready 3 ④ 同じことを繰り返す Ready 3 ⑤ 完了 Ready 3 旧Pod 新Pod(Ready) 新Pod(準備中) 旧ReplicaSetは数0で残る → rollout undo で戻れる

図3-5: maxSurge 1・maxUnavailable 0 のローリングアップデート。新しいPodがReadyになってから、古いPodを1つ減らす。

deploy.yaml の image: nginx:stable を nginx:mainline に書き換えてから、次の順で進めます。

kubectl config current-context                      # 対象を再確認
kubectl -n lab diff -f deploy.yaml                  # image の行が変わることを見る(generation の行も変わります)
kubectl -n lab apply -f deploy.yaml
kubectl -n lab rollout status deployment/api --timeout=5m
kubectl -n lab annotate deployment/api kubernetes.io/change-cause="nginx:mainline へ更新" --overwrite
kubectl -n lab rollout history deployment/api
kubectl -n lab get rs                               # 旧ReplicaSetが DESIRED 0 で残っている

rollout history の出力の例です。番号は、ここまでの操作の回数で変わります。

# 出力の例
deployment.apps/api
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>
4         <none>
5         nginx:mainline へ更新

1は第1版、2は envFrom の追加、3は ConfigMap を反映させた rollout restart、4はProbeの追加です。

戻すときは次のとおりです。

kubectl -n lab rollout undo deployment/api                   # 1つ前の版へ
kubectl -n lab rollout status deployment/api
kubectl -n lab rollout history deployment/api                # 戻した版は、新しい番号で末尾に付く
# 特定の版へ: kubectl -n lab rollout undo deployment/api --to-revision=4(番号は history で確かめてから)

rollout undo が戻すのはクラスターの中だけで、手元の deploy.yaml は mainline のままです。次に apply すると、また mainline になります。戻したら、ファイル(Git)も戻してください。古いReplicaSetは、revisionHistoryLimit(既定10)の数まで残ります。

📌 ロールバックしても、DBのスキーマ変更が不可逆なら、アプリだけは戻せません。後方互換のマイグレーション、段階的なフラグ、旧アプリでも読めるカラム、バックアップからの復旧手順まで含めて、リリースを設計します。

JobとCronJob

Jobは、指定した回数だけ正常に終わることを目指すワークロードです。データ移行、画像の一括処理、バックフィルなどに使います。CronJobは、スケジュールに従ってJobを作ります。定期処理が重なっても安全か、前回が長引いたらどうするか、履歴を何件残すかを、先に決めておきます。

# cron.yaml ― 毎時15分にレポートを作る(学習用)
apiVersion: batch/v1
kind: CronJob
metadata:
  name: report
spec:
  schedule: "15 * * * *"
  timeZone: "Asia/Tokyo"              # 省略するとkube-controller-managerのタイムゾーンで解釈
  concurrencyPolicy: Forbid           # 前回が終わっていなければ、今回は作らない
  successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2                 # 失敗したら2回まで作り直す
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: report
              image: busybox:1.36
              command: ["sh", "-c", "date; echo generate report"]
kubectl -n lab apply -f cron.yaml
kubectl -n lab get cronjob
# 15分を待たずに、CronJobの型から1回だけJobを作る
kubectl -n lab create job report-now --from=cronjob/report
kubectl -n lab get job report-now          # COMPLETIONS 1/1
kubectl -n lab logs job/report-now         # 日時と generate report
項目決めること
backoffLimit何回まで作り直すか(既定6)
concurrencyPolicy重なったときの扱い。Allow(既定)・Forbid・Replace
successfulJobsHistoryLimit・failedJobsHistoryLimit終了後のJobを何件残すか
restartPolicyJobでは Never か OnFailure のみ

Jobは再実行されるので、処理はできるだけ冪等にします。外部APIへ二重送信する処理なら、実行IDを記録して、重複を検出できるようにしてください。終了後のJobを消す運用にするなら、ログを外部へ転送して、失敗の履歴を追える保持期間を確保します。

⚠️ CronJobのスケジュールは、環境の影響を受けます。 クラスターのタイムゾーン、Kubernetesのバージョン、コントロールプレーンが止まっていたときの挙動に注意してください。timeZone が使えないバージョンやマネージド環境もあるので、自分のクラスターで kubectl explain cronjob.spec.timeZone を確かめます。

StatefulSetとDaemonSet

StatefulSetは、Podごとに安定した名前と順序、PVCとの対応が必要な、DBやキューなどに使います。ただし、StatefulSetを使えばDBが安全になるわけではありません。レプリケーション、フェイルオーバー、バックアップ、クォーラム、ストレージの性能は、自分で設計します。

DaemonSetは、各Node(または条件に合うNode)に1つずつPodを置きます。ログエージェント、Node監視、ネットワークプラグインが典型です。Nodeが増えればPodも増えるので、外部サービスへの接続数やログ量が増える影響を見積もります。特定のNodeだけに置くなら、Node label、Taint、Tolerationを組み合わせます。

DeploymentStatefulSetDaemonSet
Podの名前ランダムdb-0、db-1 の連番Nodeごとに1つ
ディスク共有か、なしPodごとに専用のPVC基本はなし。Nodeのパスを使うことも
数の決め方replicasreplicasNodeの数
典型Web APIDB・キューログ収集・監視・CNI

DaemonSetは、観察だけしておきましょう。作る必要はありません。

kubectl get daemonset -A        # kube-system に kube-proxy や CNI のDaemonSetが見える

名前は環境によって違います(kind なら kindnet など)。StatefulSetとPVCは、第4章で実際に作ります。

わざと壊す演習

完成形の api(Service・設定・Probe付き)を使って、3つ壊します。壊すたびに、利用者に影響が出ているかをEndpointSliceで確かめてから直してください。

演習1

Readinessを失敗させる(更新が止まる)

readinessProbeのパスを、nginxにない /ready に変えます。YAMLではなく、パッチで変えます。

# readinessProbe のパスを、nginxにない /ready に変える
kubectl -n lab patch deployment api --type=json \
  -p='[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/ready"}]'
kubectl -n lab rollout status deployment/api --timeout=60s
kubectl -n lab get pods

新しいPodが1つだけ作られ、READY 0/1(STATUSはRunning)になります。古い3つは 1/1 のままです。rollout status は Waiting for deployment "api" rollout to finish: 1 out of 3 new replicas have been updated... と表示したあと、error: timed out waiting for the condition で終わります。

kubectl -n lab describe pod <READY 0/1 のPod名>     # Events
#   Warning  Unhealthy  ...  Readiness probe failed: HTTP probe failed with statuscode: 404
kubectl -n lab get endpointslice -l kubernetes.io/service-name=api \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}  ready={.conditions.ready}{"\n"}{end}'
#   新しいPodだけ ready=false。通信は古い3台にだけ届いている

maxUnavailable が0なので、古いPodは減りません。利用者への影響はありません。Runningでも、Readyでなければ「使える」とは言えません。

kubectl -n lab rollout undo deployment/api
kubectl -n lab rollout status deployment/api

パッチはファイルの外で行った変更なので、deploy.yaml はそのままで構いません。

演習2

存在しないConfigMapを参照する

envFrom の1つ目、ConfigMapの名前を打ち間違えた状態を作ります。

kubectl -n lab patch deployment api --type=json \
  -p='[{"op":"replace","path":"/spec/template/spec/containers/0/envFrom/0/configMapRef/name","value":"api-confg"}]'
kubectl -n lab get pods

patch自体は成功します。API Serverは、参照先が存在するかどうかを確かめないからです。新しいPodは CreateContainerConfigError になり、Eventsに Error: configmap "api-confg" not found と出ます。古い3台は、Running・Readyのままです。第2章の言い方では、kubeletの層で止まっています。

kubectl -n lab rollout undo deployment/api
kubectl -n lab rollout status deployment/api
演習3

失敗し続けるJob

必ず exit 1 で終わるJobを作ります。

# fail-job.yaml ― 必ず exit 1 で終わる
apiVersion: batch/v1
kind: Job
metadata:
  name: fail-once
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: task
          image: busybox:1.36
          command: ["sh", "-c", "echo start; exit 1"]
kubectl -n lab apply -f fail-job.yaml
kubectl -n lab get pods -l job-name=fail-once -w    # 1分ほどで Error のPodが3つ(最初の1回+作り直し2回)。Ctrl+Cで終了
kubectl -n lab describe job fail-once               # Warning BackoffLimitExceeded  Job has reached the specified backoff limit
kubectl -n lab logs job/fail-once                   # start

作り直しの間隔は、少しずつ長くなります。同じ処理が最大3回走るので、二重に計上しない作り(冪等性)が要ります。直すときは、Jobのtemplateが作成後に書き換えられないため、一度消してから作り直します。

kubectl -n lab delete job fail-once
# fail-job.yaml の command を "echo start; exit 0" に変えて、もう一度
kubectl -n lab apply -f fail-job.yaml
kubectl -n lab get job fail-once                    # COMPLETIONS 1/1

壊したら、メモを残す

第1章・第2章と同じ6項目です。今日は、「利用者に影響があったか」を修正の欄に添えてください。

# 演習メモの例
日時/Context : 2026-10-22 21:30 / kind-kind(Namespace: lab)
症状         : patch 後、rollout status が60秒で timed out。新Podが READY 0/1
最初に見たもの: describe pod → Readiness probe failed: statuscode 404
原因         : readinessProbe のパスが存在しない /ready。止まった層は kubelet(Probe)
修正         : rollout undo。EndpointSlice は古い3台のみ ready=true で、利用者への影響なし
再発防止     : Probeのパスはアプリの実装と一緒にレビューし、ステージングで rollout status まで確認する

ワークロードを選ぶ判断表

リソースを選ぶときは、まずプロセスの寿命と更新の単位を決めます。迷ったら、図3-6を上から順に質問してください。リソース名からではなく、処理の寿命とデータから決めます。

終わる処理か? はい いいえ 決まった時刻に動くか? はい CronJob いいえ Job データ移行・一括処理など Nodeごとに1つ要るか? はい DaemonSet ログ収集・監視など いいえ Podごとの名前と 専用ディスクが要るか? はい StatefulSet DB・キューなど いいえ Deployment Web APIの第一候補

図3-6: 迷ったら上から質問する。リソース名からではなく、処理の寿命とデータから決める。

処理の性質候補
常時稼働・ステートレスなHTTP APIDeployment
完了して結果を残すJob
定刻に起動するCronJob
Nodeごとに1つ必要なエージェントDaemonSet
識別子と専用ボリュームが必要なデータサービスStatefulSet

迷ったままDeploymentでDBを動かすと、Podの再作成や順序の入れ替わりが、データの前提と合わなくなります。迷ったときは、3つの質問が役に立ちます。失敗したとき、同じPodを復元できるか。処理を再実行できるか。識別子とデータを保持する必要があるか。この答えを言えれば、選んだ理由も説明しやすくなります。

今日作ったものを、まとめて確認するコマンドです。

kubectl -n lab get deploy,statefulset,daemonset,job,cronjob
kubectl -n lab describe deployment api
kubectl -n lab get pod -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount'
kubectl -n lab rollout history deployment/api

更新前に決めること

更新の前に、Readyの判定、止めてよい数、ロールバックの条件、データの互換性を、言葉にして決めておきます。PodがRunningになっただけで成功とはせず、利用者のリクエストとバックグラウンド処理まで確認します。

決めることこの章で対応する設定・確認
Readyの判定readinessProbe・minReadySeconds
止めてよい数maxUnavailable・maxSurge
ロールバック条件rollout status のタイムアウト、エラー率などのメトリクス、rollout undo
データの互換性後方互換のマイグレーション、旧アプリでも読めるカラム、バックアップ

片付け

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

今日も、ロードバランサーやディスクは作っていません。CronJobも、Namespaceごと消えます。

まとめ

  • Webアプリは Deployment で管理し、更新は新しいReplicaSetへの入れ替えで進む
  • Serviceは selector に合い、かつ Ready なPodにだけ通信を届ける。繋がらなければ EndpointSlice を見る
  • ConfigMap・Secretはイメージの外に出す。Secretは暗号化ではなく、環境変数の変更は再起動まで届かない
  • readinessは入口から外す、livenessは再起動する。livenessに依存先の疎通を入れない
  • requestsは配置の予約、limitsは上限。値は測って決める
  • 更新は diff → apply → rollout status、失敗したら history を見て undo。データの互換性は別に設計する

🔖 明日10月23日(金)、第4章「ネットワークとストレージ」を公開します。 今日作ったServiceが名前でどう引かれるか、外から届く経路(IngressとGateway API)、NetworkPolicyでの通信の制限、PV/PVCとバックアップを扱います。

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

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

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

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

公式資料

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

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

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

無料相談を予約する →