MoTLab -GO Inc. Engineering Blog-MoTLab -GO Inc. Engineering Blog-

Helm v2からv3へのメジャーアップデート | Kubernetesアップデートストーリー第5話

SREK8s
May 26, 2022

こんにちは、技術戦略部 SREグループのカンタンです。

本記事はKubernetesアップデートストーリーシリーズの第5話になっていて、Helmのメジャーバージョンをバージョン2からバージョン3にアップデートした話になります。是非第1話から読んでいただいたほうが背景と内容ががわかりやすくなるかと思います!


Kubernetesアップデートストーリーシリーズのまとめ

本記事では以下の課題についてお話しします:

  • 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.yamlapiVersionv2 にする必要がある (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を共存させて、リリースを一つずつ移行することでコントロールしやすく安全にマイグレーションを行うことができます。

具体的には以下のステップで移行を実施しました

  1. 初期状態:リリースがHelm v2で管理されている
    • リリースアップグレードをHelm v2で行う: helm upgrade ...
  2. Helm v2でもHelm v3でも管理できるようにする
    • Helm v3バイナリを helm3 としてインストール (helm はhelm v2のまま)
    • helm-2to3プラグインをインストール: helm3 plugin install https://github.com/helm/helm-2to3
    • Helm v2リリース情報をHelm v3リリース情報に移行する: helm3 2to3 convert --tiller-ns my-namespace my-release
  3. そこからリリースをHelm v3で管理する
    • リリースアップグレードをHelm v3で行う: helm3 upgrade ...
  4. Helm v2を削除する
    • Helm v2管理用のConfigMapを削除する: kubectl delete configmap ...
    • 全てのリリース移行が完了してから、Tillerを削除する

An image from Notion

クラスタ管理ツールのバージョン管理を楽にするため、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.yamlapiVersion を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の挙動の方が自然でいいのですが、その挙動の差を意識して場合によって運用を調整する必要があります。

An image from Notion

デプロイ時にPodが消える?

HorizontalPodAutoscalerでオートスケールして作成されたPodがデプロイ時に削除されてしまう問題がありました。起きていた現象は「直変更が戻されてしまう?」と同じで、HPAで変更されたreplica数がhelm3 upgradeで元に戻されてしまうためでした。

An image from Notion

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
  • (3) manifest.jsonをvimなどで編集して、 replicas: 1 を消す
# 編集
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話に記載していますので、是非、お読みください!


We're Hiring!

📢
Mobility Technologies ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @mot_techtalk のフォローもよろしくお願いします!