Kubernetes が非推奨 API を検出する仕組み
- Authors
- Name
- ごとれん
- X
- @ren510dev
目次
- 目次
- はじめに
- Kubernetes のアーキテクチャ
- Control-Plane
- Data-Plane
- Kubernetes API のリリースサイクル
- 非推奨 API の利用が問題視される理由
- kube-apiserver の実装
- 非推奨 API の判定
- クラスタ内の非推奨 API を特定する際の課題
- Pluto による非推奨 API の検出
- ローカル検証
- 出力結果
- オプション
- コマンド
- CI ワークフロー
- まとめ
- 参考・引用
はじめに
Kubernetes API はクラスタリソースを操作するための基本的なインターフェースを提供します。 クライアントは Kubernetes API を通じて、Namespace, Deployment, Pod 等の Kubernetes の様々なオブジェクトを検索・操作することができます。
Kubernetes API はクラスタのアップグレードに伴い、既存の API が非推奨(Deprecated)または廃止(Removal)となる場合があります。 もし、非推奨 API でサービスを運用し続けると、いずれ API が使用できなくなりサービスのダウンタイムや障害を引き起こすリスクが高まります。 実際に米国ソーシャルニュースサイト Reddit の例 にあるように、非推奨 API を使用し続けたことで大規模な障害が発生した事例も報告されています。
- You Broke Reddit: The Pi-Day Outage
This was evident when we encountered the upgrade to Kubernetes 1.24, which brought an issue with service account default secrets. Consequently, it rendered the version incompatible with other Kubernetes terraform provider versions. Although it didn't cause a complete outage, it was a significant drain on our valuable time as we worked to identify the root cause of the problem.
クラスタの管理者は、現在使用している API バージョンが非推奨でないかを常に把握しておく必要があります。
Kubernetes の API サーバは API のバージョン情報を管理しており、Deprecated API を適用すると、以下のようなメッセージで警告を出してくれます。
$ 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 が非推奨 API を検出する仕組みについて紹介したいと思います。
Kubernetes のアーキテクチャ
前提として、Kubernetes のアーキテクチャについて軽く紹介します。
Kubernetes のデプロイは、クラスタと呼ばれます。 Kubernetes クラスタは、少なくとも 1 つの Control-Plane と、1 つ以上の Data-Plane で構成されています。 Control-Plane と Data-Plane は、どちらも物理デバイス、仮想マシン、またはクラウド上のインスタンスを利用することができます。
一般にマネージド Kubernetes の場合は、Control-Plane がクラウドプロバイダによって管理され、Data-Plane であるワーカーノードを自前で管理します。 一部 GKE Autopilot のようなワーカーノードも自動で管理してくれるソリューションも存在します。
Control-Plane
Control-Plane は、マスターノードやヘッドノードとも呼ばれます。 Control-Plane は、主にクラスタ内のワーカーノードや Pod を一元管理するコンポーネントです。
本番環境の場合、Control-Plane は通常複数のコンピュータで動作し、クラスタは通常複数のノードで動作し、HA(High Availability)や DR(Disaster Recovery)を持ち合わせています。 Control-Plane は、kube-apiserver と呼ばれるクライアントがクラスタと直接対話するためのインターフェースを提供します。
Data-Plane
Data-Plane は、ワーカーノードやコンピュートノードとも呼ばれます。 コンテナ化されたアプリケーションを実行するために必要なサービスを含む仮想マシンまたは物理マシンを提供します。
クラスタには最低でも 1 つのワーカーノードが必要ですが、通常は多数のワーカーノードで構成されます。
ワーカーノードは、ワークロード(Pod)を起動し、ノード上で実行されるようにスケジュール、オーケストレーションされます。 また、ノードを追加、削除することで、クラスタをスケールアップまたはスケールダウンすることができます。
Data-Plane は kubelet と呼ばれるノードコンポーネントを通じて Control-Plane から制御されます。
Kubernetes API のリリースサイクル
Kubernetes は Control-Plane の API を通じて運用されます。 API はユーザのニーズや技術の進歩を反映するよう、頻繁に変更や改良が加えられます。 その結果、古い API の仕様が不要となり、新しい機能に置き換えられたりすることがあります。
Kubernetes のマイナーバージョンは 4 ヶ月毎、年 3 回の 短いリリースサイクル を採用しています。 これは API の成熟度の進展(Alpha → Beta → GA)に反映され、特定のバージョン毎にアップグレードが追跡されます。
Kubernetes クラスタの管理者は、リリースサイクルを理解し、そのステージ(Alpha, Beta, GA)や廃止スケジュールを把握することが、安定性を担保した運用に必要不可欠です。
Kubernetes の API にはバージョンの概念があり、例えば、v1alpha1
,v1beta1
,v1
といった具合にバージョンを重ね、新 API バージョンが登場すると一定の併存期間を経た後、古い API バージョンは利用できなくなります。 API のバージョンニングは Alpha
, Beta
, Stable
の 3 種類に分類されます。
API バージョン | 例 |
---|---|
Alpha | v1alpha1 |
Beta | v2beta3 |
Stable | v1 |
Kubernetes API サーバは、リソースの種別とバージョン情報を Group/Version/Kind(GVK)で判別します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: example
この例では、Group は app
Version は v1
Kind は Deployment
となり、kube-apiserver はこれらの組み合わせでリソースを識別します。
現在の Kuberetes で推奨される Group/Version は以下のコマンドで確認することができます。
$ kubectl api-versions
また、現行クラスタにおいて優先されるバージョン(Preferred API Version)を確認する場合は以下のコマンドを利用します。
$ kubectl api-resources
非推奨 API の利用が問題視される理由
アプリケーションの設定を行う際、ユーザは利用する Kubernetes オブジェクトの API バージョンを指定します。 これは、単純な YAML マニフェストや Helm チャートであっても同様であり、apiVersion フィールドで Kubernetes オブジェクトの API バージョンを指定します。 そのため、クラスタの管理者や利用者は、廃止予定(Deprecated)の Kubernetes API バージョンや、それがどの Kubernetes リリースで削除されるかを把握しておく必要があります。
また、Kubernetes クラスタをアップグレードすると、非推奨 API を検出する可能性があります。 もしアップグレードされたバージョンで古い API がサポートされていない場合、そのリソースは kube-apiserver でブロックされるためリソースを新規に作成したり更新したりすることができません。
例えば、クラスタ内のリソースが非推奨 API バージョンを使用している場合、アプリケーションがそのリソースに依存していると、更新後のクラスタバージョンで非推奨となった API が削除されることで、アプリケーションが機能しなくなる可能性があります。
具体例として、Ingress の apiVersion extensions/v1beta1
は Kubernetes v1.22 で削除されました。 従って、最新のクラスタでは削除された API バージョンを使用すると以下のようなエラーメッセージが表示されます。
Error: UPGRADE FAILED: current release manifest contains removed kubernetes api(s) for this kubernetes version and it is therefore unable to build the kubernetes objects for performing the diff. error from kubernetes: unable to recognize "": no matches for kind "Ingress" in version "extensions/v1beta1"
kube-apiserver の実装
実際に kube-apiserver の実装を覗いてみたいと思います。
Kubernetes API サーバ(Kukube-apiserver)が Warning メッセージを表示するまでの基本的な流れは以下の通りです。
- kube-apiserver が非推奨 API 用のハンドラ登録時に Warning を返す設定を追加
- kubectl を用いて非推奨 API を利用したマニフェストを Apply
- kube-apiserver へリクエストされ、対象の API に対応した Warning メッセージをレスポンスに付与
- kubectl がそのメッセージを表示
非推奨 API の判定
各種 API には、いつ非推奨になるのか、いつ削除されるのかが定義されているため、それを利用することでリクエスト時に kube-apiserver が判定しています。 非推奨や削除予定の API には次のインタフェースが実装されています。
type apiLifecycleDeprecated interface {
APILifecycleDeprecated() (major, minor int)
}
type apiLifecycleRemoved interface {
APILifecycleRemoved() (major, minor int)
}
type apiLifecycleReplacement interface {
APILifecycleReplacement() schema.GroupVersionKind
}
例えば、batch/v1beta1
CronJob では下記のような実装となっています。
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
func (in *CronJob) APILifecycleDeprecated() (major, minor int) {
return 1, 21
}
// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type.
// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=<group>,<version>,<kind>" tags in types.go.
func (in *CronJob) APILifecycleReplacement() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "CronJob"}
}
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
func (in *CronJob) APILifecycleRemoved() (major, minor int) {
return 1, 25
}
これらを呼び出すことで対象の API がいつ非推奨および削除されるのかや、どの API に移行すべきか等を知ることができます。 また、kube-apiserver では、各 API のハンドラ追加時にその API が上記インタフェースを満たすかを確認しています。
deprecated = deprecation.IsDeprecated(versionedPtrWithGVK, currentMajor, currentMinor)
if deprecated {
removedRelease = deprecation.RemovedRelease(versionedPtrWithGVK)
warnings = append(warnings, deprecation.WarningMessage(versionedPtrWithGVK))
}
IsDeprecated()
がやっていることは至ってシンプルで、apiLifecycleDeprecated
インタフェースを満たしているかを確認し、満たしていれば現在の API サーバのバージョンと比較し、既に非推奨となっていれば true を返します。
// IsDeprecated returns true if obj implements APILifecycleDeprecated() and returns
// a major/minor version that is non-zero and is <= the specified current major/minor version.
func IsDeprecated(obj runtime.Object, currentMajor, currentMinor int) bool {
deprecated, isDeprecated := obj.(apiLifecycleDeprecated)
if !isDeprecated {
return false
}
deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated()
// no deprecation version expressed
if deprecatedMajor == 0 && deprecatedMinor == 0 {
return false
}
// no current version info available
if currentMajor == 0 && currentMinor == 0 {
return true
}
// compare deprecation version to current version
if deprecatedMajor > currentMajor {
return false
}
if deprecatedMajor == currentMajor && deprecatedMinor > currentMinor {
return false
}
return true
}
非推奨 API となっている場合、WarningMessage()
メソッドで Warning メッセージを生成し、ハンドラに登録します。 ここで登録されたメッセージが、実際に kubectl apply
時に表示されています。
// WarningMessage returns a human-readable deprecation warning if the object implements APILifecycleDeprecated()
// to indicate a non-zero deprecated major/minor version and has a populated GetObjectKind().GroupVersionKind().
func WarningMessage(obj runtime.Object) string {
deprecated, isDeprecated := obj.(apiLifecycleDeprecated)
if !isDeprecated {
return ""
}
deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated()
if deprecatedMajor == 0 && deprecatedMinor == 0 {
return ""
}
gvk := obj.GetObjectKind().GroupVersionKind()
if gvk.Empty() {
return ""
}
deprecationWarning := fmt.Sprintf("%s %s is deprecated in v%d.%d+", gvk.GroupVersion().String(), gvk.Kind, deprecatedMajor, deprecatedMinor)
if removed, hasRemovalInfo := obj.(apiLifecycleRemoved); hasRemovalInfo {
removedMajor, removedMinor := removed.APILifecycleRemoved()
if removedMajor != 0 || removedMinor != 0 {
deprecationWarning = deprecationWarning + fmt.Sprintf(", unavailable in v%d.%d+", removedMajor, removedMinor)
}
}
if replaced, hasReplacement := obj.(apiLifecycleReplacement); hasReplacement {
replacement := replaced.APILifecycleReplacement()
if !replacement.Empty() {
deprecationWarning = deprecationWarning + fmt.Sprintf("; use %s %s", replacement.GroupVersion().String(), replacement.Kind)
}
}
return deprecationWarning
}
生成されるメッセージは下記フォーマットとなっており、「いつ・どの API が非推奨・削除され、代わりに何を利用すべきか」が記載されています。
[非推奨 API] is deprecated in [非推奨となるバージョン]+, unavailable in [削除されるバージョン]+; use [移行先 API]
また、各種 API の IsDeprecated()
メソッド等は自動生成されています。 各 API にある types.go
の情報を prerelease-lifecycle-gen
が読み取り zz_generated.prerelease-lifecycle.go
が生成されます。 このファイルに非推奨 API 関連のレシーバがすべて実装されています。
例:batch/v1beta1
CronJob 用の types.go
の記載内容
// +k8s:prerelease-lifecycle-gen:deprecated=1.21
// +k8s:prerelease-lifecycle-gen:removed=1.25
// +k8s:prerelease-lifecycle-gen:replacement=batch,v1,CronJob
クラスタ内の非推奨 API を特定する際の課題
Kubernetes では非推奨または削除された API を調べるための 公式ドキュメント が提供されていますが、これらの API を利用するクラスタ内のリソースを特定するのは非常に困難な場合があります。
さらに、Kubernetes は厳格な API バージョン管理規約に従っているため、複数のリリースにわたって v1beta1
および v2beta1
API が複数回廃止されています。 Kubernetes のポリシでは、beta API バージョンは廃止後、最低 9 か月または 3 リリース(いずれか長い方)のサポートを受けることが義務付けられており、その後は削除される可能性があります。
非推奨 API が、クラスタ内のワークロードやツール、その他のコンポーネントで使用され続けている場合、利用できなくなる可能性があります。 従って、クラスタの管理者やアプリケーションの開発者は、クラスタを継続的に監視して、削除予定の API が使用されていないかを把握する必要があります。
また、kubectl コマンドで Kubernetes リソースを一覧表示するだけでは不正確な API バージョン情報が生成される可能性もあります。
Pluto による非推奨 API の検出
- https://github.com/FairwindsOps/pluto
- Fork 124 / Star 2.2K(2024.12 月時点)
Infrastructure-as-Code repos: Pluto can check both static manifests and Helm charts for deprecated apiVersions Live Helm releases: Pluto can check both Helm 2 and Helm 3 releases running in your cluster for deprecated apiVersions
Pluto は、YAML 静的マニフェストや Helm チャート、クラスタ内で動作しているアプリケーション内の Deprecated または Removal API を検知することができる OSS です。
ローカル検証
Pluto を使用することで、Kubernetes マニフェストの apiVersin に対して Deprecated(非推奨)と Removed(削除)を検出できます。 また、Pluto は CLI を用いてローカル上でマニフェストを精査したり、CI に組み込むことで、パイプライン上で検出したりすることが可能です。
今回の検証は、Kubernetes v1.29 を想定しています。
出力結果
フィールド | 概要 |
---|---|
VERSION | 現在使用されている API のバージョン |
REPLACEMENT | 移行先となる新しい API バージョン |
DEPRECATED | リソースの API バージョンが非推奨であるか |
DEPRECATED IN | リソースの API バージョンが非推奨となった Kubernetes のバージョン |
REMOVED | リソースの API バージョンが将来的に削除されるか |
REMOVED IN | リソースの API バージョンが削除されるとされる Kubernetes のバージョン |
REPL AVAIL | 移行先の API バージョンが現在利用可能であるか |
REPL AVAIL IN | 移行先の API バージョンが利用可能になった Kubernetes のバージョン |
- 例:
$ pluto detect-files -o wide
NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN REPL AVAIL REPL AVAIL IN
grpc-alb examples Ingress extensions/v1beta1 networking.k8s.io/v1 true v1.14.0 true v1.22.0 true v1.19.0
grpc-alb examples PodDisruptionBudget policy/v1beta1 policy/v1 true v1.21.0 true v1.25.0 true v1.21.0
Ingress の例では、examples
というネームスペースにある grpc-alb という名前の Ingress リソースが extensions/v1beta1
バージョンを使用していることを示します。 このバージョンは Kubernetes v1.14.0 で非推奨となり、v1.22.0 で削除される予定とのことです。(つまり削除済み)
移行先のバージョンとして networking.k8s.io/v1
があり、Kubernetes v1.19.0 以降で利用可能であることが示されています。
実行結果は、Pluto のリポジトリにて宣言的に管理されている versions.yaml
に基づいています。参考
オプション
A tool to detect Kubernetes apiVersions
Usage:
pluto [flags]
pluto [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
detect Checks a single file or stdin for deprecated apiVersions.
detect-all-in-cluster run all in-cluster detections
detect-api-resources detect-api-resources
detect-files detect-files
detect-helm detect-helm
help Help about any command
list-versions Outputs a JSON object of the versions that Pluto knows about.
version Prints the current version of the tool.
Flags:
-f, --additional-versions string Additional deprecated versions file to add to the list. Cannot contain any existing versions
--columns strings A list of columns to print. Mandatory when using --output custom, optional with --output markdown
--components strings A list of components to run checks for. If nil, will check for all found in versions.
-h, --help help for pluto
--ignore-deprecations Ignore the default behavior to exit 2 if deprecated apiVersions are found.
--ignore-removals Ignore the default behavior to exit 3 if removed apiVersions are found.
--ignore-unavailable-replacements Ignore the default behavior to exit 4 if deprecated but unavailable apiVersions are found.
--no-footer Disable footer output
-H, --no-headers When using the default or custom-column output format, don't print headers (default print headers).
-r, --only-show-removed Only display the apiVersions that have been removed in the target version.
-o, --output string The output format to use. (normal|wide|custom|json|yaml|markdown|csv) (default "normal")
-t, --target-versions stringToString A map of targetVersions to use. This flag supersedes all defaults in version files. (default [])
-v, --v Level number for the log level verbosity
Use "pluto [command] --help" for more information about a command.
オプション | コマンド例 | 概要 |
---|---|---|
--target-versions | $ pluto detect-files -o wide --target-versions k8s=v1.29.0 | 検出対象にする Kubernetes バージョンを変更できる |
--output markdown - | $ pluto detect-files -o wide --output {wide|custom|json|yaml|markdown|csv} - | 出力形式を変更する |
コマンド
- ディレクトリ内の静的マニフェストを検証
$ pluto detect-files -d . -o wide --target-versions k8s=v1.29.0 2>/dev/null
- クラスタにデプロイされている Helm チャートを検証
$ pluto detect-helm -o wide --target-versions k8s=v1.29.0 2>/dev/null
- 静的 Helm チャートを検証
$ kustomize build . --enable-helm | pluto detect --target-versions version=v1.29.0 - 2>/dev/null
- クラスタにデプロイされているマニフェスト(API)を検証
$ pluto detect-api-resources -o wide --target-versions k8s=v1.29.0 2>/dev/null
- クラスタにデプロイされている全てのリソースを検証
※ このコマンドを使用するとクラスタにデプロイされた非推奨 API を網羅的に検出できます。
$ pluto detect-all-in-cluster -o wide --target-versions k8s=v1.29.0 2>/dev/null
CI ワークフロー
CI ワークフローを定義することで、Pluto をデプロイパイプライン上に組み込むことができます。 以下は、検証のためミニマムで書いていますが、多様な GitHub Actions との統合が可能です。
name: pluto
on:
push:
branches:
- test/pluto
pull_request:
branches:
- main
jobs:
pluto:
name: pluto
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Download Pluto
uses: FairwindsOps/pluto/github-action@master
- name: Run Pluto
id: pluto
run: |
echo 'content<<EOF' >> $GITHUB_ENV
pluto detect-files --target-versions k8s=v1.29.0 -o json | jq >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
# exit status を元に後続の step を実行
pluto detect-files
- uses: mshick/add-pr-comment@v1
if: failure()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
message: |
## Pluto detect deprecation/removal
Fix from the documents below.
[Kubernetes | Deprecated API Migration Guide](https://kubernetes.io/docs/reference/using-api/deprecation-guide/)
```json
${{ env.content }}
```
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]'
allow-repeats: false
非推奨 API を含むマニフェストで PR が作成されると、Pluto によって検出されることが分かります。
Actions のステータスは Failed となっていますが、これは Pluto の実行ステータスによって、適宜ハンドリングすることが可能です。
今回は、出力結果にシンプルな JSON を用いていますが、他の形式 [normal
, wide
, custom
, json
, yaml
, markdown
, csv
] を利用することも可能です。
これらの機能により、非推奨 API の検出から対応方針の明記、Slack 等による開発者への周知まで統合的に行うことができます。
まとめ
今回のブログでは Kubernetes が非推奨 API を検出する仕組みについて、kube-apiserver の実装を見ながら確認しました。 Kubernetes はリリースサイクルが非常に短く、破壊的な変更が含まれていることもあります。 API バージョンは、成熟度に応じて段階的にアップグレードされるため、自分のサービスで非推奨バージョンを利用していないかどうかを常に把握しておくことが重要です。
とは言え、運用規模が拡大するにつれ、非推奨 API がどのリソースで使用されているのかを追従するのは困難になります。 Pluto を使用すると、継続的に非推奨 API を検出したり、CI ワークフローに組み込むことで、クラスタに適用される前にブロックしたりすることができます。