【K8s任务】使用配置文件对 Kubernetes 对象进行声明式管理
参考:https://kubernetes.io/zh/docs/tasks/manage-kubernetes-objects/declarative-config/
你可以通过在一个目录中存储多个对象配置文件、并使用 kubectl apply 来递归地创建和更新对象来创建、更新和删除 Kubernetes 对象。 这种方法会保留对现有对象已作出的修改,而不会将这些更改写回到对象配置文件中。 kubectl diff 也会给你呈现 apply 将作出的变更的预览。
如何创建对象
使用 kubectl apply 来创建指定目录中配置文件所定义的所有对象,除非对应对象已经存在:
kubectl apply -f <目录>/
此操作会在每个对象上设置 kubectl.kubernetes.io/last-applied-configuration: '{...}' 注解。注解值中包含了用来创建对象的配置文件的内容。
说明: 添加 -R 标志可以递归地处理目录。
执行 kubectl diff 可以打印出将被创建的对象:
kubectl diff -f https://k8s.io/examples/application/simple_deployment.yaml
说明:
diff 使用服务器端试运行(Server-side Dry-run) 功能特性;而该功能特性需要在 kube-apiserver 上启用。
由于 diff 操作会使用试运行模式执行服务器端 apply 请求,因此需要为 用户配置 PATCH、CREATE 和 UPDATE 操作权限。 参阅试运行授权 了解详情。
如何更新对象
你也可以使用 kubectl apply 来更新某个目录中定义的所有对象,即使那些对象已经存在。 这一操作会隐含以下行为:
- 在现时配置中设置配置文件中出现的字段;
- 在现时配置中清除配置文件中已删除的字段。
kubectl diff -f <目录>/
kubectl apply -f <目录>/
说明: 使用 -R 标志递归处理目录。
警告: 将 kubectl apply 与指令式对象配置命令 kubectl create 或 kubectl replace 混合使用是不受支持的。这是因为 create 和 replace 命令都不会保留 kubectl apply 用来计算更新内容所使用的 kubectl.kubernetes.io/last-applied-configuration 注解值。
如何删除对象
有两种方法来删除 kubectl apply 管理的对象。
建议操作:kubectl delete -f <文件名>
使用指令式命令来手动删除对象是建议的方法,因为这种方法更为明确地给出了 要删除的内容是什么,且不容易造成用户不小心删除了其他对象的情况。
kubectl delete -f <文件名>
如何查看对象
你可以使用 kubectl get 并指定 -o yaml 选项来查看现时对象的配置:
kubectl get -f <文件名 | URL> -o yaml
apply 操作是如何计算配置差异并合并变更的?
注意: patch 是一种更新操作,其作用域为对象的一些特定字段而不是整个对象。 这使得你可以更新对象的特定字段集合而不必先要读回对象。
kubectl apply 更新对象的现时配置,它是通过向 API 服务器发送一个 patch 请求 来执行更新动作的。 所提交的补丁中定义了对现时对象配置中特定字段的更新。 kubectl apply 命令会使用当前的配置文件、现时配置以及现时配置中保存的 last-applied-configuration 注解内容来计算补丁更新内容。
合并补丁计算
kubectl apply 命令将配置文件的内容写入到 kubectl.kubernetes.io/last-applied-configuration 注解中。 这些内容用来识别配置文件中已经移除的、因而也需要从现时配置中删除的字段。 用来计算要删除或设置哪些字段的步骤如下:
1.计算要删除的字段,即在 last-applied-configuration 中存在但在 配置文件中不再存在的字段。
2.计算要添加或设置的字段,即在配置文件中存在但其取值与现时配置不同的字段。
不同类型字段的合并方式
配置文件中的特定字段与现时配置合并时,合并方式取决于字段类型。 字段类型有几种:
1.基本类型:字段类型为 string、integer 或 boolean 之一。 例如:image 和 replicas 字段都是基本类型字段。
动作: 替换。
2.map:也称作 object。类型为 map 或包含子域的复杂结构。例如,labels、 annotations、spec 和 metadata 都是 map。
动作: 合并元素或子字段。
3.list:包含元素列表的字段,其中每个元素可以是基本类型或 map。 例如,containers、ports 和 args 都是 list。
动作: 不一定。
当 kubectl apply 更新某个 map 或 list 字段时,它通常不会替换整个字段,而是会 更新其中的各个子元素。例如,当合并 Deployment 的 spec 时,kubectl 并不会 将其整个替换掉。相反,实际操作会是对 replicas 这类 spec 的子字段来执行比较和更新。
合并对基本类型字段的更新
基本类型字段会被替换或清除。
说明: - 表示的是“不适用”,因为指定数值未被使用。
合并对 map 字段的变更
用来表示映射的字段在合并时会逐个子字段或元素地比较:
说明: - 表示的是“不适用”,因为指定数值未被使用。
合并 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 源代码中为每个字段定义了 patchMergeKey: types.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 进行合并。
说明: 选择上述哪种策略是由源码中给定字段的 patchStrategy 标记来控制的: types.go 如果 list 类型字段未设置 patchStrategy,则整个 list 会被替换掉。
默认字段值
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.type 为 Recreate。字段 strategy.rollingUpdate 仍会取其 默认设置值,尽管服务器期望该字段被清除。 如果 strategy.rollingUpdate 值最初于配置文件中定义,则它们需要被清除 这一点就更明确一些。
4.apply 操作失败,因为 strategy.rollingUpdate 未被清除。 strategy.rollingupdate 在 strategy.type 为 Recreate 不可被设定。
建议:以下字段应该在对象配置文件中显式定义:
- 如 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 字段。
说明: 这一步骤是可选的,因为 kubectl apply 并不会更新 status 字段,即便 配置文件中包含 status 字段。
3.设置对象上的 kubectl.kubernetes.io/last-applied-configuration 注解:
kubectl replace --save-config -f <kind>_<name>.yaml
4.更改过程,使用 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"