1 - 使用配置文件对 Kubernetes 对象进行声明式管理

你可以通过在一个目录中存储多个对象配置文件、并使用 kubectl apply 来递归地创建和更新对象来创建、更新和删除 Kubernetes 对象。 这种方法会保留对现有对象已作出的修改,而不会将这些更改写回到对象配置文件中。 kubectl diff 也会给你呈现 apply 将作出的变更的预览。

准备开始

安装 kubectl

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

权衡取舍

kubectl 工具能够支持三种对象管理方式:

  • 指令式命令
  • 指令式对象配置
  • 声明式对象配置

关于每种对象管理的优缺点的讨论,可参见 Kubernetes 对象管理

概览

声明式对象管理需要用户对 Kubernetes 对象定义和配置有比较深刻的理解。 如果你还没有这方面的知识储备,请先阅读下面的文档:

以下是本文档中使用的术语的定义:

  • 对象配置文件/配置文件:一个定义 Kubernetes 对象的配置的文件。 本主题展示如何将配置文件传递给 kubectl apply。 配置文件通常存储于类似 Git 这种源码控制系统中。
  • 现时对象配置/现时配置:由 Kubernetes 集群所观测到的对象的现时配置值。 这些配置保存在 Kubernetes 集群存储(通常是 etcd)中。
  • 声明式配置写者/声明式写者:负责更新现时对象的人或者软件组件。 本主题中的声明式写者负责改变对象配置文件并执行 kubectl apply 命令以写入变更。

如何创建对象

使用 kubectl apply 来创建指定目录中配置文件所定义的所有对象,除非对应对象已经存在:

kubectl apply -f <目录>

此操作会在每个对象上设置 kubectl.kubernetes.io/last-applied-configuration: '{...}' 注解。注解值中包含了用来创建对象的配置文件的内容。

下面是一个对象配置文件示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 5
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

执行 kubectl diff 可以打印出将被创建的对象:

kubectl diff -f https://k8s.io/examples/application/simple_deployment.yaml

使用 kubectl apply 来创建对象:

kubectl apply -f https://k8s.io/examples/application/simple_deployment.yaml

使用 kubectl get 打印其现时配置:

kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml

输出显示注解 kubectl.kubernetes.io/last-applied-configuration 被写入到现时配置中,并且其内容与配置文件相同:

kind: Deployment
metadata:
  annotations:
    # ...
    # 此为 simple_deployment.yaml 的 JSON 表示
    # 在对象创建时由 kubectl apply 命令写入
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}      
  # ...
spec:
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

如何更新对象

你也可以使用 kubectl apply 来更新某个目录中定义的所有对象,即使那些对象已经存在。 这一操作会隐含以下行为:

  1. 在现时配置中设置配置文件中出现的字段;
  2. 在现时配置中清除配置文件中已删除的字段。
kubectl diff -f <目录>
kubectl apply -f <目录>

下面是一个配置文件示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 5
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

使用 kubectl apply 来创建对象:

kubectl apply -f https://k8s.io/examples/application/simple_deployment.yaml

使用 kubectl get 打印现时配置:

kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml

输出显示,注解 kubectl.kubernetes.io/last-applied-configuration 被写入到现时配置中,并且其取值与配置文件内容相同。

kind: Deployment
metadata:
  annotations:
    # ...
    # 此为 simple_deployment.yaml 的 JSON 表示
    # 在对象创建时由 kubectl apply 命令写入
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}      
  # ...
spec:
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

通过 kubectl scale 命令直接更新现时配置中的 replicas 字段。 这一命令没有使用 kubectl apply

kubectl scale deployment/nginx-deployment --replicas=2

使用 kubectl get 来打印现时配置:

kubectl get deployment nginx-deployment -o yaml

输出显示,replicas 字段已经被设置为 2,而 last-applied-configuration 注解中并不包含 replicas 字段。

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # 注意注解中并不包含 replicas
    # 这是因为更新并不是通过 kubectl apply 来执行的
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}      
  # ...
spec:
  replicas: 2 # 由 scale 命令填写
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
      # ...

现在更新 simple_deployment.yaml 配置文件,将镜像文件从 nginx:1.14.2 更改为 nginx:1.16.1,同时删除minReadySeconds 字段:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16.1 # 更新该镜像
        ports:
        - containerPort: 80

应用对配置文件所作更改:

kubectl diff -f https://k8s.io/examples/application/update_deployment.yaml
kubectl apply -f https://k8s.io/examples/application/update_deployment.yaml

使用 kubectl get 打印现时配置:

kubectl get -f https://k8s.io/examples/application/update_deployment.yaml -o yaml

输出显示现时配置中发生了以下更改:

  • 字段 replicas 保留了 kubectl scale 命令所设置的值:2; 之所以该字段被保留是因为配置文件中并没有设置 replicas
  • 字段 image 的内容已经从 nginx:1.14.2 更改为 nginx:1.16.1
  • 注解 last-applied-configuration 内容被更改为新的镜像名称。
  • 字段 minReadySeconds 被移除。
  • 注解 last-applied-configuration 中不再包含 minReadySeconds 字段。
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # 注解中包含更新后的镜像 nginx 1.16.1
    # 但是其中并不包含更改后的 replicas 值 2
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.16.1","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}      
    # ...
spec:
  replicas: 2 # 由 `kubectl scale` 设置,被 `kubectl apply` 命令忽略
  # minReadySeconds 被 `kubectl apply` 清除
  # ...
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.16.1 # 由 `kubectl apply` 设置
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

如何删除对象

有两种方法来删除 kubectl apply 管理的对象。

建议操作:kubectl delete -f <文件名>

使用指令式命令来手动删除对象是建议的方法,因为这种方法更为明确地给出了要删除的内容是什么, 且不容易造成用户不小心删除了其他对象的情况。

kubectl delete -f <文件名>

替代方式:kubectl apply -f <目录> --prune

作为 kubectl delete 操作的替代方式,你可以在本地文件系统的目录中的清单文件被删除之后, 使用 kubectl apply 来辩识要删除的对象。

在 Kubernetes 1.32 中,kubectl apply 可使用两种剪裁模式:

  • 基于 Allowlist 的剪裁:这种模式自 kubectl v1.5 版本开始就存在, 但由于其设计存在易用性、正确性和性能问题,因此仍处于 Alpha 阶段。 基于 ApplySet 的模式设计用于取代这种模式。
  • 基于 ApplySet 的剪裁:apply set 是一个服务器端对象(默认是一个 Secret), kubectl 可以使用它来在 apply 操作中准确高效地跟踪集合成员。 这种模式在 kubectl v1.27 中以 Alpha 引入,作为基于 Allowlist 剪裁的替代方案。

特性状态: Kubernetes v1.5 [alpha]

要使用基于 Allowlist 的剪裁,可以添加以下标志到你的 kubectl apply 调用:

  • --prune:删除之前应用的、不在当前调用所传递的集合中的对象。
  • --prune-allowlist:一个需要考虑进行剪裁的组-版本-类别(group-version-kind, GVK)列表。 这个标志是可选的,但强烈建议使用,因为它的默认值是同时作用于命名空间和集群的部分类型列表, 这可能会产生令人意外的结果。
  • --selector/-l:使用标签选择算符以约束要剪裁的对象的集合。此标志是可选的,但强烈建议使用。
  • --all:用于替代 --selector/-l 以显式选择之前应用的类型为 Allowlist 的所有对象。

基于 Allowlist 的剪裁会查询 API 服务器以获取与给定标签(如果有)匹配的所有允许列出的 GVK 对象, 并尝试将返回的活动对象配置与对象清单文件进行匹配。如果一个对象与查询匹配,并且它在目录中没有对应的清单, 但它有一个 kubectl.kubernetes.io/last-applied-configuration 注解,则它将被删除。

kubectl apply -f <目录> --prune -l <标签> --prune-allowlist=<gvk 列表>

特性状态: Kubernetes v1.27 [alpha]

要使用基于 ApplySet 的剪裁,请设置 KUBECTL_APPLYSET=true 环境变量, 并添加以下标志到你的 kubectl apply 调用中:

  • --prune:删除之前应用的、不在当前调用所传递的集合中的对象。
  • --applyset:是 kubectl 可以使用的对象的名称,用于在 apply 操作中准确高效地跟踪集合成员。
KUBECTL_APPLYSET=true kubectl apply -f <目录> --prune --applyset=<名称>

默认情况下,所使用的 ApplySet 父对象的类别是 Secret。 不过也可以按格式 --applyset=configmaps/<name> 使用 ConfigMap。 使用 Secret 或 ConfigMap 时,如果对应对象尚不存在,kubectl 将创建这些对象。

还可以使用自定义资源作为 ApplySet 父对象。 要启用此功能,请为定义目标资源的 CRD 打上标签:applyset.kubernetes.io/is-parent-type: true。 然后,创建你想要用作 ApplySet 父级的对象(kubectl 不会自动为自定义资源执行此操作)。 最后,按以下方式在 applyset 标志中引用该对象: --applyset=<resource>.<group>/<name> (例如 widgets.custom.example.com/widget-name)。

使用基于 ApplySet 的剪裁时,kubectl 会在将集合中的对象发送到服务器之前将标签 applyset.kubernetes.io/part-of=<parentID> 添加到集合中的每个对象上。 出于性能原因,它还会将该集合包含的资源类型和命名空间列表收集到当前父对象上的注解中。 最后,在 apply 操作结束时,它会在 API 服务器上查找由 applyset.kubernetes.io/part-of=<parentID> 标签定义的、属于此集合所对应命名空间(或适用的集群作用域)中对应类型的对象。

注意事项和限制:

  • 每个对象最多可以是一个集合的成员。
  • 当使用任何名命名空间的父级(包括默认的 Secret)时, --namespace 标志是必需的。这意味着跨越多个命名空间的 ApplySet 必须使用集群作用域的自定义资源作为父对象。
  • 要安全地在多个目录中使用基于 ApplySet 的剪裁,请为每个目录使用唯一的 ApplySet 名称。

如何查看对象

你可以使用 kubectl get 并指定 -o yaml 选项来查看现时对象的配置:

kubectl get -f <文件名 | URL> -o yaml

apply 操作是如何计算配置差异并合并变更的?

kubectl apply 更新对象的现时配置,它是通过向 API 服务器发送一个 patch 请求来执行更新动作的。所提交的补丁中定义了对现时对象配置中特定字段的更新。 kubectl apply 命令会使用当前的配置文件、现时配置以及现时配置中保存的 last-applied-configuration 注解内容来计算补丁更新内容。

合并补丁计算

kubectl apply 命令将配置文件的内容写入到 kubectl.kubernetes.io/last-applied-configuration 注解中。 这些内容用来识别配置文件中已经移除的、因而也需要从现时配置中删除的字段。 用来计算要删除或设置哪些字段的步骤如下:

  1. 计算要删除的字段,即在 last-applied-configuration 中存在但在配置文件中不再存在的字段。
  2. 计算要添加或设置的字段,即在配置文件中存在但其取值与现时配置不同的字段。

下面是一个例子。假定此文件是某 Deployment 对象的配置文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16.1 # 更新该镜像
        ports:
        - containerPort: 80

同时假定同一 Deployment 对象的现时配置如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # 注意注解中并不包含 replicas
    # 这是因为更新并不是通过 kubectl apply 来执行的
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}      
  # ...
spec:
  replicas: 2 # 按规模填写
  # ...
  minReadySeconds: 5
  selector:
    matchLabels:
      # ...
      app: nginx
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        # ...
        name: nginx
        ports:
        - containerPort: 80
      # ...

下面是 kubectl apply 将执行的合并计算:

  1. 通过读取 last-applied-configuration 并将其与配置文件中的值相比较, 计算要删除的字段。 对于本地对象配置文件中显式设置为空的字段,清除其在现时配置中的设置, 无论这些字段是否出现在 last-applied-configuration 中。 在此例中,minReadySeconds 出现在 last-applied-configuration 注解中, 但并不存在于配置文件中。 动作: 从现时配置中删除 minReadySeconds 字段。
  2. 通过读取配置文件中的值并将其与现时配置相比较,计算要设置的字段。 在这个例子中,配置文件中的 image 值与现时配置中的 image 不匹配。 动作:设置现时配置中的 image 值。
  3. 设置 last-applied-configuration 注解的内容,使之与配置文件匹配。
  4. 将第 1、2、3 步骤得出的结果合并,构成向 API 服务器发送的补丁请求内容。

下面是此合并操作之后形成的现时配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # ...
    # 注解中包含更新后的镜像 nginx 1.16.1,
    # 但是其中并不包含更改后的 replicas 值 2
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",
      "metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
      "spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
      "spec":{"containers":[{"image":"nginx:1.16.1","name":"nginx",
      "ports":[{"containerPort":80}]}]}}}}      
    # ...
spec:
  selector:
    matchLabels:
      # ...
      app: nginx
  replicas: 2 # 由 `kubectl scale` 设置,被 `kubectl apply` 命令忽略
  # minReadySeconds  此字段被 `kubectl apply` 清除
  # ...
  template:
    metadata:
      # ...
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.16.1 # 由 `kubectl apply` 设置
        # ...
        name: nginx
        ports:
        - containerPort: 80
        # ...
      # ...
    # ...
  # ...

不同类型字段的合并方式

配置文件中的特定字段与现时配置合并时,合并方式取决于字段类型。 字段类型有几种:

  • 基本类型:字段类型为 stringintegerboolean 之一。 例如:imagereplicas 字段都是基本类型字段。

    动作: 替换。

  • map:也称作 object。类型为 map 或包含子域的复杂结构。例如,labelsannotationsspecmetadata 都是 map。

    动作: 合并元素或子字段。

  • list:包含元素列表的字段,其中每个元素可以是基本类型或 map。 例如,containersportsargs 都是 list。

    动作: 不一定。

kubectl apply 更新某个 map 或 list 字段时,它通常不会替换整个字段, 而是会更新其中的各个子元素。例如,当合并 Deployment 的 spec 时,kubectl 并不会将其整个替换掉。相反,实际操作会是对 replicas 这类 spec 的子字段来执行比较和更新。

合并对基本类型字段的更新

基本类型字段会被替换或清除。

字段在对象配置文件中字段在现时对象配置中字段在 last-applied-configuration动作
-将配置文件中值设置到现时配置上。
-将配置文件中值设置到现时配置上。
-从现时配置中移除。
-什么也不做。保持现时值。

合并对 map 字段的变更

用来表示映射的字段在合并时会逐个子字段或元素地比较:

键存在于对象配置文件中键存在于现时对象配置中键存在于 last-applied-configuration动作
-比较子域取值。
-将现时配置设置为本地配置值。
-从现时配置中删除键。
-什么也不做,保留现时值。

合并 list 类型字段的变更

对 list 类型字段的变更合并会使用以下三种策略之一:

  • 如果 list 所有元素都是基本类型则替换整个 list。
  • 如果 list 中元素是复合结构则逐个元素执行合并操作。
  • 合并基本类型元素构成的 list。

策略的选择是基于各个字段做出的。

如果 list 中元素都是基本类型则替换整个 list

将整个 list 视为一个基本类型字段。或者整个替换或者整个删除。 此操作会保持 list 中元素顺序不变

示例: 使用 kubectl apply 来更新 Pod 中 Container 的 args 字段。 此操作会将现时配置中的 args 值设为配置文件中的值。 所有之前添加到现时配置中的 args 元素都会丢失。 配置文件中的 args 元素的顺序在被添加到现时配置中时保持不变。

# last-applied-configuration 值
    args: ["a", "b"]

# 配置文件值
    args: ["a", "c"]

# 现时配置
    args: ["a", "b", "d"]

# 合并结果
    args: ["a", "c"]

解释: 合并操作将配置文件中的值当做新的 list 值。

如果 list 中元素为复合类型则逐个执行合并

此操作将 list 视为 map,并将每个元素中的特定字段当做其主键。 逐个元素地执行添加、删除或更新操作。结果顺序无法得到保证。

此合并策略会使用每个字段上的一个名为 patchMergeKey 的特殊标签。 Kubernetes 源代码中为每个字段定义了 patchMergeKeytypes.go。 当合并由 map 组成的 list 时,给定元素中被设置为 patchMergeKey 的字段会被当做该元素的 map 键值来使用。

例如: 使用 kubectl apply 来更新 Pod 规约中的 containers 字段。 此操作会将 containers 列表视作一个映射来执行合并,每个元素的主键为 name

# last-applied-configuration 值
    containers:
    - name: nginx
      image: nginx:1.16
    - name: nginx-helper-a # 键 nginx-helper-a 会被删除
      image: helper:1.3
    - name: nginx-helper-b # 键 nginx-helper-b 会被保留
      image: helper:1.3

# 配置文件值
    containers:
    - name: nginx
      image: nginx:1.16
    - name: nginx-helper-b
      image: helper:1.3
    - name: nginx-helper-c # 键 nginx-helper-c 会被添加
      image: helper:1.3

# 现时配置
    containers:
    - name: nginx
      image: nginx:1.16
    - name: nginx-helper-a
      image: helper:1.3
    - name: nginx-helper-b
      image: helper:1.3
      args: ["run"]        # 字段会被保留
    - name: nginx-helper-d # 键 nginx-helper-d 会被保留
      image: helper:1.3

# 合并结果
    containers:
    - name: nginx
      image: nginx:1.16
      # 元素 nginx-helper-a 被删除
    - name: nginx-helper-b
      image: helper:1.3
      args: ["run"]        # 字段被保留
    - name: nginx-helper-c # 新增元素
      image: helper:1.3
    - name: nginx-helper-d # 此元素被忽略(保留)
      image: helper:1.3

解释:

  • 名为 "nginx-helper-a" 的容器被删除,因为配置文件中不存在同名的容器。
  • 名为 "nginx-helper-b" 的容器的现时配置中的 args 被保留。 kubectl apply 能够辩识出现时配置中的容器 "nginx-helper-b" 与配置文件 中的容器 "nginx-helper-b" 相同,即使它们的字段值有些不同(配置文件中未给定 args 值)。这是因为 patchMergeKey 字段(name)的值在两个版本中都一样。
  • 名为 "nginx-helper-c" 的容器是新增的,因为在配置文件中的这个容器尚不存在于现时配置中。
  • 名为 "nginx-helper-d" 的容器被保留下来,因为在 last-applied-configuration 中没有与之同名的元素。

合并基本类型元素 list

在 Kubernetes 1.5 中,尚不支持对由基本类型元素构成的 list 进行合并。

默认字段值

API 服务器会在对象创建时其中某些字段未设置的情况下在现时配置中为其设置默认值。

下面是一个 Deployment 的配置文件。文件未设置 strategy

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 5
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

使用 kubectl apply 创建对象:

kubectl apply -f https://k8s.io/examples/application/simple_deployment.yaml

使用 kubectl get 打印现时配置:

kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml

输出显示 API 在现时配置中为某些字段设置了默认值。 这些字段在配置文件中并未设置。

apiVersion: apps/v1
kind: Deployment
# ...
spec:
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 5
  replicas: 1           # API 服务器所设默认值
  strategy:
    rollingUpdate:      # API 服务器基于 strategy.type 所设默认值
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate # API 服务器所设默认值
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        imagePullPolicy: IfNotPresent    # API 服务器所设默认值
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP       # API 服务器所设默认值
        resources: {}         # API 服务器所设默认值
        terminationMessagePath: /dev/termination-log    # API 服务器所设默认值
      dnsPolicy: ClusterFirst       # API 服务器所设默认值
      restartPolicy: Always         # API 服务器所设默认值
      securityContext: {}           # API 服务器所设默认值
      terminationGracePeriodSeconds: 30        # API 服务器所设默认值
# ...

在补丁请求中,已经设置了默认值的字段不会被重新设回其默认值, 除非在补丁请求中显式地要求清除。对于默认值取决于其他字段的某些字段而言, 这可能会引发一些意想不到的行为。当所依赖的其他字段后来发生改变时, 基于它们所设置的默认值只能在显式执行清除操作时才会被更新。

为此,建议在配置文件中为服务器设置默认值的字段显式提供定义, 即使所给的定义与服务器端默认值设定相同。 这样可以使得辩识无法被服务器重新基于默认值来设置的冲突字段变得容易。

示例:

# last-applied-configuration
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

# 配置文件
spec:
  strategy:
    type: Recreate   # 更新的值
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

# 现时配置
spec:
  strategy:
    type: RollingUpdate    # 默认设置的值
    rollingUpdate:         # 基于 type 设置的默认值
      maxSurge : 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

# 合并后的结果 - 出错!
spec:
  strategy:
    type: Recreate     # 更新的值:与 rollingUpdate 不兼容
    rollingUpdate:     # 默认设置的值:与 "type: Recreate" 冲突
      maxSurge : 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

解释:

  1. 用户创建 Deployment,未设置 strategy.type
  2. 服务器为 strategy.type 设置默认值 RollingUpdate,并为 strategy.rollingUpdate 设置默认值。
  3. 用户改变 strategy.typeRecreate。字段 strategy.rollingUpdate 仍会取其默认设置值,尽管服务器期望该字段被清除。 如果 strategy.rollingUpdate 值最初于配置文件中定义, 则它们需要被清除这一点就更明确一些。
  4. apply 操作失败,因为 strategy.rollingUpdate 未被清除。 strategy.rollingupdatestrategy.typeRecreate 不可被设定。

建议:以下字段应该在对象配置文件中显式定义:

  • 如 Deployment、StatefulSet、Job、DaemonSet、ReplicaSet 和 ReplicationController 这类负载的选择算符和 PodTemplate 标签
  • Deployment 的上线策略

如何清除服务器端按默认值设置的字段或者被其他写者设置的字段

没有出现在配置文件中的字段可以通过将其值设置为 null 并应用配置文件来清除。 对于由服务器按默认值设置的字段,清除操作会触发重新为字段设置新的默认值。

如何将字段的属主在配置文件和直接指令式写者之间切换

更改某个对象字段时,应该采用下面的方法:

  • 使用 kubectl apply.
  • 直接写入到现时配置,但不更改配置文件本身,例如使用 kubectl scale

将属主从直接指令式写者更改为配置文件

将字段添加到配置文件。针对该字段,不再直接执行对现时配置的修改。 修改均通过 kubectl apply 来执行。

将属主从配置文件改为直接指令式写者

在 Kubernetes 1.5 中,将字段的属主从配置文件切换到某指令式写者需要手动执行以下步骤:

  • 从配置文件中删除该字段;
  • 将字段从现时对象的 kubectl.kubernetes.io/last-applied-configuration 注解中删除。

更改管理方法

Kubernetes 对象在同一时刻应该只用一种方法来管理。 从一种方法切换到另一种方法是可能的,但这一切换是一个手动过程。

从指令式命令管理切换到声明式对象配置

从指令式命令管理切换到声明式对象配置管理的切换包含以下几个手动步骤:

  1. 将现时对象导出到本地配置文件:

    kubectl get <kind>/<name> -o yaml > <kind>_<name>.yaml
    
  2. 手动移除配置文件中的 status 字段。

  1. 设置对象上的 kubectl.kubernetes.io/last-applied-configuration 注解:

    kubectl replace --save-config -f <kind>_<name>.yaml
    
  2. 更改过程,使用 kubectl apply 专门管理对象。

从指令式对象配置切换到声明式对象配置

  1. 在对象上设置 kubectl.kubernetes.io/last-applied-configuration 注解:

    kubectl replace --save-config -f <kind>_<name>.yaml
    
  2. 自此排他性地使用 kubectl apply 来管理对象。

定义控制器选择算符和 PodTemplate 标签

建议的方法是定义一个不可变更的 PodTemplate 标签, 仅用于控制器选择算符且不包含其他语义性的含义。

示例:

selector:
  matchLabels:
      controller-selector: "apps/v1/deployment/nginx"
template:
  metadata:
    labels:
      controller-selector: "apps/v1/deployment/nginx"

接下来

2 - 使用 Kustomize 对 Kubernetes 对象进行声明式管理

Kustomize 是一个独立的工具,用来通过 kustomization 文件 定制 Kubernetes 对象。

从 1.14 版本开始,kubectl 也开始支持使用 kustomization 文件来管理 Kubernetes 对象。 要查看包含 kustomization 文件的目录中的资源,执行下面的命令:

kubectl kustomize <kustomization_directory>

要应用这些资源,使用 --kustomize-k 参数来执行 kubectl apply

kubectl apply -k <kustomization_directory>

准备开始

安装 kubectl

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

Kustomize 概述

Kustomize 是一个用来定制 Kubernetes 配置的工具。它提供以下功能特性来管理应用配置文件:

  • 从其他来源生成资源
  • 为资源设置贯穿性(Cross-Cutting)字段
  • 组织和定制资源集合

生成资源

ConfigMap 和 Secret 包含其他 Kubernetes 对象(如 Pod)所需要的配置或敏感数据。 ConfigMap 或 Secret 中数据的来源往往是集群外部,例如某个 .properties 文件或者 SSH 密钥文件。 Kustomize 提供 secretGeneratorconfigMapGenerator,可以基于文件或字面值来生成 Secret 和 ConfigMap。

configMapGenerator

要基于文件来生成 ConfigMap,可以在 configMapGeneratorfiles 列表中添加表项。 下面是一个根据 .properties 文件中的数据条目来生成 ConfigMap 的示例:

# 生成一个  application.properties 文件
cat <<EOF >application.properties
FOO=Bar
EOF

cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-1
  files:
  - application.properties
EOF

所生成的 ConfigMap 可以使用下面的命令来检查:

kubectl kustomize ./

所生成的 ConfigMap 为:

apiVersion: v1
data:
  application.properties: |
    FOO=Bar    
kind: ConfigMap
metadata:
  name: example-configmap-1-8mbdf7882g

要从 env 文件生成 ConfigMap,请在 configMapGenerator 中的 envs 列表中添加一个条目。 下面是一个用来自 .env 文件的数据生成 ConfigMap 的例子:

# 创建一个 .env 文件
cat <<EOF >.env
FOO=Bar
EOF

cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-1
  envs:
  - .env
EOF

可以使用以下命令检查生成的 ConfigMap:

kubectl kustomize ./

生成的 ConfigMap 为:

apiVersion: v1
data:
  FOO: Bar
kind: ConfigMap
metadata:
  name: example-configmap-1-42cfbf598f

ConfigMap 也可基于字面的键值偶对来生成。要基于键值偶对来生成 ConfigMap, 在 configMapGeneratorliterals 列表中添加表项。下面是一个例子, 展示如何使用键值偶对中的数据条目来生成 ConfigMap 对象:

cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-2
  literals:
  - FOO=Bar
EOF

可以用下面的命令检查所生成的 ConfigMap:

kubectl kustomize ./

所生成的 ConfigMap 为:

apiVersion: v1
data:
  FOO: Bar
kind: ConfigMap
metadata:
  name: example-configmap-2-g2hdhfc6tk

要在 Deployment 中使用生成的 ConfigMap,使用 configMapGenerator 的名称对其进行引用。 Kustomize 将自动使用生成的名称替换该名称。

这是使用生成的 ConfigMap 的 deployment 示例:

# 创建一个 application.properties 文件
cat <<EOF >application.properties
FOO=Bar
EOF

cat <<EOF >deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app
        volumeMounts:
        - name: config
          mountPath: /config
      volumes:
      - name: config
        configMap:
          name: example-configmap-1
EOF

cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
configMapGenerator:
- name: example-configmap-1
  files:
  - application.properties
EOF

生成 ConfigMap 和 Deployment:

kubectl kustomize ./

生成的 Deployment 将通过名称引用生成的 ConfigMap:

apiVersion: v1
data:
  application.properties: |
    FOO=Bar    
kind: ConfigMap
metadata:
  name: example-configmap-1-g4hk9g2ff8
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: my-app
  name: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - image: my-app
        name: app
        volumeMounts:
        - mountPath: /config
          name: config
      volumes:
      - configMap:
          name: example-configmap-1-g4hk9g2ff8
        name: config

secretGenerator

你可以基于文件或者键值偶对来生成 Secret。要使用文件内容来生成 Secret, 在 secretGenerator 下面的 files 列表中添加表项。 下面是一个根据文件中数据来生成 Secret 对象的示例:

# 创建一个 password.txt 文件
cat <<EOF >./password.txt
username=admin
password=secret
EOF

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: example-secret-1
  files:
  - password.txt
EOF

所生成的 Secret 如下:

apiVersion: v1
data:
  password.txt: dXNlcm5hbWU9YWRtaW4KcGFzc3dvcmQ9c2VjcmV0Cg==
kind: Secret
metadata:
  name: example-secret-1-t2kt65hgtb
type: Opaque

要基于键值偶对字面值生成 Secret,先要在 secretGeneratorliterals 列表中添加表项。下面是基于键值偶对中数据条目来生成 Secret 的示例:

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: example-secret-2
  literals:
  - username=admin
  - password=secret
EOF

所生成的 Secret 如下:

apiVersion: v1
data:
  password: c2VjcmV0
  username: YWRtaW4=
kind: Secret
metadata:
  name: example-secret-2-t52t6g96d8
type: Opaque

与 ConfigMap 一样,生成的 Secret 可以通过引用 secretGenerator 的名称在 Deployment 中使用:

# 创建一个 password.txt 文件
cat <<EOF >./password.txt
username=admin
password=secret
EOF

cat <<EOF >deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app
        volumeMounts:
        - name: password
          mountPath: /secrets
      volumes:
      - name: password
        secret:
          secretName: example-secret-1
EOF

cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
secretGenerator:
- name: example-secret-1
  files:
  - password.txt
EOF

generatorOptions

所生成的 ConfigMap 和 Secret 都会包含内容哈希值后缀。 这是为了确保内容发生变化时,所生成的是新的 ConfigMap 或 Secret。 要禁止自动添加后缀的行为,用户可以使用 generatorOptions。 除此以外,为生成的 ConfigMap 和 Secret 指定贯穿性选项也是可以的。

cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-3
  literals:
  - FOO=Bar
generatorOptions:
  disableNameSuffixHash: true
  labels:
    type: generated
  annotations:
    note: generated
EOF

运行 kubectl kustomize ./ 来查看所生成的 ConfigMap:

apiVersion: v1
data:
  FOO: Bar
kind: ConfigMap
metadata:
  annotations:
    note: generated
  labels:
    type: generated
  name: example-configmap-3

设置贯穿性字段

在项目中为所有 Kubernetes 对象设置贯穿性字段是一种常见操作。 贯穿性字段的一些使用场景如下:

  • 为所有资源设置相同的名字空间
  • 为所有对象添加相同的前缀或后缀
  • 为对象添加相同的标签集合
  • 为对象添加相同的注解集合

下面是一个例子:

# 创建一个 deployment.yaml
cat <<EOF >./deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
EOF

cat <<EOF >./kustomization.yaml
namespace: my-namespace
namePrefix: dev-
nameSuffix: "-001"
labels:
  - pairs:
      app: bingo
    includeSelectors: true 
commonAnnotations:
  oncallPager: 800-555-1212
resources:
- deployment.yaml
EOF

执行 kubectl kustomize ./ 查看这些字段都被设置到 Deployment 资源上:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    oncallPager: 800-555-1212
  labels:
    app: bingo
  name: dev-nginx-deployment-001
  namespace: my-namespace
spec:
  selector:
    matchLabels:
      app: bingo
  template:
    metadata:
      annotations:
        oncallPager: 800-555-1212
      labels:
        app: bingo
    spec:
      containers:
      - image: nginx
        name: nginx

组织和定制资源

一种常见的做法是在项目中构造资源集合并将其放到同一个文件或目录中管理。 Kustomize 提供基于不同文件来组织资源并向其应用补丁或者其他定制的能力。

组织

Kustomize 支持组合不同的资源。kustomization.yaml 文件的 resources 字段定义配置中要包含的资源列表。 你可以将 resources 列表中的路径设置为资源配置文件的路径。 下面是由 Deployment 和 Service 构成的 NGINX 应用的示例:

# 创建 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# 创建 service.yaml 文件
cat <<EOF > service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF

# 创建 kustomization.yaml 来组织以上两个资源
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF

kubectl kustomize ./ 所得到的资源中既包含 Deployment 也包含 Service 对象。

定制

补丁文件(Patches)可以用来对资源执行不同的定制。 Kustomize 通过 StrategicMergeJson6902 使用 patches 字段支持不同的打补丁机制。

patches 字段包含了一列按指定顺序应用的补丁。 补丁目标通过 groupversionkindnamenamespacelabelSelectorannotationSelector 来选择资源。

建议使用只做一件事的小补丁。例如,创建一个用于增加部署副本数量的补丁,以及另一个用于设置内存限制的补丁。 目标资源是通过补丁文件中的 groupversionkindname 字段进行匹配的。

# 创建 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# 生成一个补丁 increase_replicas.yaml
cat <<EOF > increase_replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
EOF

# 生成另一个补丁 set_memory.yaml
cat <<EOF > set_memory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  template:
    spec:
      containers:
      - name: my-nginx
        resources:
          limits:
            memory: 512Mi
EOF

cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
patches:
  - path: increase_replicas.yaml
  - path: set_memory.yaml
EOF

执行 kubectl kustomize ./ 来查看 Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - image: nginx
        name: my-nginx
        ports:
        - containerPort: 80
        resources:
          limits:
            memory: 512Mi

并非所有资源或者字段都支持策略性合并(strategicMerge)补丁。为了支持对任何资源的任何字段进行修改, Kustomize 提供通过 Json6902 来应用 JSON 补丁的能力。 为了给 Json6902 补丁找到正确的资源,必须在 kustomization.yaml 文件中设置 target 字段。

例如,可以通过 Json6902 补丁来增加 Deployment 对象的副本数量。 目标资源是通过 target 字段中的 groupversionkindname 进行匹配的。

# 创建一个 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# 创建一个 JSON 补丁文件
cat <<EOF > patch.yaml
- op: replace
  path: /spec/replicas
  value: 3
EOF

# 创建一个 kustomization.yaml
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml

patches:
- target:
    group: apps
    version: v1
    kind: Deployment
    name: my-nginx
  path: patch.yaml
EOF

执行 kubectl kustomize ./ 以查看 replicas 字段被更新:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - image: nginx
        name: my-nginx
        ports:
        - containerPort: 80

除了补丁之外,Kustomize 还提供定制容器镜像或者将其他对象的字段值注入到容器中的能力,并且不需要创建补丁。 例如,你可以通过在 kustomization.yaml 文件的 images 字段设置新的镜像来更改容器中使用的镜像。

cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
images:
- name: nginx
  newName: my.image.registry/nginx
  newTag: 1.4.0
EOF

执行 kubectl kustomize ./ 以查看所使用的镜像已被更新:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - image: my.image.registry/nginx:1.4.0
        name: my-nginx
        ports:
        - containerPort: 80

有些时候,Pod 中运行的应用可能需要使用来自其他对象的配置值。 例如,某 Deployment 对象的 Pod 需要从环境变量或命令行参数中读取读取 Service 的名称。 由于在 kustomization.yaml 文件中添加 namePrefixnameSuffix 时 Service 名称可能发生变化,建议不要在命令参数中硬编码 Service 名称。 对于这种使用场景,Kustomize 可以通过 replacements 将 Service 名称注入到容器中。

# 创建一个 deployment.yaml 文件(引用此处的文档分隔符)
cat <<'EOF' > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        command: ["start", "--host", "MY_SERVICE_NAME_PLACEHOLDER"]
EOF

# 创建一个 service.yaml 文件
cat <<EOF > service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF

cat <<EOF >./kustomization.yaml
namePrefix: dev-
nameSuffix: "-001"

resources:
- deployment.yaml
- service.yaml

replacements:
- source:
    kind: Service
    name: my-nginx
    fieldPath: metadata.name
  targets:
  - select:
      kind: Deployment
      name: my-nginx
    fieldPaths:
    - spec.template.spec.containers.0.command.2
EOF

执行 kubectl kustomize ./ 以查看注入到容器中的 Service 名称是 dev-my-nginx-001

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dev-my-nginx-001
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - command:
        - start
        - --host
        - dev-my-nginx-001
        image: nginx
        name: my-nginx

基准(Bases)与覆盖(Overlays)

Kustomize 中有 基准(bases)覆盖(overlays) 的概念区分。 基准 是包含 kustomization.yaml 文件的一个目录,其中包含一组资源及其相关的定制。 基准可以是本地目录或者来自远程仓库的目录,只要其中存在 kustomization.yaml 文件即可。 覆盖 也是一个目录,其中包含将其他 kustomization 目录当做 bases 来引用的 kustomization.yaml 文件。 基准不了解覆盖的存在,且可被多个覆盖所使用。

kustomization.yaml 文件位于 overlay 目录中,可以引用多个 bases, 将这些 base 中定义的所有资源合并为一个统一的配置。 此外,它还可以在这些资源之上应用定制化修改以满足特定需求。

以下是 base 的一个示例:

# 创建一个包含基准的目录
mkdir base
# 创建 base/deployment.yaml
cat <<EOF > base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
EOF

# 创建 base/service.yaml 文件
cat <<EOF > base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF

# 创建 base/kustomization.yaml
cat <<EOF > base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF

此基准可在多个覆盖中使用。你可以在不同的覆盖中添加不同的 namePrefix 或其他贯穿性字段。 下面是两个使用同一基准的覆盖:

mkdir dev
cat <<EOF > dev/kustomization.yaml
resources:
- ../base
namePrefix: dev-
EOF

mkdir prod
cat <<EOF > prod/kustomization.yaml
resources:
- ../base
namePrefix: prod-
EOF

如何使用 Kustomize 来应用、查看和删除对象

kubectl 命令中使用 --kustomize-k 参数来识别被 kustomization.yaml 所管理的资源。 注意 -k 要指向一个 kustomization 目录。例如:

kubectl apply -k <kustomization 目录>/

假定使用下面的 kustomization.yaml

# 创建 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# 创建 kustomization.yaml
cat <<EOF >./kustomization.yaml
namePrefix: dev-
labels:
  - pairs:
      app: my-nginx
    includeSelectors: true 
resources:
- deployment.yaml
EOF

执行下面的命令来应用 Deployment 对象 dev-my-nginx

> kubectl apply -k ./
deployment.apps/dev-my-nginx created

运行下面的命令之一来查看 Deployment 对象 dev-my-nginx

kubectl get -k ./
kubectl describe -k ./

执行下面的命令来比较 Deployment 对象 dev-my-nginx 与清单被应用之后集群将处于的状态:

kubectl diff -k ./

执行下面的命令删除 Deployment 对象 dev-my-nginx

> kubectl delete -k ./
deployment.apps "dev-my-nginx" deleted

Kustomize 功能特性列表

字段类型解释
bases[]string列表中每个条目都应能解析为一个包含 kustomization.yaml 文件的目录
commonAnnotationsmap[string]string要添加到所有资源的注解
commonLabelsmap[string]string要添加到所有资源和选择算符的标签
configMapGenerator[]ConfigMapArgs列表中的每个条目都会生成一个 ConfigMap
configurations[]string列表中每个条目都应能解析为一个包含 Kustomize 转换器配置 的文件
crds[]string列表中每个条目都应能够解析为 Kubernetes 类别的 OpenAPI 定义文件
generatorOptionsGeneratorOptions更改所有 ConfigMap 和 Secret 生成器的行为
images[]Image每个条目都用来更改镜像的名称、标记与/或摘要,不必生成补丁
labelsmap[string]string添加标签而不自动注入对应的选择器
namePrefixstring此字段的值将被添加到所有资源名称前面
nameSuffixstring此字段的值将被添加到所有资源名称后面
patchesJson6902[]Patch列表中每个条目都能解析为一个 Kubernetes 对象和一个 JSON 补丁
patchesStrategicMerge[]string列表中每个条目都能解析为某 Kubernetes 对象的策略性合并补丁
replacements[]Replacements将 resource 字段的值复制到任意数量的指定目标
resources[]string列表中的每个条目都必须能够解析为现有的资源配置文件
secretGenerator[]SecretArgs列表中的每个条目都会生成一个 Secret
vars[]Var每个条目用来从某资源的字段来析取文字

接下来

3 - 使用指令式命令管理 Kubernetes 对象

使用构建在 kubectl 命令行工具中的指令式命令可以直接快速创建、更新和删除 Kubernetes 对象。本文档解释这些命令的组织方式以及如何使用它们来管理活跃对象。

准备开始

安装kubectl

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

权衡取舍

kubectl 工具能够支持三种对象管理方式:

  • 指令式命令
  • 指令式对象配置
  • 声明式对象配置

关于每种对象管理的优缺点的讨论,可参见 Kubernetes 对象管理

如何创建对象

kubectl 工具支持动词驱动的命令,用来创建一些最常见的对象类别。 命令的名称设计使得不熟悉 Kubernetes 对象类型的用户也能做出判断。

  • run:创建一个新的 Pod 来运行一个容器。
  • expose:创建一个新的 Service 对象为若干 Pod 提供流量负载均衡。
  • autoscale:创建一个新的 Autoscaler 对象来自动对某控制器(例如:Deployment) 执行水平扩缩。

kubectl 命令也支持一些对象类型驱动的创建命令。 这些命令可以支持更多的对象类别,并且在其动机上体现得更为明显, 不过要求用户了解它们所要创建的对象的类别。

  • create <对象类别> [<子类别>] <实例名称>

某些对象类别拥有自己的子类别,可以在 create 命令中设置。 例如,Service 对象有 ClusterIP、LoadBalancer 和 NodePort 三种子类别。 下面是一个创建 NodePort 子类别的 Service 的示例:

kubectl create service nodeport <服务名称>

在前述示例中,create service nodeport 命令也称作 create service 命令的子命令。 可以使用 -h 标志找到一个子命令所支持的参数和标志。

kubectl create service nodeport -h

如何更新对象

kubectl 命令也支持一些动词驱动的命令,用来执行一些常见的更新操作。 这些命令的设计是为了让一些不了解 Kubernetes 对象的用户也能执行更新操作, 但不需要了解哪些字段必须设置:

  • scale:对某控制器进行水平扩缩以便通过更新控制器的副本个数来添加或删除 Pod。
  • annotate:为对象添加或删除注解。
  • label:为对象添加或删除标签。

kubectl 命令也支持由对象的某一方面来驱动的更新命令。 设置对象的这一方面可能对不同类别的对象意味着不同的字段:

  • set <字段>:设置对象的某一方面。

kubectl 工具支持以下额外的方式用来直接更新活跃对象,不过这些操作要求 用户对 Kubernetes 对象的模式定义有很好的了解:

  • edit:通过在编辑器中打开活跃对象的配置,直接编辑其原始配置。
  • patch:通过使用补丁字符串(Patch String)直接更改某活跃对象的特定字段。 关于补丁字符串的更详细信息,参见 API 约定 的 patch 节。

如何删除对象

你可以使用 delete 命令从集群中删除一个对象:

  • delete <类别>/<名称>
kubectl delete deployment/nginx

如何查看对象

用来打印对象信息的命令有好几个:

  • get:打印匹配到的对象的基本信息。使用 get -h 可以查看选项列表。
  • describe:打印匹配到的对象的详细信息的汇集版本。
  • logs:打印 Pod 中运行的容器的 stdout 和 stderr 输出。

使用 set 命令在创建对象之前修改对象

有些对象字段在 create 命令中没有对应的标志。 在这些场景中,你可以使用 setcreate 命令的组合来在对象创建之前设置字段值。 这是通过将 create 命令的输出用管道方式传递给 set 命令来实现的,最后执行 create 命令来创建对象。 下面是一个例子:

kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run=client | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f -
  1. 命令 kubectl create service -o yaml --dry-run=client 创建 Service 的配置, 但将其以 YAML 格式在标准输出上打印而不是发送给 API 服务器。
  2. 命令 kubectl set selector --local -f - -o yaml 从标准输入读入配置, 并将更新后的配置以 YAML 格式输出到标准输出。
  3. 命令 kubectl create -f - 使用标准输入上获得的配置创建对象。

在创建之前使用 --edit 更改对象

你可以用 kubectl create --edit 来在对象被创建之前执行任意的变更。 下面是一个例子:

kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run=client > /tmp/srv.yaml
kubectl create --edit -f /tmp/srv.yaml
  1. 命令 kubectl create service 创建 Service 的配置并将其保存到 /tmp/srv.yaml 文件。
  2. 命令 kubectl create --edit 在创建 Service 对象打开其配置文件进行编辑。

接下来

4 - 使用配置文件对 Kubernetes 对象进行命令式管理

可以使用 kubectl 命令行工具以及用 YAML 或 JSON 编写的对象配置文件来创建、更新和删除 Kubernetes 对象。 本文档说明了如何使用配置文件定义和管理对象。

准备开始

安装 kubectl

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

权衡

kubectl 工具支持三种对象管理:

  • 命令式命令
  • 命令式对象配置
  • 声明式对象配置

参见 Kubernetes 对象管理 中关于每种对象管理的优缺点的讨论。

如何创建对象

你可以使用 kubectl create -f 从配置文件创建一个对象。 更多细节参阅 kubernetes API 参考

  • kubectl create -f <filename|url>

如何更新对象

你可以使用 kubectl replace -f 根据配置文件更新活动对象。

  • kubectl replace -f <filename|url>

如何删除对象

你可以使用 kubectl delete -f 删除配置文件中描述的对象。

  • kubectl delete -f <filename|url>

如何查看对象

你可以使用 kubectl get -f 查看有关配置文件中描述的对象的信息。

  • kubectl get -f <filename|url> -o yaml

-o yaml 标志指定打印完整的对象配置。使用 kubectl get -h 查看选项列表。

局限性

当完全定义每个对象的配置并将其记录在其配置文件中时,createreplacedelete 命令会很好的工作。 但是,当更新一个活动对象,并且更新没有合并到其配置文件中时,下一次执行 replace 时,更新将丢失。 如果控制器,例如 HorizontalPodAutoscaler ,直接对活动对象进行更新,则会发生这种情况。 这有一个例子:

  1. 从配置文件创建一个对象。
  2. 另一个源通过更改某些字段来更新对象。
  3. 从配置文件中替换对象。在步骤2中所做的其他源的更改将丢失。

如果需要支持同一对象的多个编写器,则可以使用 kubectl apply 来管理该对象。

从 URL 创建和编辑对象而不保存配置

假设你具有对象配置文件的 URL。 你可以在创建对象之前使用 kubectl create --edit 对配置进行更改。 这对于指向可以由读者修改的配置文件的教程和任务特别有用。

kubectl create -f <url> --edit

从命令式命令迁移到命令式对象配置

从命令式命令迁移到命令式对象配置涉及几个手动步骤。

  1. 将活动对象导出到本地对象配置文件:

    kubectl get <kind>/<name> -o yaml > <kind>_<name>.yaml
    
  1. 从对象配置文件中手动删除状态字段。
  1. 对于后续的对象管理,只能使用 replace

    kubectl replace -f <kind>_<name>.yaml
    

定义控制器选择器和 PodTemplate 标签

推荐的方法是定义单个不变的 PodTemplate 标签,该标签仅由控制器选择器使用,而没有其他语义。

标签示例:

selector:
  matchLabels:
      controller-selector: "apps/v1/deployment/nginx"
template:
  metadata:
    labels:
      controller-selector: "apps/v1/deployment/nginx"

接下来

5 - 使用 kubectl patch 更新 API 对象

使用 kubectl patch 更新 Kubernetes API 对象。做一个策略性的合并 patch 或 JSON 合并 patch。

这个任务展示如何使用 kubectl patch 就地更新 API 对象。 这个任务中的练习演示了一个策略性合并 patch 和一个 JSON 合并 patch。

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

使用策略合并 patch 更新 Deployment

下面是具有两个副本的 Deployment 的配置文件。每个副本是一个 Pod,有一个容器:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: patch-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: patch-demo-ctr
        image: nginx
      tolerations:
      - effect: NoSchedule
        key: dedicated
        value: test-team

创建 Deployment:

kubectl apply -f https://k8s.io/examples/application/deployment-patch.yaml

查看与 Deployment 相关的 Pod:

kubectl get pods

输出显示 Deployment 有两个 Pod。1/1 表示每个 Pod 有一个容器:

NAME                        READY     STATUS    RESTARTS   AGE
patch-demo-28633765-670qr   1/1       Running   0          23s
patch-demo-28633765-j5qs3   1/1       Running   0          23s

把运行的 Pod 的名字记下来。稍后,你将看到这些 Pod 被终止并被新的 Pod 替换。

此时,每个 Pod 都有一个运行 nginx 镜像的容器。现在假设你希望每个 Pod 有两个容器:一个运行 nginx,另一个运行 redis。

创建一个名为 patch-file.yaml 的文件。内容如下:

spec:
  template:
    spec:
      containers:
      - name: patch-demo-ctr-2
        image: redis

修补你的 Deployment:

kubectl patch deployment patch-demo --patch-file patch-file.yaml

查看修补后的 Deployment:

kubectl get deployment patch-demo --output yaml

输出显示 Deployment 中的 PodSpec 有两个容器:

containers:
- image: redis
  imagePullPolicy: Always
  name: patch-demo-ctr-2
  ...
- image: nginx
  imagePullPolicy: Always
  name: patch-demo-ctr
  ...

查看与 patch Deployment 相关的 Pod:

kubectl get pods

输出显示正在运行的 Pod 与以前运行的 Pod 有不同的名称。Deployment 终止了旧的 Pod, 并创建了两个符合更新后的 Deployment 规约的新 Pod。2/2 表示每个 Pod 有两个容器:

NAME                          READY     STATUS    RESTARTS   AGE
patch-demo-1081991389-2wrn5   2/2       Running   0          1m
patch-demo-1081991389-jmg7b   2/2       Running   0          1m

仔细查看其中一个 patch-demo Pod:

kubectl get pod <your-pod-name> --output yaml

输出显示 Pod 有两个容器:一个运行 nginx,一个运行 redis:

containers:
- image: redis
  ...
- image: nginx
  ...

策略性合并类的 patch 的说明

你在前面的练习中所做的 patch 称为 策略性合并 patch(Strategic Merge Patch)。 请注意,patch 没有替换 containers 列表。相反,它向列表中添加了一个新 Container。换句话说, patch 中的列表与现有列表合并。当你在列表中使用策略性合并 patch 时,并不总是这样。 在某些情况下,列表是替换的,而不是合并的。

对于策略性合并 patch,列表可以根据其 patch 策略进行替换或合并。 patch 策略由 Kubernetes 源代码中字段标记中的 patchStrategy 键的值指定。 例如,PodSpec 结构体的 Containers 字段的 patchStrategymerge

type PodSpec struct {
  ...
  Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" ...`
  ...
}

你还可以在 OpenApi 规范中看到 patch 策略:

"io.k8s.api.core.v1.PodSpec": {
    ...,
    "containers": {
        "description": "List of containers belonging to the pod.  ...."
    },
    "x-kubernetes-patch-merge-key": "name",
    "x-kubernetes-patch-strategy": "merge"
}

你可以在 Kubernetes API 文档 中看到 patch 策略。

创建一个名为 patch-file-tolerations.yaml 的文件。内容如下:

spec:
  template:
    spec:
      tolerations:
      - effect: NoSchedule
        key: disktype
        value: ssd

对 Deployment 执行 patch 操作:

kubectl patch deployment patch-demo --patch-file patch-file-tolerations.yaml

查看修补后的 Deployment:

kubectl get deployment patch-demo --output yaml

输出结果显示 Deployment 中的 PodSpec 只有一个容忍度设置:

tolerations:
- effect: NoSchedule
  key: disktype
  value: ssd

请注意,PodSpec 中的 tolerations 列表被替换,而不是合并。这是因为 PodSpec 的 tolerations 的字段标签中没有 patchStrategy 键。所以策略合并 patch 操作使用默认的 patch 策略,也就是 replace

type PodSpec struct {
  ...
  Tolerations []Toleration `json:"tolerations,omitempty" protobuf:"bytes,22,opt,name=tolerations"`
  ...
}

使用 JSON 合并 patch 更新 Deployment

策略性合并 patch 不同于 JSON 合并 patch。 使用 JSON 合并 patch,如果你想更新列表,你必须指定整个新列表。新的列表完全取代现有的列表。

kubectl patch 命令有一个 type 参数,你可以将其设置为以下值之一:

参数值合并类型
jsonJSON Patch, RFC 6902
mergeJSON Merge Patch, RFC 7386
strategic策略合并 patch

有关 JSON patch 和 JSON 合并 patch 的比较,查看 JSON patch 和 JSON 合并 patch

type 参数的默认值是 strategic。在前面的练习中,我们做了一个策略性的合并 patch。

下一步,在相同的 Deployment 上执行 JSON 合并 patch。创建一个名为 patch-file-2 的文件。内容如下:

spec:
  template:
    spec:
      containers:
      - name: patch-demo-ctr-3
        image: gcr.io/google-samples/hello-app:2.0

在 patch 命令中,将 type 设置为 merge

kubectl patch deployment patch-demo --type merge --patch-file patch-file-2.yaml

查看修补后的 Deployment:

kubectl get deployment patch-demo --output yaml

patch 中指定的 containers 列表只有一个 Container。 输出显示你所给出的 Container 列表替换了现有的 containers 列表。

spec:
  containers:
  - image: gcr.io/google-samples/hello-app:2.0
    ...
    name: patch-demo-ctr-3

列出正运行的 Pod:

kubectl get pods

在输出中,你可以看到已经终止了现有的 Pod,并创建了新的 Pod。1/1 表示每个新 Pod 只运行一个容器。

NAME                          READY     STATUS    RESTARTS   AGE
patch-demo-1307768864-69308   1/1       Running   0          1m
patch-demo-1307768864-c86dc   1/1       Running   0          1m

使用带 retainKeys 策略的策略合并 patch 更新 Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: retainkeys-demo
spec:
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 30%
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: retainkeys-demo-ctr
        image: nginx

创建 Deployment:

kubectl apply -f https://k8s.io/examples/application/deployment-retainkeys.yaml

这时,Deployment 被创建,并使用 RollingUpdate 策略。

创建一个名为 patch-file-no-retainkeys.yaml 的文件,内容如下:

spec:
  strategy:
    type: Recreate

修补你的 Deployment:

kubectl patch deployment retainkeys-demo --type strategic --patch-file patch-file-no-retainkeys.yaml

在输出中,你可以看到,当 spec.strategy.rollingUpdate 已经拥有取值定义时, 将其 type 设置为 Recreate 是不可能的。

The Deployment "retainkeys-demo" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'

更新 type 取值的同时移除 spec.strategy.rollingUpdate 现有值的方法是为策略性合并操作设置 retainKeys 策略:

创建另一个名为 patch-file-retainkeys.yaml 的文件,内容如下:

spec:
  strategy:
    $retainKeys:
    - type
    type: Recreate

使用此 patch,我们表达了希望只保留 strategy 对象的 type 键。 这样,在 patch 操作期间 rollingUpdate 会被删除。

使用新的 patch 重新修补 Deployment:

kubectl patch deployment retainkeys-demo --type strategic --patch-file patch-file-retainkeys.yaml

检查 Deployment 的内容:

kubectl get deployment retainkeys-demo --output yaml

输出显示 Deployment 中的 strategy 对象不再包含 rollingUpdate 键:

spec:
  strategy:
    type: Recreate
  template:

关于使用 retainKeys 策略的策略合并 patch 操作的说明

在前文练习中所执行的称作 retainKeys 策略的策略合并 patch(Strategic Merge Patch with retainKeys Strategy)。 这种方法引入了一种新的 $retainKey 指令,具有如下策略:

  • 其中包含一个字符串列表;
  • 所有需要被保留的字段必须在 $retainKeys 列表中给出;
  • 对于已有的字段,会和对象上对应的内容合并;
  • 在修补操作期间,未找到的字段都会被清除;
  • 列表 $retainKeys 中的所有字段必须 patch 操作所给字段的超集,或者与之完全一致。

策略 retainKeys 并不能对所有对象都起作用。它仅对那些 Kubernetes 源码中 patchStrategy 字段标志值包含 retainKeys 的字段有用。 例如 DeploymentSpec 结构的 Strategy 字段就包含了 patchStrategyretainKeys 的标志。

type DeploymentSpec struct {
  ...
  // +patchStrategy=retainKeys
  Strategy DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys" ...`
  ...
}

你也可以查看 OpenAPI 规范中的 retainKeys 策略:

"io.k8s.api.apps.v1.DeploymentSpec": {
    ...,
    "strategy": {
        "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStrategy",
        "description": "The deployment strategy to use to replace existing pods with new ones.",
        "x-kubernetes-patch-strategy": "retainKeys"
    },
    ....
}

而且你也可以在 Kubernetes API 文档中看到 retainKey 策略。

kubectl patch 命令的其他形式

kubectl patch 命令使用 YAML 或 JSON。它可以接受以文件形式提供的补丁,也可以接受直接在命令行中给出的补丁。

创建一个文件名称是 patch-file.json 内容如下:

{
   "spec": {
      "template": {
         "spec": {
            "containers": [
               {
                  "name": "patch-demo-ctr-2",
                  "image": "redis"
               }
            ]
         }
      }
   }
}

以下命令是等价的:

kubectl patch deployment patch-demo --patch-file patch-file.yaml
kubectl patch deployment patch-demo --patch 'spec:\n template:\n  spec:\n   containers:\n   - name: patch-demo-ctr-2\n     image: redis'

kubectl patch deployment patch-demo --patch-file patch-file.json
kubectl patch deployment patch-demo --patch '{"spec": {"template": {"spec": {"containers": [{"name": "patch-demo-ctr-2","image": "redis"}]}}}}'

使用 kubectl patch--subresource 更新一个对象的副本数

特性状态: Kubernetes v1.24 [alpha]

使用 kubectl 命令(如 get、patch、edit 和 replace)时带上 --subresource=[subresource-name] 标志, 可以获取和更新资源的 statusscale 子资源(适用于 kubectl v1.24 或更高版本)。 这个标志可用于带有 statusscale 子资源的所有 API 资源 (内置资源和 CR 资源)。 Deployment 是支持这些子资源的其中一个例子。

下面是有两个副本的 Deployment 的清单。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # 告知 Deployment 运行 2 个与该模板匹配的 Pod
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

创建 Deployment:

kubectl apply -f https://k8s.io/examples/application/deployment.yaml

查看与 Deployment 关联的 Pod:

kubectl get pods -l app=nginx

在输出中,你可以看到此 Deployment 有两个 Pod。例如:

NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7fb96c846b-22567   1/1     Running   0          47s
nginx-deployment-7fb96c846b-mlgns   1/1     Running   0          47s

现在用 --subresource=[subresource-name] 标志修补此 Deployment:

kubectl patch deployment nginx-deployment --subresource='scale' --type='merge' -p '{"spec":{"replicas":3}}'

输出为:

scale.autoscaling/nginx-deployment patched

查看与你所修补的 Deployment 关联的 Pod:

kubectl get pods -l app=nginx

在输出中,你可以看到一个新的 Pod 被创建,因此现在你有 3 个正在运行的 Pod。

NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7fb96c846b-22567   1/1     Running   0          107s
nginx-deployment-7fb96c846b-lxfr2   1/1     Running   0          14s
nginx-deployment-7fb96c846b-mlgns   1/1     Running   0          107s

查看所修补的 Deployment:

kubectl get deployment nginx-deployment -o yaml
...
spec:
  replicas: 3
  ...
status:
  ...
  availableReplicas: 3
  readyReplicas: 3
  replicas: 3

总结

在本练习中,你使用 kubectl patch 更改了 Deployment 对象的当前配置。 你没有更改最初用于创建 Deployment 对象的配置文件。 用于更新 API 对象的其他命令包括 kubectl annotatekubectl editkubectl replacekubectl scalekubectl apply

接下来

6 - 使用存储版本迁移功能来迁移 Kubernetes 对象

特性状态: Kubernetes v1.30 [alpha] (enabled by default: false)

Kubernetes 依赖主动重写的 API 数据来支持与静态存储相关的一些维护活动。 两个著名的例子是已存储资源的版本化模式(即针对给定资源的首选存储模式从 v1 更改为 v2) 和静态加密(即基于数据加密方式的变化来重写过时的数据)。

准备开始

安装 kubectl

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.30. 要获知版本信息,请输入 kubectl version.

确保你的集群启用了 StorageVersionMigratorInformerResourceVersion 特性门控。 你需要有控制平面管理员权限才能执行此项变更。

在 API 服务器上将运行时配置 storagemigration.k8s.io/v1alpha1 设为 true,启用存储版本迁移 REST API。 有关如何执行此操作的更多信息,请阅读启用或禁用 Kubernetes API

使用存储版本迁移来重新加密 Kubernetes Secret

  • 首先配置 KMS 驱动, 以便使用如下加密配置来加密 etcd 中的静态数据。

    kind: EncryptionConfiguration
    apiVersion: apiserver.config.k8s.io/v1
    resources:
      - resources:
          - secrets
        providers:
          - aescbc:
            keys:
              - name: key1
                secret: c2VjcmV0IGlzIHNlY3VyZQ==
    

    确保通过将 --encryption-provider-config-automatic-reload 设置为 true,允许自动重新加载加密配置文件。

  • 使用 kubectl 创建 Secret。

    kubectl create secret generic my-secret --from-literal=key1=supersecret
    
  • 验证该 Secret 对象的序列化数据带有前缀 k8s:enc:aescbc:v1:key1

  • 按照以下方式更新加密配置文件,以轮换加密密钥。

    kind: EncryptionConfiguration
    apiVersion: apiserver.config.k8s.io/v1
    resources:
      - resources:
          - secrets
        providers:
          - aescbc:
            keys:
              - name: key2
                secret: c2VjcmV0IGlzIHNlY3VyZSwgaXMgaXQ/
          - aescbc:
            keys:
              - name: key1
                secret: c2VjcmV0IGlzIHNlY3VyZQ==
    
  • 要确保之前创建的 Secret my-secret 使用新密钥 key2 进行重新加密,你将使用存储版本迁移

  • 创建以下名为 migrate-secret.yaml 的 StorageVersionMigration 清单:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1alpha1
    metadata:
      name: secrets-migration
    spec:
      resource:
        group: ""
        version: v1
        resource: secrets
    

    使用以下 kubectl 命令创建对象:

    kubectl apply -f migrate-secret.yaml
    
  • 通过检查 StorageVersionMigration 的 .status 来监控 Secret 的迁移。 成功的迁移应将其 Succeeded 状况设置为 true。 获取 StorageVersionMigration 对象的方式如下:

    kubectl get storageversionmigration.storagemigration.k8s.io/secrets-migration -o yaml
    

    输出类似于:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1alpha1
    metadata:
      name: secrets-migration
      uid: 628f6922-a9cb-4514-b076-12d3c178967c
      resourceVersion: "90"
      creationTimestamp: "2024-03-12T20:29:45Z"
    spec:
      resource:
        group: ""
        version: v1
        resource: secrets
    status:
      conditions:
        - type: Running
          status: "False"
          lastUpdateTime: "2024-03-12T20:29:46Z"
          reason: StorageVersionMigrationInProgress
        - type: Succeeded
          status: "True"
          lastUpdateTime: "2024-03-12T20:29:46Z"
          reason: StorageVersionMigrationSucceeded
      resourceVersion: "84"
    
  • 验证存储的 Secret 现在带有前缀 k8s:enc:aescbc:v1:key2

更新 CRD 的首选存储模式

考虑这样一种情况: 用户创建了 CustomResourceDefinition (CRD) 来提供自定义资源 (CR),并将其设置为首选的存储模式。 当需要引入 CRD 的 v2 版本时,只需提供转换 Webhook 就可以为 v2 版本提供服务。 基于转换 Webhook 的方式能够实现更平滑的过渡,用户可以使用 v1 或 v2 模式创建 CR,并通过合适的 Webhook 执行必要的模式转换。 在将 v2 设置为首选的存储模式版本之前,重要的是要确保将当前已存储为 v1 的所有 CR 已被迁移到 v2。 这种迁移可以通过使用存储版本迁移将所有 CR 从 v1 迁移到 v2 来达成。

  • 如下针对名为 test-crd.yaml 的 CRD 创建一个清单:

    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    metadata:
      name: selfierequests.stable.example.com
    spec:
      group: stable.example.com
      names:
        plural: SelfieRequests
        singular: SelfieRequest
        kind: SelfieRequest
        listKind: SelfieRequestList
      scope: Namespaced
      versions:
        - name: v1
          served: true
          storage: true
          schema:
            openAPIV3Schema:
              type: object
              properties:
                hostPort:
                  type: string
      conversion:
        strategy: Webhook
        webhook:
          clientConfig:
            url: https://127.0.0.1:9443/crdconvert
            caBundle: <CABundle info>
        conversionReviewVersions:
          - v1
          - v2
    

    使用 kubectl 创建 CRD:

    kubectl apply -f test-crd.yaml
    
  • 为 testcrd 示例创建一个清单。命名为 cr1.yaml 并使用以下内容:

    apiVersion: stable.example.com/v1
    kind: SelfieRequest
    metadata:
      name: cr1
      namespace: default
    

    使用 kubectl 创建 CR:

    kubectl apply -f cr1.yaml
    
  • 通过从 etcd 获取对象来验证 CR 是否以 v1 格式被写入和存储。

    ETCDCTL_API=3 etcdctl get /kubernetes.io/stable.example.com/testcrds/default/cr1 [...] | hexdump -C
    

    其中 [...] 包含连接到 etcd 服务器的额外参数。

  • 如下更新 CRD test-crd.yaml,将 v2 版本设置为 served 和 storage,并将 v1 设置为仅 served:

    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    metadata:
    name: selfierequests.stable.example.com
    spec:
      group: stable.example.com
      names:
        plural: SelfieRequests
        singular: SelfieRequest
        kind: SelfieRequest
        listKind: SelfieRequestList
      scope: Namespaced
      versions:
        - name: v2
          served: true
          storage: true
          schema:
            openAPIV3Schema:
              type: object
              properties:
                host:
                  type: string
                port:
                  type: string
        - name: v1
          served: true
          storage: false
          schema:
            openAPIV3Schema:
              type: object
              properties:
                hostPort:
                  type: string
      conversion:
        strategy: Webhook
        webhook:
          clientConfig:
            url: "https://127.0.0.1:9443/crdconvert"
            caBundle: <CABundle info>
          conversionReviewVersions:
            - v1
            - v2
    

    使用 kubectl 更新 CRD:

    kubectl apply -f test-crd.yaml
    
  • 如下创建名为 cr2.yaml 的 CR 资源文件:

    apiVersion: stable.example.com/v2
    kind: SelfieRequest
    metadata:
      name: cr2
      namespace: default
    
  • 使用 kubectl 创建 CR:

    kubectl apply -f cr2.yaml
    
  • 通过从 etcd 获取对象来验证 CR 是否以 v2 格式被写入和存储。

    ETCDCTL_API=3 etcdctl get /kubernetes.io/stable.example.com/testcrds/default/cr2 [...] | hexdump -C
    

    其中 [...] 包含连接到 etcd 服务器的额外参数。

  • 如下创建名为 migrate-crd.yaml 的 StorageVersionMigration 清单:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1alpha1
    metadata:
      name: crdsvm
    spec:
      resource:
        group: stable.example.com
        version: v1
        resource: SelfieRequest
    

    使用如下 kubectl 命令创建此对象:

    kubectl apply -f migrate-crd.yaml
    
  • 使用 status 监控 Secret 的迁移。 若迁移成功,应在 status 字段中将 Succeeded 状况设置为 "True"。 获取迁移资源的方式如下:

    kubectl get storageversionmigration.storagemigration.k8s.io/crdsvm -o yaml
    

    输出类似于:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1alpha1
    metadata:
      name: crdsvm
      uid: 13062fe4-32d7-47cc-9528-5067fa0c6ac8
      resourceVersion: "111"
      creationTimestamp: "2024-03-12T22:40:01Z"
    spec:
      resource:
        group: stable.example.com
        version: v1
        resource: testcrds
    status:
      conditions:
        - type: Running
          status: "False"
          lastUpdateTime: "2024-03-12T22:40:03Z"
          reason: StorageVersionMigrationInProgress
        - type: Succeeded
          status: "True"
          lastUpdateTime: "2024-03-12T22:40:03Z"
          reason: StorageVersionMigrationSucceeded
      resourceVersion: "106"
    
  • 通过从 etcd 获取对象来验证之前创建的 cr1 是否现在以 v2 格式被写入和存储。

    ETCDCTL_API=3 etcdctl get /kubernetes.io/stable.example.com/testcrds/default/cr1 [...] | hexdump -C
    

    其中 [...] 包含连接到 etcd 服务器的额外参数。