0

Kubernetes 设计模式笔记 —— Automated Placement

 1 year ago
source link: https://rollingstarky.github.io/2023/06/19/kubernetes-patterns-reading-notes-automated-placement/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Kubernetes 设计模式笔记 —— Automated Placement

2023-06-19

| Linux

| 0

|

7.6k

|

0:08

Automated Placement 是 Kubernetes 中 scheduler 的核心功能,负责将新的 Pod 分配给合适的节点,满足容器的资源需求,同时遵守设定好的调度策略。

基于微服务的系统通常会包含数十个甚至数百个隔离的进程,容器和 Pod 为它们提供了很好的打包和部署机制,但并没有解决将众多的进程分配给适当的节点这项工作。
容器之间存在依赖关系,有些还需要关联到特定的节点,容器自身也有一定的资源需求。这些都会随着时间发生变化。同时集群本身的资源也不是恒定的,它会执行缩容或者扩容,其特定时刻下的容量也取决于已经放置的容器数量。
这些因素都会左右容器的调度。

Available Node Resources

首先需要考虑的就是节点上是否有足够的可用资源。Scheduler 会确保 Pod 申请的资源总和小于可分配节点上的可用容量。节点可用容量的计算公式:

Allocatable [capacity for application pods] =
Node Capacity [available capacity on a node]
- Kube-Reserved [Kubernetes daemons like kubelet, container runtime]
- System-Reserved [OS system daemons like sshd, udev]

Container Resource Demands

Pod 在调度时,另一个重要的考虑因素就是容器有着自己的运行时依赖和资源需求。
比如:

apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi

Placement Policies

Scheduler 配置了一组默认的优先级策略,适用于绝大多数场景。这个策略可以在 scheduler 启动时被替换掉。

scheduler 策略示例:

{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
{"name" : "PodFitsHostPorts"},
{"name" : "PodFitsResources"},
{"name" : "NoDiskConflict"},
{"name" : "NoVolumeZoneConflict"},
{"name" : "MatchNodeSelector"},
{"name" : "HostName"}
],
"priorities" : [
{"name" : "LeastRequestedPriority", "weight" : 2},
{"name" : "BalancedResourceAllocation", "weight" : 1},
{"name" : "ServiceSpreadingPriority", "weight" : 2},
{"name" : "EqualPriority", "weight" : 1}
]
}

其中 Predicate 规则用于过滤掉不合格的节点。比如 PodFitsHostsPorts 关注特定的固定主机端口,只有在这些端口可用时对应的节点才会作为候选。
Priorities 用于根据一些偏好设置来对候选的节点进行排序。比如 LeastRequestedPriority 会赋予请求了较少资源的节点更高的优先级。

可以同时运行多个 scheduler,让 Pod 自己去指定使用哪一个。只需要在 Pod 的配置中添加一条 .spec.schedulerName,其值为自定义 scheduler 的名字。

A Pod-to-node assignment

只要 Pod 创建完成且还没有被分配给任何节点,scheduler 就会挑选出该 Pod,连同所有可用的节点及优先级策略。第一阶段借助过滤策略移除所有不满足要求的节点,剩余的节点在第二阶段有权重地排序。最后一个阶段得到最终的胜出节点。

在绝大多数情况下,最好都只让 scheduler 去做 Pod-to-Node 的分配工作,不要去尝试“微操”调度逻辑。
在某些特殊场景下,如果需要强制某个 Pod 只能分配给特定的一个或一组节点,可以借助 Pod 的 .spec.nodeSelector 字段。
该字段可以指定一些键值对,对应节点身上的标签。比如想要 Pod 运行在拥有 SSD 磁盘的硬件上:

apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
nodeSelector:
disktype: ssd

除了通过自定义标签指定节点,还可以通过每个节点上都有的默认标签来筛选,比如 kubernetes.io/hostname

Node Affinity

Kubernetes 还支持更为灵活的配置调度流程的方式,比如 node affinity。其实它相当于 nodeSelector 机制的泛化,其规则可以被指定为“必需”或者“优先”。
“必需”表示相应的规则必须被满足,否则节点无法作为候选;“优先”则并不强制,只是提高匹配节点的权重。
此外,node affinity 支持多种操作符,如 InNotInExistsDoesNotExistGtLt 等,从而获得更强的表达能力。

apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: numberCores
operator: Gt
values: [ "3" ]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchFields:
- key: metadata.name
operator: NotIn
values: [ "master" ]
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator

其中 requiredDuringSchedulingIgnoredDuringExecution 用来指定节点必须具备的条件,此规则不会在执行过程中重新计算。结合后面的 nodeSelectorTerms 配置,筛选出核心数大于 3 的节点。
preferredDuringSchedulingIgnoredDuringExecution 用于指定非必须的条件,表现为一个带有权重的 selector 列表。对于每一个节点,计算出所有匹配项的权重总和,结果最高的节点被选中,只要该节点已经满足了前面的“必需”条件。

PS:matchFields 只支持 InNotIn 操作符,values 指定的列表中也只允许有一个值。

诚然,node affinity 相比于 nodeSelector 功能更为强大。它允许通过标签或者字段为 Pod 选择合适的节点,但不能够用来表达 Pod 之间的依赖关系,比如无法根据某个节点上已经部署的 Pod 判断某个新 Pod 是否也应该部署到该节点。这类需求可以通过 Pod affinity 实现。

Pod Affinity

Node affinity 工作在节点层级上,Pod affinity 则可以在多个拓扑层级上表达规则,达到粒度更细的控制。

apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
confidential: high
topologyKey: security-zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
confidential: none
topologyKey: kubernetes.io/hostname
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator

其中 podAffinity 部分的配置表示,符合条件的节点上必须有带有 confidential=high 标签的 Pod 在运行,且该节点有 security-zone 标签。
podAntiAffinity 定义的规则用于过滤掉匹配的节点。结合其配置,即节点上有带 confidential=none 标签的 Pod 在运行时,该节点不会用来部署当前 Pod。

Taints and Tolerations

Taints 和 Tolerations 是一类更高级的用于控制调度策略的特性。简单来说,node affinity 允许 Pod 根据规则选择合适的节点,taints 和 tolerations 则正相反,它允许节点自身去控制 Pod 是否应该分配给自己。
Taint 是节点自身的一种属性,当它存在时,会阻止 Pod 分配给自己,除非该 Pod 拥有针对 taint 的 tolerations。

Taint 可以使用 kubectl 命令添加。如 kubectl taint nodes master noderole.kubernetes.io/master="true":NoSchedule,等效于下面的配置。

Tainted 节点:

apiVersion: v1
kind: Node
metadata:
name: master
spec:
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master

拥有此 taint 的节点不会被分配任何 Pod,除非有 Pod 指定了对应的 toleration。比如:

apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule

在生产级别的集群中,带有 noderole.kubernetes.io/master 配置的 taint 一般会指定给 master 节点,阻止 Pod 部署到 master 上。
这里给 Pod 添加的 toleration 会覆盖 taint 的 NoSchedule 效果,即无论如何都允许此 Pod 分配给 master 节点。

Taint 可以是硬性的,阻止节点作为候选(effect=NoSchedule),也可以是软性的,尝试避免节点作为候选(effect=PreferNoSchedule),还可以强制移除节点上已经在运行的 Pod(effect=NoExecute)。

当 Pod 已经分配给某个节点,scheduler 的工作就已经算完成了,它不会再对完成的分配进行调整。除非该 Pod 被删除或者重建。随着时间的推移,这一定会导致资源的碎片化,集群利用率降低。
另一个潜在的问题是,Pod 被创建后具体分配给哪一个节点,依赖于当时集群的状态。而集群本身是动态的,节点的资源配置会更改,或者有新的节点加入进来,scheduler 并不会纠正已经存在的部署。此外,节点上的标签也有可能会变动,影响到之后的调度,但之前已经完成的调度依旧保持不变。

以上所有的场景都可以通过 descheduler 去解决。Kubernetes 的 descheduler 是一个可选的特性,通常作为 Job 执行,当管理员觉得是时候通过重新调度 Pod 来整理集群的碎片。
Descheduler 有一些预先定义的策略,可以被启用或者禁用:

  • RemoveDuplicates:该策略会确保 ReplicaSet 或 Deployment 关联的单一 Pod 只运行在唯一一个节点上。当某个节点不健康时,controller 会在其他健康的节点上启动新的 Pod。此时若之前不健康的节点恢复正常重新加入集群,正在运行的 Pod 就会大于需要的数量。此策略就可以应用于这类的场景。同时 RemoveDuplicates 还可以在策略或集群架构发生变化后,将 Pod 更均匀地分散在更多的节点上
  • LowNodeUtilization:该策略会找到使用率低的节点,并将高使用率节点上的 Pod 移除掉,希望这些移除的 Pod 可以重新分配到未充分利用的节点上。使用率低指 CPU、内存或 Pod 数量小于 thresholds 配置;使用率高指的是 CPU、内存或 Pod 数量大于 targetThresholds 配置
  • RemovePodsViolatingInterPodAntiAffinity:该策略会移除违反了 pod antiaffinity 规则的 Pod。这种情况可能发生在,添加规则时一些不符合规则的 Pod 就已经存在了
  • RemovePodsViolatingNodeAffinity:移除违反了 node affinity 规则的 Pod

不管使用何种配置的策略,descheduler 会避免移除如下类型的 Pod:

  • 在 annotation 中标记为 scheduler.alpha.kubernetes.io/criticalpod 的关键 Pod
  • 不由 ReplicaSet、Deployment 或 Job 管理的 Pod
  • 由 DaemonSet 管理的 Pod
  • 拥有本地存储的 Pod
  • 配置了 PodDisruptionBudget 的 Pod,且移除时会违反此规则
  • Descheduler Pod 本身

容器调度是一个我们希望尽可能少干预的领域。从简单到复杂,以下方法控制着调度的具体策略:

  • nodeName:最简单的分配方式,将 Pod 到节点的关系硬编码到配置中。理想的情况下,此字段应该由 scheduler 填充,策略去驱动,而不是手动指定
  • nodeSelector:键值对映射。符合条件的节点必须包含此键值对指向的标签。在控制调度策略的可接受的方式中,最简单的一种
  • Default scheduling alteration:必要的情况下,可以修改 default scheduler 的过滤规则和优先级策略、顺序、权重等
  • Pod affinity 和 antiaffinity:此机制允许 Pod 表达自身对其他 Pod 的依赖关系
  • Node affinity:允许 Pod 表达自身对节点的依赖关系,比如节点的硬件配置、地理位置等
  • Taints 和 tolerations:允许节点去控制哪些 Pod 允许哪些不允许分配给自己。比如为一组 Pod 分配一个专用节点,甚至在运行时移除 Pod
  • Custom scheduler:若上述方案都不能符合需求,还可以编写自定义的 scheduler。自定义 scheduler 可以替换掉标准的 Kubernetes scheduler,也可以两者一起运行

Kubernetes Patterns


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK