Kubernetes Pod管理对象与调度策略


2019年11月04日 13:04:40   1,480 次浏览

在前面的学习中我们了解到,在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了吧):

  1. RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod,硬规则。
  2. 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规则设置需要注意的几个点:

  1. 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可;
  2. 如果在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规则设置也是通过requiredDuringSchedulingIgnoredDuringExecutionPreferredDuringSchedulingIgnoredDuringExecution实现的。此外,NodeAffinity还需要设置topology(拓扑规则),意为表达节点所属的topology范围:

  1. kubernetes.io/hostname(节点所处的服务器)
  2. failure-domain.beta.kubernetes.io/zone(节点所处的服务器云盘分区)
  3. 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=frontendapp=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的取值有:

  1. NoScheudle:不调度;
  2. PreferNoSchedule:最好不要调度;
  3. NoExecute:不运行;
  4. 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的设置保持一致,并且满足以下条件之一:

  1. operator的值是Exists(无须指定value),如:
......
  tolerations:
    - key: "aa"
      operator: "Exists"
      effect: "NoSchedule"
  1. operator的值是Equal并且value相等。如果不指定operator,则默认值为Equal。

另外,有如下两个特例:

  1. 空的key配合Exists操作符能够匹配所有的键和值;
  2. 空的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
  1. Minutes:可出现, - * / 这4个字符,有效范围为0~59的整数。
  2. Hours:可出现, - * /这4个字符, 有效范围为0~23的整数。
  3. DayofMonth:可出现, - * / ? L W C 这8个字符,有效范围为0~31的整数。
  4. Month:可出现, - * /这4个字符,有效范围为1~12的整数或JAN~DEC。
  5. DayofWeek:可出现, - * / ? L C 这8个字符,有效范围为1~7的整数或SUN~SAT。1表示星期天,2表示星期一,以此类推。

上面特殊字符的含义如下:

  1. *:表示匹配该域的任意值,假如在Minutes域使用*,则表示每分钟都会触发事件。
  2. /:表示从起始时间开始触发,然后每隔固定时间触发一次,例如在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信息,看是否每个节点都部署了一个实例:

发表评论

邮箱地址不会被公开。 必填项已用*标注