在 k8s 中 通过
kube-scheduler
组件来实现 pod 的调度,所谓调度,即把需要创建的 pod 放到 合适的 node 上,大概流程为,通过对应的调度算法
和调度策略
,为待调度的 pod 列表中的 pod 选择一个最合适的 Node,然后目标节点上的kubelet
通过 watch 接口监听到kube-schedule
产生的Pod 绑定事件
,通过 APIService 获取对应的 Pod 清单,下载 image 并且启动容器。这里具体的
调度算法
大体上分两步,筛选出候选节点,确定最优节点,确定最优节点涉及节点打分等。常见的 Pod 的
调度策略
有选择器、指定节点、主机亲和性
方式,同时需要考虑节点的coedon
与drain
标记,今天和小伙伴分享的是调度策略
的一种, 即通过Pod拓扑分布约束
,用来实现跨集群节点均匀调度分布Pod
为什么需要跨集群节点 均匀调度分布 Pod ?
我们知道在 k8s 中 ,如果只是希望每个节点均匀调度分布一个 pod,那么可以利用
DaemonSet
来实现。如果多个,就需要 pod 的拓扑分布约束均匀调度 Pod ,实现在集群中均匀分布 Pod,可以尽可能的利用 节点的超售,Pod 的超用,以实现高可用性和高效的集群资源利用。k8s 中通过 Pod 拓扑分布约束 (PodTopologySpread) 来实现均匀调度 pod。这一特性从 v1.19 以后达到稳定状态。在 v1.25,v1.1.26 的版本中添加的部分属性。
需要说明的是,这里的 均匀调度 pod 不是说 只有对当前需要调度的 pod 在 工作节点发生均匀调度,不考虑当前节点上之前存在的 pod , 而是基于 工作节点的 均匀调度。即所谓均匀调度分布是基于工作节点的。虽然 pod 的拓扑分布约束是定义在 pod 上的。
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# 配置一个拓扑分布约束
topologySpreadConstraints:
- maxSkew:
minDomains: # 可选;自从 v1.25 开始成为 Beta
topologyKey:
whenUnsatisfiable:
labelSelector:
实例1:
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# 配置一个拓扑分布约束
topologySpreadConstraints:
- maxSkew: 1 # 以绝对均匀的方式分配 POD 即 pod 在节点分布的差值不能超过的值。
topologyKey: kubernetes.io/hostname #使用主机名这个标签作为拓扑域
whenUnsatisfiable: ScheduleAnyway #始终调度 pod,即使它不能满足 pod 的均匀分布
labelSelector:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
replicas: 10
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: test
containers:
- name: pause
image: registry.aliyuncs.com/google_containers/pause:3.5
需要注意的是,缩减 Deployment 并不能保证均匀分布
,并可能导致 Pod 分布不平衡。
maxSkew
:描述这些 Pod 可能被均匀分布的程度。你必须指定此字段且该数值必须大于零。 其语义将随着 whenUnsatisfiable 的值发生变化:简单来讲,就是如果为均匀分布,那么两个节点之前的 pod 差值最大可以为多大。
如果你选择
whenUnsatisfiable: DoNotSchedule
,则 maxSkew 定义目标拓扑中匹配 Pod 的数量与 全局最小值之间的最大允许差值。例如,如果你有 3 个可用区,分别有 2、2 和 1 个匹配的 Pod,则 MaxSkew 设为 1, 且全局最小值为 1。如果你选择
whenUnsatisfiable: ScheduleAnyway
,则该调度器会更为偏向能够降低偏差值的拓扑域。
topologyKey
:是节点标签的键。如果节点使用此键标记并且具有相同的标签值
, 则将这些节点视为处于同一拓扑域
中。我们将拓扑域中(即键值对)的每个实例称为一个域
。 调度器将尝试在每个拓扑域中放置数量均衡
的 Pod。 另外,我们将符合条件的域定义为其节点满足nodeAffinityPolicy
和nodeTaintsPolicy
要求的域。当topologyKey
的值为none
的时候。
whenUnsatisfiable
: 指示如果 Pod 不满足分布约束时如何处理:
DoNotSchedule(默认)告诉调度器不要调度。
ScheduleAnyway 告诉调度器仍然继续调度,只是根据如何能将偏差最小化来对节点进行排序。
labelSelector
: 用于查找匹配的 Pod。匹配此标签的 Pod 将被统计,以确定相应拓扑域中 Pod 的数量当 Pod 定义了
不止一个 topologySpreadConstraint
,这些约束之间是逻辑与
的关系。 kube-scheduler 会为新的 Pod 寻找一个能够满足所有约束的节点。
在这之前需要做一些准备工作,在每个工作节点上在打一个标签 disktype=node-group1
添加一个新的 Pod ,添加了两个拓扑分布约束,要求这个 pod 即在 topologyKey: kubernetes.io/hostname
的拓扑域,同时在 topologyKey: disktype
的拓扑域
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
app: test
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: test
- maxSkew: 1
topologyKey: disktype
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: test
containers:
- name: pause
image: registry.aliyuncs.com/google_containers/pause:3.5
如果所有节点都不满足 pod 的 拓扑分布约束,当前 pod 就会调度失败。下面为创建了一个新的补丁文件,修改副本数为 5,使用 kubernetes.io/hostname
作为拓扑域
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: test
name: test
namespace: test-topo-namespace
spec:
replicas: 5
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- image: registry.aliyuncs.com/google_containers/pause:3.5
name: pause
topologySpreadConstraints:
- labelSelector:
matchLabels:
app: test
maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
当前 whenUnsatisfiable: DoNotSchedule
,即不满足约束时,不发生调度, master 有污点,默认不发生调度,所以当 副本数为 5 的时候,工作节点各调度一个,剩下的一个 pod 调度到哪里都会违反 maxSkew: 1
,所以发生 pending 。
这里主要学习记录下如何尽可能实现pod在节点上的均匀调度分配。
参考资料
https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/pod-priority-preemption/
https://medium.com/geekculture/kubernetes-distributing-pods-evenly-across-cluster-c6bdc9b49699