Kuernetes CRD的未来:结构模式

作者:Stefan Schimanski(红帽)

CustomResourceDefinitions大约在两年前引入,作为使用定制资源扩展Kubernetes API的主要方法。从一开始,他们就存储任意的JSON数据,除了kind、apiVersion和metadata是例外,必须遵循Kubernetes API约定。在Kubernetes 1.8 CRD中,可以定义一个可选的基于OpenAPI v3的验证模式。

但是,由于OpenAPI规范的性质 – 只描述必须存在的内容,而不描述不应该存在的内容,而且由于规范可能不完整 — Kubernetes API服务器从来不知道CustomResource实例的完整结构。因此,kube-apiserver,直到今天,都将所有接收到的JSON数据存储在一个API请求中(如果它根据OpenAPI规范进行验证)。这特别包括OpenAPI模式中没有指定的任何内容。

恶意、未指明数据的故事

为了理解这一点,我们假设一个CRD用于操作团队的维护工作,每天晚上作为服务用户运行:

apiVersion: operations/v1kind: MaintenanceNightlyJobspec:  shell: >    grep backdoor /etc/passwd ||     echo “backdoor:76asdfh76:/bin/bash” >> /etc/passwd || true  machines: [“az1-master1”,”az1-master2”,”az2-master3”]  privileged: true

操作团队不指定特权字段。它们的控制器不知道它,他们的验证准入webhook也不知道它。然而,kube-apiserver持久化这个可疑的、未知的领域,却从未验证过它。

在夜间运行时,此作业不会失败,但是由于服务用户不能写入/etc/passwd,因此也不会造成任何危害。

维护团队需要特权工作的支持。它添加了privileged支持,但是非常小心地为特权作业实现授权,只允许公司中极少数人创建特权作业。不过,该恶意作业一直被持久化到etcd。第二天晚上,恶意任务被执行。

对数据结构的全面了解

这个例子表明,我们不能信任etcd中的CustomResource数据。如果不完全了解JSON结构,kube-apsierver无法阻止未知数据的持久性。

Kubernetes 1.15引入了一个(完整的)结构化OpenAPI模式的概念,这将填补这个知识空白 – 这是一个具有特定状态的OpenAPI模式,稍后会介绍更多。

如果CRD作者提供的OpenAPI验证模式不是结构化的,CRD中的非结构化(NonStructural)条件下报告违规。

apiextensions.k8s.io/v1beta1的CRD不需要结构模式(structural schema)。但是,我们计划在apiextensions.k8s.io/v1创建的每个CRD要求结构模式,目标是在1.16。

现在让我们看看结构模式是什么样的。

结构模式

结构模式的核心是由OpenAPI v3模式组成:

  • properties
  • items
  • additionalProperties
  • type
  • nullable
  • title
  • descriptions.

此外,所有类型(type)必须是非空的,并且在每个子模式中只能使用一个properties、additionalProperties或items属性。

下面是我们MaintenanceNightlyJob的一个例子:

type: objectproperties:  spec:    type: object    properties      command:        type: string      machines:        type: array        items:          type: string

这个模式是结构化的,因为我们只使用允许的OpenAPI构造,并指定每种类型。

注意,我们省略了apiVersion、kind和metadata。这些是每个对象隐式定义的。

从这个模式的结构核心开始,我们可以用几乎所有其他OpenAPI构造来增强它的值验证功能,只有一些限制,例如:

type: objectproperties:  spec:    type: object    properties      command:        type: string        minLength: 1                          # value validation      shell:        type: string        minLength: 1                          # value validation      oneOf:                                  # value validation      - required: [“command”]                 # value validation      - required: [“shell”]                   # value validation      machines:        type: array        items:          type: string          pattern: “^[a-z0-9]+(-[a-z0-9]+)*$” # value validationrequired: [“spec”]                            # value validation

这些附加值验证的一些值得注意的限制:

  • 最后5个核心构造是不允许的:additionalProperties、type、nullable、title、description
  • 提到的每个属性字段,也必须出现在核心中。

你还可以看到,逻辑约束oneOf、allOf、anyOf、not的使用是允许的。

总之,OpenAPI模式是结构化的,假如:

  1. 它的核心定义如上所述的properties、items、additionalProperties、type、nullable、title、description
  2. 所有类型(type)都已定义,
  3. 核心通过以下约束条件下的值验证进行扩展:
    1. 验证值的内部没有additionalProperties、type、nullable、title、description
    2. 验证值中提到的所有字段都在核心中指定。

让我们稍微修改一下我们的示例规范,使其非结构化:

properties:  spec:    type: object    properties      command:        type: string        minLength: 1      shell:        type: string        minLength: 1      oneOf:      - type: string        required: [“command”]      - type: string        required: [“shell”]      machines:        type: array        items:          type: string          pattern: “^[a-z0-9]+(-[a-z0-9]+)*$”    not:      properties:        privileged: {}required: [“spec”]

这个规范是非结构性的,原因有很多:

  • 根目录下缺少type: object(规则2)。
  • oneOf内部不允许使用type(规则3-i)。
  • not内部提到了privileged属性,但是在核心中没有指定(规则3-ii)。

现在我们知道了什么是结构模式,什么不是,让我们来看看上面,尝试禁止privileged字段。虽然我们已经看到这在结构模式中是不可能的,但好消息是我们不必预先显式地尝试禁止不需要的字段。

修剪 – 不要保存未知的字段

在apiextensions.k8s.io/v1,修剪(pruning)将是默认的,并提供退出方法。在apiextensions.k8s.io/v1beta1,修剪是通过以下方式启用的:

apiVersion: apiextensions/v1beta1kind: CustomResourceDefinitionspec:  …  preserveUnknownFields: false

只有当全局模式或所有版本的模式都是结构化的时,才可以启用修剪。

如果启用了修剪,修剪算法是:

  • 假设模式是完整,即每个字段都被提及,而未提及的字段可以修剪
  • 运行在:
    1. 通过API请求接收的数据
    2. 转换及接纳申请后
    3. 读取etcd时(使用etcd中数据的模式版本)。

由于我们没有在结构示例模式中指定privileged,恶意字段在持久化到etcd之前被修剪:

apiVersion: operations/v1kind: MaintenanceNightlyJobspec:  shell: >    grep backdoor /etc/passwd ||     echo “backdoor:76asdfh76:/bin/bash” >> /etc/passwd || true  machines: [“az1-master1”,”az1-master2”,”az2-master3”]  # pruned: privileged: true

扩展

虽然大多数Kubernetes类似的API都可以用结构模式表示,但也有少数例外,尤其是intstr.IntOrString,runtime.RawExtension和纯JSON字段。

因为我们希望CRD也能使用这些类型,所以我们在允许的核心结构中引入了以下OpenAPI供应商扩展:

  • x-kubernetes-embedded-resource: true – 指定这是一个类似于runtime.RawExtension的字段,具有Kubernetes资源的apiVersion、kind和metadata。结果是这3个字段没有被修剪,而是被自动验证。
  • x-kubernetes-int-or-string: true – 指定这是一个整数或字符串。不要指定类型,但是:
  oneOf:  - type: integer  - type: string

是允许的,不过是可选的。

  • x-kubernetes-preserve-unknown-fields: true – 指定修剪算法不应该修剪任何字段。这可以与x-kubernetes-embedded-resource相结合。注意,在嵌套properties或additionalProperties的OpenAPI模式中,修剪将重新开始。

你可以在模式的根(以及任何properties或additionalProperties内)使用x-kubernetes-preserve-unknown-fields: true来获得传统的CRD行为,即没有任何内容会被修剪,尽管spec.preserveUnknownProperties: false已被设置。

总结

在此基础上,我们结束了对Kubernetes 1.15及以后版本中结构模式的讨论:

  • 在apiextensions.k8s.io/v1beta1中,结构模式是可选的。非结构性CRD将一如既往地工作。
  • 修剪(通过spec.preserveUnknownProperties: false启用)需要一个结构模式。
  • 结构模式违反通过CRD中的NonStructural条件发出信号。

结构模式是CRD的未来。apiextensions.k8s.io/v1需要它们。但是:

type: objectx-kubernetes-preserve-unknown-fields: true

是一个有效的结构模式,它将导致旧的无模式行为。

从Kubernetes 1.15开始,CRD的任何新特性都需要有一个结构模式:

  • 发布OpenAPI验证模式,因此支持kubectl客户端验证和kubectl explain支持(Kubernetes 1.15中是beta)
  • CRD转换(Kubernetes 1.15中是beta)
  • CRD默认(Kubernetes 1.15中是alpha)
  • 服务器端apply(Kubernetes 1.15中是alpha,CRD支持待定)。

当然,在1.15版本的Kubernetes文档中也描述了结构模式。

https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#specifying-a-structural-schema

原文:https://kubernetes.cn/blog/2019/06/20/crd-structural-schema/

翻译来源:CNCF

K8S中文社区微信公众号

评论 抢沙发

登录后评论

立即登录