【Conftest】YAMLを目でレビューするのはもうヤメル【Policy as Code】


この記事は、富士通クラウドテクノロジーズ Advent Calendar 202111 日目 です。

10日目は @Tortoiseshell さんの APIを叩いて家電の遠隔操作をしてみる[SwitchBot] でした。ちょうどAmazonのセールでSwitchBot製品を買ったばかりだったので参考になります!

さて、本記事ではお手軽にYAMLの品質を維持するための方法の1つを紹介したいと思います。

はじめに

YAMLのレビューがつらくなっていく

○○を自動化するといった場面でよく登場し活躍するのがYAMLです。ただYAMLによって何かが自動で行われてもYAMLを書くのは結局人間です。YAMLの追加、修正が必要になったとき、それらの品質の維持をするために取る手段としてコードレビューと同様に YAMLをレビューする という手順が発生します。

とある日、私に下記のようなYAMLを追加するレビュー依頼がきました。内容としてはインフラ基盤上にとあるプラグインを導入するための記述です。

spec:
  template:
    metadata:
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "MyApp"

同じようなレビューは過去にしており、修正内容も同様だったのでマージしましたがこれをインフラ上に適用したところプラグインが起動せず問題が発覚しました。

問題点は dapr.io/app-id に大文字が使えないという点でした

修正を適用すれば良いだけなので大きな問題に発展したわけではありません。ただ同様のレビュー依頼が再度来た際に 大文字でないこと を確認するという レビュー観点 がふんわりと増えてしまいました。

こういった確認項目は積み重ねていくとレビューの負荷が上がり、また知見の多い特定のメンバーの最終レビューが必要となったり属人化も進んでいきます。せっかく自動化により属人化を回避したと思ったらレビューが属人化してしまいます。

とりがちな対策

YAMLレビューの観点をレビュー者間で共通化するために取られる対策として下記のようなものがとられます。

  1. プルリクのテンプレートに確認項目のチェックリストを設ける
  2. レビュー観点wikiをまとめる

これによりレビュー観点は共通化できるかもしれないですが、チェックリストを見ながら目でレビューするというのはなかなか厳しいところがあるかと思います。

そこで今回は、上記のようなレビュー観点チェックリストという考え方を Policy as Code の概念によりポリシーを書くことでチェック(テスト)する方法として紹介したいと思います。

Policy as Code

Policy as Codeとはその名の通りポリシーをコードとして記述するという考え方です。IaC(Infrastructure as Code)で有名なhashicorp社のページ1 にも詳しい説明があります。

今回の話でいうと、ポリシーを誰かの知見として持たせておいたり、日本語として曖昧性のある状態で一覧化させたりするのではなく、コード化することによりIaCなどと同じように as Codeすることによるメリットを得ようということになります。

そんなPolicy as Codeが実践できるツール Conftest で問題が解消できるか、見ていきたいと思います。

Conftest

Conftest はYAMLを始めとした構造化データに対してテストをするためのツールです。Rego という宣言型の言語によりポリシーを記述しアサーションを行います。これらはOpen Policy Agent(OPA)という Policy as Code を実践するためのツールに関連するものであり、今回のようなチェックリストをコード化したいというような状況にはぴったりのツールです。

インストール

https://www.conftest.dev/install/ に書いてある方法でインストールします。
Dockerが使えれば下記のようにaliasを張るのが楽かと思います。

> alias conftest='docker run --rm -v $(pwd):/project openpolicyagent/conftest'
> conftest --version
Version: v0.28.3

チェックリスト as Codeする

では はじめに であげていたチェック項目をポリシーとして記述してみたいと思います。

まずはYAMLを再掲します。

yaml/sample.yaml
spec:
  template:
    metadata:
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "MyApp"

このYAMLにおいて

  • dapr.io/app-id に大文字がふくまれないこと

は例えば下記のようなポリシーになります。ポリシーにヒットするものがある(不正である)場合はテストが失敗となります。
(Regoの文法が気になる方はPolicy Language を参照ください)

policy/sample_deny.rego
package main

deny_app_id_uppercase[msg] {
    # dapr.io/app-idのキーが存在する場合はそのバリューをapp_idとして定義
    app_id := input.spec.template.metadata.annotations["dapr.io/app-id"]

    # 小文字にした場合に差分がある => 大文字が含まれる
    app_id != lower(app_id)

    # 失敗理由をmsgとして定義
    msg := sprintf("dapr.io/app-id に大文字は利用不可: %v", [app_id])
}

このポリシーによりテストを流したところ、ポリシーに違反したYAMLであることが出力されました!

> conftest test -p policy/sample_deny.rego yaml/sample.yaml
FAIL - sample.yaml - main - dapr.io/app-id に大文字は利用不可: MyApp

1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions

YAML側で小文字に修正したところ

dapr.io/app-id: "my-app"

無事パスしました

> conftest test yaml

1 test, 1 passed, 0 warnings, 0 failures, 0 exceptions

これらをCIに組み込めばもうレビューの際に大文字かどうかを気にする必要はなくなりますね!
ということで記述したポリシー(コード)をレビュー依頼に出せば無事完了です。(「Regoのレビューがつらい」という声がチームメンバーから聞こえてこないことを祈りながら...)

感想

難かしさ
Regoは宣言型言語ということですが、普段GolangやPythonを書いている私としても、とくに書く上で苦労する印象はありませんでした。Rego自体それほど言語として機能が豊富でもないので、何か1つ新しい言語を覚えるというほどの障壁はなかったように思えます。(実際Conftestのドキュメントを読み始めた当日にプロダクトのCIにリンターとして組み込むことができたくらいには簡単でした)
また、Conftestはあくまでリンターのように扱えるので、導入した結果チームに合わなければリンターから外すだけという点も良いですね。
苦労したのはむしろYAMLの方で、普段YAMLを雰囲気で使っていたためか、この要素は配列なのかハッシュなのかといったあたりが曖昧で、Regoで参照する際の取り出しに苦労しました。

楽しさ
個人的にはConftestを導入するメリットとして楽しさはあると思います。Regoに存在するいくつかの組み込み関数2を利用しながら、設定したいポリシーを(レゴという名前にもあるように)パズルのように組み立てていくのは楽しいですし、そのあたりはやはりプログラミング的な面白さがあるかと思います。エンジニアはコーディングの息抜きにコーディングをするという話も聞きますが、私もまさにそういった形ですきま時間に少しづつポリシーを追加しているところです。

まとめ

「YAMLレビューを楽にしたければむしろRegoのレビューを依頼しよう」という話でした。

実はConftestはKubernetes界隈では割と有名なのですが、Kubernetesを前提知識として持っていなくても利用可能なツールなので、今回あえてKubernetesという文言は出さず記事を書いてみました。

この記事を読んでRegoに興味を持った方がいましたら OPA / Rego Advent Calendar 2021 がとても参考になると思います!


明日は @kzmake さんがDaprのActor機能を使ってみた記事を投稿するようです。
実はこの記事にも登場していたDapr、どのように利用できるものなのでしょうか?お楽しみに!