Kubernetes的调度在大部分场景下工作良好-例如,它能够确保Pod运行在具有足够空闲资源的节点上,它试图在跨节点的在传播同一集合的Pod(ReplicaSet,StatefulSet,etc),试图平衡节点的资源使用等。
但是有些时候你想过要去控制你的Pods怎么去调度,比如你可能想要保证一些Pod只会在一些特定的硬件的节点上去进行调度,或者你想过要通信频繁的的服务放在一起(co-locate)。或者你想要将一组节点用于一组的特定用户。最终,你能够知道的更多关于你的应用是怎么调度以及部署的。所以Kubernetes1.6提供了四个高级的调度特性:节点的亲和性和反亲和性,taints和tolerations, Pod的亲和性以及反亲和性。这些特性当前在Kubernetes1.6中是Beta状态.
节点的亲和性以及反亲和性
Node Affinity/Anti-Affinity是通过在调度器设置规则来确定哪些节点将会被调用。这个特性是nodeSelector特性的泛化,nodeSelector特性从Kubernetes 1.0版本就已经出现,这些规则的定义采用类似的概念:通过在节点上自定义标签和在pods上定义selector,它们可以使必选或者首选的,这取决于你希望调度程序强制执行它们的严格程序。
特定的规则必须满足Pod被调度到特定的节点上。如果没有节点满足该约束(加上所有其他正常的约束,比如有足够的空闲资源来处理该pod对资源的请求),否则该Pod将不会被调度,需要的规则通过nodeAffinity中的requiredDuringSchedulingIgnoredDuringExecution字段来指定需要的规则。
比如,如果你想要调度Pod到使用us-centrall-a的GCE zone的节点上,你可以再Pod的规格中指定如下的亲合性规则。
affinity:
nodeAffinity:
requiredDuringScheduling IgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
IgnoredDuringExecution字段表示如果node上的标签改变或者亲和性规则永远不可能满足的时候,仍然能够运行Pod.有未来的计划将会提供requiredDuringSchedulingRequiredDuringExecution,他将会从节点中驱赶(evict)pod,一旦它们不能满足node的亲和性规则的时候。
更好的规则是如果有节点匹配这些规则,他们将会首先被选中,如果没有更合适的节点可以使用的情况下,非最合适的节点被选择。你如果是更希望而不是必须pod部署到us-central1-a,可以通过稍微修改pod的规则中使用preferredDuringSchedulingIgnoredDuringExecution来达到目的。
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["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.
其它的用户使用场景是根据节点的硬件架构,操作系统版本或者专用硬件来限制调度。在Kubernetes1.6版本中节点的亲和性以及反亲和性处于Beta版本。
Taints and Tolerations(污点以及容忍)
“taints and tolerations”的相关功能是允许你标注(taint)节点,那样没有Pods可以调度到这个节点上,除非pod明确的”tolerates”这个”taint”。在节点上标注而不是pos(as innode affinity/anti-affinity)是一些场景下有特殊的使用,比如希望集群中的大部分pods不要调度到某些节点上。比如,你可能想要标注你的master节点值调度运行Kubernetes的系统组件,或者贡献一部分节点给特定的用户组,或者是保持一般的Pods不要调度到特定硬件功能的节点上,这样就能够将这些特定硬件的节点预留空间分配给需要这些硬件的Pods来使用
kubectl命令行允许你在节点上设置taints,比如如下所示:
kubectl taint nodes node1 key=value:NoSchedule
创建一个污点(taint),通过key,value,effect=NoSchedule来标注节点不要被无法容忍污点的pod来调度。其他污点的effect=preferNoSchedule,它可能是NoSchedule更好的选择,NoExecute意味着在节点上运行的任何pods将会被驱逐,除非它们容忍污点。容忍污点你可以再Pod的规格中添加对于的容忍污点的说明,就像下面代码一样:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
除了在
kubernetes1.6
中将
taints
和
tolerations
功能升级到
beta
版本,引入了一个
alpha
功能,允许你使用
taints
和
tolerations
功能来定制当节点遭遇像网络分区这样的问题的时候来隔离
pod
到某个节点的时间,而不是使用默认的五分钟的时间,详细的内容可以参数
this sections.
Pod的亲和性和反亲和性
节点的亲和性和反亲和性允许我们通过node的标签来限制那些节点来运行Pod。但是如果你想要指定规则pods应该相对于其它的Pod来调度,比如比如你想要加速或者包装一个服务中的pods或者是其它服务中相关的pods?在这种场景下,你可以使用pod的亲和性和反亲和性,在kubernetes1.6中该特性处于beta阶段。
让我们来看一个例子,比如你在S1的服务中有一个front-ends,他们需要跟服务S2中的back-ends频繁的进行通信(一个南-北向的通信模式)。所以你想要两个服务协同co-located在通过一个云提供者的分区中,但是你不想要手动的去选择分区—如果这个分区失败,你想要pods可以被重新调度到其它的分区。你可以通过pod的亲和性规则来实现该功能,假如你给服务的Pods指定了标签service=S2和其它服务的Pods指定了标签”service=S1”:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: service
operator: In
values: [“S1”]
topologyKey: failure-domain.beta.kubernetes.io/zone
跟节点的亲和性/反亲和性一样,也存在一个requiredDuringSchedulingIgnoredDuringExecution变量
Pod的亲和性和反亲和性是很灵活的。想象一下,您已经分析了你的服务的性能,发现来自服务S1的容器在共享相同节点时会干扰来自服务S2的容器,这可能是由于缓存干扰效应或饱和网络链路。或者也许是由于安全性问题,您不想要容器S1和S2共享一个节点。要实现这些规则,只需对上述代码段进行两次更改即可更改podAffinity到podAntiAffinity并将topologyKey更改为kubernetes.io/hostname。
自定义调度器
如果Kubernetes调度程序的各种功能不能为您的工作负载调度提供足够的控制权,则可以委托将任意子集的pod调度到您自己的自定义调度程序,而不是默认Kubernetes调度程序。多个调度程序在Kubernetes1.6中是beta.
每个新的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,则默认调度程序将忽略它,并且它将保持处于挂起状态。因此,我们需要一个自定义调度程序来查找和调度其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等的工程师。
参与其中
参与每周的 community meeting:
· 在Stack Overflow上发布问题
· 在Twitter @Kubernetesio 上关注最新的更新for latest updates
· 在 Slack (room#sig-scheduling)上关注社区
非常感谢你的贡献。
--Ian Lewis, Developer Advocate, and David Oppenheimer, Software Engineer,Google
翻译于 http://blog.kubernetes.io/2017/03/advanced-scheduling-in-kubernetes.html