こんにちは、技術戦略部 SREグループのカンタンです。
本記事はKubernetesアップデートストーリーシリーズの第5話になっていて、Helmのメジャーバージョンをバージョン2からバージョン3にアップデートした話になります。是非第1話から読んでいただいたほうが背景と内容ががわかりやすくなるかと思います!
Kubernetesアップデートストーリーシリーズのまとめ
本記事では以下の課題についてお話しします:
SREグループがKubernetesクラスターを運用し初めてからすぐにHelmを使うことになりました。
と、非常に便利で大変お世話になっています。
当時最新だった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バージョンを使っていて、モダンな構成にやっと追いついています!
ここからはHelm v2とHelm v3の根本的な違いと挙動の微妙な違いや、v2からv3に安全に移行するために取ったアプローチを紹介したいと思います。移行を通して気づいたことや注意した方が良いことを共有し、利用したスクリプトを一部提供させていただきます。
Helm v3の一番大きな変更点がTillerの廃止です。Helm v2の構成が複雑なだけではなく、クラスターで動いていたTillerとやりとりできるユーザは実質Tillerの権限を持っていることになるためセキュリティリスクでもありました。Helm v3ではTillerがなくなり、helmを実行するユーザが持っている権限の範囲の変更でしか変更ができないため安全です。
その他、クラスターにデプロイされているアプリケーションのリリース情報がHelm v2の場合ConfigMapに保存されており、Helm v3の場合はSecretに保存されているという違いがあります。
また互換性の問題はほぼなかったですが、Helm v3用のチャートには変更点がいくつかあります:
それ以外の大きい変更点は無く、今まで作っていたテンプレートもそのまま使えました。
Helm v3に完全に切り替えるために、以下の2つのステップが必要です:
Helm v3はHelm v2用のチャートをそのまま利用できるようになっているため、上記の2つのステップを同時に実施する必要はなく、順番に実施することで移行がシンプルになります。
リリース情報を徐々にv2からv3に移行するやり方がHelmの公式サイトに記載されています。Helm v2とHelm v3を共存させて、リリースを一つずつ移行することでコントロールしやすく安全にマイグレーションを行うことができます。
具体的には以下のステップで移行を実施しました
クラスタ管理ツールのバージョン管理を楽にするため、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の挙動の方が自然でいいのですが、その挙動の差を意識して場合によって運用を調整する必要があります。
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)
kubectl -n my-namespace get secret -l name=my-release
# 取得
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
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
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\"}]"
# パッチした内容に問題なく、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話に記載していますので、是非、お読みください!
興味のある方は 採用ページ も見ていただけると嬉しいです。
Twitter @mot_techtalk のフォローもよろしくお願いします!