Kyverno 和 Gatekeeper 的简单对比
引言
在生产环境中应用 Kubernetes 时,出于安全、合规等管控目的,经常需要对工作负载进行审计、校验以及变更,例如下列场景:
- 为了便于在监控和日志中识别特定应用,我们希望业务应用的 Pod 具备合适的标签结构,标识本 Pod 的业务角色。
- 根据职责分离的设计原则,只允许 admin 用户在形如
xxx-system
的命名空间创建工作负载。 - 为了防范供应链攻击,限制仅有来自特定仓库的容器镜像才能运行,并且镜像拉取策略必须设置为 Always。
- 恶意用户或者应用可以通过加载主机目录窃取消息或者实施破坏,因此应该限制这种加载行为。
- …
Kubernetes 提供的 Validating 和 Mutation Webhook 能够完成这些任务,如图所示:
策略引擎们更进一步,使用 DSL 来编写 Webhook 的逻辑,目前使用最为广泛的策略引擎大概是 Kyverno 和 Gatekeeper 两个产品了,因为都是面向 Kubernetes 工作的,通过 CRD 和 Webhook 技术达成目的,因此两个产品的功能和思路颇有一些相似之处,本文将对这两个产品进行一个对比,便于读者们根据实际需求进行采用。
实际上在本文之前,就已经翻译过一篇两个产品的对比,因为版本更迭,其中部分结果已经发生变化。
项目简介
Kyverno 从 2019 年 5 月 24 日在 Github 发布第一个 Release 之后,于 2020 年 11 月 19 日进入 CNCF 版图成为沙箱项目。一年半的时间里,发布了 98 个 Release,收获 1.7k Star,先后有 88 个贡献者参与了该项目的建设之中。目前其策略库中包含了 100 条策略,Kyverno 官方的架构图如下:
从上图可以看到,Kyverno 的 Webhook 组件通过 Mutating 和 Validating 两个接口和 Kubernetes 进行交互,用于处理 Kubernetes API Server 的 AdmissionReview 请求。Policy 控制器负责检视集群中的策略对象;而 GenerateControl 实现了 Kyverno 的一个亮点功能,根据策略针对部分事件生成特定资源。
Gatekeeper 的情况比较复杂,它是在 OPA 项目基础上的一个实现,所以我们经常会看到 OPA/GateKeeper 这样的说法。OPA 的全称是 Open Policy Agent 是一个开源的通用策略引擎,在 2018 年进入 CNCF 沙箱,2021 年 1 月毕业。目前 OPA 的版本为 v0.36.0,它的架构图如下:
这里不难看出,OPA 的工作流程是也是很贴近 Kubernetes 的 AdmissionReview 流程的。Gatekeeper 在 OPA 的基础之上,根据 Kubernetes 生态的习惯做法,定制了多种 CRD,并融入到 Admission Controller 的体系之中。Gatekeeper V3 的架构体现了这一思路:
上图中 Gatekeeper 将 OPA 封装起来,通过 CRD 和 Admission Controller 的交互,实现了策略的自动化管理。Gatekeeper 的策略库中也包含了几十条实用策略。
相似的架构和工作原理,决定了两者的功能也是有颇多的相似之处的,下面将从作用范围选择、策略编写、审计和可见、独特功能几个方向分别对两者进行介绍。
作用范围
Kyverno 提供了 match 和 exclude 两个关键字,用于指定策略的作用范围。两者结合能够相对精确地对 API Server 的请求进行过滤,确定是否启用策略。Kyverno 的选择条件包括四个分类,分别是:
- resources: 通过标签、命名空间、名称、注解、API 类型等进行选择;
- subjects: 通过发起动作的用户、用户组和 ServiceAccount 进行选择;
- roles 和 clusterroles:发起动作的用户角色以及集群角色。
多个条件同时作用时候,可以使用 any 和 all 关键字,分别用于指定 OR 或 AND 的逻辑关系。例如下面的规则:
spec:
validationFailureAction: enforce
background: false
rules:
- name: match-criticals-except-given-users
match:
all:
- resources:
kinds:
- Pod
selector:
matchLabels:
app: critical
exclude:
any:
- subjects:
- kind: User
name: susan
- kind: User
name: dave
这个选择器匹配 app=critical
的 Pod,但是排除了 susan
和 dave
的用户。
再看 Gatekeeper 中对目标对象的选择,它的 spec.match
字段支持以下几种选择:
- kinds:一个数组型字段,元素为 apiGroup 和 kind 的组合;
- scope:用于定义作用范围,可以是
Namespaced
,Cluster
或*
; - namespaces:一个数组型字段,元素为命名空间名称;可以支持尾部通配符,例如
kube-*
; - excludedNamespaces:数组字段,需要排除的命名空间,同样支持尾部通配符;
- labelSelector:其中包含
matchLabels
和matchExpressions
两个字段,分别用于指定标签选择器和表达式选择器; - namespaceSelector:标签选择器,用于选择命名空间;
- name:支持尾部通配符,用于过滤对象名称。
对比看来,Gatekeeper 的字段类型更丰富一些,但是 Kyverno 的优势是加入了针对特定动作发起者直接进行限制,Gatekeeper 也不是没有这种能力,它需要在策略中直接实现针对这些内容的过滤,所以二者的能力类似,但侧重点、便利程度是有所不同的。
策略编写
Kyverno 和 Gatekeeper 的策略编写方式是迥然不同的,例如一个简单的标签验证策略,在 Kyverno 中的实现:
pattern:
spec:
containers:
- name: "*"
resources:
limits:
memory: "?*"
cpu: "?*"
requests:
memory: "?*"
cpu: "?*"
而在 Gatekeeper 中就相当不同了:
package limit
resources_defined(x) {
x.resources; x.resources.limits; x.resources.requests
}
violation[{"msg": msg}] {
ctr_list = input.review.object.spec.template.spec.containers
some i
ctr = ctr_list[i]
not resources_defined(ctr)
msg = sprintf("%v containers without 'resource' fields", [ctr.name])
}
Rego 语言给 OPA/Gatekeeper 带来强大能力的同时,也带来一定的学习成本,而 Kyverno 的最复杂语法(YAML)元素大概是:
Anchors:用于表述
if-then-else
语义,“如果存在 hostPath,则其内容不能是/var/lib
”:=(hostPath): path: “!/var/lib”
foreach:用于对一个列表中的多个元素定义多种策略,例如 Pod 中的 Containers 数组。
虽然简单,但是用主要用于描述结构内容的 YAML 来表达逻辑,还是颇有些古怪的;Rego 加持的 OPA/Gatekeeper 就不会有这种烦恼了——它的一个困扰是,每种 Constraint 都是一种 CRD,当然,这种 CRD 非常灵活,能够很好地表达 Contraint 的参数化能力。
再说 Mutation,Kyverno 是使用 RFC 6902 JSONPatch
和 Strategic Merge Patch
两种方式来进行变更的,例如对 Configmap 对象的变更:
patchesJson6902: |-
- path: "/data"
op: add
value: {"ship.properties": "{\"type\": \"starship\", \"owner\": \"utany.corp\"}"}
这只是个最简单的例子,Keyverno 的 Mutation 相关语法难度远大于验证语法。
而 OPA/Gatekeeper 的这个功能就让我有些意外了,它特别定义了三种 CRD,分别是:
- AssignMetadata:用于对资源元数据进行变更
- Assign:对元数据之外的内容进行变更
- ModifySet:从列表中加入或者移除内容
例如要加入一个注解:
apiVersion: mutations.gatekeeper.sh/v1beta1
kind: AssignMetadata
metadata:
name: demo-annotation-owner
spec:
match:
scope: Namespaced
location: "metadata.annotations.owner"
parameters:
assign:
value: "admin"
两个产品都提供了对策略进行测试的能力,不过 Kyverno 很遗憾地没有提供对 arm64 的支持。
审计和可观测性
Kyverno 提供了一个单独的可观测性服务,可以用 Prometheus 抓取如下内容:
- 策略和规则的数量
- 策略和规则的执行结果
- 策略执行的延迟
- 请求数量
- 策略变更数量
另外 Kyverno 还提供了两个用于生成报告的 CRD:PolicyReport
和 ClusterPolicyReport
,例如:
$ kubectl get polr -A
NAMESPACE NAME PASS FAIL WARN ERROR SKIP AGE
default polr-ns-default 338 2 0 0 0 28h
flux-system polr-ns-flux-system 135 5 0 0 0 28h
$ kubectl describe polr polr-ns-default | grep "Result: \+fail" -B10
Message: validation error: Running as root is not allowed. The fields spec.securityContext.runAsNonRoot, spec.containers[*].securityContext.runAsNonRoot, and spec.initContainers[*].securityContext.runAsNonRoot must be `true`. Rule check-containers[0] failed at path /spec/securityContext/runAsNonRoot/. Rule check-containers[1] failed at path /spec/containers/0/securityContext/.
Policy: require-run-as-non-root
Resources:
API Version: v1
Kind: Pod
Name: add-capabilities-init-containers
Namespace: default
UID: 1caec743-faed-4d5a-90f7-5f4630febd58
Rule: check-containers
Scored: true
Result: fail
--
Message: validation error: Running as root is...
Gatekeeper 提供的指标包括:
- Constraint:Constraints 的数量
- Constraint Template:包括这一类对象的数量、处理延迟
- Webhook:请求数量、响应时间、Mutation 请求数量、Mutation 响应时间
- Audit:触发次数、审计延迟、审计运行时间戳
- 同步:缓存对象数量、同步延迟、最后同步时间戳
- Watch:Watch 的对象种类数量
Kyverno 策略中提供了 validationFailureAction
字段,用于定义策略失败后的动作,可选动作包括 audit 和 enforce。
Gatekeeper 策略中包含 DryRun、Warn 和 Deny 三种应对措施。
独特功能
Kyverno 在功能方面有两个有趣的东西:
- 创建对象:可以根据策略,在特定条件下触发对象的创建,例如随 Service 创建 Ingress 之类;
- 校验镜像签名:可以使用 cosign 对镜像签名进行校验,保障供应链安全。
太长不看
OPA 是 CNCF 的老牌项目,Kyverno 属于后起之秀,在策略编写方面,Kyverno 的表达方式更合乎 YAML 工程师们的做事风格;而 Rego 语言虽然语法还是很奇怪,然而一旦习惯了这种设定,强大的实现能力还是要优于 YAML 语法糖的。
Mutation 方面,Gatekeeper 支持的比较晚,也非常生硬。Kyverno 的高可用和监控能力则是比较晚才出现。