Kubernetes アップグレード
サポート範囲と worker pin を保持して chart を更新します
作業内容
直前の worker pin とサポート範囲を保持し、アップグレード検査結果を読んでから公開 chart を更新します。
準備
変更履歴で対象バージョンの運用者向け作業を先に読みます。
環境ごとの values、jq・helm・kubectl、現在の deployed revision と worker-previous.json、DB・S3 バックアップが必要です。kind で、承認待ちのセッションと checkpoint を残して停止したセッションを置いたまま、イメージと schema は変えず extraEnv だけを変えて Pod を再作成する helm upgrade と helm rollback(migrate は何もしませんでした)を実行し、hook の順序とセッションの状態・イベント・checkpoint の継続を確認しました(deploy/kind/upgrade.sh)。control-host イメージを変える、または新しい migration を含む upgrade・rollback(rollback 後の新しい schema と古いコード)、worker イメージが変わる upgrade・rollback、影響セッションを承認する流れは実行確認していません。fixture chart の render と公開 chart の設置を区別します。
共通の実行前ゲートの有効化順序と rollback の下限を先に照合します。Compose 専用の検査コマンドの代わりに、下記の Helm hook と直接検査を使います。
設置環境の context・namespace・fullnameを確認します。新 control-host・直前 worker の組み合わせは、試験環境で turn 1 完了 → worker Pod 削除 → checkpoint から turn 2 完了、および API の worker 要求に 400 がないことを確認します。deploy/kind/up.sh は images.worker を --set で上書きするため、EXTRA_VALUES で直前 worker イメージを試験せず、直接 helm upgrade します。
手順
1. 対象 chart と制御領域を先に更新
VERSION は正確な対象 chart バージョンです。--reuse-values を使わず、環境ごとの values だけを渡します。protocol・manifest・usage に変更があれば、control-host と worker を同じ upgrade で更新しません。
VERSION=<new-version>
DEPLOYED="$(helm -n werft history werft -o json | jq '[.[] | select(.status == "deployed")] | last.revision')"
helm -n werft get values werft --all --revision "$DEPLOYED" -o json \
| jq '{images: {worker: .images.worker, workerPrevious: .images.workerPrevious, egressProxy: .images.egressProxy}}' \
> control-phase.json
helm upgrade --install werft oci://ghcr.io/jeongjaesoon/werft --version "$VERSION" \
--namespace werft -f values-<installation>.yaml -f control-phase.json \
--set-json 'upgrade.approvedSessions=[]' --timeout 10m
kubectl -n werft rollout status deploy/werft-api --timeout=600s中間 upgrade は新 chart の control-host を適用し、worker・egress proxy は現在のものを維持します。migration、すべての API の更新、ready を確認してから次に進みます。起動済み worker はイメージ変更だけでは交代せず、次の launch から新イメージを使います。
2. 最終的な worker と proxy の更新
公開 chart は images.worker.runtime を含み、images.workerPrevious は空です。直前のイメージは環境ごとに違い、chart は判断できないためです。worker digest が変わる upgrade は、対象の images.workerPrevious が現在デプロイ済みのイメージでなければ refused で止まり、承認でも続行できません(アップグレード前検査)。そのため現在のイメージをファイルに保存して一緒に渡します。
VERSION=<new version>
DEPLOYED="$(helm -n werft history werft -o json | jq '[.[] | select(.status == "deployed")] | last.revision')"
CURRENT="$(helm -n werft get values werft --all --revision "$DEPLOYED" -o json | jq -r '.images.worker.digest')"
TARGET="$(helm show values oci://ghcr.io/jeongjaesoon/werft --version "$VERSION" \
| awk '/^ worker:/ {found = 1} found && /digest:/ {print $2; exit}')"
echo "deployed ${CURRENT}"
echo "target ${TARGET}"
PREVIOUS=worker-previous.json
if [ -n "$TARGET" ] && [ "$CURRENT" != "$TARGET" ]; then
PREVIOUS=worker-previous.next.json
helm -n werft get values werft --all --revision "$DEPLOYED" -o json \
| jq '{images: {workerPrevious: .images.worker}}' > "$PREVIOUS"
fi
set -- -f values-<installation>.yaml
if [ -f "$PREVIOUS" ]; then set -- "$@" -f "$PREVIOUS"; fi
if helm upgrade --install werft oci://ghcr.io/jeongjaesoon/werft --version "$VERSION" \
--namespace werft "$@" --timeout 10m; then
if [ "$PREVIOUS" != worker-previous.json ]; then mv "$PREVIOUS" worker-previous.json; fi
fi--revision を省くと、失敗した upgrade がある場合、その revision の値、つまり更新候補のイメージが返ります。worker digest が違えば現在のイメージを候補ファイルに書き、upgrade 成功後だけ worker-previous.json をそのファイルで置き換えます。失敗した upgrade は保存ファイルを変更しません。digest が同じなら既存ファイルをそのまま渡します。初回設置後、worker が変わらない upgrade にはまだファイルがなくても問題ありません。
worker-previous.jsonは設置環境とともに保管し、次の upgrade にも渡します。worker digest が同じ upgrade で省くと、デプロイ済みの直前イメージが失われます。その場合、事前検査はセッションを比較し、対象イメージ(現在、および存在すれば直前)のどちらとも組み合わせが合わない、または組み合わせ未記録のセッションがあればrefusedで停止します(承認可能)。誤って省いたなら承認せず、ファイルを追加して再実行します。選択セッションがなくupgrade.approvedSessionsが空なら通過します(前回承認が残っていればrefused)。見えないセッションについては下記「検査対象外」を参照してください。同日にマージされたmainchart は worker digest が同じ場合があります。--reuse-valuesは使いません。以前の chart の値を既定にするため、新 chart でもイメージが以前のまま残ります。helm get values --all全体を values ファイルとして渡しません。以前の digest が新 chart の値を上書きします。.images.workerだけを取り出します。v*タグで公開した chart も同じ手順です。
最終 upgrade には control-phase.json を渡しません。worker-previous.json は成功後だけ置き換え、以後の upgrade 用にも保持します。承認一覧は毎 upgrade で空にし、新しい affected 一覧を確認します。
3. 検査結果と例外
アップグレード前検査
helm upgrade は migrate より先に(hook weight -5)、upgrade-check Job を対象リリースの api イメージで実行します。Job は未回収 checkpoint のあるセッションごとにポインターの組み合わせを読み、対象の images.worker・images.workerPrevious のどちらでも再開できないセッションを選びます。組み合わせを記録する前に commit されたポインターも選びます。結果は Job ログの JSON 一行です。
kubectl -n werft logs job/werft-upgrade-checkverdict | exit | upgrade | 意味 |
|---|---|---|---|
proceed | 0 | 続行 | upgrade.approvedSessions が affected の sessionId と完全一致します。選択セッションがない場合、worker イメージが同じでデプロイ済み直前イメージがないか同じ場合、またはデプロイ歴がない場合は、承認一覧が空である必要があります |
refused | 2 | 停止 | 承認一覧が一つでも異なります(以前の承認が values に残る場合を含む)。worker イメージが同じでも、デプロイ済み直前イメージが対象から外れるか変更されるとセッションを比較します。対象イメージ(現在、および存在すれば直前)のどちらとも組み合わせが合わない、または未記録のセッションが一覧に入ります。または worker イメージが変わるのに対象の images.workerPrevious が現在デプロイ済み worker イメージではありません。後者は承認で続行できません |
check_failed | 1 | 停止 | DB を読めない、イメージの組み合わせが読めないか下表で必要なのにない、デプロイ済み worker イメージが不明な場合です。デプロイ済み worker があるのに UPGRADE_DEPLOYED_WORKER_IMAGE_PREVIOUS がない場合も該当します(直前イメージがなければ空値を渡します)。scheduler Deployment がないのに DB にセッションや一部 schema がある場合も同様です。迂回する設定はありません |
images.worker.runtime は初回設置時にはなくてもかまいません。upgrade 時に必要かは、デプロイ状態と対象値によります。
| デプロイ状態 | 対象 | runtime 不在時 |
|---|---|---|
| scheduler Deployment なし、DB が空またはセッションなし(初回設置の再試行) | 任意の worker イメージ、images.workerPrevious なし | proceed |
| worker イメージ一つ、runtime・直前イメージなし | 同じ worker イメージ、images.workerPrevious なし | proceed |
| worker イメージ一つ、runtime・直前イメージなし | 異なる worker イメージ | check_failed。対象に images.worker.runtime と現在のイメージを含む images.workerPrevious を設定します |
| runtime または直前イメージあり | images.worker.runtime を省いた値 | check_failed。組み合わせなしではセッションを検査できません |
runtime の取得方法は設定レファレンスの API 設定にあります。最小 values での設置は runtime なしで開始し、初めて worker イメージを変える upgrade で両イメージの runtime を入れます。
images.worker を維持し、デプロイ済みの images.workerPrevious を外すか変える upgrade はセッションを比較します。対象イメージ(現在、および存在すれば直前)のどちらとも組み合わせが合わない、または未記録のセッションがあれば refused となり、その一覧を承認すると通過します。未記録セッションはいずれの場合もイメージ変更 upgrade と同じように選びます。デプロイ済み直前イメージがなかった、または同じならセッションは比較せず、承認一覧が空なら proceed です(残った承認は refused)。イメージだけ違って組み合わせが同じ再ビルドでは、直前イメージを外しても現在のイメージで再開できるため一覧に入りません。
選んだセッションが再開できなくてもよい場合、その一覧をそのまま values に入れて同じ upgrade を再実行します。承認はセッション状態を変えません。no_image で選んだセッションは再開時に recovery_required で止まります。unrecorded で選んだセッションは新イメージで起動し、復元時の判定で継続するか recovery_required で止まります。
helm upgrade --install ... --set-json 'upgrade.approvedSessions=["<sessionId>", ...]'承認一覧は release values に残ります。次の upgrade ごとに --set-json 'upgrade.approvedSessions=[]' で空にし、その upgrade の一覧を読み直して承認します。残った一覧が今回の affected と違えば refused で止まりますが、同じでサポート範囲の条件も合えばそのまま通過します。
Job は承認一覧を UPGRADE_APPROVED_SESSIONS(JSON 配列)、デプロイ済み worker を UPGRADE_DEPLOYED_WORKER_IMAGE、デプロイ済み直前イメージを UPGRADE_DEPLOYED_WORKER_IMAGE_PREVIOUS(なければ空値)で受け取ります。デプロイ済みイメージは chart が現在の scheduler Deployment の WORKER_IMAGE・WORKER_IMAGE_PREVIOUS を lookup で読み、渡します。kind では実際の helm upgrade が lookup で読んだデプロイ済み worker イメージが対象イメージと一致し、判定は proceed でした。直前イメージを読む lookup 分岐と、直前イメージを外す・変更する実際の helm upgrade はクラスタ上で実行確認していません。検査 entry point だけを実際の PostgreSQL に対して実行確認しました。runtime 表の例外を開けるのは、chart が一緒に渡す UPGRADE_DEPLOYED_STATE だけです。absent は scheduler Deployment がなく、namespace の default ServiceAccount は読めたことを意味します(この経路にはその ServiceAccount の get 権限が必要)。unpinned は Deployment が runtime・直前イメージなしで動く状態です。helm template とクライアント --dry-run では lookup が何も読まず三つの値が空なので、その render で実行した Job は check_failed になります。--dry-run=server はクラスタを読みますが hook を実行しないため判定の代わりにはなりません。検査は DATABASE_URL が今回の DB であることを信頼します。別の空 DB を指すと absent 経路が通過します。初回設置(pre-install)と scheduler.enabled: false では Job を作りません。Job と scheduler に同じイメージを渡すため、WORKER_IMAGE・WORKER_IMAGE_PREVIOUS・WORKER_IMAGE_RUNTIME・WORKER_IMAGE_PREVIOUS_RUNTIME は *.extraEnv、config.limits、secrets.catalogEnv で渡せません。渡すと render が失敗します。images.worker と images.workerPrevious で指定します。
helm rollback の前に。 rollback は hook を実行しないため、同じコマンドを api Pod で直接実行します。値は戻す revision のもの(helm get values --revision <revision>)を指定します。
kubectl -n werft exec deploy/werft-api -- env \
UPGRADE_DEPLOYED_WORKER_IMAGE='<WORKER_IMAGE of the running scheduler>' \
UPGRADE_DEPLOYED_WORKER_IMAGE_PREVIOUS='<WORKER_IMAGE_PREVIOUS of the running scheduler, empty if none>' \
WORKER_IMAGE='<worker image of the target revision>' WORKER_IMAGE_RUNTIME='<its runtime JSON>' \
WORKER_IMAGE_PREVIOUS='<workerPrevious of that revision>' WORKER_IMAGE_PREVIOUS_RUNTIME='<its runtime JSON>' \
bun run apps/control-host/src/api/upgrade-check.ts通常の worker イメージを戻す rollback は refused です。戻す revision の images.workerPrevious が現在デプロイ済みのイメージではないためです(検査は組み合わせではなくイメージ参照を比較します)。設定や DB を読めなければ check_failed です。現在のイメージで動く worker は rollback 後も終了までその組み合わせの checkpoint を残しますが、検査には見えません。出力の affected は検査時点で既に選んだセッションです。roll-forward を優先します。それでも戻すと、再開時に scheduler が組み合わせでイメージを選びます。戻した revision の worker と直前イメージ(存在する場合)のどちらとも合わないセッション(現在のイメージが新 SDK・CLI を含む場合、rollback 中に現在のイメージで checkpoint を残したセッション)は recovery_required で止まります。images.worker を維持する rollback は、現在デプロイ済み直前イメージが戻す revision にも同じまま含まれれば、承認一覧が空のとき proceed です(残った承認は refused)。外れるか変われば、このページの upgrade 検査と同じようにセッションを比較します。このコマンドは hook ではなく、rollback を止めてはくれません。refused なら affected を読み、戻すかを判断します。デプロイ済み worker があるのに UPGRADE_DEPLOYED_WORKER_IMAGE_PREVIOUS を省くと check_failed です。
検査対象外。 単一サーバーのアップグレード検査と異なり、この検査は対象イメージを実行しません。ポインターに記録された組み合わせ(engine・SDK・CLI・profile digest 方式)だけを比較し、実際の profile digest は復元時に判定します。検査後に writer を停止して再検査することもありません。worker イメージ変更 upgrade は、対象の images.workerPrevious が現在デプロイ済みのイメージと同じ場合だけ通過するため、検査後に現在の worker が残す checkpoint は対象のサポート範囲内です。worker を維持して直前イメージを外す upgrade にはこの保証がありません。直前のイメージで動く worker は検査後もその組み合わせの checkpoint を残せます。最初の checkpoint 前のセッションも同様です。そのため直前イメージを外す前に、その worker の終了を確認します。それより古いイメージで動く worker のセッションは、ポインターが既にその組み合わせなら一覧に入ります。最初の checkpoint 前のセッションは入りません。この制限はサポート範囲から外れるすべてのイメージ、および直前イメージ変更 upgrade にも同じです。対象イメージを registry から取得できるかも検査しません。取得できなければ launch 失敗として現れます。
4. ファイル catalog の環境から移行する場合
catalog 値を使っていた chart の ConfigMap は現在の chart にはありません。Helm は hook 完了後に以前のリソースを削除するため、pre-upgrade migrate Job が同じ ConfigMap を /legacy-config に optional mount し、settings import --from-dir /legacy-config --if-present で取り込みます。次の upgrade からは ConfigMap がないため何もしません。一部ファイルだけ存在する、または値が壊れていれば Job が失敗し、Deployment は変わりません。
- 先に
PLATFORM_SECRET_KEYを Secret に設定します。既にあれば同じ値を維持します。 - values から
catalog節を削除してhelm upgradeします。api.extraEnvのPLATFORM_CONFIG_DIRも、あれば削除します。 - 設置バージョンのリポジトリ checkout とホストの Bun・kubectl を準備し、
bun run settings:k8s exportで追加項目を確認します。installation_profilesに既存行があればファイルの上限は再設定しません。手順と戻せる下限は api-server.mdにあります。
確認
kubectl -n werft get job werft-migrate
for d in api egress-proxy reconciler scheduler; do
kubectl -n werft rollout status deploy/werft-$d --timeout=600s
donekubectl -n werft port-forward svc/werft-api 13000:3000 &
PF=$!
trap 'kill "$PF" 2>/dev/null || true' EXIT INT TERM
READY=0
for attempt in $(seq 1 30); do
if curl --max-time 2 -fsS -o /dev/null http://127.0.0.1:13000/readyz; then READY=1; break; fi
sleep 1
done
if [ "$READY" != 1 ]; then
kill "$PF" 2>/dev/null || true
trap - EXIT INT TERM
false
else
curl --max-time 10 -fsS -w '\n%{http_code}\n' http://127.0.0.1:13000/readyz
curl --max-time 10 -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:13000/docs
kill "$PF" 2>/dev/null || true
trap - EXIT INT TERM
fiJob ログの実際の verdict、rollout、ready 応答を記録します。既存セッションの再開と checkpoint context は別途確認します。メトリクス名が変わる場合はコレクターとルールを更新します。
よくある問題
workerPrevious が現在デプロイ済みのイメージでなければ、承認で続行できない refused です。helm get values の revision を省くと、失敗した upgrade の候補値が返る場合があります。lookup のない helm template や client dry-run は実際の upgrade 検査を代替しません。
元に戻す
helm -n werft history werft
helm -n werft rollback werft <revision> --wait --timeout 10mrollback は migrate hook を実行しません(pre-rollback ではありません)。DB は新 schema に残り、以前のビルドは自分より先の schema でも ready になります(/readyz の schema 検査は、自分の migration 一覧が適用済み一覧の先頭部分と一致するかだけを見ます)。そのため rollback できない migration はデプロイしません。あるイメージまでしか戻せない migration(例: 0243)は、その下限をイメージごとに Compose のアップグレード表に書き、下限より前には rollback しません。worker イメージを戻す際の checkpoint 互換性は、先に worker・control-host の rollback と incremental checkpointを確認します。
リンクした共通の実行前ゲートと直接検査で rollback 可能と確認してからだけ戻します。hook がないため、rollback コマンドが自動検査で止めてはくれません。roll-forward を優先します。