目次
30 章

アップグレード戦略

5部の最後の章です。K8s マイナーリリース (14か月サポート) を安全に追っていく運用マニュアルです。コントロールプレーン → データプレーン (ノード) → アドオンの順序、deprecated API の検出 (pluto · kubent · apiserver メトリクス)、マニフェスト / Helm / Operator CR の API バージョン移行、EKS のノードグループ / Karpenter NodePool drift の流れ、ノード drain の安全装置 (PDB · terminationGracePeriodSeconds)、blast radius の最小化、ロールバックシナリオ、RPO / RTO 別のバックアップ選択、そしてアップグレード前1週 / 当日 / 後1週のチェックリストまでを一連の流れで扱います。

5部 (運用 · デバッグ · コスト) の最後の章です。27章 kubectl デバッグパターン が事故対応の要点を、28章 コスト最適化 が請求書の要点を、29章 シークレット運用 がセキュリティの要点を扱ったなら、本章は 時間の要点 を扱います。K8s は四半期ごとにマイナーバージョンが出て、EKS の標準サポート期間は14か月です。運用クラスタが標準サポートの中に収まるよう維持するには 1年に最低一度のマイナーアップグレード が必須サイクルであり、そのサイクルを安全に回すマニュアルが本章の本文です。

26章 運用チェックリスト §「EKS アップグレード」で短く挙げた流れが本章で本格的に展開されます。本章の目標は、アップグレード前1週 / 当日 / 後1週のチェックリストが1ページに収まり、四半期ごとに一度のマイナーアップグレードが事故なく回る運用モデルを作ることです。

K8s リリースサイクル — 1年のタイムテーブル #

K8s 自体のバージョンポリシーを改めて挙げます。

K8s マイナーリリースサイクル
リリース周期:        約4か月
サポート期間 (upstream): 12 ~ 14 か月
EKS 標準サポート:    14 か月
EKS 拡張サポート:    追加12か月 (有料)

deprecated 表示:     1つ前のマイナーリリース
removed 時点:        2 ~ 3 マイナーリリース後

このタイムテーブルから運用クラスタに入ってくる信号は2つです。

  • deprecated 通知 — あるマイナーリリースで「この API はまもなく削除されます」が表示されます。通常 2 ~ 3 マイナー後 (8 ~ 12 か月後) に実際に削除されます。
  • 標準サポートの満了 — EKS コンソールに「このクラスタは X か月後に標準サポートが満了します」が表示されます。その時点から新しいパッチが入らなくなり、拡張サポートへ移ると費用が加算されます。

運用クラスタではこの2つの信号を四半期カレンダーにあらかじめ定めておくのが標準です。deprecated → removed の間の 8 ~ 12 か月がマニフェストを整理できる時間の窓 です。この窓を逃すと、新しいクラスタでマニフェストが拒否されてダウンタイムが発生します。

アップグレード順序の原則 #

K8s のアップグレードは定められた順序に従わなければなりません。

アップグレードの標準順序
1. コントロールプレーン (control plane) -- API server, controller manager, scheduler, etcd
2. データプレーン (node) -- kubelet, kube-proxy, container runtime
3. アドオン -- VPC CNI, CoreDNS, kube-proxy, EBS CSI, Karpenter, ArgoCD, ...

この順序の理由は K8s の互換性ポリシー です。

  • kubelet のバージョンは API server の 同じマイナー ± 1 マイナー の中になければなりません。
  • kube-proxy のバージョンは kubelet のマイナーと同じでなければなりません。
  • アドオンは K8s マイナーバージョンに応じて互換マトリクスがあります。

この互換性のため コントロールプレーン → データプレーン の順序が強制されます。コントロールプレーンを 1.32 へ上げた後にノードが 1.31 の状態がしばらく維持されるのは正常ですが (skew 1)、ノードを先に 1.32 へ上げてコントロールプレーンが 1.31 の状態はダメです。

1マイナーずつ — skip 禁止の理由 #

EKS は 1マイナーずつのみアップグレード できます。1.30 → 1.32 の直接ジャンプは不可能で、1.30 → 1.31 → 1.32 の2段階を経なければなりません。

この制約の理由は次の2つです。

  • etcd 移行の安全性 — マイナーごとに etcd のデータ形式が一部変わることがあります。段階別の移行が安全です。
  • API conversion — 各マイナーで変換できる API バージョンが定められています。2つのマイナーを一度に飛ばすと、変換できないオブジェクトが生じることがあります。

運用クラスタが標準サポートの満了に迫っていて2マイナー遅れた状態なら、2回のアップグレードを1四半期の中で 終えなければなりません。一度先送りすると次の四半期に2倍の作業量になります。

deprecated API の検出 — マニフェストとクラスタの2ヶ所 #

アップグレードの最も大きな危険は、マニフェストに古い API が入っていることです。2ヶ所をすべて点検しなければなりません。

1. マニフェスト — pluto #

pluto — マニフェストの静的分析
pluto detect-files -d charts/ --target-versions k8s=v1.32
pluto detect-helm --target-versions k8s=v1.32

charts/ のすべての YAML をスキャンして 1.32 で deprecated または removed された API を探します。20章 GitOps のマニフェスト repo 1ヶ所だけをスキャンすれば、すべての環境の deprecated が一度に捕まります — git 単一ソースモデルの運用価値の1つです。

2. クラスタ — kubent #

kubent — クラスタに実際に存在する deprecated
kubent --target-version 1.32 --context myshop-prod

kubent (kube-no-trouble) はクラスタの実際の状態を点検します。マニフェストは整理したが、誰かがコンソールで直接作ったオブジェクト、または Operator が生成したオブジェクトに deprecated が残っていることがあります。

2つの道具の連携で 「自分のマニフェストと自分のクラスタの両方で」deprecated が消えた状態 が確保されます。

3. apiserver メトリクス — 最後の信号 #

リアルタイムの deprecated API 呼び出し追跡
apiserver_requested_deprecated_apis

このメトリクスが 0 でなければ、誰か (またはあるコンポーネント) が今も deprecated API を呼び出しているという信号です。25章 モニタリング · アラート の Prometheus がこのメトリクスをスクレイプするので、四半期点検ルールとして追加しておくのが標準です。

PrometheusRule — deprecated API 呼び出しアラート
- alert: K8sDeprecatedApisInUse
  expr: |
    sum(rate(apiserver_requested_deprecated_apis[7d])) > 0
  for: 1h
  labels:
    severity: warning
    team: platform
  annotations:
    summary: "Deprecated K8s API が呼び出されている"
    runbook_url: "https://runbooks.myshop.example.com/k8s-deprecated"

このアラートが平常時に静かなら、次のアップグレードのマニフェスト整理はほぼ終わった状態です。

API バージョン移行パターン #

deprecated が発見されたオブジェクトの API バージョンをどう移すかが次の判断ポイントです。

マニフェストの直接変換 #

kubectl convert — 古い API -> 新しい API
kubectl convert -f old.yaml --output-version networking.k8s.io/v1

kubectl convert は K8s 1.14 までのビルトインで、以降は別のプラグインです。自動変換が可能な API はこのコマンドで一度に解けます。

Helm チャートの API バージョン #

Helm チャートの templates の中に書かれた API バージョンは、チャート自体の新しいリリースで解けます。依存チャート (例: ingress-nginx, cert-manager) が K8s 1.32 に対応する新しいバージョンへ上がる必要があります。

Helm チャートの outdated 確認
helm outdated -n monitoring   # 一部の環境の別のプラグイン
helm list --all-namespaces

22章 アプリ配備の骨格 の myshop-api チャートは本書の標準マニフェストなので K8s 互換性が一定の時点まで保証されますが、外部チャート (例: aws-load-balancer-controller, external-secrets, kube-prometheus-stack) はアップグレード前に新しいバージョンが K8s 1.32 に対応するか確認しなければなりません。

Operator CR の API バージョン #

18章 CRD と Operator の CRD 自体にもバージョンがあります (v1alpha1v1beta1v1)。Operator の新しいバージョンが CRD の新しいバージョンを定義すれば、古い CR を新しい形式へ移行しなければなりません。conversion webhook がその変換を自動化する道具ですが、すべての Operator がこれを実装しているわけではありません — 導入した Operator のアップグレードノートを事前に確認するのが標準です。

EKS のアップグレードの流れ #

1. コントロールプレーン — Terraform 一行 #

terraform — cluster_version
module "eks" {
  # ...
  cluster_version = "1.32"   # 1.31 -> 1.32
}

terraform apply が EKS コントロールプレーンのマイナーアップグレードをトリガーします。EKS はコントロールプレーンを無停止でアップグレードします — 約30分 ~ 1時間ほどかかります。その時間の間、ユーザーワークロードは影響を受けず kubectl も動作し続けます。

ただしその時間の間、一部のアクションがしばらく止まります — 新しいオブジェクトの生成、scale 変更などの API 呼び出しが一時的に遅延します。prod コントロールプレーンのアップグレード時点には意図的に配備を止めるのが安全 です。

2. データプレーン — 2つのパターン #

EKS ノードのアップグレードは 26章 で2つのパターンとして挙げました — in-place rollingblue-green です。

Managed Node Group の in-place は EKS が自動的に次のサイクルを回します。

Managed NG in-place rolling
1. 新しい launch template (新しい AMI) 生成
2. ASG の desired capacity + 1 -> 新しいノード1台 join
3. 古いノード1台 cordon -> drain (Pod 移動)
4. 古いノードを ASG から削除
5. 次のノードへ繰り返し

このサイクルがノード数 × 5 ~ 10 分ほどかかります。10 ノードクラスタの一度のノードグループのアップグレードが1時間 ~ 1時間半ほどです。

Karpenter NodePool drift は自動更新の仕組みです。NodePool の template.spec.requirements の一部が変わると (例: 新しい AMI が反映されると)、Karpenter が段階的に古いノードを新しいノードへ置き換えます — drift detection と呼ばれるメカニズムです。マネージドノードグループの in-place と結果は似ていますが、Karpenter の決定で進行します。

Karpenter NodePool の disruption ポリシー
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
    budgets:
      - nodes: "10%"   # 一度に最大 10% のノードのみ置き換え
        duration: 10m
        schedule: "0 9 * * mon-fri"   # 平日午前9時からのみ

budgets がアップグレードの blast radius を制御する道具です。一度に 10 % 以下 + 平日の業務時間のみ に置くのが prod の標準パターンです。

3. アドオン — 互換マトリクス #

terraform — アドオンバージョンマトリクス
cluster_addons = {
  vpc-cni = {
    addon_version = "v1.18.1-eksbuild.3"   # 1.32 互換
  }
  coredns = {
    addon_version = "v1.11.1-eksbuild.9"
  }
  kube-proxy = {
    addon_version = "v1.32.0-eksbuild.2"
  }
  aws-ebs-csi-driver = {
    addon_version = "v1.30.0-eksbuild.1"
  }
}

AWS の EKS Addon ページに、各 K8s マイナー別の互換バージョンがマトリクスとして整理されています。most_recent = true と置いても EKS が互換性のある最新バージョンを自動的に選びますが、運用環境では 明示的なバージョンピン + 四半期ごとの手動更新 の組み合わせが変更の統制に安全です。

15章 CNI 深掘り の VPC CNI が最もやっかいなアドオンです — Pod のネットワーク IP 割り当てを担うので、誤ってアップグレードされると新しい Pod が IP を受け取れません。事前に dev で1週間回してみた後 prod に反映するのが標準です。

ノード drain の安全装置 #

アップグレードの最もよく知られた事故は drain 中のダウンタイム です。3つの安全装置が揃ってこそ安全です。

1. PodDisruptionBudget (PDB) #

22章 アプリ配備の骨格 で作った PDB の一行が本格的に活躍する時点です。

PDB — drain の可用性の下限線
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myshop-api
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: myshop-api

minAvailable: 2 が核心 — drain 時に同時に evict 可能な Pod の数が 現在の ready - 2 に制限されます。EKS の drain が PDB を尊重するので、5分の間 drain が終わらなければ EKS がアラートを出して人の決定を待ちます。

14章 RBAC / NetworkPolicy / ResourceQuota の ResourceQuota と同じく PDB も 宣言的なガードレール の一種です。マニフェストに一行書いておけば、1年後のアップグレードの安全線として自動で作動します。

2. terminationGracePeriodSeconds #

Deployment — graceful shutdown 時間
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 60   # デフォルト 30
      containers:
        - name: api
          # ...

drain 時に Pod が受ける SIGTERM から SIGKILL までの猶予時間です。12章 ヘルスチェック §「graceful shutdown」で扱った内容が本節で実際の運用の観点へつながります。

myshop-api が進行中の HTTP リクエストを終え、DB 接続を close し、in-flight の作業を仕上げる時間を 30 ~ 60 秒ほどに定めておくのが運用標準です。短すぎるとユーザーに 500 応答が落ち、長すぎると drain 自体が止まります。

3. preStop hook #

preStop — readiness 強制 false の時間確保
containers:
  - name: api
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 10"]

preStop hook が SIGTERM の前に実行されます。10秒の間 sleep する間に readiness が false になって Service の endpoints から除外され、その間に入ってきていた新しいリクエストが他の Pod へ行くようになります。接続中のリクエストと新しいリクエストを分離 するのに決定的な要素です。

--disable-eviction の危険 #

これは使わないでください
kubectl drain <node> --disable-eviction --force

このオプションは PDB を無視して強制的に evict します。運用で絶対に使ってはならないオプションです。PDB が遮った理由は通常正当で、強制 drain はダウンタイムの直接の原因になります。

blast radius の最小化 #

アップグレードの影響範囲を減らす標準パターンは次のとおりです。

blast radius の最小化
1. dev で1週間回してみる
   - コントロールプレーン + ノード + アドオンをすべてアップグレード
   - 一週間の間、動作を観察

2. staging で prod シナリオをシミュレーション
   - 負荷テスト (k6, locust)
   - DB 接続、外部 API 呼び出しまで一連の流れ

3. prod コントロールプレーンのアップグレード (ワークロード影響ほぼなし)

4. prod ノードグループのカナリ部分から
   - 新しいノードグループを小さく作って一部のワークロードを移す
   - 1週間観察した後に段階的に拡大

5. 最後にすべてのノードグループ + アドオンを一斉に揃える

この流れが四半期ごとに一度ずつ回るのが運用の目標です。一度にすべての環境を同じ日にアップグレードしないこと が最も大きな安全線です。

ロールバックシナリオ #

アップグレードが失敗したら何ができるかの選択肢です。

コントロールプレーン — ダウングレード不可 #

EKS コントロールプレーンのマイナーダウングレードは 不可能 です。1.32 へ上がった後に 1.31 へ下げられません。この制約のため コントロールプレーンのアップグレード前に dev / staging で十分に検証 するのが必須です。

ダウングレードが必要な時点なら、代替は2つです。

  • 新しいクラスタ (古いバージョン) を作ってワークロードを移動20章 GitOps のマニフェスト単一ソースモデルのおかげで、新しいクラスタに同じマニフェストを適用すればワークロードがそのまま立ち上がります。RDS のような stateful システムはそのまま残します。
  • 拡張サポートモードで古いバージョンを維持 — 費用は加算されますが時間を稼ぎます。

ノード — 古いノードグループへの復帰 #

ノードグループの blue-green ロールバック
1. 古いバージョンのノードグループをあらかじめ維持 (古い launch template)
2. 新しいノードグループに問題発見
3. Karpenter NodePool または taint の調整で古いノードへワークロード移動
4. 新しいノードグループを空にする

古いノードグループを1週間の間維持するパターンがノードレベルのロールバックの安全線です。コントロールプレーンは下げられなくてもノードは古いバージョンへ戻せる という点が核心です。

アドオン — 明示的なバージョンピン #

アドオンのロールバックは最もシンプルです。Terraform の addon_version を古い値へ戻して apply すればよいです。だから本章の前の §「アドオン」で明示的なバージョンピンを推奨しています。

バックアップと RPO / RTO #

アップグレード自体でデータが失われることはまれですが、事故時に復旧を速くするにはバックアップの設計も合わせて固めておく必要があります。

バックアップの3つの選択肢とその RPO / RTO
- RDS PITR
  RPO: 5 秒 ~ 1 分 (トランザクションログベース)
  RTO: 5 ~ 30 分 (インスタンス復旧時間)

- Velero (S3 バックアップ)
  RPO: 日1回のバックアップなら24時間平均
  RTO: クラスタ再構成 + restore = 1 ~ 4 時間

- EBS Snapshot
  RPO: snapshot 周期による (通常日1回)
  RTO: 新しい PV プロビジョニング + マウント = 10 ~ 30 分

- etcd snapshot (self-managed K8s のみ)
  RPO: snapshot 周期 (通常時間単位)
  RTO: K8s コントロールプレーン復元 = 1 ~ 2 時間

EKS はコントロールプレーンの etcd がマネージドなので、ユーザーが直接バックアップする必要がありません。だから本章のバックアップ対象は RDS + EBS + Velero の3軸 です。26章 運用チェックリスト §「バックアップと復旧」で挙げた四半期の復旧訓練が本節の RTO 検証と直接束ねられます。

アップグレードの直前には、次の3つのバックアップがすべて最新の状態かを確認するのが標準です。

アップグレード直前のバックアップ点検
1. RDS — 自動スナップショットの最新性、manual snapshot の追加を推奨
2. Velero — 最後のバックアップの時点、成功/失敗状態
3. EBS Snapshot — 重要な PV の手動 snapshot

アップグレードチェックリスト #

アップグレード前1週 / 当日 / 後1週の標準チェックリストです。

前1週 #

アップグレード1週前
[準備]
- リリースノートのレビュー (deprecated / removed API, breaking changes)
- pluto + kubent で deprecated API を整理
- apiserver_requested_deprecated_apis メトリクスが 0 か確認
- 外部チャート (LB Controller, ESO, kube-prometheus-stack など) の新バージョン互換確認
- Operator の CRD 移行ノート確認

[検証]
- dev クラスタのアップグレード + 1週間の間回してみる
- staging 負荷テスト
- すべてのアラートルールが新バージョンでも作動するか確認

[準備物]
- RDS / Velero / EBS バックアップの最新確認
- 古いノードグループの launch template 保存確認
- 変更通知 (ユーザー / 社内告知)
- Slack 事故チャンネル準備

当日 #

アップグレード当日
[順序]
1. 変更 freeze (配備の一時中断)
2. 最後の manual snapshot (RDS, EBS)
3. コントロールプレーンのアップグレード (Terraform)
4. 1時間モニタリング (kubectl, アラート, ユーザーメトリクス)
5. カナリノードグループのアップグレード (10 ~ 20%)
6. 1時間モニタリング
7. 残りのノードグループのアップグレード (PDB + drain budgets)
8. アドオンのアップグレード (vpc-cni を最後)
9. 変更 freeze 解除

[観察]
- Pod の状態変化 (Running 比率)
- 5xx 比率 (myshop-api の核心 SLI)
- P95 latency
- ノードの Ready 比率
- ALB target の healthy 数

後1週 #

アップグレード1週後
[検証]
- アラート発火頻度 (平常比)
- コスト変化 (Karpenter / ノード価格の変化)
- 残存リソースの整理 (古い launch template, 古いノード)
- 新規 deprecated 通知の登場の有無 (次の四半期の準備)

[文書化]
- アップグレードの振り返り (うまくいった点, 事故 / 改善ポイント)
- runbook の更新 (次の四半期に適用可能なパターン)
- 次の四半期のアップグレード予想日程

このチェックリストが1ページに収まるのが運用チームの目標です。26章 の定期運用カレンダーで、四半期アップグレード項目の本格的な基準としてこのチェックリストを置きます。

練習問題 #

  1. dev EKS クラスタの K8s バージョンを1段階マイナーアップグレードしてみます。§「前1週」チェックリストの項目を上からたどり、各項目に実際の所要時間を記録します。plutokubent の出力の違い (マニフェスト vs クラスタの実際の状態) がどこで発生するかを一段落で整理し、25章 モニタリング · アラートapiserver_requested_deprecated_apis アラートルールを追加します。
  2. myshop-api の PDB と terminationGracePeriodSeconds の効果を測定する実験をします。preStop hook の sleep 時間を 0 秒 / 10 秒 / 30 秒の3種類に変えながら、ノード drain 中にユーザーが受ける 5xx 比率の違いを測定します (k6 で負荷を与えながらノードを cordon → drain)。どの組み合わせが最適だったか、その理由が 12章 ヘルスチェック の readiness probe モデルとどう束ねられるかを一段落で整理します。
  3. 自分の運用 (または学習) クラスタに §「アップグレードチェックリスト」の1ページを適用します。最も直前のアップグレードが何だったかを git history で確認し、そのとき抜けた項目または事後の事故につながった要因を本章のチェックリストにマッピングします。次のアップグレード日程と担当者を 26章 の定期運用カレンダーに追加します。

一行まとめ: K8s は四半期ごとにマイナーバージョンが出て EKS 標準サポートは14か月なので、1年に最低一度のアップグレードが必須。順序はコントロールプレーン → データプレーン → アドオンで、1マイナーずつしか進めず、deprecated API の整理が最も大きな作業。マニフェストは pluto、クラスタは kubent、リアルタイムは apiserver_requested_deprecated_apis メトリクスの3つの道具が deprecated の有無を一緒に捕まえる。drain の安全装置は PDB + terminationGracePeriodSeconds + preStop hook の3つで、--disable-eviction は絶対に使わない。blast radius の最小化は dev 1週 → staging 負荷 → prod カナリノードグループ → 段階的拡大の流れで、コントロールプレーンはダウングレード不可 · ノードは古いグループへ復帰可能 · アドオンはバージョンピンの戻し のロールバックマトリクスを頭の中に置く。アップグレード前1週 / 当日 / 後1週のチェックリストが1ページに収まるのが運用の目標。

次の章 — 6部のキャップストーン #

本章で5部 (運用 · デバッグ · コスト) の4章が締めくくられ、本書の30章がすべて手に入りました。次の章であり本書の最後の章では、その30章のすべての道具が1つのシステムの中でどう噛み合うのかを1つのプロジェクトとして束ねます。

31章 フルスタックアプリの EKS 配備 では、React の Next.js アプリと モダンPython の FastAPI アプリを1つの EKS クラスタに一緒に配備します。Terraform + Karpenter + IRSA + ALB Controller + ExternalDNS + cert-manager のクラスタセットアップから、RDS + External Secrets + IRSA RDS IAM auth の DB 連携、Helm + ArgoCD ApplicationSet の環境別配備、Prometheus + OpenTelemetry の可観測性、k6 負荷テスト + OpenCost コスト推定までの一連の流れを13個の PR で整理します。1 ~ 30章のすべての道具が1つのシステムの中でどう噛み合うのかは、本キャップストーンで確かめられます。

最後に 付録A — docker-compose から K8s へ が入門読者のための移行ガイドの役割を果たして本書を締めくくります。

X