はじめに
今回はCustom Resourceに対してのValidationを何とかできないかと考え、GitHub Actions + k3sを組み合わせてみた結果を残しておきます。
経緯
Kubernetesのマニフェストファイルに対するValidation
Kubernetesにリソースを作成する場合、マニフェストファイルに各リソースを定義し、kubectl
コマンド等でデプロイを行ったり、Argo CDやFluxを利用してGitOpsによる自動デプロイを行うことが多いかと思います。
Kubernetesは各リソースをYAML/JSON形式のマニフェストファイルで定義するため、ファイルの書き方(構文)に問題がある場合は、リソースの作成に失敗します。そのため、作成したマニフェストファイルをデプロイする前に、何かしらの方法でファイルの構文をチェックする必要があります。
またプロジェクトによっては、特定のセキュリティポリシーへの準拠やラベルの付与などをルールとして定めており、それに従っているかをチェックする必要もあります。
これらを全て人の目でチェックするのは、大規模環境はもちろん、中・小規模の環境でも相当な労力となってしまいます。そのため、マニフェストファイルに対するValidationを行う各種ツールが開発されています。
kubeval: Kubernetes YAML/JSONファイルのValidationを行う。Kubernetes OpenAPIを利用して生成されるスキーマを利用するため、Kubernetesの複数のバージョンをまたいで利用できる。
kube-score: Kubernetesのマニフェストファイルに対する静的コード解析を行うツール。信頼性・セキュリティを中心とした項目に対しての解析を行う。
conftest: 構造化された設定ファイルに対してのテストを利用者が定義し、ポリシーを満たすかをテストするツール。Kubernetesのマニフェストファイルだけでなく、TerraformやTekton Pipelineなどの定義ファイルに対しても利用できる。
これらのツールを利用することで、クラスターへのデプロイを行う前に、マニフェストファイルの構文やポリシーのチェックを行うことができます。
Custom Resourceに対するValidation
一方で、KubernetesはCustom Resource (CR)を利用することでKubernetes APIを拡張し、独自のリソースをKubernetes上に展開することができます。Kubernetesはアプリケーションやプラットフォームを実現するための基盤であり、運用の自動化・効率化やユーザーへ提供する機能を実現するため、どのような形でKubernetesを利用するかに関わらず、Custom Resourceを利用するケースはかなり多いと思われます。
Custom Resourceを利用する場合もマニフェストファイル上に定義をするため、こちらも構文やポリシーをチェックする必要が出てきます。
※参考:
ここで、Custom Resourceに対するValidationをするうえで、それに対応したツールというのが現状あまり見当たらない、という課題が出てきました。正確にはツールが全くない、というわけではなく、例えば前述のconftestを利用することで、各OSSのCustom Resourceに対するポリシーを定義すれば、テストすることは可能です。しかし、これにはいくつかの課題があると考えました。
1. プロダクトごとに異なるポリシーを用意しなければならない: あるプロダクトのために用意したポリシーを、別のプロダクトに使いまわすことは難しい。
2. プロダクトのアップデートに合わせてメンテナンスが必要になる: プロダクトのバージョンアップにより、マニフェストファイルのスキーマが変更された場合、利用するプロダクトのバージョンに追従するだけでなく、テストのほうも修正をする必要がある。
このため、すべてのテストをconftestのようなツールで行う場合、ポリシーの実装とメンテナンスのコストがかなり高くなるのではないか、と考えました。
そこで、Custom Resourceに対するValidationを、より簡単に、より汎用的に実現できないかと考え、表題のようにGitHub Actions + k3sでのテストを試してみました。
Custom Resourceを利用するにはCustom Resource Definition
を事前に用意する必要があります。Custom Resource Definition
にはOpenAPI schemaを利用したValidation機能が備わっており、Custom Resourceデプロイ時に問題があればエラーを返します。今回はこの機能を利用し、k3sへテスト対象のリソースを実際にデプロイすることで、Validationを行いました。
※下記画像はこちらの記事中の画像を一部編集したもの
※参考:
Kubernetes 公式ドキュメント - Extend the Kubernetes API with CustomResourceDefinitions #Validation
Kubernetes 公式ブログ - A Guide to Kubernetes Admission Controllers
GitHub Actions + k3sの実行方法
今回試したのは以下のような構成です。なお、マニフェストファイルはGitHubリポジトリ上で管理することを前提としております。
まずGitHubリポジトリへマニフェストファイルをPushすることでGitHub Actionsが起動します。GitHub Actionsでは最初にk3sをワークフロー用インスタンス上にインストールし、そこへ対象のOSSをインストールします。最後に、テスト対象のマニフェストファイルをkubectl
コマンドでデプロイし、エラーが発生しないかを確認します。
今回は検証するOSSとして、Argo CDとRookを選択しました。2つのOSSを利用する理由としては、複数のOSSのCustom Resourceを対象にすることで、この方法が共通利用できるかを確認したかったためです。
リポジトリ中のフォルダ構成は以下のようにしています。
/testrepo | |-- .github | | | `-- workflows | | | |-- argocd-validate.yaml | `-- rook-validate.yaml | |-- app | | | `-- sample-app-argocd.yaml | `-- storage | `-- sample-block-storage.yaml
各Workflowの定義ファイルは以下の通りです。今回は各OSS毎に個別にWorkflowファイルを用意し、GitHub Actionsで並列に実行するようにしました。
各OSSのインストール中、必要に応じてsleep
コマンドを行っていますが、それぞれの処理毎に必要な待機時間については測定していないため、適宜変更する必要があります。
また、k3sの利用に際し、こちらのActionを利用しました。
argocd-validate.yaml
name: ArgoCD on: push: branches: [ main ] workflow_dispatch: jobs: argocd-validation: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: k3s-install uses: debianmaster/actions-k3s@master id: k3s with: version: 'v1.17.4-k3s1' - name: k3s-install-check run: | kubectl get nodes - name: argocd-install run: | kubectl create namespace argocd sleep 3 kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml - name: argocd-app-deploy-test run: | kubectl apply -f ./app/
rook-validate.yaml
name: Rook on: push: branches: [ main ] workflow_dispatch: jobs: rook-validation: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: k3s-install uses: debianmaster/actions-k3s@master id: k3s with: version: 'v1.17.4-k3s1' - name: k3s-install-check run: | kubectl get nodes - name: rook-install run: | git clone https://github.com/rook/rook.git sleep 5 kubectl create -f rook/cluster/examples/kubernetes/ceph/crds.yaml sleep 5 kubectl create -f rook/cluster/examples/kubernetes/ceph/common.yaml sleep 5 kubectl create -f rook/cluster/examples/kubernetes/ceph/operator.yaml sleep 60 kubectl create -f rook/cluster/examples/kubernetes/ceph/cluster-test.yaml - name: rook-storage-deploy-test run: | kubectl apply -f ./storage/
今回テスト対象としたのは、以下の2ファイルです。
sample-app-argocd-yaml
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: sampleapp namespace: argocd spec: project: default source: repoURL: https://github.com/argoproj/argocd-example-apps.git targetRevision: HEAD path: guestbook destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: false selfHeal: false
sample-block-storage.yaml
apiVersion: ceph.rook.io/v1 kind: CephBlockPool metadata: name: replicapool namespace: rook-ceph spec: failureDomain: host replicated: size: 1 requireSafeReplicaSize: false --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: rook-ceph-block provisioner: rook-ceph.rbd.csi.ceph.com parameters: clusterID: rook-ceph pool: replicapool imageFormat: "2" imageFeature: layering csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph csi.storage.k8s.io/fstype: ext4 allowVolumeExpansion: true reclaimPolicy: Delete
上記ファイルを用意できたら、対象のGitHubリポジトリへPushし、GitHub Actionsを起動します。
Validation成功時
まずは定義ファイルに問題がない場合です。デプロイに成功した場合は、以下の画像のように、エラーも発生せず正常終了します(一部ワークフロー中のコマンドやファイル名が異なりますが、結果には影響ありません)。
Validation失敗時
次に定義ファイルに問題がある場合です。今回は以下のように、必須項目の部分を一部修正し、エラーを発生させるようにしました。
sample-app-argocd-yaml
(中略) spec: project: default source: repoURL: https://github.com/argoproj/argocd-example-apps.git targetRevision: HEAD path: guestbook destinations: # “spec.destination”は必須項目のためエラーとなる server: https://kubernetes.default.svc (中略)
sample-storage-block.yaml
apiVersion: ceph.rook.io/v1 kind: CephBLockPool # スペルミス metadata: (中略)
これらをリポジトリへPushした結果は以下の通りです。それぞれエラーが発生し、Jobが失敗している様子が確認できます。
ここまでで、ひとまず簡単なValidationを行えることは確認できました。
追加検証
ここから、個人的に気になっていたポイントについて、もう少し試してみました。
同時に複数ファイルをテストするとどうなるか
Custom Resourceを1つテストする分には特に問題なく動作しましたが、複数のリソースを同時にデプロイするとどうなるかを見てみました。
今回はArgo CDで利用するApplication
リソースを複数用意し、それらを同時にデプロイしました。
# 100個のコピーを作成し、"metadata.name"を変更 $ for i in `seq 100`; do cp -p app/sample-app-argocd.yaml app/sample-app-argocd-$i.yaml; done $ ll app/ | grep sample-app | wc -l 101 $ for i in `seq 100`; do sed -i -e "s/name: sampleapp/name: sampleapp-$i/g" app/sample-app-argocd-$i.yaml; done
上記操作後、一部Application
リソースのみエラーを起こすよう修正し、リポジトリへPushしました。その結果、以下の画像の通り、エラーを含むリソースが表示され、Jobの結果もエラーとなって終了しました。
余分な項目が追加されている場合は検出できるか
前項では、設定が必須の項目や指定された値以外を設定した場合を見ましたが、余分なデータ、例えば本来設定する必要のないデータが含まれる場合はどうなるか見てみました。
ここでは、以下のようなデータを追加して、リポジトリへPushしてみます。
(中略) spec: project: default source: repoURL: https://github.com/argoproj/argocd-example-apps.git targetRevision: HEAD path: guestbook errorData: true # 追加 destination: server: https://kubernetes.default.svc namespace: argocd errorData: true # 追加 (中略)
(中略) spec: failureDomain: host replicated: size: 1 requireSafeReplicaSize: false errorData: true # 追加 (中略)
すると、エラーは発生せず、リソースの作成が正常に完了してしまいました。
これはkubevalを利用する場合とも共通した問題となりそうですが、意味のない余分なデータの検出は難しいと思われます。
気になること
現時点で気になることは色々とあるのですが、ポジティブな点とネガティブな点について書いておきます。
ポジティブな点
今回GitHub Actions中にk3sへCustom Resourceをデプロイすることで、Custom Resourceを定義するマニフェストファイル中の構文エラーを検出することができました。また、2つのOSSのCustom Resourceに対してチェックを行い、どちらも構文の間違いを検出できたため、Custom Resourceを利用するプロダクトに対しては、ある程度汎用的に使えるのではないか、と思っています。
また、今回は各OSSをk3sへインストールする際に、最新版をデプロイするように指定をしております。これにより、バージョン更新によるマニフェストのスキーマ変更に気づけるのではないか、とも考えています。OSSはバージョン更新の際、マニフェストファイルのスキーマが変更される場合があり、古いバージョンでのマニフェストの書き方と一致しなくなる場合があります。CIパイプライン等に今回の仕組みを導入しておくことで、旧バージョン仕様のCustom Resourceのデプロイが失敗することを契機として、スキーマ変更に気づけるかもしれません。
ネガティブな点
GitHub Actions + k3sで簡単にValidationができそうだと思った一方、いくつかの課題もありそうです。
まずはシンプルに実行結果が見にくいと感じました。テストの成功・失敗はGitHub Actions上のステータスからわかるので良いですが、失敗した場合の理由や内容が少し見にくいと感じました。
また、もっと大きな問題として、一度に全てのエラー箇所を表示してくれない、というものがあります。例えばkind
などの指定が誤っている場合、エラー内容としては以下の画像のように、該当するリソースが見つからない、と表記されます。
しかし、ここでテストを行ったマニフェストファイルを見てみます。マニフェストファイルを見てみると、kind
の他にも複数のエラーが含まれており、これらはこの時点では検出されません。
apiVersion: argoproj.io/v1alpha1 kind: Applications # エラー metadata: name: sampleapp namespace: argocd spec: project: default source: repoURL: https://github.com/argoproj/argocd-example-apps.git targetRevision: HEAD path: guestbook destinations: # エラー server: https://kubernetes.default.svc namespace: argocd syncPolicy: automate: # エラー prune: false selfHeal: false
kind
の箇所を修正して再びPushすると、今度はspec.destination
が見つからない、というエラーメッセージが表示されますが、その下にあるspec.syncPolicy.automate
のエラーは表示されません。
このように、一度に全てのエラーが表示されないため、何度も同じような修正作業を行う必要が出てしまいます。
最後に、これもkubevalでも同様の問題がありそうですが、テスト対象のファイルを含むフォルダの構成は検討したほうがよさそうだとも感じました。テスト対象のマニフェストファイルが複数のフォルダに分散して配置されている場合、GitHub Actionsのワークフロー定義ファイル中で、それらを個別に指定する必要があります。これはテストを用意するうえで手間となりますし、ワークフローの定義ファイルをコピーして別のマニフェストファイルに対するテストを用意するときも面倒です。
そのため、可能であれば、テスト対象のファイルは、まとめて一つのフォルダに格納したほうがよさそうです。