conftest: 各 Deployment に対応する PodDisruptionBudget が存在するか

6 min read読了の目安(約3900字

open-policy-agent/conftest を使って
「任意の Deployment には、それに対応する PodDisruptionBudget(=PDB) が存在しなければならない」 というポリシーを記述する。

バージョン

$ conftest --version
Version: 0.20.0
Commit: 8415206
Date: 2020-07-16T13:35:34Z

方針

conftest は Rego コード内の deny/violation/warn という3つの変数を調べて実行結果を決定する。そのため、ユーザーが記述する rule は最終的にすべてその3つの変数の値に帰着させることになる。

「Deployment と PDB が対応している」という性質をどう表現するかだが、これは spec.selector が同じかどうかで判定することにする。

今回は、以下の3つの rule を作る。

  • 「Deploymentに対応するPDBの数」と「Deploymentの数」を比較して、前者のほうが少なければ violation に要素を追加
  • Deploymentに対応するPDBリスト
  • Deploymentリスト

また今回は複数ドキュメントを一度に取り扱うのでconftestには--combineオプションを渡す。

自分の場合はこんな感じの起動になる。conftestは起動時の設定によってinputの構造が変わってくるので注意。

$ kustomize app/overlays/production | conftest test -p validation.rego --combine  -

作例

こんな感じになった。

package main

violation[msg] {
    count(pdb) < count(deployment)
    msg := sprintf("the number of PodDisruptionBudget is smaller than the number of Deployment. count(pdb)=%d, count(deployment)=%d", [count(pdb), count(deployment)])
}

pdb[pdb.metadata.name] {
    resource := input[_]
    resource[i].kind == "Deployment"
    deploy := resource[i]
    resource[j].kind == "PodDisruptionBudget"
    pdb := resource[j]
    deploy.spec.selector == pdb.spec.selector
}

deployment[name] {
    resource := input[_]
    resource[i].kind == "Deployment"
    name := resource[i].metadata.name
}

ちなみにテストコードはこんな感じ。

test_pdb_is_matched_to_deploy {
    input := [[
        {
            "kind": "Deployment",
            "metadata": {
                "name": "foo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "foo": "bar"
                    }
                }
            }
        },
        {
            "kind": "PodDisruptionBudget",
            "metadata": {
                "name": "foo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "foo": "bar"
                    }
                }
            }
        }
    ]]
    count(violation) == 0 with input as input
}

test_pdb_is_not_matched_to_deploy {
    input := [[
        {
            "kind": "Deployment",
            "metadata": {
                "name": "foo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "foo": "bar"
                    }
                }
            }
        },
        {
            "kind": "PodDisruptionBudget",
            "metadata": {
                "name": "foo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "foo": "aaa"
                    }
                }
            }
        }
    ]]
    violation with input as input
}

test_pdb_is_smaller_than_deploy {
    input := [[
        {
            "kind": "Deployment",
            "metadata": {
                "name": "foo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "foo": "bar"
                    }
                }
            }
        },
        {
            "kind": "Deployment",
            "metadata": {
                "name": "fooo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "hoge": "fuga"
                    }
                }
            }
        },
        {
            "kind": "PodDisruptionBudget",
            "metadata": {
                "name": "foo"
            },
            "spec": {
                "selector": {
                    "matchLabels": {
                        "foo": "bar"
                    }
                }
            }
        }
    ]]
    violation with input as input
}