TT Lab
はじめる
学ぶ 学習パス コース

CKAD — Kubernetesアプリケーション開発者

デプロイは「変えること」ではなく「戻せるように変えること」だ

TT Labで続きを見る

一言でいうと

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にトラフィックが送られます。

replicas 4、maxSurge 1、maxUnavailable 0のときに、ローリングアップデートが2つのReplicaSetの間で交代する様子。新しいものを1つ増やして一瞬5個になり、その後は1つ増やすたびに1つ減らして常に4個以上が生きていて、最後に新しいもの4個だけが残ります。古いReplicaSetはレプリカ0のまま残り、rollout undoを受けます

どう動くのか

デプロイ戦略を表にまとめると、次のようになります。

戦略 実装 戻す速度 リソース
ローリングアップデート 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は使えず、必ずローカルのパスだけを使用してください。