🦉

k8sはどのように非推奨APIの利用を検知しているのか

2022/05/29に公開約6,000字

Kubernetesにはこのような機能があるのを知っているだろうか?

$ kubectl apply -f cronjob.batch.v1beta1.yaml
Warning: batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+; use batch/v1 CronJob
cronjob.batch/hello created

これはv1.21のクラスタに対して、batch/v1beta1 CronJobを利用したリソースを定義したマニフェストをApplyした際に返される結果。
kubectlがWarningを返しているのがわかる。

Warning: batch/v1beta1 CronJob is deprecated in v1.21+, unavailable in v1.25+; use batch/v1 CronJob

Kubernetesではv1.19で廃止予定のAPIの利用に対する警告が導入されている。
この機能はApplyした際に対象のマニフェストで非推奨(deprecated)、もしくは削除予定(removal)のAPIを利用していた場合に警告が返ってくるといったもの。
上記の例では、v1.21ではすでに batch/v1beta1 CronJob は非推奨となっており、batch/v1 CronJob を利用すべきなのでその情報が返ってきている。

(ちなみに、自分は最近まで知らなかった。CI/CDで自動Applyしていると意外と気付かないんじゃなかろうか?)

この非推奨APIの検知のしくみはどのようになっているのか?が気になり、調べたのでその結果について記載する。
また、このしくみを利用して、Apply前に非推奨APIを検出するためのOpen Policy Agentのポリシーファイルとそれを自動生成するツールを作ったので紹介する。

そもそも何が起きている?

そもそもこの挙動がどのような順で発生しているのかというと、こんな感じ。

  1. kube-apiserverが非推奨API用のハンドラ登録時にWarningを返す設定を追加
  2. kubectlを用いて非推奨APIを利用したマニフェストをApply
  3. kube-apiserverへリクエストされ、対象のAPIに対応したWarningメッセージをレスポンスに付与
  4. kubectlがそのメッセージを表示

今回は、1番目のserverの処理について調べた。

どのように非推奨APIと判定しているのか

各種APIにはそれがいつ非推奨になるのか、いつ削除されるのかが定義されているため、それを利用してkube-apiserverが判定している。

非推奨や削除予定のAPIには下記インタフェースが実装されている。

https://github.com/kubernetes/kubernetes/blob/dfefd5a698e35dbbab8b341754beb5d38827bf76/staging/src/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go#L29-L39

たとえば、batch/v1beta1 CronJob では下記のような実装がされている。

https://github.com/kubernetes/kubernetes/blob/dfefd5a698e35dbbab8b341754beb5d38827bf76/staging/src/k8s.io/api/batch/v1beta1/zz_generated.prerelease-lifecycle.go#L34-L50

これらを呼び出すことで対象のAPIがいつ非推奨となり削除されるのかや、どのAPIに移行すべきかなどがわかる。

kube-apiserverではどのように利用しているのかというと、各APIのハンドラ追加時にそのAPIが上記インタフェースを満たすかを確認している。

こちらがハンドラ登録時の処理の一部。

https://github.com/kubernetes/kubernetes/blob/dfefd5a698e35dbbab8b341754beb5d38827bf76/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go#L681-L685

IsDeprecated() がやっていることはとても簡単。 apiLifecycleDeprecated インタフェースを満たしているかを確認し、満たしていれば現在のserverのバージョンと比較。すでに非推奨となっていればtrueを返している。

https://github.com/kubernetes/kubernetes/blob/dfefd5a698e35dbbab8b341754beb5d38827bf76/staging/src/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go#L60-L85

非推奨APIであることがわかったら、WarningMessage()(実装はここ) を利用してWarningメッセージを生成し、ハンドラに登録しておく。ここで登録されたメッセージが実際にApply時に表示されている。
生成されるメッセージは下記フォーマットとなっており、「いつ・どのAPIが非推奨・削除され、代わりに何を利用すべきか」が記載されている。

<非推奨API> is deprecated in <非推奨となるバージョン>+, unavailable in <削除されるバージョン>+; use <移行先API>

これで無事に、Apply時に非推奨APIが利用されていた場合は、警告が表示されるようになる。

ちなみに、各種APIの IsDeprecated() などは自動生成されている。
各APIにあるtypes.goの情報をprerelease-lifecycle-genが読み取り zz_generated.prerelease-lifecycle.go が生成される。このファイルに非推奨API関連のレシーバがすべて実装されている。

下記は、batch/v1beta1 CronJob用のtypes.goの記載内容。

https://github.com/kubernetes/kubernetes/blob/dfefd5a698e35dbbab8b341754beb5d38827bf76/staging/src/k8s.io/api/batch/v1beta1/types.go#L59-L61

事前に非推奨APIを検知するために

ここからは、上記の機能を利用して実装した非推奨APIをApply前に検出するためのポリシーファイルやそれを自動生成するツールについて紹介する。
そもそも、最初は非推奨APIを事前に検出したくてKubernetesのしくみが利用できないかと思って調べたのがきっかけ。

既存の検出用ツールやポリシーファイルは下記などがある。

https://github.com/FairwindsOps/pluto

https://github.com/swade1987/deprek8ion

pluto は便利なツールなのだが、自分は普段マニフェストなどに対してOPAを利用してたので、ニーズが合わなかった。
deprek8ionはOPAのポリシーファイルとして検出ポリシーを提供してくれているのだが、すでにアーカイブされており継続的に利用ができない。

というわけで、自分で作ってみようと思って作成してみた。

作ったもの

https://github.com/x-color/k8sdeprecated

このリポジトリは、検出ポリシーとそれを自動生成するためのツールが入っている。
毎月初日にKubernetesのリポジトリを確認し、非推奨APIの情報が更新されていればそれをポリシーファイルに反映している。

使い方

OPAのポリシーファイルとして作成しているので、OPAを利用できるツールで検知可能。
例として、Conftestを用いる場合は下記のように使用できる。

非推奨APIの検知方法

まず最初に検知したいポリシーを取得する。
たとえばv1.22に非推奨となったAPIについて検知したい場合は、policy/1_22.rego を取得する。

$ conftest pull https://github.com/x-color/k8sdeprecated/blob/main/policy/1_22.rego

また、v1.22までに非推奨となっているすべてのAPIを検知したい場合は、上記と同様にそれ以前のバージョンのポリシーも取得する必要がある。

$ conftest pull https://github.com/x-color/k8sdeprecated/blob/main/policy/1_8.rego
# :
$ conftest pull https://github.com/x-color/k8sdeprecated/blob/main/policy/1_21.rego

検知したいポリシーの取得が終わったら、下記コマンドで検査したいマニフェストファイル(ディレクトリも可)を指定してテストを実施する。

$ conftest test <manifest file>

実行すると、マニフェストで利用されているAPIがすでに非推奨となっている場合は、警告を返し下記のようなメッセージが表示される。

networking.k8s.io/v1beta1 Ingress is deprecated in 1.19+. use networking.k8s.io/v1 Ingress

もし、すでに削除されたAPIであれば、エラーが返され下記のようなメッセージが表示される。

networking.k8s.io/v1beta1 Ingress is removed in 1.22+. use networking.k8s.io/v1 Ingress

このしくみをCI/CDに組み込んでおけば、Apply以前に対象のマニフェストで非推奨APIを利用しているか検知できる。
Kubernetesのバージョンを上げる際に現在利用しているマニフェストに対して利用してバージョンを上げても影響がないかを確認するのにも使える。

ポリシーの自動生成方法

最後に自動生成方法について。
ツールは2つに分かれており、それぞれ下記となっている。
これら自動生成ツールは毎月初日にGitHub Actionsで起動されている。

  1. Kubernetesリポジトリを確認してすべてのAPIを検出。その後、それらAPIを返すパッケージを生成するツール
  2. 1で生成したパッケージを利用して、すべてのAPIを呼び出し、非推奨APIを検出。その情報を利用して、ポリシーファイルを生成するツール。

KubernetesのすべてのAPIを検出するツール

愚直にリポジトリのAPIが定義されているディレクトリを探索し、その中に定義されているAPIを列挙し、テンプレートに埋め込んでいるだけ。

テンプレート内部では、呼び出し時にすべてのAPIを登録して、この構造体のSliceで返却する処理が記載されている。

https://github.com/x-color/k8sdeprecated/blob/3888bcb5d59d52a2ae9d7eb9279403a0bf49d3d3/k8sapi/k8sapi.go#L59-L63

ここで生成されたパッケージはインポートして利用可能ですので、もしKubernetesの全APIをSliceでほしいなどあればどうぞ。

https://github.com/x-color/k8sdeprecated/tree/main/k8sapi

非推奨API検出ポリシーを生成するツール

まず、1で生成されたパッケージを利用し、いったんすべてのAPIを取得。
その後、kube-apiseverと同様に非推奨APIのインタフェースを満たすかどうかを確認する。
満たすAPIを列挙し、それをOPAのポリシーファイルに書き出しているだけ。

終わりに

初めてKubernetesのコードをしっかりと読んだ。さすがに規模が大きいので追いかけるのは難しいが、各機能の実装はとてもきれいなので読みやすかった。
普段利用させてもらっているコアな機能を読んでみるのはとてもおもしろいし、勉強になる。

Discussion

ログインするとコメントできます