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 定义参数结构,约束实例可以传入不同的配置值,实现策略的灵活复用。
参数化流程:
- 模板定义参数结构:在
openAPIV3Schema中声明参数名称、类型和默认值 - 约束实例传入参数:在 Constraint YAML 的
spec.parameters中填写具体值 - 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 关系图
4.8 内联 SVG:Rego 规则执行流程
4.9 常见错误与调试技巧
常见错误 1:Rego 语法错误
症状:Gatekeeper 日志中出现
解决:使用
症状:Gatekeeper 日志中出现
rego_parse_error 或 rego_type_error。解决:使用
opa check 命令本地验证 Rego 语法:opa check template.rego。
常见错误 2:参数未正确传递
症状:约束实例中的参数未生效,Rego 中读取到空值。
解决:检查
症状:约束实例中的参数未生效,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进行本地验证和调试