本文已超过一年。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否已失效。
Kubernetes 中的高级调度
编者注:本文是介绍 Kubernetes 1.6 新特性系列深度文章的一部分
Kubernetes 调度器的默认行为在大多数情况下都能很好地工作——例如,它确保 Pod 只被放置在具有足够空闲资源的节点上,它会尝试将来自同一组(ReplicaSet、StatefulSet 等)的 Pod 分散到不同的节点上,它会尝试平衡节点的资源利用率等。
但有时你希望控制你的 Pod 如何被调度。例如,你可能希望确保某些 Pod 只调度到具有特殊硬件的节点上,或者你希望将频繁通信的服务 colocated,或者你想将一组节点专用于特定用户。归根结底,你比 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 Spec 中指定以下亲和性规则:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
“IgnoredDuringExecution” 意味着如果节点上的标签发生变化导致亲和性规则不再满足,Pod 仍将继续运行。未来计划提供 requiredDuringSchedulingRequiredDuringExecution,它将在 Pod 不满足节点亲和性规则时立即将其从节点中逐出 (evict)。
偏好规则意味着如果节点匹配规则,它们将被优先选择;只有在没有偏好节点可用时,才会选择非偏好节点。你可以通过略微更改 Pod Spec,使用 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(PreferNoSchedule 的偏好版本)和 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 中的后端频繁通信(一种“南北”通信模式)。因此你希望这两个服务 colocated 在同一个云提供商区域,但你不想手动选择区域——如果该区域发生故障,你希望 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)
非常感谢你的贡献。