3. ConstraintTemplate 基础:理解 ConstraintTemplate 的结构、Rego 语言入门、编写第一个简单的约束模板

3.1 ConstraintTemplate 核心结构

ConstraintTemplate 是 OPA Gatekeeper 中定义策略逻辑与参数模式的 CRD(Custom Resource Definition)。它由两个主要部分组成:CRD 规范(crd.spec) 和 目标代码(targets)。

组成部分 字段路径 作用描述
CRD 规范 spec.crd.spec 定义该模板生成的约束(Constraint)资源所接受的参数(parameters)结构,即用户在使用约束时可以传入哪些字段。
目标代码 spec.targets 包含 Rego 策略逻辑,以及目标平台(如 admission.k8s.gatekeeper.sh)的标识。Rego 代码必须定义 violation 规则。
元数据 metadata.name 模板名称,通常采用 k8s<策略名> 的命名惯例,例如 k8srequiredlabels。
关键原则: ConstraintTemplate 是“模板工厂”,它不直接执行策略,而是生成一个名为 Constraint 的 CRD 实例。用户通过创建 Constraint 资源来激活具体的策略规则。

3.2 Rego 语言入门:策略即代码

Rego 是 OPA 的声明式查询语言,专为策略定义而生。与传统的命令式编程不同,Rego 关注的是“数据应该满足什么条件”,而不是“如何一步步检查数据”。

3.2.1 核心概念速览

  • 规则(Rule): 以 rule_name := expression 或 rule_name { expression } 形式定义,是 Rego 的基本逻辑单元。
  • 输入(input): 在 Gatekeeper 中,input 代表被审查的 Kubernetes 资源对象(如 Pod、Deployment)。
  • 数据(data): 包含外部数据,如从 Constraint 参数传入的 data.parameters。
  • 默认规则(default): 用于设置规则的默认值,避免未定义情况。
  • violation 规则: Gatekeeper 强制要求 Rego 代码中必须包含一个名为 violation 的规则,该规则返回 true 表示违反策略。

3.2.2 Rego 语法速查表

语法元素 示例 说明
字面量 "nginx", 8080, true 字符串、数字、布尔值。
变量 name := input.metadata.name 使用 := 赋值,变量不可变。
数组/对象访问 input.spec.containers[0].image 点号访问嵌套字段,方括号索引。
迭代 some container; container := input.spec.containers[_] some 关键字用于遍历集合。
条件 container.image == "nginx:latest" 使用 == 比较,!= 不等。
逻辑运算 a == 1; b == 2 (AND) 分号表示 AND,多个条件需同时满足。
内置函数 startswith(image, "nginx") 字符串、正则、集合等丰富内置函数。
学习建议: 将 Rego 视为“数据查询语言”而非“编程语言”。你只需要描述“什么样的数据是合规的”,OPA 引擎会自动匹配。

3.3 编写第一个约束模板:强制标签存在

下面我们编写一个完整的 ConstraintTemplate,要求所有命名空间(Namespace)必须包含 owner 标签。

3.3.1 模板 YAML 定义

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels

        violation[{"msg": msg}] {
          # 获取约束参数中定义的必须存在的标签列表
          required_labels := input.parameters.labels

          # 遍历每个必须存在的标签
          some label
          label := required_labels[_]

          # 检查命名空间是否缺少该标签
          not input.review.object.metadata.labels[label]

          # 构造违规消息
          msg := sprintf("命名空间必须包含标签: %v", [label])
        }

3.3.2 代码逐行解析

  1. 包声明: package k8srequiredlabels 必须与模板名称(去除 k8s 前缀)一致。
  2. violation 规则: 返回一个包含 msg 的对象,当规则体为真时触发违规。
  3. 参数读取: input.parameters.labels 从 Constraint 实例中获取用户传入的标签列表。
  4. 迭代检查: some label; label := required_labels[_] 遍历所有必须的标签。
  5. 缺失判断: not input.review.object.metadata.labels[label] 检查标签是否不存在。
  6. 消息构造: sprintf 格式化输出违规详情。
常见错误: 忘记在 violation 规则中使用 input.parameters 读取参数,而是硬编码标签值。这会导致模板失去灵活性,无法复用。

3.4 ConstraintTemplate 与 Constraint 的关系

下图展示了从模板定义到策略生效的完整链路:

ConstraintTemplate → Constraint → 策略执行链路 ConstraintTemplate 定义 Rego 逻辑 + 参数结构 生成 CRD K8sRequiredLabels Constraint 实例 传入具体参数(标签列表) K8s API 请求 创建/更新资源 Gatekeeper 评估 执行 Rego 策略 允许 / 拒绝 返回决策结果 参数注入 说明:ConstraintTemplate 定义“怎么做”,Constraint 实例定义“做什么”,两者结合完成策略执行。

3.5 创建并使用第一个约束

完成模板部署后,需要创建对应的 Constraint 实例来激活策略。

3.5.1 部署模板

kubectl apply -f k8srequiredlabels.yaml

3.5.2 创建 Constraint 实例

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: ns-must-have-owner
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels:
      - "owner"

3.5.3 验证效果

尝试创建一个没有 owner 标签的命名空间:

kubectl create namespace test-ns
# 预期输出:Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: 命名空间必须包含标签: "owner"
调试技巧: 使用 kubectl describe constrainttemplate k8srequiredlabels 查看模板状态;使用 kubectl get constraint 查看所有已激活的约束实例。

3.6 本章小结

  • ConstraintTemplate 是 Gatekeeper 策略的“蓝图”,包含 CRD 规范和 Rego 代码。
  • Rego 是声明式策略语言,核心是定义 violation 规则来描述违规条件。
  • 通过 input.parameters 读取约束参数,实现策略的灵活复用。
  • 模板 + 约束实例 = 完整的策略执行单元。