こんにちは、技術戦略部 SREグループのカンタンです。
本記事はKubernetesアップデートストーリーシリーズの第5話になっていて、Helmのメジャーバージョンをバージョン2からバージョン3にアップデートした話になります。是非第1話から読んでいただいたほうが背景と内容ががわかりやすくなるかと思います!
Kubernetesアップデートストーリーシリーズのまとめ
- 数年をかけたKubernetes環境の大幅アップデート | Kubernetesアップデートストーリー第1話
- Kubernetes v1.10からv1.21へのアップデート | Kubernetesアップデートストーリー第2話
- Amazon EKSロードバランサー構成の大幅変更 | Kubernetesアップデートストーリー第3話
- 数時間でIstio v1.2からv1.13へアップデート | Kubernetesアップデートストーリー第4話
- Helm v2からv3へのメジャーアップデート | Kubernetesアップデートストーリー第5話
本記事では以下の課題についてお話しします:
- Helm v2とv3の違い
- Helm v2管理からHelm v3管理への移行方法
- Helm v3の挙動の注意点、管理方法のコツ、スクリプトのサンプル
課題
SREグループがKubernetesクラスターを運用し初めてからすぐにHelmを使うことになりました。
- テンプレートエンジンのおかげで重複が減る
- パッケージ化することでアプリケーション設定のバージョン管理ができるようになる
- 複数のk8sリソースをセットとしてデプロイしたりロールバックしたりできる
- コミュニティが提供している様々なチャートを簡単にインストールできる
と、非常に便利で大変お世話になっています。
当時最新だったHelm v2を使っていましたが、EKSを本番運用し初めてから1年弱が経った2019年11月にHelm v3がリリースされました。しかしバグや互換性の問題がいくつかあったことと、Helm v2用のチャートもメンテナンスされていたためv3アップデートを放置してしまいました。その後2020年8月Helm v2の廃止が発表され、手元の数十件のアプリケーションを移行するにはそれなりの検証と時間がかかるだろうということに気づきました。
それでもHelm v2が正常に動いていたためそこまで危機感を感じず、そのまま使い続けました。ただしHelmの公式チャートリポジトリも廃止方向になっていたためチャートがアップデートされなくなり、k8sバージョン1.16のようなk8s APIが廃止されるバージョンにアップデートするためにチャートをアップデートする必要がありました。ものによって別の保存場所から提供されていたチャートに移行したり、社内のチャートをAmazon S3に保存するために使っていたhelm s3プラグインを活かして、自分でアップデートしたチャートをS3に保管するようにしました。
あまりスケールしないやり方だったため、Helm v2が廃止されてから1年5ヶ月後の2022年1月に全てのアプリケーションをHelm v2からHelm v3にアップデートしました。現在は最新のHelm 3.8バージョンを使っていて、モダンな構成にやっと追いついています!
v2からv3へ
ここからはHelm v2とHelm v3の根本的な違いと挙動の微妙な違いや、v2からv3に安全に移行するために取ったアプローチを紹介したいと思います。移行を通して気づいたことや注意した方が良いことを共有し、利用したスクリプトを一部提供させていただきます。
v2とv3の違い
Helm v3の一番大きな変更点がTillerの廃止です。Helm v2の構成が複雑なだけではなく、クラスターで動いていたTillerとやりとりできるユーザは実質Tillerの権限を持っていることになるためセキュリティリスクでもありました。Helm v3ではTillerがなくなり、helmを実行するユーザが持っている権限の範囲の変更でしか変更ができないため安全です。
その他、クラスターにデプロイされているアプリケーションのリリース情報がHelm v2の場合ConfigMapに保存されており、Helm v3の場合はSecretに保存されているという違いがあります。
また互換性の問題はほぼなかったですが、Helm v3用のチャートには変更点がいくつかあります:
- チャートをHelm v3でビルドするように、
Chart.yaml
のapiVersion
をv2
にする必要がある (Helm v2用のチャートのapiVersionがv1で、Helm v3用のチャートのapiVersionがv2) - チャートの依存関係管理用の
requirements.yaml
ファイルが無くなり、依存関係管理をChart.yaml
ファイルで行うようになった
それ以外の大きい変更点は無く、今まで作っていたテンプレートもそのまま使えました。
移行方法
Helm v3に完全に切り替えるために、以下の2つのステップが必要です:
- クラスターにデプロイされているHelmリリース情報をHelm v2管理からHelm v3管理に移行する
- 利用しているチャートをHelm v2用のチャートからHelm v3用のチャートに切り替える
Helm v3はHelm v2用のチャートをそのまま利用できるようになっているため、上記の2つのステップを同時に実施する必要はなく、順番に実施することで移行がシンプルになります。
リリース情報移行
リリース情報を徐々にv2からv3に移行するやり方がHelmの公式サイトに記載されています。Helm v2とHelm v3を共存させて、リリースを一つずつ移行することでコントロールしやすく安全にマイグレーションを行うことができます。
具体的には以下のステップで移行を実施しました
- 初期状態:リリースがHelm v2で管理されている
- リリースアップグレードをHelm v2で行う:
helm upgrade ...
- リリースアップグレードをHelm v2で行う:
- Helm v2でもHelm v3でも管理できるようにする
- そこからリリースをHelm v3で管理する
- リリースアップグレードをHelm v3で行う:
helm3 upgrade ...
- リリースアップグレードをHelm v3で行う:
- Helm v2を削除する
- Helm v2管理用のConfigMapを削除する:
kubectl delete configmap ...
- 全てのリリース移行が完了してから、Tillerを削除する
- Helm v2管理用のConfigMapを削除する:
クラスタ管理ツールのバージョン管理を楽にするため、MoTではSREグループが提供しているDockerイメージにkubectl, helmなど様々なツールがインストールされています。helmコマンドを自分の端末にインストールして直接実行する形ではなく、SREが提供したHelm v2とHelm v3両方が初期化されているDockerイメージを使うことにしたため、helm-2to3プラグインの move
コマンドは使いませんでした。また、Helm v2のリリース情報やTillerなどの削除をよりコントロールできるように、helm-2to3プラグインの cleanup
コマンドを使わずにConfigMapとTillerを手動で削除しました。
チャート移行
リリース情報の移行が完了すると、Helm v2用のチャートもHelm v3用のチャートも使えますが、Helm v3に完全に移行するために利用していた全てのチャートをHelm v3用のチャートに移行しました。
社内で作っているチャートに関しては requirements.yaml
の内容を Chart.yaml
に移行して、 Chart.yaml
の apiVersion
をv2に切り替えて、helm3バイナリでパッケージ化することだけで概ね問題なくチャートを用意できました。コミュニティが提供しているチャートに関しては、helm v3版のチャートを探して、場合によって完全に別の提供者のものに切り替える必要がありました。
MoTでは社内で作っている全てのチャートはSREグループが提供しているテンプレートパッケージをベースに作成されているため(詳細はこちらに参考)、SRE側でHelm v3用のテンプレートパッケージバージョンを一つ用意するだけで準備ができました。アプリケーションが利用しているテンプレートパッケージのバージョンを上げるだけでHelm v3用のチャートに切り替えることができたため、設定の変更量を最小限にでき移行が楽でした。NGINX Ingress Controller、Prometheusなどコミュニティが提供しているチャートはSREが提供しているクラスターの基本機能にしか使っていないため、SREだけで完結する話で移行しやすかったです。
注意事項とコツ
思ったよりマイグレーションは簡単でしたが、それでもいくつか注意しないといけないところがありました。
直変更が戻されてしまう?
Helm v2とHelm v3のリソース適用時の差分チェック方法が変化しています。
Helm v2の場合、Helmが最後に適用したk8s yaml設定と、適用しようとしているk8s yaml設定を比較して、変更のあった箇所のみを適用しています。kubectlを使って例えばDeploymentのCPUリクエストを変更したとしても、Helmからすると何も変わっていないため、手動で変更したCPUリクエストが戻されることはありません。
Helm v3の場合、Helmが最後に適用したk8s yaml設定と適用しようとしているk8s yaml設定に加えて、現在クラスターにデプロイされているリソース状態(live状態)という3つの情報を比較して、適用すべき設定を判断しています。先ほどの例で言うと、DeploymentのCPUリクエストが変わったことを認識して、ターゲット設定に合わせるようにCPUリクエストを元に戻してくれます。
Helm v2の挙動だとクラスターの状態とHelmの状態がどんどんずれていく可能性が高いため、どちらかと言うとHelm v3の挙動の方が自然でいいのですが、その挙動の差を意識して場合によって運用を調整する必要があります。
デプロイ時にPodが消える?
HorizontalPodAutoscalerでオートスケールして作成されたPodがデプロイ時に削除されてしまう問題がありました。起きていた現象は「直変更が戻されてしまう?」と同じで、HPAで変更されたreplica数がhelm3 upgradeで元に戻されてしまうためでした。
HPAでオートスケールさせたいDeploymentの replicas
をHelmの設定に含めないことで、replicasがHelmの管理対象外項目になって、HPAに変更されてもHelmが変更することはなくなります。
ただしデプロイ済みのHelmリリースの replicas
設定を削除すると、k8sのデフォルト値 replicas: 1
が適用されてしまって、Helm 3に移行してからの最初のアップグレードだけレプリカ数が強制的に 1
になってしまう現象が起きていました。それ以降の適用は replicas
がHelm管理対象外になっているため問題はなく、レプリカ数が変更されることもなかったです。
適用タイミングなどを調整することでものによって最初のアップグレードで 1
レプリカに戻されることを許容できましたが、許容できないアプリケーションもありました。その場合、helm-2to3経由でリリース情報をHelm v2のConfigMapからHelm v3のSecretに移行した後に、Helm v3のSecret情報を直接変更して replicas
項目を消すことで replicas
を強制的にHelmの適用対象外にでき、最初のアップグレードでもレプリカ数を戻されないようにすることができました。その手順を以下にまとめます (linux)
- (1) 最後に適用されたリリース情報のSecretを特定する (例:
sh.helm.release.v1.my-release.v30
)
kubectl -n my-namespace get secret -l name=my-release
- (2) helm3的に適用されているマニフェストを取得
# 取得 rm -f manifest.json kubectl -n my-namespace get secret sh.helm.release.v1.my-release.v30 -o json | jq .data.release -r | base64 -d | base64 -d | gunzip - | jq . > manifest.json # 確認 cat manifest.json | jq .manifest -r
# 編集 vim manifest.json # 確認 cat manifest.json | jq .manifest -r
- (4) マニフェストの差分を確認する
helm3 -n my-namespace get manifest my-release --revision 30 > old-manifest cat manifest.json | jq .manifest -r > new-manifest colordiff old-manifest new-manifest
- (5) 編集後のマニフェストを適用する
DATA=`cat manifest.json | gzip -c | base64 | base64 | tr -d '\n'` kubectl -n my-namespace patch secret sh.helm.release.v1.my-release.v30 --type='json' -p="[{\"op\":\"replace\",\"path\":\"/data/release\",\"value\":\"$DATA\"}]"
- (6) リリース情報を確認する
# パッチした内容に問題なく、helmに認識されていることを確認 helm3 -n my-namespace list # マニフェスト確認 helm3 -n my-namespace get manifest my-release --revision 30
ヘルパースクリプト
ネームスペースごとにTillerを一つ動かしていたため、以下の手順でTillerを削除しました(実行する際は十分ご注意ください)
# Tiller Pod確認 kubectl get pod -l app=helm --all-namespaces # /!\ 注意 /!\ 削除 for each in $(kubectl get ns -o jsonpath="{.items[*].metadata.name}"); do kubectl delete deploy tiller-deploy -n $each kubectl delete svc tiller-deploy -n $each kubectl delete sa tiller -n $each kubectl delete role tiller -n $each kubectl delete rolebinding tiller -n $each kubectl delete clusterrolebinding tiller-$each done
TillerやHelm v2用のConfigMapが残っていないことを確認する:
# Tiller確認 kubectl get pod -l app=helm --all-namespaces kubectl get svc -l app=helm --all-namespaces kubectl get sa -l app=helm --all-namespaces kubectl get clusterrole -l app=helm --all-namespaces kubectl get clusterrolebinding -l app=helm --all-namespaces kubectl get role -l app=helm --all-namespaces kubectl get rolebinding -l app=helm --all-namespaces # Helm v2 ConfigMap確認 kubectl get configmap --all-namespaces | grep '\.v'
まとめ
Helm v2が廃止されて1年半後にHelm v3に移行できました。アプリケーションが100件以上あったため移行作業に少し時間がかかりましたが、クラスターに適用されているチャート、その適用方法と適用ツールをSREグループでコントロールするようにしているため、SREグループだけで移行作業ができたことと変更箇所を最小限にできてよかったです。
振り返ってみると、互換性の問題は小さかったですし、移行作業もそこまで複雑ではなかったのでもっと早めにやればよかったと思いました。廃止されてから1年半後でも問題なくHelm v2を使えたというのはHelmの安定性のおかげで、これから安心してHelm v3を使えるでしょう。
Helmの最新チャートをArtifact Hubで一元管理できて、新しいチャートのリリース通知を受けることもできるようになったので、これからその機能を活かして利用しているチャートを定期的にアップデートしていきたいと思います!
これでKubernetesアップデートストーリーシリーズの第5話が終わります。
シリーズ全体のまとめと今後の課題は第1話に記載していますので、是非、お読みください!