Kubernetes のバージョンアップに Conftest を使用した
本記事は ZOZOテクノロジーズ #3 Advent Calendar 2020 21日目の記事です。
- ZOZOテクノロジーズ #1 Advent Calendar 2020
- ZOZOテクノロジーズ #2 Advent Calendar 2020
- ZOZOテクノロジーズ #3 Advent Calendar 2020
- ZOZOテクノロジーズ #4 Advent Calendar 2020
現在、私が所属しているチームでは Kubernetes のマネージドサービスに Amazon EKS を利用しています。
EKS は過去4世代の Kubernetes のマイナーバージョンをサポートしていますが、約3ヶ月おきに新しいマイナーバージョンがリリースされるので、最低でも1年に1度バージョンアップ作業を行うことになります。
Amazon EKS Kubernetes バージョン - Amazon EKS
その際に必要なのが、 Kubernetes のマニフェストファイル内から非推奨または廃止となる API を探し修正する作業です。
最近行った弊社のとあるプロダクトの EKS バージョンアップでは、その作業に Conftest を使用しました。
Conftestとは
Open Policy Agent の Rego という言語で書かれたポリシーを基に Kubernetes に限らず様々なツールの設定ファイルが、そのポリシーに沿っているかテストを行うためのツールです。
ポリシーは Amazon S3 などへpush、pullして異なるプロジェクト同士で利用可能です。
現在 Conftest は入力として下記のファイルをサポートしています。
- YAML
- JSON
- INI
- TOML
- HOCON
- HCL
- HCL 2
- CUE
- Dockerfile
- EDN
- VCL
- XML
- Jsonnet
また、出力として下記のタイプをサポートしています。
- Plaintext
- JSON
- TAP
- Table
- JUnit
Open Policy Agent(OPA)とは
汎用的なポリシーエンジンで CNCF のプロジェクトの一つです。
API を介してアプリケーションから送られてきたクエリ(JSONなどの構造化データ)とポリシーを評価して結果を返します。これにより各アプリケーションにポリシーの管理または判定処理を実装する手間を省くことができます。
また、ポリシーを定義するための Rego という言語を用いて、 Policy as Code が実現できます。
類似ツール
Conftest を導入する上で類似ツールの調査も行いました。
-
kubeval
- 導入にいたらなかった理由: Kubernetes v1.14.0 までしか対応していなかった。
- garethr/kubernetes-json-schema
-
kubetest
- 導入にいたらなかった理由: レポジトリがアーカイブ済み。 Conftest に移行したとのこと。
-
kube-lint
- 導入にいたらなかった理由: Pod の lint にしか対応していなかった。
- https://github.com/viglesiasce/kube-lint#introduction
-
open-policy-agent/gatekeeper
- 導入にいたらなかった理由: 自分の調べた範囲ではどうやらクラスタに apply する段階でチェックが行われるようだった。今回はそれ以前のタイミングでチェックしたかった。
-
copper
- 導入にいたらなかった理由: Copper DSL という独自のDSLを書く必要があり作業コストがかかりそうだと判断した。
ポリシーファイル
少々古くて申し訳ないのですが、下記に v1.15 以前のマニフェストファイルに対して v1.16 での非推奨 API を調査したときのポリシーを掲載します。
こちらの記事 で引用されていた ポリシー を利用させていただいたのですが、 Kubernetes オフィシャルの情報 と改めて過不足がないか確認した後にメッセージを日本語に訳しました。
このファイルを <任意の名前>.rego
としてプロジェクトディレクトなどに保存します。
package main
deny[msg] {
input.apiVersion == "v1"
input.kind == "List"
obj := input.items[_]
msg := _deny with input as obj
}
deny[msg] {
input.apiVersion != "v1"
input.kind != "List"
msg := _deny
}
warn[msg] {
input.apiVersion == "v1"
input.kind == "List"
obj := input.items[_]
msg := _warn with input as obj
}
warn[msg] {
input.apiVersion != "v1"
input.kind != "List"
msg := _warn
}
# Based on https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.16.md
# All resources under apps/v1beta1 and apps/v1beta2 - use apps/v1 instead
_deny = msg {
apis := ["apps/v1beta1", "apps/v1beta2"]
input.apiVersion == apis[_]
msg := sprintf("%s/%s: API %s はデフォルトで提供されなくなりました。代わりに apps/v1 を使用してください。", [input.kind, input.metadata.name, input.apiVersion])
}
# daemonsets, deployments, replicasets resources under extensions/v1beta1 - use apps/v1 instead
_deny = msg {
resources := ["DaemonSet", "Deployment", "ReplicaSet"]
input.apiVersion == "extensions/v1beta1"
input.kind == resources[_]
msg := sprintf("%s/%s: %s のAPI extensions/v1beta1 はデフォルトで提供されなくなりました。代わりに apps/v1 を使用してください。", [input.kind, input.metadata.name, input.kind])
}
# networkpolicies resources under extensions/v1beta1 - use networking.k8s.io/v1 instead
_deny = msg {
input.apiVersion == "extensions/v1beta1"
input.kind == "NetworkPolicy"
msg := sprintf("%s/%s: NetworkPolicyのAPI extensions/v1beta1 はデフォルトで提供されなくなりました。代わりに networking.k8s.io/v1 を使用してください。", [input.kind, input.metadata.name])
}
# podsecuritypolicies resources under extensionsDeployment /v1beta1 - use policy/v1beta1 instead
_deny = msg {
input.apiVersion == "extensions/v1beta1"
input.kind == "PodSecurityPolicy"
msg := sprintf("%s/%s: PodSecurityPolicyのAPI extensions/v1beta1 はデフォルトで提供されなくなりました。代わりに policy/v1beta1 を使用してください。", [input.kind, input.metadata.name])
}
# Ingress resources will no longer be served from extensions/v1beta1 in v1.20. Migrate use to the networking.k8s.io/v1beta1 API, available since v1.14.
_warn = msg {
input.apiVersion == "extensions/v1beta1"
input.kind == "Ingress"
msg := sprintf("%s/%s: Ingress用のAPI extensions/v1beta1 は非推奨です。代わりに networking.k8s.io/v1beta1 を使用してください。", [input.kind, input.metadata.name])
}
# PriorityClass resources will no longer be served from scheduling.k8s.io/v1beta1 and scheduling.k8s.io/v1alpha1 in v1.17.
_warn = msg {
apis := ["scheduling.k8s.io/v1beta1", "scheduling.k8s.io/v1alpha1"]
input.apiVersion == apis[_]
input.kind == "PriorityClass"
msg := sprintf("%s/%s: PriorityClassのAPI %s は非推奨です。代わりに scheduling.k8s.io/v1 を使用してください。", [input.kind, input.metadata.name, input.apiVersion])
}
# The apiextensions.k8s.io/v1beta1 version of CustomResourceDefinition is deprecated and will no longer be served in v1.19. Use apiextensions.k8s.io/v1 instead.
_warn = msg {
input.apiVersion == "apiextensions.k8s.io/v1beta1"
input.kind == "CustomResourceDefinition"
msg := sprintf("%s/%s: CustomResourceDefinitionのAPI apiextensions.k8s.io/v1beta1 は非推奨です。代わりに apiextensions.k8s.io/v1 を使用してください。", [input.kind, input.metadata.name])
}
# The admissionregistration.k8s.io/v1beta1 versions of MutatingWebhookConfiguration and ValidatingWebhookConfiguration are deprecated and will no longer be served in v1.19. Use admissionregistration.k8s.io/v1 instead.
_warn = msg {
kinds := ["MutatingWebhookConfiguration", "ValidatingWebhookConfiguration"]
input.apiVersion == "admissionregistration.k8s.io/v1beta1"
input.kind == kinds[_]
msg := sprintf("%s/%s: %s のAPI admissionregistration.k8s.io/v1beta1 は非推奨です。代わりに admissionregistration.k8s.io/v1 を使用してください。", [input.kind, input.metadata.name, input.kind])
}
# Based on https://github.com/jetstack/cert-manager/releases/tag/v0.11.0
_deny = msg {
kinds := ["Certificate", "Issuer", "ClusterIssuer", "CertificateRequest"]
input.apiVersion == "certmanager.k8s.io/v1alpha1"
input.kind == kinds[_]
msg := sprintf("%s/%s: %s のAPI certmanager.k8s.io/v1alpha1 は廃止されました。代わりに cert-manager.io/v1alpha2 を使用してください。", [input.kind, input.metadata.name, input.kind])
}
_deny = msg {
kinds := ["Order", "Challenge"]
input.apiVersion == "certmanager.k8s.io/v1alpha1"
input.kind == kinds[_]
msg := sprintf("%s/%s: %s のAPI certmanager.k8s.io/v1alpha1 は廃止されました。代わりに acme.cert-manager.io/v1alpha2 を使用してください。", [input.kind, input.metadata.name, input.kind])
}
Conftest のインストールと実行
以下のコマンドで Conftest を各環境にインストールします。
Linux
$ wget https://github.com/open-policy-agent/conftest/releases/download/v0.21.0/conftest_0.21.0_Linux_x86_64.tar.gz
$ tar xzf conftest_0.21.0_Linux_x86_64.tar.gz
$ sudo mv conftest /usr/local/bin
Homebrew
brew tap instrumenta/instrumenta
brew install conftest
Scoop
scoop bucket add instrumenta https://github.com/instrumenta/scoop-instrumenta
scoop install conftest
Docker
$ docker run --rm -v $(pwd):/project openpolicyagent/conftest test deployment.yaml
実行
基本の使い方としては、先ほど用意したポリシーファイルとテスト対象のファイルを引数に指定してコマンドを実行します。
conftest test --policy <ポリシーファイル> <テスト対象のマニフェストファイル>
API に非推奨のものがある場合は次のように出力されます。
conftest test --policy <ポリシーファイル> <テスト対象のマニフェストファイル>
WARN - <ファイル名> - CustomResourceDefinition/<リソース名>: CustomResourceDefinitionのAPI apiextensions.k8s.io/v1beta1 は非推奨です。代わりに apiextensions.k8s.io/v1 を使用してください。
62 tests, 61 passed, 1 warnings, 0 failures
Kustomize 環境の場合
Kustomize を使用している場合は一度マニフェストファイルをビルドしてから Conftest を実行する必要があります。
kubectl kustomize <kustomization.yaml> | conftest test --policy <ポリシーファイル> -
これだと複数ファイルをチェックするのが大変なので、例えば、 /kustomize/overlays
下にエントリーポイントとなる kustomization.yaml があったとして、 /policy
下にポリシーファイルを設置し次のようなシェルスクリプトを適当な名前(今回は manifests_check.sh
)を付けて作成します。
#!/usr/bin/env bash
CURRENT_DIR=$(cd $(dirname $0); pwd)
if [ $# = 0 ]; then
printf "ERROR: kustomization.yaml までのファイルパスもしくは -a を引数にしてください\n"
exit 1
fi
if [ $1 = "-a" ]; then
for CLUSTER_DIR in `find ${CURRENT_DIR}/kustomize/overlays -name kustomization.yaml`; do
printf ${CLUSTER_DIR} | awk -F "/" '{print "NAME: " $(NF-2)"/"$(NF-1)"\n"}'
kubectl kustomize $(dirname ${CLUSTER_DIR}) | conftest test --policy ${CURRENT_DIR}/policy -
printf '%s\n' '--------------------------------------------------'
done
else
printf $1 | awk -F "/" '{print "NAME: " $(NF-2)"/"$(NF-1)"\n"}'
kubectl kustomize $(dirname $1) | conftest test --policy ${CURRENT_DIR}/policy -
fi
以下のコマンドでテストできるようになります。
マニフェストファイルを1つだけテストする場合
./manifests_check.sh <エントリーポイントとなるkustomization.yaml>
マニフェストファイルを全てテストする場合
./manifests_check.sh -a
感想
今回は非推奨または廃止となる API のチェックのため Conftest を導入しました。
この場合の懸念点はEKSのバージョンアップに合わせたポリシーファイルのメンテナンスコストだと思います。他のプロジェクトと共有することで、それがどれくらい圧縮できるのか確認が必要そうです。
しかし導入自体は非常に簡単なので、今後は API チェック以外のマニフェストファイルのバリデーションにも Conftest を利用し CI/CD にも組み込んで、より安全なデプロイフローの構築に役立ていこうと思います。
Discussion