LabHub

ブログ

Kubernetesマルチクラスターアップグレードランブック 2026

한국어English日本語

Kubernetesマルチクラスターアップグレードランブック 2026

ランブックの概要

このドキュメントは 2つ以上のKubernetesクラスターを運用しているチーム がバージョンアップグレードを実行するときに従うべき段階的な手順書(Runbook)である。障害発生時の判断を最小限にし、事前に定めた手順どおりに実行できるように作成した。

適用対象

バージョンskewポリシーの要約

Kubernetesはマイナーバージョンの飛ばしを許可しない。必ず順番にアップグレードする必要がある。

コンポーネント許容されるskew例 (CPがv1.33のとき)
kubeletCP基準で -2v1.31, v1.32, v1.33を許容
kube-proxyCPと同じマイナーv1.33のみ許容
kubectlCP基準で +/-1v1.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 - CriticalAPI 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分全体がReadyNotReadyノードを調査
サービス応答時間 (p99)5分アップグレード前と比べ10%以内プロファイリング
etcd latency5分< 100msetcdのディスク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 の結果を人が確認し、その時点のサービス状態を踏まえて進めるかどうかを判断するためである。||

参考資料

コメント

まだコメントはありません。

ログインするとコメントできます