Gatekeeper入門

5 min read読了の目安(約4500字

イントロ

Kubernetesを利用していると、Deploymentのkindに対しては必ずlabelsに管理しているチームのIDを付与することを強制したい といったリソースへの制約を課したくなることがあると思います。
そういった成約を自前で準備するのはいささか大変です。

GatekeeperCloud Native Computing Foundation傘下のプロジェクトであり、CRDベースでこういった制約を定義してKubernetesに対して適応することができるツールです。

Kubernetesではadmission controller webhooksを利用することでPolicyの決定を内部のAPIサーバーから引き剥がすことができるのですが、
Gatekeeperはそのwebhooksを利用してこの機能を実現しているようです。

Gatekeeperでは設定によりKubernetesのアドミッションコントロールをカスタマイズする方法を提供しています。

ちなみに、OPA with its sidecar kube-mgmtという方法でバリデーションを掛けることもできますが、Gatekeeperだと以下のメリットがあるらしいです。

  • An extensible, parameterized policy library
  • Native Kubernetes CRDs for instantiating the policy library (aka "constraints")
  • Native Kubernetes CRDs for extending the policy library (aka "constraint templates")
  • Audit functionality

定義方法

How to use Gatekeeperによると、まずは ConstraintTemplateを定義する必要があるらしいです。
ConstraintTemplate を定義することでCRDが生成され、その生成されたCRDを使って具体的な制約を定義する流れです。

以下の例では、ConstraintTemplate を定義することで K8sRequiredLabels というCRDが動的に生成されます。
制約の定義はRegoというクエリー言語で定義する必要があります。

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        # ここで定義した名称でCRDが生成される
        kind: K8sRequiredLabels
      validation:
        # K8sRequiredLabelsを利用する際に指定するパラメータ
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      # Regoという言語を使って制約を実装している
      # `.metadata.labels`に parametersで指定された値が存在することを検証している
      rego: |
        package k8srequiredlabels

        violation[{"msg": msg, "details": {"missing_labels": missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("you must provide labels: %v", [missing])
        }

生成されたCRDを使って、以下のようにどのリソースに対してどのような制約を掛けるか定義します。
この場合はすべてのNamespaceリソースに対して .metadata.labelsgatekeeperという値が付与されていることを強制しています。

apiVersion: constrants.gatekeeper.sh/v1beta1
kind: K8srequiredlabels
metadata:
  name: ns-must-have-gk
spec:
  match:
    kinds:
      - apiGroup: [""]
        kinds: ["Namespace"]
  parameters:
    labels: ["gatekeeper"]

制約を掛けたくないリソースがある場合には以下のように Config リソースを定義することで適応から除外できるらしいです。

apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
  namespace: "gatekeeper-system"
spec:
  match:
    - excludedNamespaces: ["kube-system", "gatekeeper-system"]
      processes: ["*"]
    - excludedNamespaces: ["audit-excluded-ns"]
-     processes: ["audit"]

CIどうするの?

Gatekeeperはあくまでもapplyなどでリソースを適応する際にバリデーションするのであり、事前にその定義を満たしているかは検証できません。
そのため、個人的にはConftestを使ってKubernetesの定義に対してテストを記載するのが良いのかなと思っています。

公式(open-policy-agent/conftest)によると、ConftestはYAMLなどの定義に対してRegoを使って定義が意図どおりであるかを検証するツールです。Kubernetesの定義以外にもTerraformや他の定義に対しても使えるらしいです。
公式のものをそのまま拝借していますが、以下のように定義して $ conftest test deployment.yaml といった感じでテストを実行します。
手軽で良さそうですね。

package main

deny[msg] {
  input.kind == "Deployment"
  not input.spec.template.spec.securityContext.runAsNonRoot

  msg := "Containers must not run as root"
}

deny[msg] {
  input.kind == "Deployment"
  not input.spec.selector.matchLabels.app

  msg := "Containers must provide app label for pod selectors"
}

なお、Kubernetesのマニフェストに対するテストツールは他にも色々あるようです。
Validating Kubernetes YAML for best practice and policiesなどが参考になるかもしれません。

参考