デプロイは「変えること」ではなく「戻せるように変えること」だ
一言でいうと
Deployment・Helm・Kustomizeはすべて同じ問題を解決します。変更している間もサービスを止めず、間違ったときに以前の状態に戻れるようにすることです。そのため、3つのツールはどれも「リビジョン」という概念を持っています。
なぜ必要なのか
Podを直接作成して運用すると、イメージを変える瞬間にダウンタイムが発生します。削除して作り直す間、誰もいなくなるからです。ReplicaSetが個数を維持してくれますが、「イメージを変える」という概念がありません。
Deploymentは、その上にもう1層を重ねました。DeploymentはReplicaSetを複数管理します。イメージを変えると新しいReplicaSetを作成し、新しいものを増やしながら古いものを減らします。古いReplicaSetはレプリカ0のまま残っているので、戻せという命令が来たら、その方向を逆にするだけで済みます。これがrollout undoが速い理由です。イメージを再度取得する必要がありません。
速度は2つの数値で調整します。maxSurgeはdesiredをどれだけ超えてよいか、maxUnavailableはdesiredからどれだけ不足してよいかを表します。maxUnavailable: 0にすると常に元の個数だけ生きていますが、その代わり新しいPodがReadyになるまで待つので、デプロイが遅くなります。ここで、Readiness Probeがないと、この安全装置はすべて無力化されます。プローブがないと、コンテナが起動した瞬間にReadyとみなされ、まだ初期化中のPodにトラフィックが送られます。
どう動くのか
デプロイ戦略を表にまとめると、次のようになります。
| 戦略 | 実装 | 戻す速度 | リソース |
|---|---|---|---|
| ローリングアップデート | Deploymentが1つ、maxSurge/maxUnavailable |
中程度(再度ローリング) | +maxSurge |
| ブルーグリーン | Deploymentが2つ + Serviceのセレクターの切り替え | 即座(セレクターを戻す) | 2倍 |
| カナリア | Deploymentが2つ + 同じServiceのセレクター、レプリカの比率 | 即座(カナリアを0に) | 少し増える |
ブルーグリーンはラベルの付け替えで成り立ちます。checkout-blueにはversion: blue、checkout-greenにはversion: greenのラベルを付け、Serviceのセレクターにversion: greenを入れると、その瞬間にトラフィックが丸ごと切り替わります。カナリアは逆に、セレクターを共通のラベルだけに絞って2つのDeploymentのPodがどちらもエンドポイントに入るようにし、レプリカ数の比率でトラフィックの比率を作ります。9:1ならおよそ10%です。
kubectl rollout historyに表示されるCHANGE-CAUSEは魔法ではなく、kubernetes.io/change-causeアノテーションです。以前の--recordフラグは非推奨になったので、kubectl annotate deployment web kubernetes.io/change-cause="..."で直接付けます。これがないと、rollout historyはリビジョン番号を並べるだけで、何が変わったのかがわかりません。
Helmは同じアイデアをパッケージレベルに引き上げたものです。Helm releaseの各リビジョンが、ネームスペース内のSecret(sh.helm.release.v1.<이름>.v<번호>、プレースホルダーは名前と番号)として保存され、そこにレンダリングされたマニフェストとvaluesが丸ごと入っています。helm rollbackはそのリビジョンのチャートとvaluesで新しいリビジョンを作成します。時間を巻き戻すのではなく、先へ進みながら古い内容を再適用するのです。helm upgradeは3-wayマージを行います。以前のリビジョンのマニフェスト、新しくレンダリングしたマニフェスト、そしてクラスターの現在の実際の状態の3つを比較して、必要な変更だけを適用します。そのため、他のツールが手を加えた部分をむやみに元に戻しません。
Kustomizeはテンプレートなしで同じことをします。baseに共通のマニフェストを置き、overlays/<환경>(プレースホルダーは環境名です)でnamePrefix、ラベル、パッチを重ねます。文字列の置換ではなくYAMLの構造を理解してマージするため、結果が常に有効なYAMLになるという利点があります。kubectl kustomize <경로>(プレースホルダーはパスです)でレンダリング結果だけを見て、kubectl apply -k <경로>(同じくパスです)で適用します。
現場での姿
ホームラボのクラスターにGPU Operatorを導入しているときの出来事です。最初はtoolkit.enabled=falseでインストールしましたが、それが誤った判断で、直すにはその値だけを変える必要がありました。
helm upgrade gpu-operator nvidia/gpu-operator --version v26.3.3 \
-n gpu-operator --reuse-values --set toolkit.enabled=true
--reuse-valuesがなければ、以前のリビジョンの値がすべて捨てられ、チャートのデフォルト値に初期化されたはずです。するとdriver.enabled=falseがデフォルトのtrueに戻り、Operatorがドライバーを再インストールしようとして、すでに570.195.03が入っているホストと衝突し、ノードがGPUを失っていたでしょう。フラグ1つが事故を防ぎました。
アップグレード後の履歴は、次のように残りました。
REVISION STATUS CHART DESCRIPTION
1 superseded gpu-operator-v26.3.3 Install complete
2 deployed gpu-operator-v26.3.3 Upgrade complete
リビジョン1が削除されずにsupersededとして残っている点が核心です。helm rollback gpu-operator 1で戻せる状態だったという意味です。
もう1つ注目してほしいのは、HelmがDaemonSetを直接作成していないという点です。Helmがしたことは、ClusterPolicyカスタムリソースのtoolkit.enabledをfalseからtrueに変えただけで、その変更を検知したOperatorコントローラーがDaemonSetを作成しました。宣言はHelmが、組み立てはコントローラーが行います。デプロイツールの責任の境界がどこまでかを示す場面です。
次のラボですること
ckad-deployネームスペースでローリングアップデートのパラメーターを調整し、イメージを2回更新したあと--to-revisionで特定のリビジョンに戻し、ブルーグリーンとカナリアをラベルとレプリカの比率で自分で構成します。続いてhelm createでローカルのチャートを作成してvaluesを編集し、helm template/install/upgradeを実行したあと、Kustomizeのbaseとoverlayを作成してkubectl kustomizeとapply -kで適用します。この環境にはインターネットがないため、helm repo addは使えず、必ずローカルのパスだけを使用してください。