Kubernetes マニフェストの差分や静的解析の結果を PR 上でいい感じに可視化したい
これは GLOBIS Advent Calendar 2023 2日目の記事です。
はじめに
弊社ではアプリケーションの実行基盤として AWS の EKS を利用しています。そのため、EKS 上で動くリソースはすべて Kubernetes マニフェスト(YAML)を通して管理されます。
プロダクション運用ではコンテナに関連する様々な設定を記述する必要があるため、それらを愚直に YAML で表現するとかなりの分量になってしまいます。
大体は Kustomize や Helm といったツールを用いて設定を共通化し、効率的に管理することになるのですが、運用を進めていくと扱いに困る場面が出てきました。
- ある設定を変更したとき、Kustomize や Helm を使って展開しないと本当に変更が適用されるかわからない
- (Kustomize や Helm に限らない話ではあるが)書いたマニフェストが組織のポリシーやセキュリティの基準を満たしているか判断するのが難しい
当初は主に SRE チームのメンバーがマニフェストを管理していて、上記の観点について人力でレビューする運用でも何とかなっていました。
しかし、最近は開発チームへの権限委譲がある程度進んでいるため、Kubernetes に精通していない人でもレビューしやすい、かつレビューされやすい環境が必要になってきました。
そこで、今回 GitHub Actions 上で差分検知や静的解析を自動化することで、一定の品質を保証する仕組みを導入しました。
自動化したいこと
まずは GitHub Actions で自動化したいことを整理します。
- Kustomize や Helm で書かれたマニフェストを展開する
- 展開後のマニフェストの差分を検知する
- マニフェストの静的解析(スキーマ、セキュリティ、推奨設定など)を実行する
- 各リソース(Deployment など)の API バージョンをチェックする
- 上記の結果を何かしらの手段で通知する
準備(各種ツールのインストール)
1つずつインストールしていくのは大変なので、asdf を GitHub Actions で利用します。これだけの記述で、後続のステップでパスが通った状態になるので非常に便利です。
steps:
- uses: asdf-vm/actions/install@v2 # 実際は Renovate で SHA に固定
with:
tool_versions: |
kustomize 5.0.1 # Argo CD v2.7.0
kube-score 1.16.1
kubeconform 0.6.1
pluto 5.16.1
dyff 1.5.8
余談ですが、サードパーティのアクションを利用するときは、セキュリティの観点からタグやブランチではなく SHA で固定するべきです。
Renovate を使えば SHA に固定したまま更新してくれるのでオススメです。
マニフェストを展開する
こちらは特筆することはなく、Kustomize であれば kustomize build
、Helm であれば helm template
を使って展開するだけです。弊社ではアプリケーションのマニフェストには Kustomize を使っているのでその前提で進めます。
GitHub Actions 上で実行することを考えると、ログを出したりといった取り回しがしやすいようにシェルスクリプトに切り出して実行する形にしました。
- uses: actions/checkout@v4 # 実際は Renovate で SHA に固定
- uses: actions/checkout@v4
with:
path: main
ref: main
- id: kustomize
run: kustomize.sh
shell: bash
env:
BASE_DIR: ./main/overlays/dev
HEAD_DIR: ./overlays/dev
差分を検知する
そのまま展開したファイル同士の差分を取っても良いのですが、せっかくなので何かいい感じにしたいです。そこで Kubernetes に対応した差分を出せる dyff というツールを利用します。
このように Kubernetes のリソースごとに差分をまとめてくれるので視認性が上がります。
また、Kustomize を使っているとある箇所で変更を加えても実は他の patch で書き換えられているので無意味だった、というようなことは普通にあるので、展開後の差分を取ることは重要です。
静的解析を実行する
kubeconform でスキーマのバリデーション、kube-score でセキュリティと推奨設定の検査、pluto で API バージョンの検査を行います。
各ツールの解説はこちらの記事が詳しいので割愛します。
GitHub Actions で実行する際には、途中でエラーが帰ってきても終了させず、結果の出力まですべて完了させてから exit 1
する、といった工夫をしています。こうすることで、どの静的解析が成功 or 失敗したかをすべて確認することができるようになります。
if [[ $rc1 -ne 0 || $rc2 -ne 0 || $rc3 -ne 0 ]]; then
echo "kubeconform, kube-score or pluto failed."
exit 1
fi
結果を可視化する
これまでの差分や静的解析の結果は可視化のためにフォーマットしていて、tee コマンドで標準出力(GitHub Actions の実行画面で確認する用)とログファイル(可視化する用)に出力しています。
最初はこのログファイルをそのまま PR のコメントとして通知する形を考えましたが、1コメントあたりの文字数制限があることやコミット毎にコメントが増えていくことに対応しなければならないため、まずはコメントではなく Job summary を使うことにしました。
Job summary は github-script アクションを使って簡単に追加できるのでオススメです。
- uses: actions/github-script@v6 # 実際は Renovate で SHA に固定
with:
script: |
const { promises: fs } = require('fs');
const dyff = await fs.readFile('${{ steps.dyff.outputs.file }}', 'utf8');
const lint = await fs.readFile('${{ steps.lint.outputs.file }}', 'utf8');
await core.summary.addRaw(dyff).addEOL().addRaw(lint).write();
if: always()
PR のページではなく GitHub Actions の Job ページに飛ぶ必要はありますが、このように可視化することができました。
まとめ
社内にはマニフェスト管理のリポジトリが複数あるため、上記の操作を独自のアクションに切り出すことで、最終的なワークフローはこのようにシンプルな形にできました。
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # 実際は Renovate で SHA に固定
- uses: actions/checkout@v4
with:
path: main
ref: main
- name: Lint manifests
uses: globis-org/sre-actions/manifest-analyzer@v1
with:
base-dir: main/overlays/dev
head-dir: overlays/dev
k8s-version: v1.24.0
また、独自アクションで実行している各検査のシェルスクリプトはこちらにまとめているので、気になる方はご覧ください。
最後に
静的解析に関してはまだまだ道半ばで、Argo Rollouts への対応や独自のポリシーを追加することがまだできていません。可視化に関してもやはり PR のページで結果がわかる方が楽なので、今後も継続的に改良していく必要があると考えています。
グロービスの開発組織では、こういった課題に一緒に取り組んでくれる仲間を募集しています。
興味がある方はぜひご連絡ください!
Discussion