Kubernetes 设计模式笔记 —— Automated Placement

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容