リリースはシークレット一つだ
一言でいうと
Helmのreleaseは魔法ではなく、ネームスペースの中のSecret1つです。その事実を知れば、ロールバックが何を元に戻すのか、なぜ元に戻せないものがあるのかが、すべて説明できます。
なぜ必要なのか
helm rollbackを実行したのに、データベースのマイグレーションが元に戻らず、サービスが壊れた経験は、よくあります。逆に、「ロールバックしたのに、何も変わらなかった」という報告もよくあります。どちらも、同じ誤解から来ています。ロールバックが時間を巻き戻すと思っていることです。
Helmがやっていることは、はるかに単純です。helm installをすると、レンダリングされたマニフェスト全体をgzipで圧縮して、Secretに保存します。名前はsh.helm.release.v1.<릴리스>.v<리비전>(プレースホルダーはrelease名とリビジョンです)です。helm upgradeをすると、新しいリビジョンのSecretをもう1つ作ります。helm rollback 3は、リビジョン3のSecretを取り出して、そのマニフェストをもう一度applyすることです。
そのため、規則が導かれます。マニフェストに書かれていたものは、元に戻ります。マニフェストの外で起きたこと、つまり、マイグレーションが変えたデータ、Jobが作ったファイル、外部APIに送ったリクエストは、元に戻りません。
どう動くのか
自分で確認できます。
kubectl get secret -l owner=helm
kubectl get secret sh.helm.release.v1.demo.v1 -o jsonpath='{.data.release}' \
| base64 -d | base64 -d | gzip -d | head -40
base64を2回デコードするのは変に見えますが、正しいです。Kubernetesが値を1回エンコードし、Helmがその中でもう1回エンコードしておいたのです。
helm historyは、これらのSecretの一覧です。リビジョンごとに状態があります。
| 状態 | 意味 |
|---|---|
deployed |
今生きているリビジョン。常に1つだけです |
superseded |
以前デプロイされたあと、次のリビジョンに席を譲ったもの |
failed |
適用に失敗したリビジョン |
pending-upgrade |
アップグレードを開始したのに終わっていないもの。ここに閉じ込められると、次のデプロイが止まります |
よくある勘違い
ロールバックは、リビジョン番号を戻しません。helm rollback demo 1をすると、リビジョン1に戻るのではなく、リビジョン1の内容で新しいリビジョン3を作ります。historyを見ると、3行目にRollback to 1と書かれています。これは、監査記録を消さないための設計です。何があったかが、履歴に残る必要があります。
保持する個数には上限があります。デフォルトは10個です(--history-max)。古いリビジョンは削除されるので、「6か月前にロールバック」は、たいてい不可能です。元に戻せる範囲は、思ったより狭いです。
releaseのSecretを直接のぞいてみる
Helm 3は、releaseごとにSecretを1つ作ります。この構造を知っていれば、事故のときに手で復旧できます。
kubectl -n labhub-prod get secret -l owner=helm,name=labhub
NAME TYPE DATA AGE
sh.helm.release.v1.labhub.v247 helm.sh/release.v1 1 2d
sh.helm.release.v1.labhub.v248 helm.sh/release.v1 1 1d
sh.helm.release.v1.labhub.v249 helm.sh/release.v1 1 3h
# 안을 풀어 본다 — base64 → gzip → JSON
kubectl get secret sh.helm.release.v1.labhub.v249 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gzip -d | jq '.info, .chart.metadata.version'
base64が2回あるのが落とし穴です。KubernetesのSecret自体が1層、Helmが保存するときにもう1層です。
ここから出てくる結論が2つあります。
- release履歴が、ネームスペースのSecretを食います。
--history-maxを決めなければ、デフォルトの10個が積み上がり、チャートが大きければ、それぞれ数百KBです。 - ネームスペースを削除すると、releaseも消えます。バックアップの対象ではなく、再現できるものである必要があるので、値ファイルとチャートのバージョンをgitに置くことが、実際のバックアップです。
3つの状態を区別する
helm list → deployed 만 보인다
helm list --all → failed, pending-upgrade, superseded 까지
helm history <릴리스> → 리비전별 상태와 설명
supersededは正常です。新しいリビジョンが出て、退いたのです。問題はpending-*です。この状態で残っていると、次のデプロイが拒否され、プロセスはすでに死んでいて、表示だけが残っているのです。
failedも、そのままにしてはいけません。次のデプロイはできますが、--atomicのロールバックの基準点がぼやけます。原因を直して、成功したデプロイを1回作っておくことが、整理です。
release名とリソース名
チャートのリソース名は、たいてい{{ .Release.Name }}-{{ .Chart.Name }}で作られます。そのため、release名を変えると、リソースがすべて新しく作られます。古いものは残り、新しいものができて、2つが共存します。
同じ理由で、release名は最初に慎重に決め、変える必要があるなら、古いreleaseを削除して新しくインストールする手順を計画します。PVCを使うワークロードなら、その間にデータを移すステップが入ります。
実務で本当に大切なこと
releaseがSecretだということは、サイズの上限があるという意味でもあります。etcdのオブジェクトの上限は1MiBです。チャートが大きくなると(特にCRDをたくさん入れると)、releaseのSecretがその上限にぶつかって、アップグレードが失敗します。そのとき出るエラーメッセージは、原因をまったく教えてくれないので、この構造を知っていることが、そのまま解決の速さです。