K8S计划任务、污点容忍和亲和性

K8S计划任务、污点容忍和亲和性

Job

Kubernetes (K8S) 中的 Job 是一种控制器,它的主要作用是确保一组 Pod 按照设定的次数成功运行完成。Job 通常用于执行一次性任务或批处理任务,这些任务不需要长期运行,但需要在成功完成后自动退出。以下是 Job 的主要功能和使用场景:

  1. 一次性任务 Job 用于执行一次性任务,例如数据处理、备份、批量计算等。这些任务在完成后自动退出,并且 Kubernetes 会确保任务在设定的次数内成功执行。
  2. 确保任务成功 如果某个 Pod 因为错误或其他原因失败,Job 控制器会启动一个新的 Pod 来代替它,直到任务成功执行指定的次数。
  3. 并行处理 Job 支持并行处理,可以配置多个 Pod 同时执行任务。这对于需要并行计算或处理大数据集的场景非常有用。
  4. 定时任务配合 CronJob Job 通常与 CronJob 结合使用,CronJob 是基于时间调度的任务管理器。通过 CronJob,可以按照设定的时间间隔定期执行 Job。
  5. 任务成功与失败的处理 定义任务的重试策略,指定最大失败次数等。Job 还可以通过 backoffLimit 参数控制重试的次数。

Job的配置如下所示:

[root@master-01 ~]# vim /k8spod/job/job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    job-name: echo
  name: echo
  namespace: default
spec:
  suspend: false # 1.21+
  ttlSecondsAfterFinished: 100
  backoffLimit: 4
  completions: 1
  parallelism: 1
  template:
    spec:
      restartPolicy: Never
      containers:
      - command:
        - echo
        - Hello, Job
        image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/busybox
        imagePullPolicy: IfNotPresent
        name: echo
参数 含义
backoffLimit 如果任务执行失败,失败多少次后不再执行
completions 有多少个Pod执行成功,认为任务是成功的,若数值为空,则默认和parallelism数值一样
parallelism 并行执行任务的数量。 如果parallelism数值大于未完成任务数,只会创建未完成的数量;比如completions是4,并发是3,第一次会创建3个Pod执行任务,第二次只会创建1个Pod执行任务
ttlSecondsAfterFinished Job在执行结束之后(状态为completed或Failed)自动清理。设置为0表示执行结束立即删除,不设置则不会清除,需要开启TTLAfterFinished特性

创建资源并查看资源状态

[root@master-01 ~]# kubectl create -f /k8spod/job/job.yaml
job.batch/echo created
[root@master-01 ~]# kubectl get jobs.batch
NAME   STATUS    COMPLETIONS   DURATION   AGE
echo   Running   0/1           5s         5s
[root@master-01 ~]# kubectl get pods
NAME                            READY   STATUS      RESTARTS        AGE
cluster-test-665f554bcc-bcw5v   1/1     Running     279 (39m ago)   77d
echo-v2lfp                      0/1     Completed   0               12s

可以查看日志,观察Job是否正常输出内容。等待100秒后,容器和Job都会被清理

[root@master-01 ~]# kubectl logs echo-v2lfp
Hello, Job

读者可以修改suspend字段为true,创建后,可以观察到Job被暂停,等待恢复

[root@master-01 ~]# kubectl get jobs.batch
NAME   STATUS      COMPLETIONS   DURATION   AGE
echo   Suspended   0/1                      3s

CronJob

Kubernetes 的 CronJob 是用于定期执行任务的资源对象,类似于 Linux 的 cron 定时任务。CronJob 可以根据指定的时间表在集群中创建 Job,Job 再启动一个或多个 Pod 来执行任务。这在需要定期运行脚本、批处理任务或维护任务时非常有用。

1.定期执行任务

通过 CronJob,可以按照特定的时间表周期性地运行任务,比如每天、每小时、每分钟等。

2.任务调度

CronJob 使用类 cron 的语法来定义任务的时间表,可以灵活地安排任务的执行时间。

3.任务失败后的重试

配置 Job 的重试策略,如果任务失败,CronJob 会按照设置的策略重试任务。

4.并发策略

CronJob 支持控制并发执行的 Job 数量,允许选择是否允许并发执行,还是需要等上一个任务完成后再执行。

5.历史记录管理

可以配置保留的成功和失败的 Job 的数量,以便管理和监控任务的执行情况。

常见应用场景
  • 日志轮转:定期清理日志文件或归档日志。
  • 备份:定期备份数据库或其他重要数据。
  • 定期报告:每天或每周生成报告,并将其发送到指定位置。
  • 健康检查:定期检查系统健康状态,并根据结果执行相应操作。
注意事项
  • 时区:CronJob默认使用 UTC 时区。如果你需要使用本地时间,可能需要在容器中手动处理时区转换。
  • 性能影响:大量的并发 Job 可能会对集群性能产生影响,因此应合理配置 Job的并发策略和历史记录限制。
  • 失败处理:应设置合适的重试策略和错误处理机制,避免因临时性问题导致任务执行失败。

CronJob的配置如下所示:

[root@master-01 ~]# vim /k8spod/cronjob/cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  labels:
    run: hello
  name: hello
  namespace: default
spec:
  schedule: '*/1 * * * *'
  successfulJobsHistoryLimit: 3
  suspend: false
  concurrencyPolicy: Allow
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            run: hello
        spec:
          restartPolicy: OnFailure
          containers:
          - name: hello
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
            image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/busybox
            imagePullPolicy: IfNotPresent
参数 含义
schedule 调度周期,和Linux一致,分别是分时日月周
successfulJobsHistoryLimit 保留多少已完成的任务,按需配置
suspend 如果设置为true,则暂停后续的任务,默认为false
concurrencyPolicy 并发调度策略。可选参数如下:Allow:允许同时运行多个任务,Forbid:不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建,Replace:如果之前的任务尚未完成,新的任务会替换的之前的任务
restartPolicy 重启策略,和Pod一致

创建资源并查看资源状态,由于执行间隔为1分钟,所有刚开始时无法看到Pod

[root@master-01 ~]# kubectl get cronjobs.batch
NAME    SCHEDULE      TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   <none>     False     0        <none>          7s
[root@master-01 ~]# kubectl get pods
NAME                            READY   STATUS    RESTARTS          AGE
cluster-test-665f554bcc-bcw5v   1/1     Running   280 (5m39s ago)   77d

看到Pod内执行结果,并观察到时间间隔为1分钟

[root@master-01 ~]# kubectl get pods
NAME                            READY   STATUS      RESTARTS          AGE
cluster-test-665f554bcc-bcw5v   1/1     Running     280 (7m27s ago)   77d
hello-28747930-gjpv4            0/1     Completed   0                 75s
hello-28747931-6vxnb            0/1     Completed   0                 15s
[root@master-01 ~]# kubectl logs hello-28747930-gjpv4
Wed Aug 28 20:10:01 UTC 2024
Hello from the Kubernetes cluster
[root@master-01 ~]# kubectl logs hello-28747931-6vxnb
Wed Aug 28 20:11:01 UTC 2024
Hello from the Kubernetes cluster

初始化容器(InitContainer)

K8S初始化容器(init containers)的作用是在主容器启动之前执行一些必要的设置和准备工作。Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码,可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。Init容器可以以root身份运行,执行一些高权限命令。Init容器相关操作执行完成以后即退出,不会给业务容器带来安全隐患。初始化容器通常用于配置环境变量、准备共享卷、等待依赖服务的启动等任务。

初始化容器与postStart生命周期钩子的主要区别在于它们在Pod生命周期中的作用和执行时机。postStart是主容器生命周期的一部分,它在主容器启动后立即执行,但执行是异步的,不保证在主容器的入口(entrypoint)之前运行。postStart钩子主要用于资源部署、环境准备等,如果执行失败,主容器会根据重启策略决定是否重启。

相比之下,初始化容器是在主容器启动之前独立运行的容器,它们的成功执行是主容器启动的前提条件。初始化容器提供了一种确保主容器启动所需环境已经正确设置的机制。一旦初始化容器执行完成并退出,主容器及其后续的生命周期钩子(如postStart)才会开始执行。

总结来说,初始化容器专注于为主容器提供准备工作,而postStart钩子专注于主容器启动后的初始化任务。初始化容器在主容器之前运行,而postStart在主容器之后运行

初始化容器和普通容器大致相同,除以下这几点,它们总是运行到完成,上一个运行完成才会运行下一个,如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止,但是Pod对应的 restartPolicy 值为 Never,Kubernetes 不会重新启动 Pod。Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe

初始化容器实例如下所示:

[root@master-01 ~]# vim /k8spod/initpod/initpod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test-init
  name: test-init
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: test-init
  template:
    metadata:
      labels:
        app: test-init
    spec:
      volumes:
      - name: data
        emptyDir: {}
      initContainers:
      - command:
        - sh
        - -c
        - touch /mnt/test-init.txt
        image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        imagePullPolicy: IfNotPresent
        name: init-touch
        volumeMounts:
        - name: data
          mountPath: /mnt
      - command:
        - sh
        - -c
        - for i in `seq 1 100`; do echo $i; sleep 1; done
        image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        imagePullPolicy: IfNotPresent
        name: echo
      containers:
      - image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        imagePullPolicy: IfNotPresent
        name: test-init
        volumeMounts:
        - name: data
          mountPath: /mnt

创建资源,并查看资源创建情况,可以观察到当前正在初始化容器,而在初始化完成之前,主容器是不会启动的

[root@master-01 ~]# kubectl create -f /k8spod/initpod/initpod.yaml
deployment.apps/test-init created
[root@master-01 ~]# kubectl get pods
NAME                            READY   STATUS     RESTARTS        AGE
cluster-test-665f554bcc-bcw5v   1/1     Running    281 (81s ago)   77d
test-init-54ccc78b57-f929f      0/1     Init:0/2   0               5s
test-init-54ccc78b57-smtxn      0/1     Init:1/2   0               5s
test-init-54ccc78b57-zkslp      0/1     Init:1/2   0               5s

查看第二个初始化容器的日志,可以观察到,使用输出1-100来模拟初始化的任务,比如下载某些软件和依赖包或者修改内核参数等

[root@master-01 ~]# kubectl logs test-init-54ccc78b57-zkslp -c echo
1
2
3
...省略部分输出...

临时容器(EphemeralContainer)

Kubernetes中的临时容器(Ephemeral Containers)是一种在Pod中运行临时任务的机制,通常用于调试目的。与初始化容器(init containers)和主容器(main containers)不同,临时容器不在Pod创建时定义,而是可以在Pod运行时动态添加和运行。

临时容器的特点
  1. 动态添加:可以在Pod运行时添加和删除临时容器,用于调试和诊断。
  2. 无需重启Pod:添加临时容器不需要重新创建或重启Pod,这使得调试更加方便。
  3. 短生命周期:临时容器通常用于短暂的调试任务,比如检查Pod的状态、查看文件系统等。
使用场景
  1. 调试:当你需要在Pod内运行一些临时命令或工具(如busyboxdebug工具)来排查问题时,临时容器非常有用。
  2. 排查问题:可以用来检查容器的网络连接、文件系统或应用的运行状态,而不需要修改Pod的原始定义。

使用刚才的初始化容器,尝试观察IP地址和端口,提示没有此命令

[root@master-01 ~]# kubectl get pods -owide
NAME                            READY   STATUS    RESTARTS        AGE   IP               NODE        NOMINATED NODE   READINESS GATES
cluster-test-665f554bcc-bcw5v   1/1     Running   281 (30m ago)   77d   172.16.184.126   master-01   <none>           <none>
test-init-54ccc78b57-f929f      1/1     Running   0               28m   172.16.222.41    master-02   <none>           <none>
[root@master-01 ~]# kubectl exec -it test-init-54ccc78b57-f929f -- bash
Defaulted container "test-init" out of: test-init, init-touch (init), echo (init)
root@test-init-54ccc78b57-f929f:/# ip
bash: ip: command not found
root@test-init-54ccc78b57-f929f:/# ss
bash: ss: command not found

使用临时容器进行调试

[root@master-01 ~]# kubectl debug test-init-54ccc78b57-f929f -it --image=registry.cn-beijing.aliyuncs.com/caijxlinux/debug-tools
Defaulting debug container name to debugger-kpvw5.
If you don't see a command prompt, try pressing enter.
(21:54 test-init-54ccc78b57-f929f:/)
(21:54 test-init-54ccc78b57-f929f:/) ip ad s | grep 172
    inet 172.16.222.41/32 scope global eth0
(21:55 test-init-54ccc78b57-f929f:/) ss | tail -1
u_str  ESTAB      0      0       * 8542556               * 8542557

输入exit退出临时容器,提示下次可用attach参数再次连接,但是尝试连接会提示报错

(21:50 test-init-54ccc78b57-f929f:/) exit
exit
Session ended, the ephemeral container will not be restarted but may be reattached using 'kubectl attach test-init-54ccc78b57-f929f -c debugger-j9wmw -i -t' if it is still running
[root@master-01 ~]# kubectl attach test-init-54ccc78b57-f929f -c debugger-j9wmw -i -t
If you don't see a command prompt, try pressing enter.
error: Internal error occurred: unable to upgrade connection: container debugger-j9wmw not found in pod test-init-54ccc78b57-f929f_default

通过describe命令查看临时容器的状态,发现是Terminated。所以在使用exit命令退出时,不能再次连接,只能再次创建临时容器。

[root@master-01 ~]# kubectl describe pod test-init-54ccc78b57-f929f
...省略部分输出...
Ephemeral Containers:
  debugger-j9wmw:
    Container ID:   containerd://6b72cb98135aa92d57b04b12de0a37029c9b552fefd92c27dcbc6d8ac6e6fb18
    Image:          registry.cn-beijing.aliyuncs.com/caijxlinux/debug-tools
    Image ID:       registry.cn-beijing.aliyuncs.com/caijxlinux/debug-tools@sha256:e5bb1f4eb56039e0e82d7ec3ec788c62e615ec6ba167690f1b675b1dc4a15f36
    Port:           <none>
    Host Port:      <none>
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Thu, 29 Aug 2024 05:36:17 +0800
      Finished:     Thu, 29 Aug 2024 05:54:26 +0800
    Ready:          False
...省略部分输出...

读者也可以在创建临时容器时,使用--attach参数来防止这种情况。 如果会话断开连接,也可以继续使kubectl attach(一次使用)或kubectl exec(多次使用) 重新连接。

[root@master-01 ~]# kubectl debug test-init-54ccc78b57-f929f -it --image=registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools --attach=false
Defaulting debug container name to debugger-xmkxg.
[root@master-01 ~]# kubectl exec -it test-init-54ccc78b57-f929f -c debugger-xmkxg -- bash
(22:14 test-init-54ccc78b57-f929f:/) 

K8S污点和容忍(Taint&Toleration)

在Kubernetes中,污点(Taints) 是用于限制或控制Pod调度到特定节点上的一种机制。通过给节点打上污点,Kubernetes可以防止Pod被调度到这些节点上,除非这些Pod具有相应的容忍度(Tolerations)。

污点的作用

污点用于控制Pod的调度行为,确保只有特定的Pod可以被调度到带有污点的节点上。这在以下场景中非常有用:

  1. 隔离关键工作负载:可以使用污点将关键的、延迟敏感的工作负载隔离到特定节点,防止其他Pod干扰这些节点。
  2. 维护节点:如果一个节点需要维护,可以打上污点,将Pod从该节点上迁移走,防止新的Pod被调度到该节点上。
  3. 限制资源使用:在资源有限的节点上打上污点,防止资源密集型的Pod被调度到这些节点上。

节点添加污点的示例如下:(一个节点可以有多个污点)

kubectl taint nodes node1 key=value:NoSchedule
字段 含义
NoSchedule 禁止调度到该节点,已经在该节点上的Pod不受影响
NoExecute 禁止调度到该节点,如果不符合这个污点,会立马被驱逐(或在一段时间后)
PreferNoSchedule 尽量避免将Pod调度到指定的节点上,如果没有更合适的节点,可以部署到该节点

在Kubernetes中,容忍(Toleration) 是与污点(Taint)相对应的一种机制。容忍允许Pod调度到带有特定污点的节点上,而不受污点的限制。通过容忍,Pod可以“容忍”节点上的污点,从而允许自己被调度到这些节点上。

使用场景
  • 节点维护:当节点需要进行维护时,打上NoScheduleNoExecute污点,只有具备相应容忍度的Pod才会继续留在节点上或调度到该节点。
  • 关键任务隔离:关键任务Pod可以配置容忍度,以确保它们可以调度到带有特定污点的节点上,从而隔离非关键任务的Pod。
  • 资源管理:通过配置污点和容忍度,可以更精细地控制Pod的调度行为,以实现资源的合理分配和利用。

假设node-01节点为高性能节点,内置SSD硬盘,只有需要高性能存储的Pod才能调度到该节点上。为节点添加污点和标签

[root@master-01 ~]# kubectl taint nodes node-01 ssd=true:NoExecute
node/node-01 tainted
[root@master-01 ~]# kubectl taint nodes node-01 ssd=true:NoSchedule
node/node-01 tainted
[root@master-01 ~]# kubectl label nodes node-01 ssd=true
node/node-01 labeled

查看node-01节点上添加的污点

[root@master-01 ~]# kubectl describe nodes node-01 | grep Taints -A 2
Taints:             ssd=true:NoExecute
                    ssd=true:NoSchedule
Unschedulable:      false

配置Deployment控制器,并添加条件(通过nodeSelector和tolerations字段),模拟需要调度到node-01节点的情况(正常创建DP控制器,不会调度到node-01节点,因为有污点)

[root@master-01 ~]# vim taints_pods.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: taints-pods
  name: taints-pods
spec:
  replicas: 3
  selector:
    matchLabels:
      app: taints-pods
  template:
    metadata:
      labels:
        app: taints-pods
    spec:
      containers:
      - image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        name: nginx
      nodeSelector:
        ssd: "true"
      tolerations:
      - key: "ssd"
        operator: "Exists"

创建资源并查看资源状态,可以观察容器在node-01节点上运行

[root@master-01 ~]# kubectl get pods -owide
NAME                            READY   STATUS    RESTARTS      AGE    IP              NODE        NOMINATED NODE   READINESS GATES
cluster-test-665f554bcc-kg54c   1/1     Running   7 (56m ago)   94d    172.16.184.66   master-01   <none>           <none>
taints-pods-55956cdf4f-b9ltg    1/1     Running   0             3m1s   172.16.190.7    node-01     <none>           <none>
taints-pods-55956cdf4f-ff6wc    1/1     Running   0             3m4s   172.16.190.5    node-01     <none>           <none>
taints-pods-55956cdf4f-lc8hb    1/1     Running   0             3m1s   172.16.190.6    node-01     <none>           <none>

K8S内内置了一些污点,具体如下表所示

字段 含义
node.kubernetes.io/not-ready 节点未准备好,相当于节点状态Ready的值为False
node.kubernetes.io/unreachable Node Controller访问不到节点,相当于节点状态Ready的值为Unknown
node.kubernetes.io/out-of-disk 节点磁盘耗尽
node.kubernetes.io/memory-pressure 节点存在内存压力
node.kubernetes.io/disk-pressure 节点存在磁盘压力
node.kubernetes.io/network-unavailable 节点网络不可达
node.kubernetes.io/unschedulable 节点不可调度
node.cloudprovider.kubernetes.io/uninitialized 如果Kubelet启动时指定了一个外部的cloudprovider,它将给当前节点添加一个Taint将其标记为不可用。在cloud-controller-manager的一个controller初始化这个节点后,Kubelet将删除这个Taint

节点宕机快速恢复业务示例如下

[root@master-01 ~]# vim fastrecovery.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: tolerations-second
  name: tolerations-second
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tolerations-second
  template:
    metadata:
      labels:
        app: tolerations-second
    spec:
      containers:
      - image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        name: nginx
      nodeSelector:
        ssd: "true"
      tolerations:
      - key: ssd
        operator: Equal
        value: "true"
      - effect: NoExecute
        key: node.kubernetes.io/unreachable
        operator: Exists
        tolerationSeconds: 10
      - effect: NoExecute
        key: node.kubernetes.io/not-ready
        operator: Exists
        tolerationSeconds: 10

创建资源,并查看资源状态,可以观察到容器在node-01节点被创建

[root@master-01 ~]# kubectl create -f  fastrecovery.yaml
deployment.apps/tolerations-second created
[root@master-01 ~]# kubectl get pods -owide
NAME                                  READY   STATUS    RESTARTS      AGE   IP              NODE        NOMINATED NODE   READINESS GATES
tolerations-second-77b86c9947-w8t6j   1/1     Running   0             19s   172.16.190.9    node-01     <none>           <none>

node-02节点添加标签和污点

[root@master-01 ~]# kubectl taint node node-02 ssd=true:NoExecute
node/node-02 tainted
[root@master-01 ~]# kubectl taint node node-02 ssd=true:NoSchedule
node/node-02 tainted
[root@master-01 ~]# kubectl label nodes node-02 ssd=true
node/node-02 labeled

将node-01节点关机,大概1min后,K8S检测到节点故障,为节点添加内置污点

[root@node-01 ~]# shutdown -h now
[root@master-01 ~]# kubectl get nodes
NAME        STATUS     ROLES    AGE   VERSION
master-01   Ready      <none>   95d   v1.30.1
master-02   Ready      <none>   95d   v1.30.1
master-03   Ready      <none>   95d   v1.30.1
node-01     NotReady   <none>   95d   v1.30.1
node-02     Ready      <none>   95d   v1.30.1
[root@master-01 ~]# kubectl describe nodes node-01
...省略部分输出...
Taints:             node.kubernetes.io/unreachable:NoExecute
                    ssd=true:NoExecute
                    node.kubernetes.io/unreachable:NoSchedule
                    ssd=true:NoSchedule
Unschedulable:      false
...省略部分输出...

查看容器,可以观察到容器已经在node-02节点重建

[root@master-01 ~]# kubectl get pods -owide
NAME                                  READY   STATUS        RESTARTS      AGE     IP              NODE        NOMINATED NODE   READINESS GATES
tolerations-second-77b86c9947-6zkn9   1/1     Running       0             85s     172.16.184.5    node-02     <none>           <none>
tolerations-second-77b86c9947-w8t6j   1/1     Terminating   0             4m53s   172.16.190.9    node-01     <none>           <none>
Taint命令常用示例

查看一个节点的污点

[root@master-01 ~]# kubectl get node node-01 -o go-template --template {{.spec.taints}}
[map[effect:NoSchedule key:ssd value:true] map[effect:NoExecute key:ssd value:true]]

删除污点(和label类似)

[root@master-01 ~]# kubectl taint nodes node-01 ssd-
node/node-01 untainted

修改污点

[root@master-01 ~]# kubectl taint nodes node-01 ssd=true:PreferNoSchedule --overwrite
node/node-01 modified
Affinity

在 Kubernetes 中,Affinity(亲和性) 是一种用于控制 Pod 调度的规则,它允许你定义 Pod 如何相互靠近(或远离)调度到集群中的节点上。通过使用 Affinity,你可以更好地控制工作负载在集群中的分布,以实现性能优化、资源利用率最大化、提高容错能力等目标。

Kubernetes 提供了两种主要的 Affinity 规则:

  1. Node Affinity(节点亲和性)
  2. Pod Affinity/Anti-Affinity(Pod 亲和性/反亲和性)
1. Node Affinity(节点亲和性)

Node Affinity 允许你指定 Pod 应该调度到哪些节点上。这与旧的 Node Selector 类似,但 Node Affinity 提供了更多的灵活性和条件表达能力。

Node Affinity 通常分为两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution: 必须满足的硬性规则。如果条件不满足,Pod 将无法被调度。
  • preferredDuringSchedulingIgnoredDuringExecution: 优选的软性规则。调度器会尽量满足这些条件,但即使无法满足,Pod 也会被调度。
2. Pod Affinity/Anti-Affinity(Pod 亲和性/反亲和性)

Pod Affinity 允许你指定 Pod 应该与哪些其他 Pod 一起调度到同一个节点上。Pod Anti-Affinity 则相反,用于指定 Pod 不应该与哪些其他 Pod 调度到同一个节点上。

Affinity 使用场景
  1. 性能优化:将相关的工作负载调度到相同的节点上,以减少网络延迟。
  2. 高可用性:将相同类型的 Pod 分散到不同的节点上,以减少单点故障的影响。
  3. 资源隔离:将对资源需求不同的 Pod 分开调度,避免资源争抢。
  4. 容错能力:通过 Anti-Affinity 将关键组件分布在不同的节点或区域,以提高容错能力。

某些Pod优先选择有ssd=true标签的节点,如果没有在考虑部署到其它节点;某些Pod需要部署在ssd=true和type=physical的节点上,但是优先部署在ssd=true的节点上(Pod和节点之间的关系)同一个应用的Pod不同的副本或者同一个项目的应用尽量或必须不部署在同一个节点或者符合某个标签的一类节点上或者不同的区域;相互依赖的两个Pod尽量或必须部署在同一个节点上或者同一个域内(Pod和Pod之间的关系)

节点亲和力配置如下所示

[root@master-01 ~]# vim nodeaffinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
            - physical
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: network
            operator: In
            values:
            - 100M
  containers:
  - name: with-node-affinity
    image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1

requiredDuringSchedulingIgnoredDuringExecution(硬亲和力配置)

参数 配置
nodeSelectorTerms 节点选择器配置,可以配置多个matchExpressions(满足其一),每个matchExpressions下可以配置多个key、value类型的选择器(都需要满足),其中values可以配置多个(满足其一)

preferredDuringSchedulingIgnoredDuringExecution(软亲和力配置)

参数 配置
weight 软亲和力的权重,权重越高优先级越大,范围1-100
preference 软亲和力配置项,和weight同级,可以配置多个,matchExpressions和硬亲和力一致

operator(标签匹配的方式)

参数 配置
In 相当于key = value的形式
NotIn 相当于key != value的形式
Exists 节点存在label的key为指定的值即可,不能配置values字段
DoesNotExist 节点不存在label的key为指定的值即可,不能配置values字段
Gt 大于value指定的值
Lt 小于value指定的值
Pod亲和力和反亲和力

Pod亲和力和反亲和力配置如下所示:(具体KV需要根据实际环境进行修改)

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          namespaces:
          - default
          topologyKey: failure-domain.beta.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
参数 含义
labelSelector Pod选择器配置,可以配置多个
matchExpressions 和节点亲和力配置一致
operator 配置和节点亲和力一致,但是没有Gt和Lt
topologyKey 匹配的拓扑域的key,也就是节点上label的key,key和value相同的为同一个域,可以用于标注不同的机房和地区
Namespaces 和哪个命名空间的Pod进行匹配,为空为当前命名空间

下面提供几种场景给读者理解

同一个应用部署在不同的宿主机

[root@master-01 ~]# vim mustbediffnode.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: must-be-diff-nodes
  name: must-be-diff-nodes
  namespace: kube-public
spec:
  replicas: 3
  selector:
    matchLabels:
      app: must-be-diff-nodes
  template:
    metadata:
      labels:
        app: must-be-diff-nodes
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - must-be-diff-nodes
            topologyKey: kubernetes.io/hostname
      containers:
      - image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        imagePullPolicy: IfNotPresent
        name: must-be-diff-nodes

创建资源,并查看创建的Pod是否在不同的节点上

[root@master-01 ~]# kubectl create -f mustbediffnode.yaml
deployment.apps/must-be-diff-nodes created
[root@master-01 ~]# kubectl get pod -owide -n kube-public
NAME                                 READY   STATUS    RESTARTS   AGE   IP               NODE        NOMINATED NODE   READINESS GATES
must-be-diff-nodes-595f4db45-74kb8   1/1     Running   0          43s   172.16.133.134   master-03   <none>           <none>
must-be-diff-nodes-595f4db45-grhpp   1/1     Running   0          43s   172.16.184.7     node-02     <none>           <none>
must-be-diff-nodes-595f4db45-rn2g7   1/1     Running   0          43s   172.16.190.19    node-01     <none>           <none>

应用和缓存尽量部署在同一个域内(读者可以自行添加某些容器作为缓存角色,观察效果)

[root@master-01 ~]# vim appandcache.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - store
              topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1

尽量调度到高配置服务器

[root@master-01 ~]# vim highperformance.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: prefer-ssd
  name: prefer-ssd
  namespace: kube-public
spec:
  replicas: 3
  selector:
    matchLabels:
      app: prefer-ssd
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: prefer-ssd
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - preference:
              matchExpressions:
              - key: ssd
                operator: In
                values:
                - "true"
              - key: master
                operator: NotIn
                values:
                - "true"
            weight: 100
          - preference:
              matchExpressions:
              - key: type
                operator: In
                values:
                - physical
            weight: 50
      containers:
      - name: perfer-ssd
        image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1

拓扑域(TopologyKey)

在 Kubernetes 中,Topology Key 是一个用于定义调度策略的概念,特别是与 Pod AffinityPod Anti-Affinity 相关的调度策略。它指定了用于组织和分类节点的键,这些键帮助调度器决定 Pod 的调度位置,以满足亲和性或反亲和性的要求。

Topology Key 的作用

  1. Pod Affinity
    • 当你希望将某个 Pod 调度到与已经运行的某些 Pod 同一个拓扑域(如同一个区域、同一个节点等)内时,可以使用 Topology Key 来指定这个要求。
  2. Pod Anti-Affinity
    • 当你希望将某个 Pod 调度到与已经运行的某些 Pod 分离的拓扑域内时,可以使用 Topology Key 来指定这个要求。

常见的 Topology Key

  • kubernetes.io/hostname: 指定同一个物理主机或虚拟机。用于要求 Pod 调度到同一台机器上,或者避免在同一台机器上调度。
  • failure-domain.beta.kubernetes.io/zone: 指定同一可用区或区域。用于要求 Pod 调度到同一区域,或者避免在同一区域调度。
  • failure-domain.beta.kubernetes.io/region: 指定同一地区。用于要求 Pod 调度到同一地区,或者避免在同一地区调度。
  • topology.kubernetes.io/zone: 与 failure-domain.beta.kubernetes.io/zone类似,用于指定同一可用区或区域。

配置注意事项

  • 一致性: 确保你所使用的 Topology Key 在所有节点上都是一致的。比如,kubernetes.io/hostname 是每个节点的唯一标识,而 failure-domain.beta.kubernetes.io/zone 和 topology.kubernetes.io/zone应该在所有节点上都存在。

  • 合适的拓扑域: 根据你的实际需求选择合适的拓扑域。例如,如果你的目标是避免在同一台物理机上运行多个副本,你可以使用 kubernetes.io/hostname;如果希望在不同区域内运行,可以使用 failure-domain.beta.kubernetes.io/zone 或 topology.kubernetes.io/zone。

  • 原则上,topologyKey 可以是任何合法的标签键。出于性能和安全原因,topologyKey 有一些限制:

    对于 Pod 亲和性而言,在 requiredDuringSchedulingIgnoredDuringExecution 和preferredDuringSchedulingIgnoredDuringExecution 中,topologyKey 不允许为空。对于requiredDuringSchedulingIgnoredDuringExecution 要求的 Pod 反亲和性, 准入控制器 LimitPodHardAntiAffinityTopology 要求 topologyKey 只能是 kubernetes.io/hostname。

举例,此例子并不准确(个人认为),因为会被重复调度

[root@master-01 ~]# vim topologykey.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: must-be-diff-zone
  name: must-be-diff-zone
  namespace: kube-public
spec:
  replicas: 6
  selector:
    matchLabels:
      app: must-be-diff-zone
  template:
    metadata:
      labels:
        app: must-be-diff-zone
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - must-be-diff-zone
            topologyKey: region
      containers:
      - image: registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:v1.15.1
        imagePullPolicy: IfNotPresent
        name: must-be-diff-zone