# Kyverno 和 Gatekeeper 的简单对比

## 引言

在生产环境中应用 Kubernetes 时，出于安全、合规等管控目的，经常需要对工作负载进行审计、校验以及变更，例如下列场景：

1. 为了便于在监控和日志中识别特定应用，我们希望业务应用的 Pod 具备合适的标签结构，标识本 Pod 的业务角色。
    
2. 根据职责分离的设计原则，只允许 admin 用户在形如 `xxx-system` 的命名空间创建工作负载。
    
3. 为了防范供应链攻击，限制仅有来自特定仓库的容器镜像才能运行，并且镜像拉取策略必须设置为 Always。
    
4. 恶意用户或者应用可以通过加载主机目录窃取消息或者实施破坏，因此应该限制这种加载行为。
    
5. ...
    

Kubernetes 提供的 Validating 和 Mutation Webhook 能够完成这些任务，如图所示：

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745923644391/77d97014-eb51-4657-9fa5-963918ef081e.png align="center")

策略引擎们更进一步，使用 DSL 来编写 Webhook 的逻辑，目前使用最为广泛的策略引擎大概是 Kyverno 和 Gatekeeper 两个产品了，因为都是面向 Kubernetes 工作的，通过 CRD 和 Webhook 技术达成目的，因此两个产品的功能和思路颇有一些相似之处，本文将对这两个产品进行一个对比，便于读者们根据实际需求进行采用。

> 实际上在本文之前，就已经翻译过一篇[两个产品的对比](https://blog.fleeto.us/post/k8s-policy-comparison/)，因为版本更迭，其中部分结果已经发生变化。

## 项目简介

Kyverno 从 2019 年 5 月 24 日在 Github 发布第一个 Release 之后，于 2020 年 11 月 19 日进入 CNCF 版图成为沙箱项目。一年半的时间里，发布了 98 个 Release，收获 1.7k Star，先后有 88 个贡献者参与了该项目的建设之中。目前其[策略库](https://kyverno.io/policies/)中包含了 100 条策略，Kyverno 官方的架构图如下：

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745923653230/5b88c923-5c98-4638-8b9b-1d2cc604f6ae.png align="center")

从上图可以看到，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，它的架构图如下

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745923694429/6c6bf5ea-8363-4647-b303-58a4bc4ea84b.png align="center")

：

这里不难看出，OPA 的工作流程是也是很贴近 Kubernetes 的 AdmissionReview 流程的。Gatekeeper 在 OPA 的基础之上，根据 Kubernetes 生态的习惯做法，定制了多种 CRD，并融入到 Admission Controller 的体系之中。Gatekeeper V3 的架构体现了这一思路：

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745923669395/a106c458-7a93-4aa4-8437-fc2bb9a50d22.png align="center")

上图中 Gatekeeper 将 OPA 封装起来，通过 CRD 和 Admission Controller 的交互，实现了策略的自动化管理。Gatekeeper 的[策略库](https://github.com/open-policy-agent/gatekeeper-library)中也包含了几十条实用策略。

相似的架构和工作原理，决定了两者的功能也是有颇多的相似之处的，下面将从作用范围选择、策略编写、审计和可见、独特功能几个方向分别对两者进行介绍。

## 作用范围

Kyverno 提供了 match 和 exclude 两个关键字，用于指定策略的作用范围。两者结合能够相对精确地对 API Server 的请求进行过滤，确定是否启用策略。Kyverno 的选择条件包括四个分类，分别是：

* resources: 通过标签、命名空间、名称、注解、API 类型等进行选择；
    
* subjects: 通过发起动作的用户、用户组和 ServiceAccount 进行选择；
    
* roles 和 clusterroles：发起动作的用户角色以及集群角色。
    

多个条件同时作用时候，可以使用 any 和 all 关键字，分别用于指定 OR 或 AND 的逻辑关系。例如下面的规则：

```yaml
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 中的实现：

```yaml
  pattern:
    spec:
      containers:
      - name: "*"
        resources:
          limits:
            memory: "?*"
            cpu: "?*"
          requests:
            memory: "?*"
            cpu: "?*"
```

而在 Gatekeeper 中就相当不同了：

```rego
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`”：
    
    ```yaml
    =(hostPath):
        path: “!/var/lib”
    ```
    
* foreach：用于对一个列表中的多个元素定义多种策略，例如 Pod 中的 Containers 数组。
    

虽然简单，但是用主要用于描述结构内容的 YAML 来表达逻辑，还是颇有些古怪的；Rego 加持的 OPA/Gatekeeper 就不会有这种烦恼了——它的一个困扰是，每种 Constraint 都是一种 CRD，当然，这种 CRD 非常灵活，能够很好地表达 Contraint 的参数化能力。

再说 Mutation，Kyverno 是使用 `RFC 6902 JSONPatch` 和 `Strategic Merge Patch` 两种方式来进行变更的，例如对 Configmap 对象的变更：

```yaml
patchesJson6902: |-
  - path: "/data"
    op: add
    value: {"ship.properties": "{\"type\": \"starship\", \"owner\": \"utany.corp\"}"}
```

这只是个最简单的例子，Keyverno 的 Mutation 相关语法难度远大于验证语法。

而 OPA/Gatekeeper 的这个功能就让我有些意外了，它特别定义了三种 CRD，分别是：

* AssignMetadata：用于对资源元数据进行变更
    
* Assign：对元数据之外的内容进行变更
    
* ModifySet：从列表中加入或者移除内容
    

例如要加入一个注解：

```yaml
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`，例如：

```bash
$ 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 的高可用和监控能力则是比较晚才出现。
