Kubernetesのバージョンアップとの付き合い方
2023年02月07日 火曜日
CONTENTS
Kubernetesは現在は4ヶ月に一回マイナーアップグレードリリースが実施されています。そして、基本的には最新の3つのマイナーリリースについてサポートが行われるポリシーとなっています(深刻なセキュリティフィックスは例外)。
https://kubernetes.io/ja/docs/setup/release/version-skew-policy/
ところが、DataDogのレポートによると古いバージョンのKubernetesを使い続けている組織が多いのだそうです。Kubernetesは後方互換性が高いレベルで保たれているソフトウェアですが、まだまだ発展途上にあり、繰り返される仕様の追加、変更に遅れることなくキャッチアップしていくことが強いられるシステムです。ましてや、プロダクション環境で利用しているとなれば、かなりの深い理解がなければ手を出しづらいメンテナンスではあります。
https://www.datadoghq.com/ja/container-report/#6
ですが、プロダクション環境でKubernetesを使う以上、アップグレードを避けては通れません。Kubernetes自身のセキュリティを保つためだけでなく、Kubernetes上で動くサードパーティ製コントローラ類のバージョンを上げてしまうといずれは古いKubernetesでは動かなくなり、エコシステム全体を健全に保つにはKubernetesをアップデートせざるを得ない状況がやってきます。それに、マイナーバージョンを一つスキップしてバージョンを上げることはできず、一度置き去りにされると追いつくには大変な苦労を伴います。Kuberenetesを利用すると決めたら、最初からアップグレードの計画をロードマップに含めておきましょう。
Kubernetesのバージョンアップは難しい?
もっとも、Kubernetesのバージョンアップは作業だけならば何も難しいことはありません。kubeadmにしろ、その他さまざまなKubernetesディストリビューションにしろ、クラウドのマネージドKubernetesサービスにしろ、ほとんどの場合アップグレードのツールが提供され、簡易なコマンドでアップグレードが可能です。Kubernetesのバージョンアップとはコントロールプレーンおよびノードエージェントである以下コンポーネント群の入れ替えがほぼすべてであり、ついでに各種証明書の更新を行うのがアップグレードメンテナンスの実態と言っていいでしょう。そこそこ大きなクラスタであっても、何事もなければ1時間程度で全ノードのアップグレードが終えることができます。
- kube-apiserver
- kube-controller-manager
- kube-scheduler
- kube-proxy
- kubelet
もちろん、その前にはUrgent Upgrade Notesを熟読し、アップグレード前にしかるべき準備を整えておく必要はありますが、毎回面倒な対応が必須なわけでもありません。
apiVersionの廃止を乗り越えろ
アップグレードが特に難しいのは、古いapiVersionが廃止されるときです。KubernetesのAPIにはバージョンの概念がありv1alpha1, v1beta1, v1といった具合にバージョンを重ね、新APIバージョンが登場すると一定の併存期間を経た後、古いAPIバージョンは利用できなくなります。
例えば、Kubernetes v1.22ではv1beta1のIngressが削除され、networking.k8s.io/v1のIngressのみとなりました。その結果、最近のバージョンでは以下のようにapiVersionにv1beta1を明示してIngressリソースを参照すると、そのようなリソースタイプは存在しないとエラーメッセージが表示されてしまいます。
$ kubectl get ingress.v1beta1.networking.k8s.io error: the server doesn't have a resource type "ingress" $ kubectl get ingress.v1.networking.k8s.io NAME CLASS HOSTS ADDRESS PORTS AGE nginx default www.example.jp 172.28.244.91 80 346d
apiVersionの一覧を見ても、v1しか存在しないので当然です。
$ kubectl api-versions | grep networking networking.k8s.io/v1
KubernetesのAPIが興味深いのは、新旧APIが併存している場合、どちらのバージョンでも読み書き可能であるし、どちらのバージョンで書き込んでも両方のバージョンで読み出せるところです。例えば、v1beta1で作成したマニフェストであっても、v1で読み出せることが保証されているのです。spec下のフォーマットが変更されていたとしても、適切に変換され、リクエストしたバージョンのフォーマットで参照が可能です。そうでないとアップグレードがままならないのでわかる話ですが、それどころか新しいv1でマニフェストを作成しても、古いv1beta1で読み出すことすら可能です(許可されているバージョンに限る)。Kubernetes APIは新旧APIのマニフェストが双方向に変換可能であり、その過程で情報が失われてはならず、必要ならば古いAPIバージョンの定義を更新すべしと決められているからです。おもしろいですね。ただし、その後新規作成は新しいバージョンでしかできなくなり、読み出しだけ両バージョンで可能となり、最終的に新バージョンのみの状態へ移行します。
こうした手厚いサポートがあって、すでにデプロイ済のワークロードに影響が及ぶことはなく、ユーザーとしては古いAPIバージョンが利用できなくなる前に手持ちのマニフェストを新バージョンへ変換しておけばよいということになります。
helmの利用者は要注意
ですが、helmを利用している場合は注意が必要です。helmは過去のリリースバージョンへロールバックできるように、リリース時のマニフェストをアーカイブしてSecretリソースに保存しています。そして、helm upgradeコマンドを実行する際には直前のアーカイブと比較し、差分を確認してデプロイするようなのですが、その際にアーカイブされたマニフェストに廃止された古いAPIバージョンが含まれていると、helm upgradeに失敗してしまいます。
これを回避する方法はいくつかありますが、もっともスマートなのはAPIが廃止される前に最新のマニフェストでhelmチャートを更新し、一度アップグレードしておくことです。ただ、えてしてこの問題に気が付くのはKubernetesをアップグレードしたあとなので、手遅れになっている可能性があります。その場合はやむを得ません。念のため保存したうえでSecretリソースを削除し、履歴を無かったことにしてあらためてhelm installしましょう。既存のワークロードに影響を与えることなく更新が可能です。
$ kubectl get secret --selector=owner=helm NAME TYPE DATA AGE sh.helm.release.v1.example.v1 helm.sh/release.v1 1 56d sh.helm.release.v1.example.v2 helm.sh/release.v1 1 56d
コントローラのバージョンアップを忘れずに
マニフェストの書き換えに加えてもう一つ忘れてはいけないのが、サードパーティ製コントローラのバージョンアップです。
例えば、前述の通りKubernetes 1.22でv1beta1のIngressが廃止されたわけですが、その影響はkubectlコマンドで読み書きできなくなるだけでなく、古いIngress Controller(nginx-ingress-controller, traefik, etc)も動かなくなります。
一般的にコントローラは特定のAPIバージョンでリソースを参照するように実装されるため、該当APIバージョンが廃止され読み出せなくなると機能しなくなります。具体的には、コントローラが起動時に対象リソースをすべて読み出してキャッシュしようとするため、その時点でエラーになり起動しなくなる場合が多いでしょう。つまり、v1beta1をターゲットに実装されているコントローラは、Kubernetesをバージョンアップしていくといずれ機能しなくなる運命にあるということです。
この種のKubernetes標準APIでありながら、サードパーティ製コントローラを利用するリソースの種類は多くはなく、Ingressとストレージ(CSI)関連がほとんどでしょう。対応をお忘れなく。
カスタムリソースにも廃止の日はやってくる
コントローラはKubernetesのバージョンに合わせてバージョンアップを要求されがちなわけですが、CRDで定義されたカスタムリソースを扱うコントローラの場合、安易にバージョンアップすると痛い目を見る可能性があります。
前述したように、KubernetesのAPIはバージョン間で互換性を保ち、相互変換できることが求められます。これはカスタムリソースでも同様で、コントローラを実装する際には必ず考慮しなければなりません。その仕組みのことをKubernetesのドキュメントではストアドバージョンを中心としたハブ&スポークと表現されており、ストアドバージョンに定めた特定のAPIバージョンだけをデータベース(etcd)に保存しておき、それ以外のバージョンで読み書きするときは必ずストアドバージョンを経由して変換するように求められています。
引用:https://book.kubebuilder.io/multiversion-tutorial/conversion-concepts.html
問題は、ストアドバージョンがいつか廃止される可能性があるということです。ストアドバージョンが廃止されるとハブ&スポークの仕組みが働かなくなり、すべてのカスタムリソースが消えたようになってしまいます。それを避けるには、コントローラをアップグレードする前にマイグレーションツールを使って、ストアドバージョンを変換しておく必要があります。古くからcert-managerを利用していたユーザーは、おそらく一度このプロセスを経験しているのではないでしょうか。
ストアドバージョンの変更はKubernetesのバージョンに依存する話ではありませんが、Kubernetesのバージョンアップに合わせてコントローラをアップデートしなければならず、アップグレードしたら以前のストアドバージョンが廃止されていて読み出せなくなった、というのは今後もありそうな話です。世のコントローラはこの手間を嫌ってストアドバージョンの変更をためらっているケースが多いかもしれませんが、利用者としてはこうした手間が必要になりうるということは知っておく必要があります。
メトリクス名の変更に備えよ
Kubernetesのモニタリングにはほとんどの場合Prometheusを活用されていると思いますが、アップグレードによってメトリクスの名称が変わり、おかげでgrafanaのダッシュボードが壊れてしまうということが過去たびたびありました。Kubernetes本体に限らず、prometheusのexporterは躊躇なくメトリクス名が変更され、苦労させられるケースが珍しくもありません。アップグレードノートには記載されているので、忘れずにチェックしましょう。
こうしてみると思いのほか考慮すべきことが多く、まったくもって「難しくはない」わけではないですね。嘘をついてしまったようです。謝罪します。ですが、アップグレードは怠らずに定期的なメンテナンスを心がけましょう。