今天直接进入正题。
Pod Priority Preemption: Pod优先级调度
对于运行各种负载(如Service、Job)的中等规模或者大规模的集群来说,出于各种原因,我们需要尽可能提高集群的资源利用率。而提高资源利用率的常规做法是采用优先级方案,即不同类型的负载对应不同的优先级,同时允许集群中的所有负载所需的资源总量超过集群可提供的资源,在这种情况下,当发生资源不足的情况时,系统可以选择释放一些不重要的负载(优先级最低的),保障最重要的负载能够获取足够的资源稳定运行。
Kubernetes 1.8版本之前,当集群的可用资源不足时,在用户提交新的Pod创建请求后,该Pod会一直处于Pending状态,即使这个Pod是一个很重要(很有身份)的Pod,也只能被动等待其他Pod被删除并释放资源,才能有机会被调度成功。Kubernetes 1.8版本引入了基于Pod优先级抢占(Pod Priority Preemption)的调度策略,此时Kubernetes会尝试释放目标节点上低优先级的Pod,以腾出空间(资源)安置高优先级的Pod,这种调度方式被称为“抢占式调度”。在Kubernetes 1.11版本中,该特性升级为Beta版本,默认开启,在后继的Kubernetes 1.14版本中正式Release。如何声明一个负载相对其他负载“更重要”?我们可以通过以下几个维度来定义:
◎ Priority,优先级;
◎ QoS,服务质量等级;
◎ 系统定义的其他度量指标。
优先级抢占调度策略的核心行为分别是驱逐(Eviction)与抢占(Preemption),这两种行为的使用场景不同,效果相同。Eviction是kubelet进程的行为,即当一个Node发生资源不足(under resource pressure)的情况时,该节点上的kubelet进程会执行驱逐动作,此时Kubelet会综合考虑Pod的优先级、资源申请量与实际使用量等信息来计算哪些Pod需要被驱逐;当同样优先级的Pod需要被驱逐时,实际使用的资源量超过申请量最大倍数的高耗能Pod会被首先驱逐。对于QoS等级为“Best Effort”的Pod来说,由于没有定义资源申请(CPU/Memory Request),所以它们实际使用的资源可能非常大。Preemption则是Scheduler执行的行为,当一个新的Pod因为资源无法满足而不能被调度时,Scheduler可能(有权决定)选择驱逐部分低优先级的Pod实例来满足此Pod的调度目标,这就是Preemption机制。
需要注意的是,Schedule可能会去做Node A上的一个Pod来满足Node B上的一个新Pod的调度任务,比如下面的这个例子:
一个低优先级的Pod A在Node A(属于机架R)上运行,此时有一个高优先级的Pod B等待调度,目标节点是同属机架R的Node B,他们中的一个或全部都定义了anti-affinity规则,不允许在同一个机架上运行,此时Scheduler只好“丢车保帅”,驱逐低优先级的Pod A以满足高优先级的Pod B的调度。
Pod优先级调度实例如下。
首先,由集群管理员创建PriorityClasses,PriorityClass不属于任何命名空间:
1
2
3
4
5
6
7
8
9 1apiVersion: scheduling.k8s.io/v1beta1
2kind: PriorityClass
3metadata:
4 name: high-priority
5value: 100000
6globalDefault: false
7description: "This priority class should be used for xyz service pods only"
8
9
上述YAML文件定义了一个名为high-priority的优先级类别,优先级为100000,数字越大,优先级越高,超过一亿的数字被系统保留,用于指派给系统组件。
我们可以在任意Pod中引用上述Pod优先级类别:
1
2
3
4
5
6
7
8
9
10
11
12
13 1apiVersion: v1
2kind: Pod
3metadata:
4 name: nginx
5 labels:
6 env: test
7spec:
8 containers:
9 - name: nginx
10 image: nginx
11 imagePullPolicy: IfNotPresent
12 priorityClassName: high-priority
13
如果发生了需要抢占的调度,高优先级Pod就可能抢占节点N,并将其低优先级Pod驱逐出节点N,高优先级Pod的status信息中的nominatedNodeName字段会记录目标节点N的名称。需要注意,高优先级Pod仍然无法保证最终被调度到节点N上,在节点N上低优先级Pod被驱逐的过程中,如果有新的节点满足高优先级Pod的需求,就会把它调度到新的Node上。而如果在等待低优先级的Pod退出的过程中,又出现了优先级更高的Pod,调度器将会调度这个更高优先级的Pod到节点N上,并重新调度之前等待的高优先级Pod。
优先级抢占的调度方式可能会导致调度陷入“死循环”状态。当Kubernetes集群配置了多个调度器(Scheduler)时,这一行为可能就会发生,比如下面这个例子:
Scheduler A为了调度一个(批)Pod,特地驱逐了一些Pod,因此在集群中有了空余的空间可以用来调度,此时Scheduler B恰好抢在Scheduler A之前调度了一个新的Pod,消耗了相应的资源,因此,当Scheduler A清理完资源后正式发起Pod的调度时,却发现资源不足,被目标节点的kubelet进程拒绝了调度请求!这种情况的确无解,因此最好的做法是让多个Scheduler相互协作来共同实现一个目标。
最后要指出一点:使用优先级抢占的调度策略可能会导致某些Pod永远无法被成功调度。因此优先级调度不但增加了系统的复杂性,还可能带来额外不稳定的因素。因此,一旦发生资源紧张的局面,首先要考虑的是集群扩容,如果无法扩容,则再考虑有监管的优先级调度特性,比如结合基于Namespace的资源配额限制来约束任意优先级抢占行为。
DaemonSet:在每个Node上都调度一个Pod
DaemonSet是Kubernetes 1.2版本新增的一种资源对象,用于管理在集群中每个Node上仅运行一份Pod的副本实例。在每个Node上都运行一个monitor。
这种用法适合有这种需求的应用。
◎ 在每个Node上都运行一个GlusterFS存储或者Ceph存储的Daemon进程。
◎ 在每个Node上都运行一个日志采集程序,例如Fluentd或者Logstach。
◎ 在每个Node上都运行一个性能监控程序,采集该Node的运行性能数据,例如Prometheus Node Exporter、collectd、New Relic agent或者Ganglia gmond等。
DaemonSet的Pod调度策略与RC类似,除了使用系统内置的算法在每个Node上进行调度,也可以在Pod的定义中使用NodeSelector或NodeAffinity来指定满足条件的Node范围进行调度。
下面的例子定义为在每个Node上都启动一个fluentd容器,配置文件fluentd-ds.yaml的内容如下,其中挂载了物理机的两个目录“/var/log”和“/var/lib/docker/containers”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 1apiVersion: apps/v1
2kind: DaemonSet
3metadata:
4 name: fluentd-cloud-logging
5 labels:
6 k8s-app: fluentd-cloud-logging
7spec:
8 selector:
9 matchLabels:
10 k8s-app: fluentd-cloud-logging
11 template:
12 metadata:
13 namespace: kube-system
14 labels:
15 k8s-app: fluentd-cloud-logging
16 spec:
17 containers:
18 - name: fluentd-cloud-logging
19 image: fluentd-elasticsearch:1.17
20 resources:
21 limits:
22 cpu: 100m
23 memory: 200Mi
24 env:
25 - name: FLUENTD_ARGS
26 value: -q
27 volumeMounts:
28 - name: varlog
29 mountPath: /var/log
30 readOnly: false
31 - name: containers
32 mountPath: /var/lib/docker/containers
33 readOnly: false
34 volumes:
35 - name: containers
36 hostPath:
37 path: /var/lib/docker/containers
38 - name: varlog
39 hostPath:
40 path: /var/log
41
42
需要镜像的可以去docker hub上下载,之后push到自己的镜像仓库中哦。
在Kubernetes 1.6以后的版本中,DaemonSet也能执行滚动升级了,即在更新一个DaemonSet模板的时候,旧的Pod副本会被自动删除,同时新的Pod副本会被自动创建,此时DaemonSet的更新策略(updateStrategy)为RollingUpdate,如下所示:
1
2
3
4
5
6
7
8 1apiVersion:apps/v1
2kind:DaemonSet
3metadata:
4 name: goldpinger
5spec:
6 updateStrategy:
7 type: RollingUpdate
8
updateStrategy的另外一个值是OnDelete,即只有手工删除了DaemonSet创建的Pod副本,新的Pod副本才会被创建出来。如果不设置updateStrategy的值,则在Kubernetes 1.6之后的版本中会被默认设置为RollingUpdate。
小结:
进来家里发生了点事情,耽搁了更新进度,希望大家谅解。
谢谢大家的支持。