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

Kubernetes 中的高级调度

编者注:本文是关于 Kubernetes 1.6 新功能的一系列深度文章的一部分

Kubernetes 调度器的默认行为在大多数情况下都表现良好——例如,它确保 Pod 只部署在拥有足够空闲资源的节点上,它尝试将来自同一组的 Pod(ReplicaSetStatefulSet 等)分散到不同的节点上,它尝试平衡节点的资源利用率等等。

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

节点亲和性/反亲和性

节点亲和性/反亲和性 是调度器选择节点的规则之一。此功能是自 Kubernetes 1.0 版本以来一直存在的 nodeSelector 功能的泛化。这些规则使用节点上的自定义标签和 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 规范以使用 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 为 key,value 为 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 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 proxy 一起运行才能使其工作。

#!/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)

非常感谢您的贡献。