Kubernetes特集
夜の港で、管制塔の画面が光り、並んだコンテナの山とクレーンを見守っている

Kubernetes特集 | 第5章(Day 6 / 6・最終章)

Kubernetesの本番運用
壊れても判断できる仕組み

可用性・RBAC・イメージ・監視・オートスケール・アップグレード・障害対応。
「動いた」の先にある、本番に出す前の準備をまとめます。

📅 Day 6 | 10/24(土)公開

最終日です。昨日の第4章では、別のNamespaceからServiceを名前で呼び、NetworkPolicyで通信を絞り、PVCでPodを消しても残るデータを作りました。ここまでで、アプリを動かし、届け、データを残すところまでは手が届いています。

今日はその先です。Nodeが1台止まったら。誰かが権限を持ちすぎていたら。夜中にPodが Pending のまま増えていったら。本番で困るのは、Kubernetesの機能を知らないことより、壊れたときに何を見てどう判断するかが決まっていないことです。この章では、可用性、権限、イメージ、観測、オートスケール、アップグレード、障害対応を順に見て、最後に本番導入のチェックリストにまとめます。

今日だけは、Nodeが複数あるクラスターを使います。Node障害やドレイン(Nodeを空にする作業)は、Nodeが1台では試せないからです。作り方は下の「今日つくる構成」に書きました。

この章の結論

💡 本番のKubernetesで難しいのは、クラスターを作ることより、壊れたときに安全に判断できる運用を作ることです。可用性はreplicasの数だけでは決まりません。Podを複数の障害ドメインへ散らし、PodDisruptionBudget(PDB)で同時に止めてよい数を決め、アプリが止まり方を知っていて、初めて「1台止まっても大丈夫」と言えます。セキュリティはRBACだけでなく、イメージ、Secret、NetworkPolicy、Admission、監査、Nodeの更新の組み合わせです。監視・ログ・トレースで「遅い」「失敗した」「どこで詰まった」を分け、アップグレードは緊急作業にせず、段階適用とロールバックを定期的に練習します。

言い換えると、この章のテーマは「仕組みにする」です。経験のある人が夜中に勘でコマンドを打つ運用は、その人がいない日に崩れます。設定、手順、確認項目を決めておけば、当番が誰でも同じ判断に近づけます。

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

  • replicasを3にしても全滅する理由を図5-2で説明し、topologySpreadConstraintsとPDBで防げる
  • ServiceAccountに必要な権限だけを与え、kubectl auth can-i で確かめられる
  • イメージをダイジェストで固定し、Pod Securityの警告を読んで直す項目が分かる
  • メトリクス・ログ・トレースの役割の違いと、当番を起こすべきアラートを説明できる
  • HPAとNodeの自動スケールの関係、requestsがずれると何が起きるかを説明できる
  • アップグレードと障害対応を、手順と確認項目に分けて書ける

今日つくる構成

kindで、Control Plane 1台とworker 3台のクラスター ops を作ります。workerにはゾーンのラベルを付けて、クラウドの「3つのアベイラビリティゾーン」に見立てます。そこへ第3章と同じ api を3つ置き、PDB、ServiceAccount、HPAを足していきます。最終的に図5-1の形になります。

クラスター ops(kind・Nodeは4台) ops-control-plane ops-worker zone-a ops-worker2 zone-b ops-worker3 zone-c api Pod api Pod api Pod ゾーンごとに1つ (topologySpreadConstraints) Namespace: lab PDB api 同時に止めてよいのは1つまで (maxUnavailable: 1) HPA api CPUを見て 3〜6 個に増減 (metrics-server が必要) ServiceAccount ci Role ci-deployer で Deploymentの更新だけ許可

図5-1: 今日つくる構成。worker 3台をゾーンに見立て、api を1つずつ散らす。

# kind-ops.yaml ― Control Plane 1台、worker 3台
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
  - role: worker
kind create cluster --name ops --config kind-ops.yaml
kubectl config current-context        # kind-ops になっていることを確認(本番に向いていないこと)

# worker にゾーンのラベルを付けて、3つのゾーンに見立てる
kubectl label node ops-worker  topology.kubernetes.io/zone=zone-a
kubectl label node ops-worker2 topology.kubernetes.io/zone=zone-b
kubectl label node ops-worker3 topology.kubernetes.io/zone=zone-c
kubectl get nodes -L topology.kubernetes.io/zone

kubectl create namespace lab
# 出力の例(バージョンは環境によって違う)
NAME                STATUS   ROLES           AGE   VERSION   ZONE
ops-control-plane   Ready    control-plane   60s   v1.33.1
ops-worker          Ready    <none>          40s   v1.33.1   zone-a
ops-worker2         Ready    <none>          40s   v1.33.1   zone-b
ops-worker3         Ready    <none>          40s   v1.33.1   zone-c

topology.kubernetes.io/zone は、クラウドのマネージドKubernetesならNodeに自動で付くラベルです。kindには付かないので、今日は手で付けました。

⚠️ Nodeが1台の環境では、この章の演習の一部ができません。 minikubeなら minikube start -p ops --nodes 4 で複数Nodeにできます(Node名は ops、ops-m02 のようになるので、コマンド中の名前を読み替えてください)。Docker DesktopのKubernetesは既定で1台です。その場合は、ドレインの演習は読むだけにして、RBACとHPAの演習を試してください。kindのクラスターを作ると、PCのメモリを数GB使います。今日の最後に kind delete cluster --name ops で消します。

可用性を設計する

まず、よくある思い込みから崩します。「replicasを3にしたから、1台壊れても大丈夫」。これは、3つのPodが別々のNodeに載っているときだけ正しい話です。

❌ 同じNodeに3つ Node A(停止) api × api × api × Node B (apiなし) 残ったPod: 0 → 全停止 スケジューラーは、空いているNodeに 寄せることがある ✅ ゾーンに1つずつ zone-a(停止) api × zone-b api zone-c api 残ったPod: 2 → 縮退して継続 2つで負荷を受けきれるかは 別に確かめる(requestsと余力)

図5-2: replicasが同じでも、載る場所しだいで結果が変わる。

スケジューラーは、何も指定しなければ空いている資源を見てPodを置きます。結果として同じNodeに寄ることがあります。散らしたいなら、それを宣言します。使うのは topologySpreadConstraints です。

# api.yaml ― 第3章の api に、本番向けの設定を足したもの
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels: {app: api}
  template:
    metadata:
      labels: {app: api}
    spec:
      automountServiceAccountToken: false   # APIサーバーを呼ばないアプリにはトークンを渡さない
      terminationGracePeriodSeconds: 30
      topologySpreadConstraints:
        - maxSkew: 1                         # ゾーン間のPod数の差は1まで
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule   # 守れないなら置かない(Pendingになる)
          labelSelector:
            matchLabels: {app: api}
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - name: http
              containerPort: 80
          resources:
            requests: {cpu: 100m, memory: 64Mi}
            limits: {memory: 128Mi}
          readinessProbe:
            httpGet: {path: /, port: http}
            periodSeconds: 5
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "5"]      # 転送先から外れるのを待ってから止まる
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector: {app: api}
  ports:
    - port: 80
      targetPort: http

📝 適用すると裏で起きること

  1. スケジューラーは、Podを1つ置くたびに、ゾーンごとの app: api の数を数え、差が1を超えない場所を選ぶ
  2. Control Planeには、Podを置かないための印(taint)が付いているので、3つはworkerの3ゾーンに1つずつ載る
  3. Podを止めるとき、kubeletはまず preStop の sleep 5 を実行し、その間にEndpointSliceから外れる。そのあとコンテナに停止シグナルが送られる
kubectl -n lab apply -f api.yaml
kubectl -n lab rollout status deployment/api
kubectl -n lab get pod -l app=api -o wide     # NODE 列が3台に分かれているか
# 出力の例(Pod名とIPは環境によって違う)
NAME                   READY   STATUS    RESTARTS   AGE   IP           NODE
api-6c8d7b9f5d-2kq8x   1/1     Running   0          20s   10.244.1.2   ops-worker
api-6c8d7b9f5d-h7wzp   1/1     Running   0          20s   10.244.3.2   ops-worker3
api-6c8d7b9f5d-tn4c9   1/1     Running   0          20s   10.244.2.2   ops-worker2

PodDisruptionBudget ― 同時に止めてよい数

Nodeの更新やドレインのように、人や仕組みが計画して起こす中断を「自発的な中断」と呼びます。PodDisruptionBudget(PDB)は、そのとき同時に失ってよいPodの数を制限します。

# pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1          # 同時に追い出してよいのは1つまで
  selector:
    matchLabels: {app: api}
kubectl -n lab apply -f pdb.yaml
kubectl -n lab get pdb api
NAME   MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
api    N/A             1                 1                     5s

見るのは ALLOWED DISRUPTIONS です。いま何個まで追い出してよいかを表します。ここが 0 のままだと、ドレインはそのPodを追い出せずに止まります(演習1で起こします)。PDBが守るのは自発的な中断だけで、Nodeの電源断のような突然の障害は防げません。それは散らす設定と余力で受けます。

PDBは、厳しすぎても緩すぎても困ります。3つしかないのに minAvailable: 3 にするとメンテナンスが進まず、何も書かなければ更新で全部同時に止まることがあります。可用性の目標と、メンテナンスにかけられる時間を一緒に決めてください。

止まり方を、アプリに教える

Podが消されるとき、コンテナには停止シグナル(SIGTERM。イメージによってはSIGQUIT)が送られ、terminationGracePeriodSeconds を過ぎると強制終了されます。アプリは、シグナルを受けたら新しい接続の受付を止め、処理中のリクエストを終えてから終了するのが理想です。これをGraceful Shutdownと呼びます。

もう1つの落とし穴は、Podが止まり始めてから、ServiceのEndpointSliceから外れるまでの短い時間差です。その間に届いたリクエストは、止まりかけのPodへ行きます。上のYAMLの preStop の sleep 5 は、この時間差を待つための定番の手です。猶予時間を長くするだけでは、切り替えの順序の問題は解消しません。負荷をかけながらPodを消し、エラーが出ないかを実際に確かめてください。

RBACと最小権限

RBACは、誰が(Subject)・どのリソースに(Resource)・何をできるか(Verb)を決める仕組みです。第2章で、kubectlのリクエストはAPIサーバーで認証と認可を通ると説明しました。その「認可」の中身がRBACです。

リソース役割範囲
Roleできることの一覧(例: Deploymentの get と patch)1つのNamespace
ClusterRole同じく一覧。Nodeのようなクラスター全体のリソースも書けるクラスター全体
RoleBindingRoleやClusterRoleを、人やServiceAccountに結び付ける1つのNamespace
ClusterRoleBindingClusterRoleを、クラスター全体で結び付けるクラスター全体

基本は、Namespace単位のRoleとRoleBindingです。cluster-admin を開発者やCIに常に持たせるのは避けます。CIから更新するためのServiceAccountを作り、Deploymentの更新だけを許可してみます。

# rbac-ci.yaml ― CI用の身元と、許可する操作
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ci-deployer
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer
subjects:
  - kind: ServiceAccount
    name: ci
    namespace: lab
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ci-deployer
kubectl -n lab apply -f rbac-ci.yaml

# --as で「ci として」聞いてみる(いまの自分が管理者なのでできる)
SA=system:serviceaccount:lab:ci
kubectl -n lab auth can-i patch deployments --as=$SA     # yes
kubectl -n lab auth can-i delete pods --as=$SA           # no
kubectl -n lab auth can-i get secrets --as=$SA           # no
kubectl -n lab auth can-i --list --as=$SA                # 許可されている操作の一覧

権限を足したら、足りているかだけでなく、余計なものが入っていないかを can-i で確かめる習慣を付けます。特に注意したいのは、Podを作れる権限です。Podを作れる人は、そのNamespaceのSecretをマウントしたPodや、設定によってはNodeのファイルを覗くPodを作れます。「読むだけの権限」より、ずっと重く扱ってください。

ServiceAccountは、ワークロードの身元です。上の api では automountServiceAccountToken: false にして、APIサーバーを呼ばないアプリにトークンを渡していません。CIの認証情報を、期限のない固定のSecretで持ち続ける方法も避けます。クラウドのIAMと連携する仕組み(EKSのPod Identity、GKEのWorkload Identityなど)を使えば、短い期限の認証情報で済みます。Roleの変更は監査ログに残し、誰がどの権限を持っているかを定期的に棚卸しします。

イメージと実行時のセキュリティ

本番で nginx:latest のようなタグを使うと、同じManifestでも、取得した日によって中身が変わります。障害が起きて「昨日のバージョンに戻したい」と思ったとき、昨日の中身を再現できません。タグに加えて、中身のハッシュであるイメージダイジェストで固定します。

# いま動いている api のイメージが、実際にはどのダイジェストか
kubectl -n lab get pod -l app=api \
  -o jsonpath='{.items[0].status.containerStatuses[0].imageID}{"\n"}'
# 出力の例(ダイジェストは取得した時期によって違う)
docker.io/library/nginx@sha256:(64桁の16進数)
# Manifest では、タグとダイジェストを並べて書ける(読む人のためにタグを残す)
          image: nginx:1.27@sha256:<上で確かめたダイジェスト>

ダイジェストの更新は、CIが明示的に行います。そのほか、信頼できるレジストリ、脆弱性スキャン、署名、SBOM(イメージに何が入っているかの一覧)を組み合わせて、「どこから来た、何が入ったイメージか」を説明できるようにします。

Pod Securityの警告を読む

Kubernetesには、Namespaceにラベルを付けるだけで、危ないPodの設定を警告・拒否する仕組み(Pod Security Admission)があります。いきなり拒否にすると既存のアプリが止まるので、まず warn(警告だけ)で、いまのPodが何を満たしていないかを見ます。

kubectl label namespace lab \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/enforce=baseline
kubectl -n lab rollout restart deployment/api     # Podを作り直して警告を出す
# 出力の例(改行を足している)
Warning: would violate PodSecurity "restricted:latest":
  allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false),
  unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]),
  runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true),
  seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

enforce=baseline は、特権コンテナやNodeのディレクトリのマウントのような、明らかに危ない設定だけを拒否する水準です。restricted はもっと厳しく、警告の4項目がそれにあたります。公式の nginx イメージはrootで起動する前提なので、runAsNonRoot を満たすには、非rootで動くよう作られたイメージ(例: nginxinc/nginx-unprivileged。待ち受けは8080番)に替える必要があります。設定だけで直らない項目があるので、新しいアプリは最初から非rootで作るほうが楽です。

非root化のほかに、読み取り専用のルートファイルシステム、不要なLinux capabilityの削除、seccompも検討します。どうしても例外が必要なPodは、期限なしで許可せず、所有者と期限を記録してください。

監視・ログ・トレース

「遅い」「エラーが出ている」と言われたとき、手元に何があれば原因に近づけるか。答えは、3種類の信号です。役割が違うので、どれか1つでは足りません。

📈 メトリクス 「おかしい」に気づく しきい値 リクエスト数・遅延・エラー率 📜 ログ 「何が起きたか」を読む level=info req=a1 200 12ms level=error req=b7 db timeout level=info req=c3 200 9ms 時刻・リクエストID・重大度 🧵 トレース 「どこで詰まったか」を追う 入口 GET /orders 820ms api DB 760ms ← ここ 1つのリクエストの経路と時間 サービス名・環境名・リクエストID で3つを結び付ける

図5-3: メトリクスで気づき、トレースで場所を絞り、ログで理由を読む。

メトリクスは、クラスターのCPU使用率だけでは足りません。リクエスト数、レイテンシ、エラー率、キューの長さ、DBの接続数のように、利用者への影響に近い指標を集めます。ログは、Podの標準出力をまとめて集め、時刻、リクエストID、サービス名、重大度を決まった形(構造化ログ)で出します。Secret、Authorizationヘッダー、個人情報はログに出さない設計にします。

トレースは、入口のHTTPリクエストが、API、キュー、DB、外部APIのどこで遅くなったかを追います。全部のリクエストを記録すると、費用と保管量が増えるので、どの割合で記録するか(サンプリング率)を決めます。3つの信号を同じサービス名・環境名で結び付けておき、アラートからダッシュボード、そこからログへ、迷わずたどれるようにします。

学習環境でも、Kubernetes自身が出す情報は見られます。

kubectl -n lab logs deploy/api --tail=20           # 3つのうち1つのPodのログ
kubectl -n lab logs -l app=api --prefix --tail=5   # ラベルで全Podのログを、Pod名付きで
kubectl get events -A --sort-by=.lastTimestamp | tail -20

ただし、kubectl logs で見えるのは、いまあるPodのログだけです。Podが消えたり、Nodeごと落ちたりすると読めなくなります。本番では、ログをクラスターの外へ送って残す仕組みが必須です。

当番を起こすアラート、起こさないアラート

アラートを増やしすぎると、当番は通知を見なくなります。「CPUが高い」だけで夜中に起こすのはやめて、利用者への影響と、放っておくと確実に壊れるものに絞ります。

アラートなぜ見るか緊急度の目安
SLO違反・エラー予算の急な消費利用者が実際に困っているすぐ対応
Ready Podの不足あと1つ落ちたら止まるすぐ対応
Pendingの増加スケールしたいのに置き場所がないすぐ〜営業時間
PVCの容量逼迫満杯になるとアプリが書けなくなる残りの日数で決める
証明書の期限切れた瞬間に全部の通信が止まる30日前・7日前に通知
バックアップの失敗壊れたときに戻せない翌営業日

どのアラートにも、最初に見るダッシュボードと、対応手順(後で説明するRunbook)へのリンクを付けておきます。「通知は来たが、何をすればいいか分からない」をなくすためです。

オートスケールとコスト

HorizontalPodAutoscaler(HPA)は、CPUやメモリ、独自の指標を見てPodの数を増減させます。HPAがCPUの値を知るには、metrics-serverが要ります。kindには入っていないので、入れます。

# バージョンを固定して入れる(latest を使わない理由は、イメージの節と同じ)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/components.yaml

# kind の kubelet は自己署名の証明書なので、学習環境に限って検証を外す
kubectl -n kube-system patch deployment metrics-server --type=json \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl -n kube-system rollout status deployment/metrics-server
kubectl top node                    # 数値が出れば準備OK(出るまで1分ほどかかる)
# hpa.yaml ― CPU使用率の平均が requests の60%を超えたら増やす
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

📝 適用すると裏で起きること

  1. HPAのコントローラーが、既定では15秒ごとに、api のPodのCPU使用量をmetrics-serverから読む
  2. 使用量を requestsの値(100m)で割って使用率にし、60%に近づくようにreplicasを計算する
  3. Deploymentの replicas を書き換える。増えたPodは、さっきの散らす設定に従って置かれる
kubectl -n lab apply -f hpa.yaml
kubectl -n lab get hpa api
NAME   REFERENCE        TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
api    Deployment/api   cpu: 0%/60%   3         6         3          30s

負荷をかけて、増えるところを見ます。別のターミナルで kubectl -n lab get hpa api -w を開いておくと、変化が分かりやすいです。

# 負荷をかけるPod(止めるまでアクセスし続ける)
kubectl -n lab run load --image=busybox:1.36 --restart=Never -- \
  /bin/sh -c "while true; do wget -q -O /dev/null http://api; done"

# 数分見たら止める。Podは、5分ほどの様子見のあとで減る
kubectl -n lab delete pod load

nginxは軽いので、PCの性能によっては60%に届かず、増えないことがあります。そのときは averageUtilization を小さくするか、負荷のPodを2つにしてください。増えたPodが Pending になった場合は、Nodeの空きが足りていません。それが次の話です。

Podが増えても、Nodeが増えなければ

HPAが増やすのはPodだけです。Podを置くNodeの空きがなくなれば、増えたPodは Pending で止まります。Node自体を増減させるのは、Cluster AutoscalerやKarpenter、クラウドのNodeの自動スケールの役割です。kindにはこの仕組みがないので、今日は試せません。

Nodeの自動スケールが判断に使うのは、実際の使用量ではなく、Podのrequestsです。requestsが実態より大きいとNodeが無駄に増え、小さいと詰め込みすぎて性能が落ちます。HPAもrequestsを基準に使用率を計算するので、requestsのずれはスケールの判断を両方とも歪めます(演習3で、requestsがないとHPAが動かないところを見ます)。起動の遅いアプリでは、増やしてから効くまで時間がかかるので、キューの長さやレイテンシで早めに増やすことも考えます。逆に、夜間に縮めたいのに、DaemonSetやPDB、Nodeのローカルに置いたデータが邪魔して、Nodeが減らないこともあります。

コストは、Nodeの料金だけではない

見落としやすいのは、ロードバランサー、ディスク、スナップショット、ログの保管、外部IP、データ転送、監視サービスの費用です。第4章の片付けで「消し忘れたディスクに料金がかかり続ける」と書いたのも、この一部です。Namespaceやチームごとに、費用を割り振るためのラベルを決めておき、requestsが大きすぎるワークロードを定期的に見直します。反対に、安いNodeに詰め込みすぎて、障害のときに受け止める余力がなくなるのも避けてください。

アップグレードと変更管理

Kubernetesは、年に3回ほど新しいマイナーバージョンが出て、古いものはサポートが終わります。つまり、アップグレードは「いつかやる」ではなく、毎年何度も回ってくる定期作業です。毎回を緊急作業にしないために、手順を決めて練習します。

① 確認 非推奨APIアドオン対応表サポート期限 ② 検証環境 バックアップから復元代表の動作確認 ③ Control Plane 先に上げる(Nodeより新しく) ④ アドオン CNI・CSIIngress監視・Webhook ⑤ Node グループを1つずつ(PDBが効く) ⑥ 確認 Pod・DNS証明書・ログHPA・バックアップ ③〜⑥のどこでも、決めておいた「戻す条件」に当たったら止める エラー率・レイテンシ・Ready Pod数を、何分見るかを先に書いておく

図5-4: アップグレードは段階に分け、段階ごとに確かめる。黄色が本番で触る段階。

Kubernetes本体、Node、CNI、CSI、Ingress Controller、監視、Admission Webhookは、それぞれ別々に対応バージョンを持っています。本体だけ上げたら、ネットワークプラグインが新しい版に対応していなかった、ということが起こります。上げる前に、使っているアドオンの対応表と、クラウドの提供版のサポート表を確認します。Nodeのkubeletは、Control Planeより新しくできません。だからControl Planeが先です。

もう1つの確認は、非推奨・削除予定のAPIを使っていないかです。APIサーバーは、非推奨のAPIが呼ばれた回数を自分のメトリクスに出しています。

kubectl version
# 非推奨のAPIを呼んだ記録があるか(何も出なければ、起動してからは呼ばれていない)
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis

このメトリクスは、APIサーバーが起動してから実際に呼ばれたものしか出ません。月に1回しか動かないJobやCIのManifestは、別途ファイルを検索して確かめます。Manifestの apiVersion を書き換えれば済むとは限らず、項目の名前や既定値が変わっていることもあります。

本番を上げたあとは、次のように層ごとに確認します。

kubectl get nodes -o wide                              # 全Nodeが Ready で、VERSION がそろったか
kubectl get pods -A | grep -v -E 'Running|Completed'   # 動いていないPodが残っていないか
kubectl get events -A --sort-by=.lastTimestamp | tail -30
kubectl -n lab rollout status deployment/api

アプリの変更も同じ考え方です。更新する前に kubectl diff -f api.yaml で差分を残し、何分間エラー率とレイテンシを見るか、どうなったら戻すかを先に書きます。ロールバックで戻るのはDeploymentだけで、DBのスキーマ変更や送ってしまったメールは戻りません。アプリのリリースとデータの変更は、同じ計画の中で考えます。

⚠️ マネージドサービスでは、手順も表示も提供元の形式になります。 EKS・GKE・AKSでは、Control Planeの更新はコンソールやCLIで依頼し、Nodeの更新はNodeグループ単位で行うのが一般的です。kubectl version のサーバー側の表示に、提供元独自の文字列が付くこともあります。出力だけでサポート期間を判断せず、提供元の更新ポリシーとサポート表を確認してください。延長サポートに追加料金がかかるサービスもあります。

障害対応の手順

障害の最中は、誰でも焦ります。焦った状態で「とりあえずPodを消す」と、原因の手がかりも一緒に消えます。順番を決めておくのは、焦っても同じ動きができるようにするためです。

① 記録 影響範囲開始時刻直前の変更 ② 影響 エラー率レイテンシダッシュボード ③ 層ごと Service・EndpointPod・EventsNode・アプリログ ④ 止血 証拠を残してロールバック機能の縮退 ⑤ 確認 主要な利用の流れ裏の処理アラート ⑥ 振り返り 時系列仕組みの改善個人を責めない 「たぶん直った」で閉じない。⑤で、利用者の操作が通ることを確かめてから閉じる

図5-5: 障害対応の流れ。④の前に、Podの状態・ログ・Eventsを保存しておく。

最初に記録するのは、影響範囲、開始時刻、直前の変更、いまの症状です。障害の原因でいちばん多いのは直前の変更なので、デプロイや設定変更の履歴をすぐ見られるようにしておきます。変更が原因と分かれば、証拠を残したうえで、ロールバックや機能の縮退を判断します。

層ごとに見るときは、この特集のこれまでの章が、そのまま手引きになります。

症状まず疑う場所詳しくは
PendingNodeの空き(requests)、PVC、散らす設定・taint第2章・第4章・この章の演習
ImagePullBackOffイメージ名・タグ、レジストリの認証、Nodeからの通信第1章
CrashLoopBackOffアプリの起動、設定値・Secret、Probeの設定第3章
503・繋がらないEndpointSlice、readiness、ポート、NetworkPolicy、Ingress第4章
ForbiddenRBAC、ServiceAccount、Admissionの拒否この章の演習2
Nodeが NotReadyクラウドのインスタンス状態、ディスク、CNI、kubelet下の注意

Nodeの障害では、いきなりPodを消して証拠を失わないことです。先にクラウドのインスタンスの状態、ディスクの空き、CNI、kubeletのログを確かめます。データを伴う障害では、書き込みを止める、二重に処理しない、どの時点まで戻すか、戻したあとに整合性を確かめる、を優先します。直ったと思ったら、主要な利用の流れ、裏で動く処理、メトリクス、ログ、アラートを確認し、時系列と再発防止策を残します。

障害のあとの振り返り(ポストモーテム)では、個人を責めません。検知、判断、復旧、連絡のどこに仕組みの改善余地があったかを確認します。「Aさんがコマンドを間違えた」で終わると、次はBさんが同じ間違いをします。

マネージドKubernetesを選ぶ判断

EKS、GKE、AKSのようなマネージドサービスは、Control Planeの可用性や更新など、運用の一部を提供元が引き受けます。ただし、全部ではありません。

項目提供元が担うことが多い自分たちに残る
Control PlaneAPIサーバーとetcdの可用性、パッチ更新のタイミングの判断、アドオンとの互換性の確認
Nodeイメージの提供、自動更新の仕組み(サービスによる)Nodeの種類と台数、更新の実施、余力の確保
ネットワークCNIの既定の実装、ロードバランサーとの連携NetworkPolicy、Ingress・Gateway、IPアドレスの設計
ストレージCSIドライバー、ディスクの提供StorageClassの選択、バックアップと復元テスト
権限クラウドのIAMとの連携の仕組みRBAC、ServiceAccountとIAMの対応付け、棚卸し
観測・費用基本のログ・メトリクスの出力先何を集めるか、アラート、費用の配賦と見直し

選ぶときは、既存のクラウドとの統合、チームの経験、必要な拡張性、Node管理の手間、サポート期限、マルチクラウドの要件、障害のときの連絡経路を比べます。Kubernetes APIと互換だから同じ、とは考えず、使うアドオンと運用体制を含めた総コストで判断してください。

そして、特集トップで書いたことに戻ります。単一のWebサービスや少数の定期ジョブなら、AWS ECSやCloud Runのような、もっと抽象度の高いマネージドコンテナやPaaSのほうが合理的なことがよくあります。この章の表を見て「自分たちに残る」列を担える人がいないなら、それは「まだKubernetesでなくていい」という判断材料です。

わざと壊す演習

今日作った api・PDB・ci・HPAを使って、3つ壊します。どれも、本番で「設定は入れたのに、肝心なときに効かなかった」となる典型です。HPAで api が増えたままなら、3つに戻るまで数分待ってから始めてください(kubectl -n lab get deploy api)。

演習1

PDBが厳しすぎて、ドレインが終わらない

「絶対に1つも減らしたくない」と考えて、3つのPodに minAvailable: 3 を設定した状態を作ります。そのうえで、Nodeのメンテナンスのためにドレインします。

kubectl -n lab patch pdb api --type=merge \
  -p '{"spec":{"maxUnavailable":null,"minAvailable":3}}'
kubectl -n lab get pdb api            # ALLOWED DISRUPTIONS が 0

kubectl drain ops-worker --ignore-daemonsets --delete-emptydir-data --timeout=60s
# 出力の例(Pod名は環境によって違う。60秒間、同じ行がくり返される)
node/ops-worker cordoned
evicting pod lab/api-6c8d7b9f5d-2kq8x
error when evicting pods/"api-6c8d7b9f5d-2kq8x" -n "lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
...
error: unable to drain node "ops-worker" due to error: [error when evicting pods/"api-6c8d7b9f5d-2kq8x" -n "lab": global timeout reached: 1m0s], continuing command...

ドレインは、PDBを守るために追い出しを待ち続け、時間切れで止まりました。本番でタイムアウトを付けずに実行すると、メンテナンスの時間枠が終わるまで待ち続けます。もう1つ大事なのは、失敗してもNodeは「新しいPodを置かない」状態のまま残ることです。

kubectl get nodes                     # ops-worker が Ready,SchedulingDisabled のまま

直すときは、PDBを外すのではなく、可用性の目標に合った値に戻します。3つのうち1つずつなら止めてよい、が今日の目標でした。

kubectl -n lab apply -f pdb.yaml      # maxUnavailable: 1 に戻す
kubectl uncordon ops-worker           # 置かない状態を解除する
kubectl get nodes

PDBの値は、「単一のレプリカ」「Nodeのローカルに置いたデータ」「起動に何分もかかるアプリ」と組み合わさると、もっと詰まりやすくなります。ドレインが止まったときに、PDBだけを緩めて押し通すのではなく、なぜ追い出せないのかを先に確かめてください。

演習2

更新の権限はあるのに、スケールできない

CI用の ci には、Deploymentの patch を許可しました。では、ci としてPodの数を4にしてみます。

SA=system:serviceaccount:lab:ci
kubectl -n lab scale deployment api --replicas=4 --as=$SA
# 出力の例
Error from server (Forbidden): deployments.apps "api" is forbidden: User "system:serviceaccount:lab:ci" cannot patch resource "deployments/scale" in API group "apps" in the namespace "lab"

エラーの文に答えが書いてあります。kubectl scale が触るのは deployments ではなく、deployments/scale というサブリソースです。RBACでは、サブリソースは別のリソースとして扱われます。Podのログ(pods/log)や、Podの中でコマンドを実行する pods/exec も同じです。

kubectl -n lab auth can-i patch deployments --as=$SA                       # yes
kubectl -n lab auth can-i patch deployments --subresource=scale --as=$SA   # no

直すには、Roleにサブリソースの行を足します。rbac-ci.yaml の rules を次のようにして、もう一度 apply します。

rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "patch"]
  - apiGroups: ["apps"]
    resources: ["deployments/scale"]
    verbs: ["get", "patch", "update"]
kubectl -n lab apply -f rbac-ci.yaml
kubectl -n lab auth can-i patch deployments --subresource=scale --as=$SA   # yes
kubectl -n lab scale deployment api --replicas=4 --as=$SA
kubectl -n lab get hpa api -w        # 数分後、HPAが3に戻す

スケールは通りましたが、しばらくするとHPAが3に戻します。HPAが管理しているDeploymentでは、手で変えた数は長続きしません。本番でPodを増やしたいときは、HPAの minReplicas を変えます。権限の設計では、「その操作を、そもそもCIにさせるべきか」も一緒に考えてください。

演習3

requestsを消したら、HPAが止まった

「requestsはよく分からないので書かない」状態を作ります。第3章で、requestsはスケジューラーが使う予約の量だと説明しました。HPAにとっても、計算の基準です。

kubectl -n lab patch deployment api --type=json \
  -p='[{"op":"remove","path":"/spec/template/spec/containers/0/resources/requests"}]'
kubectl -n lab rollout status deployment/api
kubectl -n lab get hpa api
# 出力の例
NAME   REFERENCE        TARGETS              MINPODS   MAXPODS   REPLICAS   AGE
api    Deployment/api   cpu: <unknown>/60%   3         6         3          25m

Podは問題なく動いていて、アクセスも通ります。気づきにくいのはそこです。HPAは静かに、判断をやめています。理由はEventsにあります。

kubectl -n lab describe hpa api | tail -5
# 出力の例(文言はバージョンによって違う)
Warning  FailedGetResourceMetric  ...  failed to get cpu utilization: missing request for cpu in container "nginx" of Pod "api-..."

使用率は「使用量 ÷ requests」なので、requestsがなければ計算できません。この状態で負荷が来ても、Podは増えません。HPAを入れたら、TARGETS に数字が出ていることまでを確認項目にしてください。

kubectl -n lab apply -f api.yaml      # requests を戻す
kubectl -n lab get hpa api            # 1分ほどで cpu: 0%/60% のように数字に戻る

ここで1つ注意があります。api.yaml には replicas: 3 が書いてあるので、applyするたびに、HPAが決めた数を3に上書きします。本番でHPAを使うDeploymentは、Manifestから replicas を消し、数はHPAに任せます。

壊したら、メモを残す

第1章から使っている6項目です。今日は「再発防止」に、仕組みで防ぐ方法(設定、確認項目、権限)を書いてください。

# 演習メモの例
日時/Context : 2026-10-24 21:30 / kind-ops(Namespace: lab)
症状         : ops-worker のドレインが60秒で時間切れ。Node は SchedulingDisabled のまま
最初に見たもの: drain の出力(violate the pod's disruption budget)→ get pdb で ALLOWED DISRUPTIONS 0
原因         : replicas 3 に対して PDB が minAvailable: 3。1つも追い出せない
修正         : PDB を maxUnavailable: 1 に戻し、uncordon した
再発防止     : PDB の値をレビュー項目にする。ALLOWED DISRUPTIONS が 0 のPDBをアラートの対象にする

本番前のゲームデイ

チェックリストを読むだけでは、障害のときに使える運用かどうかは分かりません。わざと壊して確かめる練習を、ゲームデイと呼びます。演習1を、正しいPDBでもう一度やってみます。今度はドレインが通るはずです。そのあとに何が起きるかを見てください。

kubectl -n lab get pdb api                          # ALLOWED DISRUPTIONS が 1
kubectl -n lab get pods -o wide
kubectl drain ops-worker --ignore-daemonsets --delete-emptydir-data --timeout=5m
kubectl -n lab get pods -o wide                     # 追い出された api の代わりはどこへ?

ドレインは成功します。ところが、追い出された api の代わりのPodが、Pending のまま残ることがあります。describe で理由を見ます。

kubectl -n lab get pods -l app=api --field-selector=status.phase=Pending
kubectl -n lab describe pod <Pendingのapi のPod名>lt;Pendingになったapi Pod名<Pendingのapi のPod名>gt; | tail -5
# 出力の例(改行を足している)
0/4 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
1 node(s) were unschedulable, 2 node(s) didn't match pod topology spread constraints.

zone-aにはNodeが1台しかなく、そのNodeをドレインしました。残りのzone-bとzone-cに置くと、ゾーン間の差が2になり、maxSkew: 1 と DoNotSchedule を守れません。スケジューラーは指示どおり、置かずに待っています。設定は正しく効いていて、そのせいでメンテナンス中は2つで動くことになりました。

これがゲームデイで見つけたい「残った課題」です。対策は、1つのゾーンに複数のNodeを置く、メンテナンス中は2つで受けきれるだけの余力を持たせる、散らす設定を ScheduleAnyway(できるだけ守る)にする、などがあり、どれを選ぶかは可用性の目標で決まります。

kubectl uncordon ops-worker
kubectl -n lab get pods -o wide                     # Pending だった api が ops-worker に置かれる

Node名やドレインのオプションは、環境に合わせます。--delete-emptydir-data は、emptyDirの一時データを消すことに同意する指定です(今日はmetrics-serverが使っています)。本番では、意味を確かめずに付けないでください。ゲームデイには、止める条件と連絡先を先に決め、結果と残った手作業を記録します。成功の条件は「コマンドが終わった」ではなく、SLOを守れたか、復旧までの時間を測れたかです。

本番導入チェックリスト

ここまでの内容を、本番に出す前の確認項目にまとめました。全部に印が付くまで出してはいけない、という意味ではありません。印が付かない項目は、そのリスクを誰が受け入れたのかを記録してください。

  • 複数のレプリカを障害ドメインへ散らし、PDBとGraceful Shutdownを試した
  • RBAC、ServiceAccount、Secretの保存時暗号化、NetworkPolicyを確認した
  • イメージをスキャンし、署名・SBOM・ダイジェストでの固定を運用に組み込んだ
  • メトリクス、構造化ログ、トレース、SLOに基づくアラートがある
  • HPAとNodeの自動スケールを負荷試験し、コストを見える化した
  • etcd・PVC・アプリのデータのバックアップを分け、復元テストをした
  • クラスターとアドオンの更新計画、非推奨APIの確認、ロールバック手順がある
  • Pending、CrashLoopBackOff、503、Node障害の訓練をした
  • マネージドサービスの責任分界とサポート期限を文書にした

運用を属人化させない

当番が経験でコマンドを選ぶだけでは、夜間の障害の復旧の質が安定しません。サービスごとに、次のような手順書(Runbook)を用意します。

# Runbook の例(サービス api)
依存しているもの : DB(本番RDS)、決済API(外部)、Namespace: app
SLO            : 成功率 99.9%(30日)、p95 レイテンシ 300ms 以下
見るダッシュボード: api-overview(エラー率・p95・Ready Pod数)
ログの検索     : service="api" env="prod" level="error"
戻す条件       : デプロイ後10分以内に エラー率 1% 超 または p95 500ms 超
戻し方         : kubectl -n app rollout undo deployment/api(DBの変更を伴う版は不可。下の連絡先へ)
データの連絡先 : DB担当の当番(連絡方法を書く)
最終確認日     : 2026-10-24(検証クラスターで手順を実行)

Runbookは、平常時に読み合わせ、検証クラスターで実際にコマンドを実行して、権限不足や古いリソース名を直します。書いたきり一度も実行していない手順は、障害のときにだいたい動きません。

運用の指標は、稼働率だけでなく、変更の失敗率、平均復旧時間、バックアップからの復元にかかる時間、残っている脆弱性の数、当番に届いたアラートの数でも見直します。Nodeを増やす前に、requests、リトライの設定、ログの量、常に動かしておく必要のないワークロードを確かめます。新しい機能を足すときは、解決する問題と、やめるときの外し方も書いておきます。

続けて見直す項目

頻度見直す項目
毎月使っていないNamespaceとロードバランサー、大きすぎるrequests、失敗したJob、期限の近い証明書、バックアップの容量、脆弱性が残ったイメージ
四半期ごと復元の演習、権限の棚卸し、クラスターとアドオンのサポート期限の更新
規模が変わったときNamespaceの設計、GitOps、Policy as Code、費用の配賦、当番の体制。逆に、運用担当が1人しかいないなら、もっと高レベルなマネージド基盤へ移す判断も含める

技術の採用そのものを目的にせず、サービスの信頼性と変更の速さを測って判断してください。

片付け

今日はクラスターごと作ったので、クラスターごと消します。

kind delete cluster --name ops
kind get clusters                     # ops が消えたことを確認
kubectl config get-contexts           # 次に使うクラスターを確かめる
kubectl config use-context kind-kind  # 第1章から使ってきたクラスターに戻す場合

kindのクラスターを消すと、中のPV、metrics-server、RBACの設定もすべて消えます。クラウドで同じ練習をした場合は、Nodeグループ、ロードバランサー、ディスク、ログの保管先が残っていないかを、コンソールでも確かめてください。

まとめ

  • replicasの数だけでは可用性は決まらない。散らす設定・PDB・止まり方・余力をそろえる
  • PDBは自発的な中断を制限する。厳しすぎるとドレインが止まり、Nodeは置かない状態のまま残る
  • RBACは Namespace 単位の Role が基本。サブリソース(scale・log・exec)は別扱い。auth can-i で確かめる
  • イメージはダイジェストで固定する。Pod Securityはまず warn で現状を見る
  • メトリクスで気づき、トレースで場所を絞り、ログで理由を読む。アラートには手順を付ける
  • HPAもNodeの自動スケールも requests が基準。requestsがないとHPAは静かに止まる
  • アップグレードは定期作業。確認 → 検証 → Control Plane → アドオン → Node → 確認 の順に、戻す条件を決めて進める
  • 障害対応は 記録 → 影響 → 層ごと → 止血 → 確認 → 振り返り。証拠を消さない

特集のまとめ ― 6日間をふり返る

5つの章を通して、Kubernetesが「Podを起動する仕組み」から、状態を宣言し、通信・ストレージ・更新・観測・復旧を組み立てる運用の基盤へと、広がって見えてきたと思います。

章手に入れたもの
🧱 第1章 基礎「あるべき状態を宣言すると、近づけてくれる」という考え方。Context確認と演習メモの習慣
⚙️ 第2章 アーキテクチャAPIサーバー・etcd・スケジューラー・kubeletが、YAMLをどうPodに変えるか
📦 第3章 ワークロードDeployment・Probe・requests/limits・ConfigMap/Secret。安全に更新して戻す
🌐 第4章 ネットワークとストレージ届く経路を層に分けて調べる目と、Podが消えても残るデータ
🏭 第5章 本番運用(このページ)壊れても判断できる仕組み。可用性・権限・観測・更新・障害対応

大切なのは、リソースの種類を増やすことではありません。変更を再現でき、失敗に気づけて、データを戻せることです。Kubernetesを採用するなら、最初から本番に近い課題を、今日のように小さく練習してください。採用しないと決めた場合も、コンテナ、サービスディスカバリ、ヘルスチェック、資源の制御、監視という設計の考え方は、ECSでもCloud Runでもそのまま使えます。

🎉 Kubernetes特集は、この第5章で完結です。 6日間、毎日読んでくださった方、本当にありがとうございました。途中で手が止まった章があっても大丈夫です。全ページは特集トップからいつでも読めます。演習メモが手元に残っていれば、それがあなた専用のトラブルシューティング集になります。

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

全6ページがそろいました。障害対応で「あの切り分けの表はどこだっけ」となったとき、特集トップを1つ保存しておけば、どの章にもすぐ戻れます。チームの新しいメンバーに渡すときも、特集トップのURLを1つ送るだけで済みます。

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

新しい特集や記事の公開は、X(@BusinessHub413)でお知らせします。

公式資料

※本章の記述は2026年10月時点の公式ドキュメントに基づきます。エラーやイベントの文言、kubectl の出力形式、スケジューラーの判断の細部は、Kubernetesのバージョンや学習環境(kind・minikube・Docker Desktop)によって異なることがあります。マネージドサービスの責任分界とサポート期限は、提供元の最新の資料で確認してください。本章のコマンドは手順として整理したもので、すべての環境での動作を確認したものではありません。

Kubernetesの本番導入や運用設計についてご相談ください

「本番に出す前に、何が足りないかを見てほしい」「ECSやCloud Runで足りるのか」から、
クラスター設計・運用体制づくりまで、30分の無料相談で承ります。

無料相談を予約する →