4. ConstraintTemplate 开发入门:编写第一个 ConstraintTemplate(禁止特权容器)

ConstraintTemplate(约束模板)是 OPA Gatekeeper 的核心扩展点,它定义了一类约束的通用逻辑结构。本章将带领你从零开发第一个 ConstraintTemplate,实现“禁止创建特权容器”的准入控制规则,并深入理解 Rego 规则的核心结构。

4.1 什么是 ConstraintTemplate

ConstraintTemplate 是一个 CRD(Custom Resource Definition),它告诉 Gatekeeper:

  • 约束的输入参数(parameters)—— 约束实例可以传入哪些配置
  • 约束的评估逻辑(rego)—— 用 Rego 语言编写的策略规则
  • 约束的目标资源(targets)—— 该模板作用于哪些 Kubernetes 资源类型

一个 ConstraintTemplate 可以实例化为多个 Constraint(约束实例),每个实例可以传入不同的参数值,实现“模板一次编写,多场景复用”。

核心概念: ConstraintTemplate ≈ 策略函数定义;Constraint ≈ 策略函数调用。模板定义逻辑,实例传入参数并绑定生效范围。

4.2 第一个 ConstraintTemplate:禁止特权容器

下面是一个完整的 ConstraintTemplate YAML,它禁止 Pod 以特权模式运行:

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

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          container.securityContext.privileged == true
          not exempt_container(container)
          msg := sprintf("特权容器 '%v' 不允许被创建", [container.name])
        }

        exempt_container(container) {
          exempt_images := object.get(input.parameters, "exemptImages", [])
          container.image == exempt_images[_]
        }
关键点:
  • crd.spec.names.kind 定义了约束实例的 API 类型名称(如 K8sPSPPrivilegedContainer)
  • crd.spec.validation.openAPIV3Schema 声明了约束实例可以传入的参数结构(本例中为 exemptImages 数组)
  • targets[0].rego 是 Rego 策略代码,使用 violation 规则判断是否违规

4.3 Rego 规则结构:violation、deny、msg

在 Gatekeeper 的 Rego 策略中,有三种核心规则结构,它们决定了策略的触发方式和返回信息:

规则类型 用途 返回值 示例
violation 定义违规条件,当规则体为真时产生违规 返回包含 msg 字段的对象 violation[{"msg": msg}] { ... }
deny 直接拒绝请求,返回自定义错误信息 返回布尔值或字符串 deny[msg] { ... }
msg 违规或拒绝时返回给用户的提示信息 字符串(通常使用 sprintf 格式化) msg := "容器 %v 使用了特权模式"
重要区别: violation 是 Gatekeeper 推荐的规则结构,它允许一个请求产生多条违规消息;deny 是 OPA 原生规则,一旦触发立即拒绝请求,且只返回一条消息。在 Gatekeeper 中优先使用 violation。

4.4 Rego 规则结构详解

以禁止特权容器的 Rego 代码为例,逐行解析规则结构:

package k8spspprivilegedcontainer

# violation 规则:当条件全部满足时,产生一条违规消息
violation[{"msg": msg}] {
  # 遍历 Pod 中的所有容器
  container := input.review.object.spec.containers[_]
  # 检查容器的 securityContext.privileged 是否为 true
  container.securityContext.privileged == true
  # 排除豁免镜像列表中的容器
  not exempt_container(container)
  # 格式化违规消息,包含容器名称
  msg := sprintf("特权容器 '%v' 不允许被创建", [container.name])
}

# 辅助规则:判断容器是否属于豁免镜像
exempt_container(container) {
  # 从约束实例参数中获取 exemptImages 列表,默认为空数组
  exempt_images := object.get(input.parameters, "exemptImages", [])
  # 检查容器镜像是否在豁免列表中
  container.image == exempt_images[_]
}
Rego 迭代技巧: input.review.object.spec.containers[_] 中的下划线 _ 表示“遍历所有元素”,Rego 会自动为每个容器生成一条规则实例。如果任意一个容器违规,violation 规则就会产生一条消息。

4.5 模板参数化:让约束更灵活

参数化是 ConstraintTemplate 的核心能力。通过 crd.spec.validation.openAPIV3Schema 定义参数结构,约束实例可以传入不同的配置值,实现策略的灵活复用。

参数化流程:

  1. 模板定义参数结构:在 openAPIV3Schema 中声明参数名称、类型和默认值
  2. 约束实例传入参数:在 Constraint YAML 的 spec.parameters 中填写具体值
  3. Rego 读取参数:通过 input.parameters 访问传入的参数

参数化示例:

约束实例 YAML(传入豁免镜像列表):

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: no-privileged-containers
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    exemptImages:
      - "gcr.io/google-containers/pause:latest"
      - "k8s.gcr.io/pause:3.6"

Rego 中读取参数:

exempt_images := object.get(input.parameters, "exemptImages", [])
参数化优势:
  • 一个模板可以应对多种场景(如不同命名空间使用不同的豁免镜像列表)
  • 无需修改模板代码即可调整策略行为
  • 支持数组、对象、字符串等多种参数类型

4.6 ConstraintTemplate 开发流程总结

步骤 操作 关键文件/命令
1 编写 ConstraintTemplate YAML template.yaml(包含 CRD 定义和 Rego 代码)
2 应用模板到集群 kubectl apply -f template.yaml
3 编写 Constraint 实例 YAML constraint.yaml(包含 match 和 parameters)
4 应用约束实例 kubectl apply -f constraint.yaml
5 测试策略效果 创建特权 Pod 验证是否被拒绝

4.7 内联 SVG:ConstraintTemplate 与 Constraint 关系图

ConstraintTemplate 与 Constraint 关系图 ConstraintTemplate kind: K8sPSPPrivilegedContainer Rego 策略逻辑 参数结构定义 Constraint 实例 kind: K8sPSPPrivilegedContainer match: 作用于 Pod parameters: exemptImages 实例化 输入参数 exemptImages: ["pause:latest"] 被审查资源 Pod: securityContext.privileged 审查 违规结果:拒绝请求 + 返回 msg

4.8 内联 SVG:Rego 规则执行流程

Rego 规则执行流程(violation 规则) 输入请求 遍历 containers[_] securityContext.privileged == true ? 否 通过 是 not exempt_container(container) ? 豁免 通过 非豁免 违规

4.9 常见错误与调试技巧

常见错误 1:Rego 语法错误
症状:Gatekeeper 日志中出现 rego_parse_error 或 rego_type_error。
解决:使用 opa check 命令本地验证 Rego 语法:opa check template.rego。
常见错误 2:参数未正确传递
症状:约束实例中的参数未生效,Rego 中读取到空值。
解决:检查 openAPIV3Schema 中参数路径是否与 input.parameters 一致;使用 object.get 设置默认值。
调试技巧: 在 Rego 代码中添加 trace 语句输出中间变量值,然后在 Gatekeeper 日志中查看:trace(sprintf("当前容器: %v", [container.name]))。

4.10 本章小结

  • ConstraintTemplate 是 Gatekeeper 策略的“函数定义”,Constraint 是“函数调用”
  • violation 规则是 Gatekeeper 推荐的核心规则结构,返回 msg 字段
  • 参数化通过 openAPIV3Schema 定义参数结构,input.parameters 读取参数值
  • 开发流程:编写模板 → 应用模板 → 编写约束实例 → 应用约束 → 测试验证
  • 使用 opa check 和 trace 进行本地验证和调试