本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

Kubernetes 中的高级调度

编者注:这篇文章是关于 Kubernetes 1.6 新特性的系列深度文章的一部分

Kubernetes 调度器的默认行为在大多数情况下都能很好地工作——例如,它确保 Pod 仅放置在具有足够空闲资源的节点上,它试图将同一组(ReplicaSetStatefulSet 等)的 Pod 分散到各个节点上,它试图平衡节点的资源利用率等等。

但有时您想控制 Pod 的调度方式。例如,您可能想确保某些 Pod 仅在具有专用硬件的节点上调度,或者您想将频繁通信的服务放在一起,或者您想将一组节点专用于特定的一组用户。归根结底,您比 Kubernetes 更了解如何调度和部署您的应用程序。因此,Kubernetes 1.6 提供了四个高级调度功能:节点亲和性/反亲和性、污点和容忍度、Pod 亲和性/反亲和性以及自定义调度器。这些功能在 Kubernetes 1.6 中都处于*beta*(测试)阶段。

节点亲和性/反亲和性

节点亲和性/反亲和性是一种设置调度器选择哪些节点的规则的方式。此功能是 nodeSelector 功能的推广,该功能自 Kubernetes 1.0 版本以来就已存在。规则是使用节点上的自定义标签和 Pod 中指定的选择器的熟悉概念来定义的,并且它们可以是必需的或首选的,具体取决于您希望调度器强制执行它们的严格程度。

要使 Pod 调度到特定节点,必须满足必需的规则。如果没有节点符合条件(加上所有其他正常条件,例如具有足够的空闲资源来满足 Pod 的资源请求),则该 Pod 将不会被调度。必需的规则在 nodeAffinity 的 requiredDuringSchedulingIgnoredDuringExecution 字段中指定。

例如,如果我们想要求调度在多区域 Kubernetes 集群的 us-central1-a GCE 区域中的节点上,我们可以将以下亲和性规则指定为 Pod 规范的一部分

  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: "failure-domain.beta.kubernetes.io/zone"
              operator: In
              values: ["us-central1-a"]

“IgnoredDuringExecution” 表示如果节点上的标签发生更改并且不再满足亲和性规则,则 Pod 仍将运行。未来计划提供 requiredDuringSchedulingRequiredDuringExecution,一旦 Pod 不满足节点亲和性规则,它就会将 Pod 从节点驱逐出去。

首选规则意味着如果节点符合规则,则将首先选择它们,并且只有在没有首选节点可用时,才会选择非首选节点。您可以通过稍微更改 Pod 规范以使用 preferredDuringSchedulingIgnoredDuringExecution 来首选而不是要求将 Pod 部署到 us-central1-a

  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: "failure-domain.beta.kubernetes.io/zone"
              operator: In
              values: ["us-central1-a"]

可以通过使用否定运算符来实现节点反亲和性。因此,例如,如果我们希望我们的 Pod 避免使用 us-central1-a,我们可以这样做

  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: "failure-domain.beta.kubernetes.io/zone"
              operator: NotIn
              values: ["us-central1-a"]

您可以使用的有效运算符为 In、NotIn、Exists、DoesNotExist、Gt 和 Lt。

此功能的其他用例包括根据节点的硬件架构、操作系统版本或专用硬件来限制调度。节点亲和性/反亲和性在 Kubernetes 1.6 中处于 *beta*(测试)阶段。

污点和容忍度

一个相关的功能是“污点和容忍度”,它允许您标记(“污点”)一个节点,以便除非 Pod 明确“容忍”该污点,否则任何 Pod 都不能调度到该节点上。标记节点而不是 Pod(如节点亲和性/反亲和性中那样)对于集群中的大多数 Pod 都应避免调度到该节点上的情况特别有用。例如,您可能希望将您的主节点标记为仅可由 Kubernetes 系统组件调度,或者将一组节点专用于特定的用户组,或者将常规 Pod 与具有特殊硬件的节点隔离,以便为需要特殊硬件的 Pod 留出空间。

kubectl 命令允许您在节点上设置污点,例如

kubectl taint nodes node1 key=value:NoSchedule

创建一个污点,该污点将节点标记为任何没有容忍带有键 key、值 value 和效果 NoSchedule 的污点的 Pod 都不可调度。(其他污点效果是 PreferNoSchedule,它是 NoSchedule 的首选版本,以及 NoExecute,这意味着当应用污点时,任何在该节点上运行的 Pod 都将被驱逐,除非它们容忍该污点。)您要添加到 PodSpec 以使相应的 Pod 容忍此污点的容忍度如下所示

  tolerations:
  - key: "key"
    operator: "Equal"
    value: "value"
    effect: "NoSchedule"

除了将污点和容忍度移至 Kubernetes 1.6 中的 *beta*(测试)阶段之外,我们还引入了一个 *alpha*(实验性)功能,该功能使用污点和容忍度来允许您自定义当节点遇到网络分区等问题时 Pod 绑定到节点的时间,而不是使用默认的五分钟。有关更多详细信息,请参阅文档的此部分

Pod 亲和性/反亲和性

节点亲和性/反亲和性允许您根据节点的标签约束 Pod 可以在哪些节点上运行。但是,如果您想指定关于 Pod 应如何彼此相对放置的规则,例如,在服务内或相对于其他服务中的 Pod 分散或打包 Pod,该怎么办?为此,您可以使用Pod 亲和性/反亲和性,它在 Kubernetes 1.6 中也处于 *beta*(测试)阶段。

让我们看一个例子。假设您在服务 S1 中有前端,并且它们与服务 S2 中的后端频繁通信(一种“南北”通信模式)。因此,您希望这两个服务位于同一云提供商区域中,但您不想手动选择区域——如果该区域出现故障,您希望将 Pod 重新调度到另一个(单个)区域。您可以使用如下所示的 Pod 亲和性规则来指定此规则(假设您为该服务的 Pod 提供标签“service=S2”,为另一个服务的 Pod 提供标签“service=S1”)

affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: service
            operator: In
            values: [“S1”]
        topologyKey: failure-domain.beta.kubernetes.io/zone

与节点亲和性/反亲和性一样,也存在 preferredDuringSchedulingIgnoredDuringExecution 变体。

Pod 亲和性/反亲和性非常灵活。假设您已经分析了服务的性能,并且发现来自服务 S1 的容器在共享同一节点时会干扰来自服务 S2 的容器,这可能是由于缓存干扰效应或使网络链路饱和所致。或者,由于安全问题,您永远不希望 S1 和 S2 的容器共享一个节点。要实施这些规则,只需对上面的代码片段进行两处更改——将 podAffinity 更改为 podAntiAffinity,并将 topologyKey 更改为 kubernetes.io/hostname。

自定义调度器

如果 Kubernetes 调度器的各种功能无法让您充分控制工作负载的调度,您可以将调度任意 Pod 子集的责任委托给您自己的自定义调度器,这些调度器与默认 Kubernetes 调度器一起运行或代替默认 Kubernetes 调度器。多个调度器在 Kubernetes 1.6 中处于 *beta*(测试)阶段。

每个新 Pod 通常由默认调度器调度。但是,如果您提供自己的自定义调度器的名称,则默认调度器将忽略该 Pod,并允许您的调度器将该 Pod 调度到节点。让我们看一个例子。

这里我们有一个 Pod,在其中指定了 schedulerName 字段

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  schedulerName: my-scheduler
  containers:
  - name: nginx
    image: nginx:1.10

如果我们创建此 Pod 而不部署自定义调度器,则默认调度器将忽略它,并且它将保持在 Pending 状态。因此,我们需要一个自定义调度器来查找和调度 schedulerName 字段为 my-scheduler 的 Pod。

自定义调度器可以用任何语言编写,并且可以根据您的需要简单或复杂。这是一个用 Bash 编写的自定义调度器的非常简单的示例,它随机分配一个节点。请注意,您需要将其与 kubectl 代理一起运行才能使其工作。

#!/bin/bash

SERVER='localhost:8001'

while true;

do

    for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')

;

    do

        NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))


        NUMNODES=${#NODES[@]}

        CHOSEN=${NODES[$[$RANDOM % $NUMNODES]]}

        curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind"

: "Node", "name": "'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/

        echo "Assigned $PODNAME to $CHOSEN"

    done

    sleep 1

done

了解更多

Kubernetes 1.6 的发行说明包含有关这些功能的更多信息,包括如果您已在使用这些功能中的一个或多个的 alpha 版本时如何更改配置的详细信息(这是必需的,因为从 alpha 到 beta 的转变对于这些功能来说是一项重大更改)。

致谢

此处描述的功能,无论是其 alpha 形式还是 beta 形式,都是真正的社区努力,涉及来自 Google、华为、IBM、Red Hat 等公司的工程师。

参与其中

在我们每周的社区会议上分享您的声音

  • Stack Overflow 上发布问题(或回答问题)
  • 在 Twitter 上关注我们 @Kubernetesio 以获取最新更新
  • Slack(#sig-scheduling 房间)上与社区联系

非常感谢您的贡献。