
- ランブックの概要
- 事前準備チェックリスト
- 本番アップグレードの実行手順
- ロールバック手順
- トラブルシューティング: マルチクラスターアップグレード中の実際の障害シナリオ
- アップグレード後の観察期間
- ポストモーテムのテンプレート
- 自動化: CI/CDパイプラインでのアップグレード
- 参考資料
ランブックの概要
このドキュメントは 2つ以上のKubernetesクラスターを運用しているチーム がバージョンアップグレードを実行するときに従うべき段階的な手順書(Runbook)である。障害発生時の判断を最小限にし、事前に定めた手順どおりに実行できるように作成した。
適用対象
- マルチリージョンまたはマルチ環境(dev/staging/prod)のクラスター運用
- マネージドサービス(EKS, GKE, AKS)と自己管理(kubeadm, kOps)が混在する環境
- アップグレード対象バージョン: v1.32 → v1.33 (または類似のマイナーバージョンアップグレード)
バージョンskewポリシーの要約
Kubernetesはマイナーバージョンの飛ばしを許可しない。必ず順番にアップグレードする必要がある。
| コンポーネント | 許容されるskew | 例 (CPがv1.33のとき) |
|---|---|---|
| kubelet | CP基準で -2 | v1.31, v1.32, v1.33を許容 |
| kube-proxy | CPと同じマイナー | v1.33のみ許容 |
| kubectl | CP基準で +/-1 | v1.32, v1.33, v1.34を許容 |
| etcd | 特定のバージョン組合せ | リリースノートで確認 |
事前準備チェックリスト
アップグレード作業のD-7(1週間前)から下の項目を順に完了させる。
D-7: 変更点の分析
#!/bin/bash
# d7-changelog-review.sh
# アップグレード対象バージョンの変更点を整理する
TARGET_VERSION="v1.33"
echo "=== ${TARGET_VERSION} 変更点のチェック ==="
echo ""
echo "[1] Deprecated APIの確認"
echo " - リリースノートのRemoved/Deprecatedセクションを確認"
echo " - Endpoints API → EndpointSlice の移行が必要かどうか"
echo " - flowcontrol.apiserver.k8s.io/v1beta3 → v1 への移行"
echo ""
echo "[2] Feature Gateの変更確認"
echo " - InPlacePodVerticalScaling: Beta (デフォルトで有効)"
echo " - UserNamespacesSupport: Stable"
echo " - SidecarContainers: Stable"
echo " - NFTablesProxyMode: Stable"
echo ""
echo "[3] 使用中のAddonの互換性確認"
# 各addonの互換性マトリクスを確認する
ADDONS=(
"calico"
"cilium"
"ingress-nginx"
"cert-manager"
"external-dns"
"prometheus-operator"
"argocd"
)
for addon in "${ADDONS[@]}"; do
echo " - $addon: [ ] 互換性の確認完了"
done
D-5: ステージングクラスターのアップグレード
本番の前に必ずステージングで先にアップグレードする。ステージングがなければ、devクラスターだけでも先に進める。
# マルチクラスター環境でクラスター一覧を確認
kubectl config get-contexts
# ステージングクラスターのコンテキストに切り替え
kubectl config use-context staging-cluster
# ステージングのアップグレードを実行 (マネージドサービスの例: EKS)
aws eks update-cluster-version \
--name staging-cluster \
--kubernetes-version 1.33
# アップグレード状態のモニタリング
aws eks describe-update \
--name staging-cluster \
--update-id <update-id>
D-3: ステージング検証の完了
ステージングで最低48時間以上観察したうえで、下の項目を確認する。
- [ ] すべてのノードがReady
- [ ] すべてのシステムPodがRunning
- [ ] アプリケーションPodが正常に動作
- [ ] Ingress/Serviceのトラフィックが正常
- [ ] CronJobが1回以上成功
- [ ] HPA/VPAが正常に動作
- [ ] モニタリングメトリクスの収集が正常
- [ ] ログパイプラインが正常
- [ ] Deprecated APIの警告がない
- [ ] 性能テストに合格 (応答時間、スループット)
D-1: 本番アップグレードの最終準備
#!/bin/bash
# d1-final-prep.sh
echo "=== D-1 最終準備 ==="
# 1. 変更告知
echo "[1] 変更告知の送信有無: [ ]"
echo " - 社内チームへのSlack告知"
echo " - 外部ステータスページの更新"
echo " - メンテナンス時間帯の共有"
# 2. オンコール担当者の確認
echo "[2] オンコール担当者:"
echo " - Primary: ___________"
echo " - Secondary: ___________"
echo " - エスカレーション経路: Primary → Secondary → Tech Lead"
# 3. ロールバック準備
echo "[3] ロールバックの準備状況:"
echo " - [ ] etcdスナップショットの作成完了"
echo " - [ ] 以前のバージョンのバイナリを保管"
echo " - [ ] ロールバック手順書の確認"
echo " - [ ] ロールバック所要時間の見積り: ___分"
# 4. メンテナンス時間帯
echo "[4] 作業時間帯:"
echo " - 開始: ___:___ (KST)"
echo " - 終了予定: ___:___ (KST)"
echo " - 最大許容: ___:___ (KST)"
本番アップグレードの実行手順
Phase 1: アップグレード開始の宣言
# Slack Webhookで作業開始を通知
curl -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d '{
"text": ":wrench: [MAINTENANCE] Kubernetesアップグレード開始\nクラスター: prod-cluster-01\n対象バージョン: v1.33\n作業者: @oncall\n想定所要時間: 2時間"
}'
Phase 2: マルチクラスターの順次アップグレード
マルチクラスター環境では トラフィックの比重が低いクラスターから 順にアップグレードする。
アップグレードの順序:
1. canary-cluster (トラフィック5%) ← 先にアップグレード、1時間観察
2. prod-cluster-02 (トラフィック30%) ← canaryが安定してから
3. prod-cluster-01 (トラフィック65%) ← 最後
マネージドサービス別のアップグレードコマンド
EKS:
# Control Planeのアップグレード
aws eks update-cluster-version \
--name prod-cluster-01 \
--kubernetes-version 1.33
# アップグレード完了を待つ
aws eks wait cluster-active --name prod-cluster-01
# Managed Node Groupのアップグレード
aws eks update-nodegroup-version \
--cluster-name prod-cluster-01 \
--nodegroup-name workers-general \
--kubernetes-version 1.33
# Addonのアップグレード
for ADDON in vpc-cni coredns kube-proxy; do
LATEST=$(aws eks describe-addon-versions \
--addon-name $ADDON \
--kubernetes-version 1.33 \
--query 'addons[0].addonVersions[0].addonVersion' \
--output text)
aws eks update-addon \
--cluster-name prod-cluster-01 \
--addon-name $ADDON \
--addon-version $LATEST
done
GKE:
# Control Planeのアップグレード
gcloud container clusters upgrade prod-cluster-01 \
--master \
--cluster-version 1.33.0-gke.100 \
--zone asia-northeast3-a
# Node Poolのアップグレード
gcloud container clusters upgrade prod-cluster-01 \
--node-pool workers-general \
--cluster-version 1.33.0-gke.100 \
--zone asia-northeast3-a
AKS:
# アップグレード可能なバージョンを確認
az aks get-upgrades \
--resource-group myResourceGroup \
--name prod-cluster-01 \
--output table
# Control PlaneとNode Poolの同時アップグレード
az aks upgrade \
--resource-group myResourceGroup \
--name prod-cluster-01 \
--kubernetes-version 1.33.0
Phase 3: クラスターごとの健全性確認
各クラスターのアップグレード完了後に必ず実行する。
#!/bin/bash
# cluster-health-check.sh <cluster-context>
CONTEXT=$1
echo "=== Health Check: $CONTEXT ==="
kubectl --context "$CONTEXT" get nodes -o wide
# すべてのノードのバージョンを確認
echo ""
echo "Node versions:"
kubectl --context "$CONTEXT" get nodes \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kubeletVersion}{"\n"}{end}'
# システムPodの状態
echo ""
echo "System pods:"
FAILING=$(kubectl --context "$CONTEXT" -n kube-system get pods --no-headers | \
grep -v "Running\|Completed" | wc -l)
echo "Failing system pods: $FAILING"
# 本番ワークロードの状態
echo ""
echo "Production workloads:"
kubectl --context "$CONTEXT" -n production get deployments -o wide
# Pending Podの確認
echo ""
echo "Pending pods:"
kubectl --context "$CONTEXT" get pods -A --field-selector=status.phase=Pending
# イベントの確認 (直近10分)
echo ""
echo "Recent warnings:"
kubectl --context "$CONTEXT" get events -A --sort-by='.lastTimestamp' \
--field-selector type=Warning | tail -20
Phase 4: トラフィックの移行と観察
マルチクラスターでトラフィックを制御する方法はインフラによって異なる。
# AWS Route53の重み付けベースルーティングの例
# canaryクラスターへトラフィックを段階的に移す
# ステップ1: canaryに5%のトラフィック
# prod-cluster-01: weight 65
# prod-cluster-02: weight 30
# canary-cluster: weight 5
# ステップ2: canaryの安定を確認したうえで30分待機
# ステップ3: canaryに20%のトラフィック
# prod-cluster-01: weight 50
# prod-cluster-02: weight 30
# canary-cluster: weight 20
# Route53の重みを更新
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890 \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "canary",
"Weight": 20,
"AliasTarget": {
"HostedZoneId": "Z9876543210",
"DNSName": "canary-alb.ap-northeast-2.elb.amazonaws.com",
"EvaluateTargetHealth": true
}
}
}]
}'
ロールバック手順
アップグレード後に問題が発生した場合は、下の判断基準に従ってロールバックするかどうかを決める。
ロールバックの判断基準
| 深刻度 | 症状 | 対応 |
|---|---|---|
| P1 - Critical | API Serverが応答しない、大規模なPod CrashLoop、データ消失のリスク | 直ちにロールバック |
| P2 - High | 特定ワークロードの障害、10%以上のエラー率上昇、メトリクス収集不可 | 30分以内に原因を特定できなければロールバック |
| P3 - Medium | マイナー機能の障害、警告レベルのエラー増加、性能低下10%以内 | 原因を分析したうえでホットフィックスを優先 |
| P4 - Low | ログ警告のみ発生、動作に影響なし | モニタリングを継続し、次のメンテナンスで対応 |
マネージドサービスのロールバック
マネージドサービスではControl Planeのダウングレードがサポートされない場合が多い。代わりにノードグループを以前のバージョンに置き換える。
# EKS: 以前のバージョンのノードグループを作成
aws eks create-nodegroup \
--cluster-name prod-cluster-01 \
--nodegroup-name workers-rollback \
--kubernetes-version 1.32 \
--node-role arn:aws:iam::123456789:role/eks-node-role \
--subnets subnet-abc subnet-def \
--instance-types m6i.xlarge \
--scaling-config minSize=3,maxSize=10,desiredSize=5
# 既存ノードグループのPodを新しいノードグループへ移す
kubectl cordon -l eks.amazonaws.com/nodegroup=workers-general
kubectl drain -l eks.amazonaws.com/nodegroup=workers-general \
--ignore-daemonsets --delete-emptydir-data
# 既存ノードグループの削除
aws eks delete-nodegroup \
--cluster-name prod-cluster-01 \
--nodegroup-name workers-general
マルチクラスターのトラフィックロールバック
問題が発生したクラスターからトラフィックを外す。
# 問題のクラスターのトラフィック重みを0に設定
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890 \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "canary",
"Weight": 0,
"AliasTarget": {
"HostedZoneId": "Z9876543210",
"DNSName": "canary-alb.ap-northeast-2.elb.amazonaws.com",
"EvaluateTargetHealth": true
}
}
}]
}'
トラブルシューティング: マルチクラスターアップグレード中の実際の障害シナリオ
シナリオ1: Node Groupアップグレード中にPDB違反でdrainが失敗
$ kubectl drain node-xyz --ignore-daemonsets
error: Cannot evict pod as it would violate the pod's disruption budget.
原因: PDBの minAvailable が現在のRunning Pod数と同じで、1つも退去させられない状態。
解決:
# PDBの状態を確認
kubectl get pdb -A -o wide
# 出力例:
# NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS
# production web-pdb 3 N/A 0
# ↑ 0ならdrain不可
# オプション1: レプリカ数を先に増やして余裕を確保
kubectl -n production scale deployment web --replicas=4
# 1~2分後にもう一度drainを試す
# オプション2: PDBを一時的に緩和する (注意: 可用性が下がる)
kubectl -n production patch pdb web-pdb \
--type=merge -p '{"spec":{"minAvailable":2}}'
# drain完了後に元へ戻す
kubectl -n production patch pdb web-pdb \
--type=merge -p '{"spec":{"minAvailable":3}}'
シナリオ2: アップグレード後のAddon非互換
$ kubectl -n kube-system logs ingress-nginx-controller-xyz
Error: the server could not find the requested resource (get ingresses.networking.k8s.io)
原因: Ingress Controllerのバージョンが新しいKubernetes APIバージョンと互換性がない。
解決:
# Addonの互換バージョンを確認してアップグレード
helm repo update
# ingress-nginxの互換バージョンを確認
helm search repo ingress-nginx/ingress-nginx --versions | head -10
# 互換バージョンへアップグレード
helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
--namespace kube-system \
--version 4.12.0 \
--reuse-values
シナリオ3: マルチクラスター間のバージョン不一致によるサービスメッシュ障害
# クラスターA: v1.33、クラスターB: v1.32
# Istioのマルチクラスター構成で一部のトラフィックルーティングが失敗
$ istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS
web-v1-xyz.production cluster-a SYNCED SYNCED SYNCED SYNCED
web-v1-abc.production cluster-b STALE STALE STALE STALE
原因: Istioのバージョンが両方のクラスターのKubernetesバージョンをどちらもサポートしているかの確認が必要。
解決:
# Istioの互換性を確認
istioctl version
# 両方のクラスターで対応しているIstioバージョンに統一する
# Istio 1.24+はKubernetes v1.31~v1.33に対応
istioctl upgrade --set revision=1-24-0
シナリオ4: EKS Addonのアップグレード順序の誤り
$ aws eks update-addon --addon-name vpc-cni ...
An error occurred (InvalidParameterException): Addon version v1.19.0 is not
compatible with cluster version 1.33. Available versions: v1.19.2, v1.20.0
解決:
# 互換性のある最新バージョンを照会
aws eks describe-addon-versions \
--addon-name vpc-cni \
--kubernetes-version 1.33 \
--query 'addons[0].addonVersions[:5].{version:addonVersion,default:compatibilities[0].defaultVersion}' \
--output table
# 互換バージョンへ更新
aws eks update-addon \
--cluster-name prod-cluster-01 \
--addon-name vpc-cni \
--addon-version v1.20.0 \
--resolve-conflicts OVERWRITE
アップグレード後の観察期間
観察項目と期間
| 観察項目 | 確認間隔 | 正常の基準 | 異常時の対応 |
|---|---|---|---|
| API Serverのエラー率 | 5分 | < 0.1% | ログ確認 + エスカレーション |
| Podの再起動回数 | 15分 | アップグレード前から増加なし | 再起動したPodのログを確認 |
| ノードの状態 | 10分 | 全体がReady | NotReadyノードを調査 |
| サービス応答時間 (p99) | 5分 | アップグレード前と比べ10%以内 | プロファイリング |
| etcd latency | 5分 | < 100ms | etcdのディスクI/Oを確認 |
| Deprecated APIの警告 | 1時間 | 0件 | 該当コンポーネントのコード修正 |
観察期間の定義
アップグレード後 0~2時間: 集中観察 (5分間隔で確認)
アップグレード後 2~24時間: 通常観察 (30分間隔)
アップグレード後 24~72時間: 警戒観察 (異常アラートに対応)
アップグレード後 72時間~: 通常運用へ移行、ポストモーテムを作成
ポストモーテムのテンプレート
アップグレード完了後72時間以内にポストモーテムを作成する。障害の有無に関係なく作成し、次のアップグレードに反映する。
# クラスターアップグレードのポストモーテム
## 基本情報
- 対象クラスター: \_\_\_
- 以前のバージョン: v1.32.x → 現在のバージョン: v1.33.x
- 作業時間: YYYY-MM-DD HH:MM ~ HH:MM (合計 \_\_\_時間)
- 作業者: \_\_\_
## 進行の要約
- [ ] ステージングのアップグレード: 問題なし / 問題発生 (詳細: \_\_\_)
- [ ] 本番のアップグレード: 問題なし / 問題発生 (詳細: \_\_\_)
- [ ] ロールバックの実行有無: Yes / No
## 発見された課題
| 課題 | 深刻度 | 解決方法 | 所要時間 |
| ------ | ------ | -------- | --------- |
| \_\_\_ | P1~P4 | \_\_\_ | \_\_\_分 |
## 改善事項 (次のアップグレードに反映)
1. ***
2. ***
3. ***
## 更新が必要なドキュメント
- [ ] このランブック
- [ ] オンコールガイド
- [ ] モニタリングダッシュボード
自動化: CI/CDパイプラインでのアップグレード
繰り返されるマルチクラスターアップグレードをGitHub Actionsで自動化する例。
# .github/workflows/cluster-upgrade.yml
name: Kubernetes Cluster Upgrade
on:
workflow_dispatch:
inputs:
target_version:
description: 'Target Kubernetes version'
required: true
default: '1.33'
cluster:
description: 'Cluster to upgrade'
required: true
type: choice
options:
- canary-cluster
- prod-cluster-02
- prod-cluster-01
dry_run:
description: 'Dry run mode'
required: true
type: boolean
default: true
jobs:
pre-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ap-northeast-2
- name: Pre-upgrade checks
run: |
aws eks describe-cluster --name ${{ inputs.cluster }} \
--query 'cluster.{version:version,status:status}' \
--output table
# 現在のバージョンを確認
CURRENT=$(aws eks describe-cluster --name ${{ inputs.cluster }} \
--query 'cluster.version' --output text)
echo "Current version: $CURRENT"
echo "Target version: ${{ inputs.target_version }}"
# Addonの互換性を確認
for ADDON in vpc-cni coredns kube-proxy; do
echo "Checking $ADDON compatibility..."
aws eks describe-addon-versions \
--addon-name $ADDON \
--kubernetes-version ${{ inputs.target_version }} \
--query 'addons[0].addonVersions[0].addonVersion' \
--output text
done
upgrade:
needs: pre-check
runs-on: ubuntu-latest
if: ${{ !inputs.dry_run }}
environment: production # 手動承認が必要
steps:
- name: Upgrade Control Plane
run: |
aws eks update-cluster-version \
--name ${{ inputs.cluster }} \
--kubernetes-version ${{ inputs.target_version }}
echo "Waiting for control plane upgrade..."
aws eks wait cluster-active --name ${{ inputs.cluster }}
- name: Upgrade Node Groups
run: |
NODEGROUPS=$(aws eks list-nodegroups \
--cluster-name ${{ inputs.cluster }} \
--query 'nodegroups[]' --output text)
for NG in $NODEGROUPS; do
echo "Upgrading node group: $NG"
aws eks update-nodegroup-version \
--cluster-name ${{ inputs.cluster }} \
--nodegroup-name $NG \
--kubernetes-version ${{ inputs.target_version }}
done
- name: Upgrade Addons
run: |
for ADDON in vpc-cni coredns kube-proxy; do
LATEST=$(aws eks describe-addon-versions \
--addon-name $ADDON \
--kubernetes-version ${{ inputs.target_version }} \
--query 'addons[0].addonVersions[0].addonVersion' \
--output text)
aws eks update-addon \
--cluster-name ${{ inputs.cluster }} \
--addon-name $ADDON \
--addon-version $LATEST \
--resolve-conflicts OVERWRITE
done
post-verify:
needs: upgrade
runs-on: ubuntu-latest
steps:
- name: Health Check
run: |
aws eks update-kubeconfig --name ${{ inputs.cluster }}
echo "Node status:"
kubectl get nodes -o wide
echo "System pods:"
kubectl -n kube-system get pods
echo "Failing pods:"
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
- name: Notify
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H 'Content-type: application/json' \
-d "{
\"text\": \":white_check_mark: Cluster upgrade complete\nCluster: ${{ inputs.cluster }}\nVersion: ${{ inputs.target_version }}\"
}"
クイズ
Q1. マルチクラスターアップグレードでcanaryクラスターを先にアップグレードする理由は?
正解: ||トラフィックの比重が最も低いクラスターで先にアップグレードし、問題が起きたときの影響範囲を
最小化する。canaryで安定性を確認してから残りのクラスターへ進む。||
Q2. kubeletのバージョンskewの許容範囲がCP基準で -2 であることの実務的な意味は?
正解: ||Control Planeをv1.33へアップグレードしてもWorkerノードはv1.31まで互換なので、Workerノードを
一度にアップグレードせず段階的に進められる。||
Q3. PDBのALLOWED DISRUPTIONSが0のときにdrainが失敗する理由と解決方法は?
正解: ||PDBのminAvailableが現在のRunning Pod数と同じで、1つも退去させる余裕がない状態である。
レプリカ数を先に増やして余裕を確保するか、PDBを一時的に緩和する必要がある。||
Q4. ステージングのアップグレード後に最低48時間観察する理由は?
正解: ||CronJob、定期バッチ処理、証明書更新など、すぐには表面化しない問題を見つけるには最低
1~2日の観察が必要である。一部の障害はトラフィックパターンの変化(日中/夜間)の中でしか現れない。||
Q5. マネージドサービス(EKS/GKE/AKS)でControl Planeのロールバックが難しい理由は?
正解: ||マネージドサービスはControl Planeのダウングレードをサポートしない。代わりにノードグループを
以前のバージョンで作成してトラフィックを移すか、新しいクラスターを作って移行する必要がある。||
Q6. ポストモーテムを障害の有無に関係なく作成すべき理由は?
正解: ||アップグレードの過程で見つかった改善点、想定より時間がかかった手順、ドキュメントと実際の
手順の差などを記録してこそ、次のアップグレードでランブックを改善できる。障害がなくてもプロセス
そのものを改善できる機会である。||
Q7. GitHub Actionsでproduction environmentに手動承認をかける理由は?
正解: ||自動化されたパイプラインであっても、本番のアップグレードは人が最終承認すべきである。pre-check
の結果を人が確認し、その時点のサービス状態を踏まえて進めるかどうかを判断するためである。||