Kyverno 和 Gatekeeper 的简单对比

引言

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

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

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

admission controller

策略引擎们更进一步,使用 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 architecture

从上图可以看到,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 arch

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

gatekeepr arch

上图中 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,但是排除了 susandave 的用户。

再看 Gatekeeper 中对目标对象的选择,它的 spec.match 字段支持以下几种选择:

  • kinds:一个数组型字段,元素为 apiGroup 和 kind 的组合;
  • scope:用于定义作用范围,可以是 Namespaced, Cluster*
  • namespaces:一个数组型字段,元素为命名空间名称;可以支持尾部通配符,例如 kube-*
  • excludedNamespaces:数组字段,需要排除的命名空间,同样支持尾部通配符;
  • labelSelector:其中包含 matchLabelsmatchExpressions 两个字段,分别用于指定标签选择器和表达式选择器;
  • 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 JSONPatchStrategic 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:PolicyReportClusterPolicyReport,例如:

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

Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
上一页