在前面的学习中我们了解到,在Kubernetes中,Pod管理对象主要有RC(RS)、Deployment、StatefulSet、DaemonSet和Job(CronJob)等。其中RC(RS)和Deployment的用法已经大致了解,这里主要记录下StatefulSet、DaemonSet和Job(CronJob)的用法。默认情况下,Pod管理对象在创建Pod的时候是根据系统自动调度算法来完成部署的,我们可以设置调度策略来实现Pod的精准调度。
Pod调度策略
NodeSelector
我们可以该某个节点Node设置标签,然后通过NodeSelector让Pod调度到该节点上。
前面搭建的Kubernetes集群有两个worker节点node1和node2,我们给node2打个标签:
kubectl label nodes node2 tier=frontend
接着定义一个RC配置(node-select-pod.yml):
apiVersion: v1 kind: ReplicationController metadata: name: test-rc spec: replicas: 1 selector: name: nginx template: metadata: labels: name: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 nodeSelector: tier: frontend
创建该RC,观察Pod最终调度的节点:
可以看到nginx pod已经成功调度到了node2节点上。
Kubernetes会给每个node贴上一些默认的标签,通过kubectl get node node1 -o yaml
可以看到这些默认的标签:
beta.kubernetes.io/arch: amd64 beta.kubernetes.io/os: linux kubernetes.io/arch: amd64 kubernetes.io/hostname: node1 kubernetes.io/os: linux
这些默认的标签在下面这些调度策略中也是蛮常用的。
NodeAffinity
Affinity[əˈfɪnəti]:亲和力,喜好。NodeAffinity为Node亲和力调度,主要有两个规则(名称有点长,快18cm了吧):
- RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod,硬规则。
- PreferredDuringSchedulingIgnoredDuringExecution:软规则,最好满足所列出的条件才调度Pod。
IgnoredDuringExecution的意思是,如果一个Pod已经调度到某个节点上了,然后这个节点的标签发生了改变,那么不影响已经调度好的Pod,ignore。
定义一个配置文件(node-affinity.yml),用于演示NodeAffinity:
apiVersion: v1 kind: ReplicationController metadata: name: test-rc spec: replicas: 1 selector: name: nginx template: metadata: labels: name: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: beta.kubernetes.io/arch operator: In values: - amd64 preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: disk-type operator: In values: - ssd
上面配置使得RC在调度nginx pod的时候需要满足:节点具有beta.kubernetes.io/arch=amd64标签,如果具有disk-type=ssd标签就更好了,换句话说就是希望nginx pod调度在cpu架构为amd,磁盘为ssd的节点上。
上面配置中,操作符operator
除了可以使用In外,还可以使用NotIn、Exists、DoesNotExist、Gt、Lt。
NodeAffinity规则设置需要注意的几个点:
- 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可;
- 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。
运行上面这个配置:
可以看到,因为node1和node2都具有beta.kubernetes.io/arch=amd64标签,而没有disk-type=ssd标签,所以nginx pod有可能被调度到node1也有可能被调度到node2。
我们给node1添加disk-type=ssd标签,重新运行上面的配置文件:
可以看到,nginx pod被调度到了node1上。
PodAffinity
PodAffinity指的是Pod亲和力调度股则,比如某些节点已经存在一些Pod了,新的Pod在调度的时候希望和指定Pod在一台节点上,亦或有意避开和指定Pod在一台节点上,这时候就可以用PodAffinity实现。
NodeAffinity规则设置也是通过requiredDuringSchedulingIgnoredDuringExecution
和PreferredDuringSchedulingIgnoredDuringExecution
实现的。此外,NodeAffinity还需要设置topology(拓扑规则),意为表达节点所属的topology范围:
- kubernetes.io/hostname(节点所处的服务器)
- failure-domain.beta.kubernetes.io/zone(节点所处的服务器云盘分区)
- failure-domain.beta.kubernetes.io/region(节点所处的服务器云盘所在的地区)
要演示PodAffinity的使用,需要先创建一个参照Pod,创建一个参照Pod的配置文件(flag-pod.yml):
apiVersion: v1 kind: Pod metadata: name: flag-pod labels: tier: frontend app: flag-nginx spec: containers: - name: flag-nginx image: nginx ports: - containerPort: 8081
这个Pod具有tier=frontend
和app=flag-nginx
标签。
创建这个参照Pod:
参照pod运行在了node1节点上。
创建好后,接着定义一个新的配置类(pod-affinity.yml):
apiVersion: v1 kind: Pod metadata: name: pod-affinity spec: containers: - name: nginx image: nginx ports: - containerPort: 80 affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: tier operator: In values: - frontend topologyKey: kubernetes.io/hostname
上述配置要求,nginx pod需要和标签包含tier=frontend的Pod分配在同一个Node上(topologyKey: kubernetes.io/hostname),创建该Pod,观察它被调度到哪个节点上了:
podAntiAffinity和podAffinity刚好相反,举个例子,创建一个新的配置类(pod-affinity-two.yml):
apiVersion: v1 kind: Pod metadata: name: pod-affinity-two spec: containers: - name: nginx image: nginx ports: - containerPort: 80 affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - flag-nginx topologyKey: kubernetes.io/hostname
上面配置希望nginx pod不和app=flag-nginx的Pod在同一个节点。
创建该Pod,观察它被调度到哪个节点上了:
可以看到,它和flag-nginx处于不同的节点,位于node2。
Taints & Tolerations
Taints用于给节点添加污点,而Tolerations用于定义Pod对节点污点的容忍度。在Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污点,否则无法在这些Node上运行。Toleration是Pod的属性,让Pod能够(注意,只是能够,而非必须)运行在标注了Taint的Node上。
给节点设置污点的语法为:
kubectl taint nodes [nodeName] key=value:rule
其中rule的取值有:
- NoScheudle:不调度;
- PreferNoSchedule:最好不要调度;
- NoExecute:不运行;
- PreferNoExecute:最好不运行。
- NoSchedule不调度,如果是在调度后设置的污点,并且Pod没有容忍该污点,也能继续执行
- NoExecute不执行,如果是在调度后设置的污点,并且Pod没有容忍该污点,则会被驱逐。可以设置驱逐时间
tolerationSeconds: xx
。
NoSchedule和NoExecute区别:
我们给node1节点设置一个污点:
kubectl taint nodes node1 aa=bb:NoSchedule
然后新建一个Pod配置类(pod-taint-test.yml):
apiVersion: v1 kind: Pod metadata: name: pod-taint spec: containers: - name: nginx image: nginx ports: - containerPort: 80 tolerations: - key: "aa" operator: "Equal" value: "bb" effect: "NoSchedule"
Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一:
- operator的值是Exists(无须指定value),如:
...... tolerations: - key: "aa" operator: "Exists" effect: "NoSchedule"
- operator的值是Equal并且value相等。如果不指定operator,则默认值为Equal。
另外,有如下两个特例:
- 空的key配合Exists操作符能够匹配所有的键和值;
- 空的effect匹配所有的effect。
回到pod-taint-test.yml,该配置文件定义nginx pod可以容忍aa=bb:NoSchedule
这个污点,所以它有可能会被调度到node1上。创建该Pod,观察:
这时候在nginx pod所运行的节点node1上新增一个污点:
kubectl taint nodes node1 cc=dd:NoExecute
观察nginx pod情况:
可以看到它被驱逐了,已经没有正在运行的pod了。
Pod Priority Preemption
我们可以给Pod指定优先级,优先级可以通过PriorityClass对象创建,比如创建一个优先级为10000的PriorityClass:
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: hign-priority value: 10000 globalDefault: false description: "10000优先级"
上述文件定义了一个名为high-priority的优先级类别,优先级为10000,数字越大,优先越高。超过一亿的数字被系统保留,用于指派给系统组件。
优先级创建后,可以在Pod定义中引用该优先级:
... spec: containers: - name: nginx image: nginx ports: - containerPort: 80 priorityClassName: hign-priority
Pod管理对象
Job
Job是一种特殊的Pod管理对象,是一种一次性Pod运行任务,任务结束后,Pod的生命周期也就结束了。
定义一个Job配置类(job.yml):
apiVersion: batch/v1 kind: Job metadata: name: job-ex spec: template: spec: containers: - name: job image: busybox command: ["echo", "hello job"] restartPolicy: Never
Job的重启策略restartPolicy
只支持Never和OnFailure。
创建这个Job:
查看Job状态:
COMPLETIONS 1/1表示总共需要执行1个任务,共执行完1个任务。
查看对应Pod的状态:
状态为Completed。
查看Job日志:
Job还可以设置并发数量和总Job数,修改上面的job.yml:
apiVersion: batch/v1 kind: Job metadata: name: job-ex spec: parallelism: 2 # 并发数2 completions: 6 # 总的Job数量 template: spec: containers: - name: job image: busybox command: ["echo", "hello job"] restartPolicy: Never
运行该Job:
查看Pod:
CronJob
CronJob顾名思义就是支持Cron表达式的Job,不过Kubernetes的Cron表达式和传统的Cron表达式不太一样,不支持到秒级。具体规则如下:
Minutes Hours DayofMonth Month DayofWeek Year |
- Minutes:可出现
,
-
*
/
这4个字符,有效范围为0~59的整数。 - Hours:可出现
,
-
*
/
这4个字符, 有效范围为0~23的整数。 - DayofMonth:可出现
,
-
*
/
?
L
W
C
这8个字符,有效范围为0~31的整数。 - Month:可出现
,
-
*
/
这4个字符,有效范围为1~12的整数或JAN~DEC。 - DayofWeek:可出现
,
-
*
/
?
L
C
#
这8个字符,有效范围为1~7的整数或SUN~SAT。1表示星期天,2表示星期一,以此类推。
上面特殊字符的含义如下:
*
:表示匹配该域的任意值,假如在Minutes域使用*
,则表示每分钟都会触发事件。/
:表示从起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域设置为5/20,则意味着第1次触发在第5min时,接下来每20min触发一次,将在第25min、第45min等时刻分别触发。
定义一个CronJob的配置类(cron-job.yml):
apiVersion: batch/v1beta1 kind: CronJob metadata: name: cron-job-ex spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox command: ["sh", "-c", "date;echo hello cronJob"] restartPolicy: Never
上面的定时任务每分钟执行一次。
创建该CronJob:
过个两三分钟查看运行情况:
DaemonSet
DaemonSet适用于在每个Node都需要运行一个Pod的时候使用,比如:每一个Node上运行一个日志采集、性能监控的Pod。
比如我们现在需要在每个节点上都部署一个node-exporter来采集节点信息,可以通过DaemonSet来实现。
定义一个DaemonSet配置文件(daemonset.yml):
apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter-d spec: selector: matchLabels: name: node-exporter template: metadata: labels: name: node-exporter spec: containers: - name: node-exporter image: prom/node-exporter ports: - containerPort: 9100
创建该DaemonSet之前先删除上面在node1节点上创建的污点:
kubectl taint nodes node1 aa:NoSchedule- kubectl taint nodes node1 cc:NoExecute-
创建该DaemonSet,然后观察Pod信息,看是否每个节点都部署了一个实例: