Kubernetes Pod调度
自动调度
在使用控制方式(Deployment、RC、RS等)部署Pod时,如果未指定NodeSelector、NodeAffonity、PodAffinity等的调度策略,那么就由Master的Scheduler经过一些列算法计算出Pod中的容器具体部署在某个节点上。
以Deployment为例,创建一个Deployment,保持ReplicaSet为3.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
上述创建出一个Deployment,并且Deployment创建出三个副本,通过查看每个副本的详细信息(describe)可以知道副本部署的节点位置。
NodeSelector(Node定向调度)
kubernetes Master上的Scheduler(kube-scheduler)服务负责实现Pod的调度,通过一些列的的复杂算法,为Pod计算出一个最佳的目标节点,整个过程时自动完成的,如果需要查看Pod具体被调度在那个节点上就需要查看Pod的详细信息。但是在实际应用中,我们通常需要将一些特定的Pod调度在指定Node上,所以在此基础上,我们可以选择通过Node上的标签(Label)和Pod的nodeSelector属性进行匹配,已达到该目的。
- 首先给Node打上相关的标签
kubectl label nodes node1 zone=beijing
# 给node1打上zone=beijing的标签
- 在Pod定义中添加nodeSelector
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master
labels:
name: redis-master
spec:
replicas: 1
selector:
name: redis-master
template:
metadata:
labels:
name: redis-master
spec:
containers:
- name: master
image: kubeguide/redis-master
ports:
- containerPort: 6379
nodeSelector:
zone: beijing # 指定在含有zone=beijing的node上运行
通过上述文件创建出Pod,查看该Pod的详细信息则能发现,该Pod被调度到node1上。
需要注意的是,在指定Pod的nodeSelector时,如果该集群中没有包含相应的标签或者集群中多个node都存在相应的标签,这时候Pod可能面临着调度失败的情况。
但是在节点亲和性(NodeAffinity)越来越能够体现nodeSelector的功能,未来有可能被取代
NodeAffinity(Node亲和性调度)
NodeAffinity是Node亲和性的调度策略,用于替代nodeSelector的全新调度方式,目前有两种节点亲和性表达。
- requiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod到指定的Node上,和nodeSelector很像,属于硬限制。
- preferredDuringSchedulingIgnoredDuringExecution:强调有限满足指定规则,调度器尝试调度Pod到指定Node上,但是并不强求,属于软限制。多个优先级规则还可以设定权重,定义先后顺序。
IgnoredDuringExecution表示在Pod运行期间,Node上的相关标签被修改,不符合Pod亲和性的要求,这事系统忽略Node上的Label变化,该Pod正常运行。
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 要求只运行在amd64的节点上
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution: # 要求尽量运行在磁盘类型为ssd的节点上
- weight: 1
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
containers:
- name: with-node-affinity
image: gcr.io/google_containers/pause:2.0
从上述文件中可以看出In操作符,在nodeAffinity语法支持的操作符包含In、NotIn、Exists、DoesNotExist、Gt、Lt。虽然没有节点排斥功能,但是可以用NotIn和DoesNotExist可以实现
nodeAffinity使用时注意:
- 如果同时定义了nodeAffinity和nodeSelector,那么必须满足两个条件才能调度成功
- 如果nodeAffinity定义了多个nodeSelectorTerms,那么满足其中一个就可以匹配成功
- 如果在nodeSelectorTerms中有多个matchExpressions,则节点必须满足所有的matchExpressions才能实现调度成功
PodAffinity(Pod亲和性和互斥性调度)
在使用kubernetes的过程中往往会遇到希望两个或者多个Pod在同一个Node上运行,或者是希望两个或者多个Pod不在同一个Node上运行的情况,在这种应用场景下,就需要使用到Pod亲和性和互斥性了。
这里补充一个拓扑域的知识,在master添加节点的时候,会为每个Node创建相应的拓扑域,kubernetes同时内置了一些常用的默认拓扑域:
- kubernetes.io/hostname
- topology.kubernetes.io/region
- topology.kubernetes.io/zone
以上的拓扑域由kubernetes自己维护。
以下测试Pod亲和性
---
apiVersion: v1
kind: Pod
metadata:
name: pod-flag
labels:
security: "S1"
app: "nginx"
spec:
containers:
- name: nginx
image: nginx
通过以上代码创建一个Pod,并且带着security=S1和app=nginx标签
---
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity
spec:
affinity:
podAffinity: # 希望调度到带有security=S1标签的Pod所在的节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: gcr.io/google_containers/pause:2.0
通过以上的代码创建出第二个Pod,在创建Pod之后u,可以用过kubectl get pods -o wide查看两个Pod在同一个Node上运行
---
apiVersion: v1
kind: Pod
metadata:
name: anti-affinity
spec:
affinity:
podAffinity: # 希望调度到带有security=S1标签的Pod所在的节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity: # 希望调度到不带有app=nginx标签的Pod所在的节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- name: anti-affinity
image: gcr.io/google_containers/pause:2.0
通过以上的代码创建第三个Pod,使用kubectl get pods -o wide可以发现Pod被调度到一个新的Node上,并且该节点也是属于kubernetes.io/hostname拓扑域的节点。
podAffinity使用时注意:
- 除了设置Label Selector和topologyKey,还可以设定Namespace列表进行限制,同样使用Label Selector和Namespace进行选择,Namespace和topologyKey、Label Selector同级。如果省略这表示和当前Pod同一个命名空间,如果为“”空值,则表示所有命名空间。
- 在所有关联requiredDuringSchedulingIgnoredDuringExecution的matchExpressions全部满足后,系统才能将Pod调度到某个Node上。
Taints和Tolerations(污点和容忍)
污点表示Node拒绝Pod调度,被Taints标记的Node表示该Node暂时不接受Pod的调度,但是也是有效的工作节点,如果在这个阶段中,任然需要一些指定的Pod调度在这个节点上,则需要Tolerations属性来实现。
在默认情况下,Node上被设定一个或者多个污点之后,除非Pod存在相应的Tolerations属性,否则无法被调度到指定的Node,可以使用kubectl taint命令设定Node的污点信息:
kubectl taint nodes node1 key=value:Noschedule
以上命令为node1加上了一个taint,该taint的键为key,值为value,效果是NoSchedule,意味着除非Pod明确声明可以容忍这个Taint,否则不会被调度到这个node1上。
在Pod中需要做以下设定
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
或者
tolerations:
- key: "key"
operator: "Exists"
effect: "NoSchedule"
Pod的tolerations声明中的key和effect需要和taint保持一致,并且满足以下条件之一:
- operator的值是Exists(无须指定value)
- operator的值是Equal并且value相等
另外两个特例: - 空的key配合Exists操作符能够匹配所有键值
- 空的effect匹配所有effect
其中effect的值为NoSchedule,还可以设定为PreferNoSchedule,意思为优先,算作NoSchedule的软限制,一个Pod如果没有声明容忍这个污点,系统则会尽量避免把Pod调度到这个Node上,但不是强制的。
注意如果Node上存在多个taint,则toleration也需要匹配多个taint,如果在已经运行Pod的Node上添加新的taint,则Pod集训运行。
一般来说,如果Node设定了effect=NoExecute的Taint,那么在Node上运行的无对应的tolerations的Pod将会被驱逐,而有对应的tolerations则不会,但是系统允许给具有NoExecute效果的toleration加入tolerationSeconds字段,表明了Pod可以在Taint添加到Node上之后能延迟运行多长时间。
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoExecute"
tolerationSeconds: 3600 # 秒
一般来说创建Taint和tolerations的作用大致如下:
- 独占节点
- 具有特殊硬件设备的节点
- 当节点故障
Pod Priority Preemption(Pod优先级调度)
在集群中可能会出现集群内的的资源不足的情况,在此情况下,新建的Pod都会处于Pending状态,只能被动的等待其他Pod被删除,释放出资源之后才能正常创建,在kubernetes 1.8之后引入了Pod优先级抢占的机制(Pod Priority Preemption),在高优先级的Pod被创建时,如果资源不足,系统则会尝试释放低优先级的Pod,腾出空间给予高优先级的Pod使用。这种方式称为抢占式调度。
优先级抢占调度策略的核心行为分别是驱逐(Evication)和抢占(Preemption),使用场景不同,但是效果是相同的。
Evication是由kubelet控制的,当Node的资源不足时,kubelet会综合考虑Pod的优先级、资源申请和实际使用量等信息来计算哪些Pod需要被驱逐,当同样优先年纪的Pod被驱逐时,实际使用的资源量超过申请量最大倍数的高耗能Pod将被驱逐。
Preemption则是Scheduler控制的,当新的Pod因为资源问题无法被调度时,Scheduler可能驱逐低优先级的Pod实例来满足新的Pod的调度目标
创建PriorityClass,PriorityClass不属于任何命名空间
---
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
上述代码中定义了一个high-priority的优先级类别,优先级数字为1000000,数字越大,优先级越高,超过一亿的数字被系统保留用于系统组件。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
上述文件中priorityClassName指定了该Pod的优先级。
但是如果新建的Pod不希望发生优先级调度时,则可以添加新的属性preemptionPolicy,当它的值为preemptionLowerPolicy(默认)时,就会拥有抢占的功能,当设定为never时则失去抢占资源的能力,而是静静排队,等待调度机会。
需要注意的是使用优先级抢占的调度策略,可能会使某些Pod永远无法正常被调度,因此优先级调度不但增加了系统的复杂性,还可能会出现不稳定问题,因此在发生集群中资源不足的情况,优先考虑扩展集群,慎用!!
DaemonSet(每个Node调度一个Pod)
DaemonSet是一个资源对象,用于管理集群中没有Node上仅运行一份Pod实例副本。
DaemonSet的Pod调度和RC类似,使用内置的算法在每一个Node进行调度,也可以使用NodeSelector或者NodeAffinity指定满足条件的Node范围进行调度。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-cloud-logging
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
template:
metadata:
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
containers:
- name: fluentd-cloud-logging
image: gcr.io/google_containers/fluentd-elasticsearch:1.17
resources:
limits:
cpu: 100m
memory: 200Mi
env:
- name: FLUENTD_ARGS
value: -q
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: false
- name: containers
mountPath: /var/lib/docker/containers
readOnly: false
volumes:
- name: containers
hostPath:
path: /var/lib/docker/containers
- name: varlog
hostPath:
path: /var/log
上述文件定义了在每个Node上都启动一个fluentd容器,其中挂载了物理机的两个目录,/var/log和/var/lib/docker/containers