-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Kubernetes是一个容器编排系统,编排的一个主要方面是 部署管理
,其中部署流程的管控是其依赖的主要能力,这部分能力主要体现在对Pod和容器(container)的生命周期管理能力,但也有一些 crd 实现了暂停点这样的编排能力。
能力
一组Pod的编排
暂停点
社区 openkruise 的cloneset实现了暂停点功能:更新过程中可暂停更新,可以利用这项能力实现灰度发布这样的发布策略。其实现原理是在 Reconcile 过程中检查 cs.Spec.Paused 的值,如果是 true 则表示启用暂停,Reconcile 不做任何操作直接返回。
优先选择符合条件的 Pod 执行某操作
场景:例如横向缩容时优先选择状态异常的 Pod
在cloneset中,spec.scaleStrategy.podsToDelete记录了要删除的Pod,我们可以有一个定时程序定时更新cloneset中异常状态pod,当缩容时优先delete标记为异常的 Pods。
Pod 级别
Pod生命周期过程中的编排控制能力有创建(调度)、删除(优雅退出)等
调度
调度过程决定了 Pod 创建到哪个Node,用到的策略主要有 Pod 和 Node 亲和性、Pod 间亲和性、反亲和性:
Pod 和 Node 亲和性,包括标签(Labels)、污点(Taints)、容忍度(Tolerations)
-- 标签,Node的特征标记,可以把Pod定向调度到满足资源需求的Node节点上,例如 需要 ssd磁盘、A30 GPU等硬件资源
-- 污点,排斥一类 pod
-- 容忍度,允许调度器调度带有对应污点的Pod,容忍度并不保证调度到带有污点的机器,所以往往结合 label 和 affinity 使用
例子:圈一批Node供给某业务,不允许其它业务使用(i.e. 实现资源隔离)
假设有一个集群3个node,给自动驾驶大模型业务使用一台,节点名为 kind-worker2,我们需要:
- 给Node打标签,
kubectl label nodes kind-worker2 business=self-drive-models

- 给Node打污点,防止其它业务被调度过来,
kubectl taint nodes kind-worker2 business=self-drive-models:NoSchedule

- 为允许的业务Pod添加容忍度和节点亲和性,容忍度保证Pod可以被调度到带有该污点的节点上,亲和性保证Pod只会调度到带有特定标签的节点上,否则也可能调度到其它机器上
apiVersion: v1
kind: Pod
metadata:
name: self-drive-models-ins-01
spec:
tolerations:
- key: "business"
operator: "Equal"
value: "self-drive-models"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "business"
operator: "In"
values:
- "self-drive-models"
containers:
- name: main
image: nginx:1.25.5-alpine
部署业务Pod,我们可以看到确实调度到了指定的node

如果我们写错亲和性标签到一个不存在的node,那么调度器就会报错:

从报错信息中我们也可以看到,control-plane 节点,也就是 master 节点也是通过 taint 防止业务pod调度的。
这也反映出 NodeAffinity 是强制的,没有满足的,Pod 就会 Pending 等待有满足条件的 Node 出现。
Pod间亲和性与反亲和性
与 Node 亲和性不同的是,Pod间亲和性与反亲和性是基于在节点上运行的Pod标签来约束Pod可以调度的节点,而不是基于节点上的标签。
主要效果是体现应用间或同一应用的不同实例的拓扑关系。例如如果两个应用间有直接访问关系,部署到一台机器上可能是更好的选择;如一个应用是重IO操作,最好避免多个实例同时部署到一个机器,否则磁盘IO会限制应用的性能;
例子:某使用GPU的模型预估服务,有很高的网络吞吐,需要打散防止Node网卡成为瓶颈
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-app
spec:
replicas: 3
selector:
matchLabels:
app: gpu-app
template:
metadata:
labels:
app: gpu-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- gpu-app
topologyKey: "kubernetes.io/hostname"
containers:
- name: gpu-container
image: nginx:1.25.5-alpine
创建deployment,查看调度结果:

会发现有一个在等待调度(Pending),这是因为 worker2 被打上了NoSchedule
,且我们指定的是 硬反亲和性
,其实还有 软反亲和性(preferredDuringSchedulingIgnoredDuringExecution)
来解决这种问题(集群node不够):
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-app
spec:
replicas: 3
selector:
matchLabels:
app: gpu-app
template:
metadata:
labels:
app: gpu-app
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- gpu-app
topologyKey: "kubernetes.io/hostname"
topologySpreadConstraints:
- maxSkew: 2
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: gpu-app
containers:
- name: gpu-container
image: nginx:1.25.5-alpine
创建deployment后,都调度上了:

注意我们用了 topologySpreadConstraints
保证每个 Node 同一应用的副本不会超过 2 个。
优雅退出
terminationGracePeriodSeconds
,当执行 kubectl delete pod/xxx 时,pod不会马上被delete,而是等待 terminationGracePeriodSeconds
指定的时间后,再退出。
容器级别
主要是容器生命周期钩子
,包括 postStart
、preStop
:
- postStart,在容器创建之后立即执行,但不能保证回调会在容器入口点(ENTRYPOINT)之前执行。这是因为 容器启动过程包括创建和启动两个阶段,ENTRYPOINT 或 CMD 在启动阶段,所以 postStart 不一定在启动完成后,可能的原因是脚本启动进程前做了其它初始化工作,例如加载数据,或者启动时间长,而k8s不是容器运行引擎,控制粒度没有这么细致,所以不做这个保证;
- preStop,指容器收到终止信号(SIGTERM)时和在真正退出前这段时间
例子
应用进程停止之前需要做一些清理工作,是比较常见的诉求,这个场景下如何实现业务进程优雅退出?
- 如果业务进程实现了优雅退出逻辑(监听 SIGTERM信号并执行优雅退出动作),那么我们只需要设置容器的
terminationGracePeriodSeconds
即可,它会在容器收到 SIGTERM 之后等待设置的时长。如果在指定的时间后,k8s会发出 SIGKILL信号强制容器退出,与之对应的,应用提前完成工作,容器提前退出。 - 如果业务进程没实现优雅退出逻辑,而是把退出前的工作放在了容器内某个脚本中,则需要结合容器生命周期钩子和优雅退出等待两个能力。如下 example.yaml:
apiVersion: v1
kind: Pod
metadata:
name: graceful-shutdown-example
spec:
terminationGracePeriodSeconds: 60
containers:
- name: my-app
image: redis:7.2.5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 'Received SIGTERM, shutting down...'; sleep 5"]
kubectl apply -f example.yaml
会创建 pod/graceful-shutdown-examplekubectl logs -f pod/graceful-shutdown-example
监视容器输出kubectl delete pod/graceful-shutdown-example
向容器发送 SIGTERM 信号
我们会看到控制台 5 秒后输出 Received SIGTERM, shutting down...
后退出

接下来把
terminationGracePeriodSeconds: 60
...
command: ["/bin/sh", "-c", "echo 'Received SIGTERM, shutting down...'; sleep 5"]
改成
terminationGracePeriodSeconds: 10
command: ["/bin/sh", "-c", "echo 'Received SIGTERM, shutting down...'; sleep 20"]
即模拟pod等待时间小于容器做清理任务的时间,这时 10秒后就会输出然后退出。