K8S初级入门系列之五-Pod的高级特性

一、前言

      前一篇我们了解了Pod的基本概念和操作,本篇我们继续研究Pod的一些高级特性,包括Pod的生命周期,pod探针,pod的调度等。

二、生命周期

1、Pod的生命周期

Pod的生命周期示意图如下:

  • 挂起(Pending),接受创建Pod指令,相关信息存入了etcd,但是还未完成调度
  • 容器创建(ContainerCreating),Pod完成调度,分配到指定的Node,容器处于创建过程中,一般是镜像正在拉取。
  • 运行(Running),所有的容器都已经创建完成,至少有一个容器正在运行,或者正处于启动或重启状态。
  • 失败(Failed),Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
  • 成功(Succeed),Pod 中的所有容器都被成功终止,并且不会再重启。
  • 未知(Unknown),因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。

    下面我们来演示下整个过程,使用kubectl get pod  --watch来追踪pod状态的变化。此演示需要打开两个终端会话。

首先打开一个终端会话(A)输入:

[root@k8s-master ~]# kubectl get pod  --watch

然后再打开另一个终端会话(B)创建pod(使用上一篇的busybox-pod.yaml)

[root@k8s-master yaml]# kubectl apply -f busybox-pod.yaml 
pod/busybox-pod created

可以从终端A看到状态的变化过程

NAME          READY   STATUS    RESTARTS   AGE
busybox-pod   0/2     Pending   0          0s
busybox-pod   0/2     Pending   0          0s
busybox-pod   0/2     ContainerCreating   0          0s
busybox-pod   0/2     ContainerCreating   0          0s
busybox-pod   2/2     Running             0          17s

在终端A中删除该pod

[root@k8s-master yaml]# kubectl delete pod busybox-pod
pod "busybox-pod" deleted

在终端B中看到状态变化

busybox-pod   2/2     Terminating         0          3m52s

至于failed状态,我们下面再看。

2、Pod Hook

K8S提供了两种生命周期钩子,PostStart和PreStop。

  • PostStart:这个钩⼦在容器创建后立即执⾏。但是,并不能保证钩子将在容器 ENTRYPOINT 之前运⾏,因为没有参数传递给处理程序。主要⽤于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以⾄于不能运⾏或者挂起, 容器将不能达到 running 状态。
  • PreStop:这个钩子在容器终止之前立即被调⽤。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调⽤发出之前完成。主要⽤于优雅关闭应⽤程序、通知其他系统等。如果钩⼦在执⾏期间挂起, Pod阶段将停留在 running 状态并且永不会达到 failed 状态。

     一方面,钩子执行失败, 会直接杀死容器;另一方面,钩子执行时同步阻塞过程,所以这两个钩子尽量需要轻量

     有两种⽅式来实现上⾯的钩⼦函数:

  • Exec – ⽤于执⾏⼀段特定的命令,不过要注意的是该命令消耗的资源会被计⼊容器。
  • HTTP – 对容器上的特定的端点执⾏ HTTP 请求。

下面我们就来用Exec演示下,创建lifecycle-pod.yaml文件,内容如下:

[root@k8s-master yaml]# cat lifecycle-pod.yaml 
apiVersion: v1
kind: Pod
metadata:name: lifecycle-podlabels:app: lifecycle
spec:containers:- name: lifecycle-demoimage: nginxports:- name: webportcontainerPort: 80volumeMounts:- name: messagemountPath: /usr/share/lifecycle:postStart:exec:command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]preStop:exec:command: ['/bin/sh', '-c', 'echo Hello from the preStop Handler > /usr/share/message']volumes:- name: messagehostPath:path: /tmp

     我们来分析下,定义了lifecycle属性,包含了postStart和preStop两个钩子函数,该函数分别往容器的/usr/share/message文件里打印一段话,为了查询文件方面,我们将这个文件挂载到节点的/tmp下(这部分内容后面存储章节我们在详细介绍)。

    接下来我们执行下这个文件,创建该pod。

[root@k8s-master yaml]# kubectl get pod -o wide
NAME                        READY   STATUS             RESTARTS           AGE     IP               NODE         NOMINATED NODE   READINESS GATES
lifecycle-pod               1/1     Running            0                  65m     10.244.36.86     k8s-node1    <none>           <none>

 等到pod处于running状态时,再看下节点(k8s-node1)的/tmp目录下,已经生成了message文件,并写入了postStart钩子函数的语句。

[root@k8s-node1 tmp]# cat message 
Hello from the postStart handler

再手动删除该pod后,看下该文件

[root@k8s-node1 tmp]# cat message 
Hello from the preStop Handler

代表preStop钩子在容器停止前成功执行了。

如果有错误,可以使用describe指令,看到 FailedPostStartHook 或 FailedPreStopHook 这样的 event。

3、容器重启策略

     Pod对于内部运行所有容器,通过restartPolicy指定重启策略,其值包括Always , OnFailure以及Never三种。

  •  Always,当容器终止退出后,总是重启容器,默认策略
  •  OnFailure,仅当容器异常退出(退出状态码非0)时,重启容器。
  •  Never,当容器终止退出,从不重启容器。

下面分别用容器异常退出和正常退出两个场景实例,演示下这三种重启策略的效果。

(1)异常退出

     首先演示异常退出的场景,创建restartpolicy-unhealthy-pod.yaml文件,容器内执行脚本,20s后退出,状态码为3(非0,表示异常退出),内容如下:

[root@k8s-master yaml]# cat restartpolicy-unhealthy-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: restartpolicy-unhealthy-podname: restartpolicy-unhealthy-pod
spec:restartPolicy: Alwayscontainers:- name: unhealthyimage: busyboxargs:- /bin/sh- -c-  sleep 20; exit 3; 

restartPolicy设置为Always,容器失败退出后,Pod重启

[root@k8s-master yaml]# kubectl apply -f restartpolicy-healthy-pod.yaml 
pod/restartpolicy-healthy-pod created
[root@k8s-master yaml]# kubectl get pod --watch
NAME                          READY   STATUS    RESTARTS   AGE
restartpolicy-unhealthy-pod   1/1     Running   0          12s
restartpolicy-unhealthy-pod   0/1     Error     0          22s
restartpolicy-unhealthy-pod   1/1     Running   1 (17s ago)   38s

restartPolicy设置为OnFailure,容器失败退出后,Pod重启

[root@k8s-master ~]# kubectl get pod --watch
NAME                          READY   STATUS    RESTARTS   AGE
restartpolicy-unhealthy-pod   0/1     Pending   0          0s
restartpolicy-unhealthy-pod   0/1     Pending   0          0s
restartpolicy-unhealthy-pod   0/1     ContainerCreating   0          0s
restartpolicy-unhealthy-pod   0/1     ContainerCreating   0          1s
restartpolicy-unhealthy-pod   1/1     Running             0          17s
restartpolicy-unhealthy-pod   0/1     Error               0          37s
restartpolicy-unhealthy-pod   1/1     Running             1 (1s ago)   38s

restartPolicy设置为Never,失败退出后,Pod结束。

[root@k8s-master ~]# kubectl get pod --watch
NAME                          READY   STATUS    RESTARTS   AGE
restartpolicy-unhealthy-pod   0/1     Pending   0          0s
restartpolicy-unhealthy-pod   0/1     Pending   0          0s
restartpolicy-unhealthy-pod   0/1     ContainerCreating   0          0s
restartpolicy-unhealthy-pod   0/1     ContainerCreating   0          1s
restartpolicy-unhealthy-pod   1/1     Running             0          2s
restartpolicy-unhealthy-pod   0/1     Error               0          23s
restartpolicy-unhealthy-pod   0/1     Error               0          23s

(2)正常退出

    我们再演示一个正常退出的场景,创建restartpolicy-healthy-pod.yaml文件,容器内执行脚本,20s后退出,状态码为0(正常退出),内容如下:

[root@k8s-master yaml]# cat restartpolicy-healthy-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: restartpolicy-healthy-podname: restartpolicy-healthy-pod
spec:restartPolicy: Alwayscontainers:- name: healthyimage: busyboxargs:- /bin/sh- -c-  sleep 20; exit 0; 

restartPolicy设置为Always,容器正常退出后,Pod重启

NAME                        READY   STATUS    RESTARTS   AGE
restartpolicy-healthy-pod   0/1     Pending   0          0s
restartpolicy-healthy-pod   0/1     Pending   0          0s
restartpolicy-healthy-pod   0/1     ContainerCreating   0          0s
restartpolicy-healthy-pod   0/1     ContainerCreating   0          1s
restartpolicy-healthy-pod   1/1     Running             0          16s
restartpolicy-healthy-pod   0/1     Completed           0          26s
restartpolicy-healthy-pod   1/1     Running             1 (16s ago)   42s
restartpolicy-healthy-pod   0/1     Completed           1 (26s ago)   52s
restartpolicy-healthy-pod   0/1     CrashLoopBackOff    1 (13s ago)   65s
restartpolicy-healthy-pod   1/1     Running             2 (15s ago)   67s
restartpolicy-healthy-pod   0/1     Completed           2 (25s ago)   77s

restartPolicy设置为OnFailure,容器正常退出后,Pod退出

[root@k8s-master ~]# kubectl get pod --watch
NAME                        READY   STATUS    RESTARTS   AGE
restartpolicy-healthy-pod   0/1     Pending   0          0s
restartpolicy-healthy-pod   0/1     Pending   0          0s
restartpolicy-healthy-pod   0/1     ContainerCreating   0          0s
restartpolicy-healthy-pod   0/1     ContainerCreating   0          0s
restartpolicy-healthy-pod   1/1     Running             0          16s
restartpolicy-healthy-pod   0/1     Completed           0          26s
restartpolicy-healthy-pod   0/1     Completed           0          26s

restartPolicy设置为Never,容器正常退出后,Pod退出。

[root@k8s-master ~]# kubectl get pod --watch
NAME                        READY   STATUS    RESTARTS   AGE
restartpolicy-healthy-pod   0/1     Pending   0          0s
restartpolicy-healthy-pod   0/1     Pending   0          0s
restartpolicy-healthy-pod   0/1     ContainerCreating   0          0s
restartpolicy-healthy-pod   0/1     ContainerCreating   0          1s
restartpolicy-healthy-pod   1/1     Running             0          17s
restartpolicy-healthy-pod   0/1     Completed           0          27s
restartpolicy-healthy-pod   0/1     Completed           0          27s

综上所述,我们来总结下: 

 restartPolicyAlways无论异常还是正常退出,Pod都会重启

 restartPolicyOnFailure仅异常退出时,Pod才会重启

 restartPolicyNever无论异常还是正常退出,Pod都不会重启

4、探针

     上面重启策略是针对容器的运行状态,但很多情况下,容器是处于正常状态,但是容器中的应用已经异常,无法工作了,此种情况也需要Pod进行处理的。此时Pod就需要感知应用的状态,再结合重启策略,进行下一步处理。由于应用的运行状态,只有业务方才能感知和定义,所以Pod通过开放"钩子"定义,并定时执行"钩子",以便获取应用状态。这些"钩子"就是探针,根据探针的目的不同,又分为以下三种类型:

  • LivenessProbe(存活探针)
  • ReadinessProbe(就绪探针)
  • StartupProbe(启动探针)

每次探测都将获得以下三种结果之一:

  • 成功:容器通过了诊断。
  • 失败:容器未通过诊断。
  • 未知:诊断失败,因此不会采取任何行动。

(1)LivenessProbe(存活探针)

    存活探针主要是在容器启动后,探测容器内应用的存活状态,这也是常用的检索机制,其包含三种探测类型。

  • exec,在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功
  • httpGet,对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
  • tcpSocket,容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。

其主要配置参数有:

  • initialDelaySeconds,容器启动后,多长时间才开始第一次探测, 默认是 0 秒,最小值是 0。
  • periodSeconds,执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。
  • timeoutSeconds,探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
  • failureThreshold,当探测失败时,Kubernetes 的重试次数。
  • successThreshold,当探针在失败后,被视为成功的最小连续成功数。默认值是 1。

下面我用一张图描述探测过程中,各个参数的作用。

 我们使用exec模式演示下面实例:

1)创建一个liveness-exec-pod.yaml文件,内容如下:

[root@k8s-master yaml]# cat liveness-exec-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: liveness-exec-podname: liveness-exec-pod
spec:containers:- name: livenessimage: busyboxargs:- /bin/sh- -c- echo ok > /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600livenessProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 5timeoutSeconds: 1periodSeconds: 5

     我们先来理论分析下该过程。该容器启动后,通过脚本创建/tmp/healthy文件,并写入"ok", sleep 30s后,删除该文件,模拟业务异常。探针使用cat指令,显示文件内容,如果文件不存在,则返回非0。在容器启动5s后,首次探索,后续每个5s探测一次,当30s删除文件后,容器进程还正在运行,但是由于文件删除,导致探测失败,认为业务已经异常,将重启容器。

  2) 创建pod

[root@k8s-master yaml]# kubectl apply -f liveness-exec-pod.yaml 
pod/liveness-exec-pod created

3)查找状态

[root@k8s-master yaml]# kubectl describe pod liveness-exec-pod

这里我们在详情中查看状态,结果如下,探针检查识别后失败后(3次重试),重启容器。 

 

4)设置restartPolicy重启策略

      业务存活探针的结果对于重启策略的影响,是否和容器状态对于重启策略是一致的?

      在liveness-exec-pod.yaml文件中,没有定义restartPolicy,默认为Always,探测失败后Pod重启。我们再将其设置为其他两种(OnFailure,Never)看下。

     修改iveness-exec-pod.yaml,在spec中增加restartPolicy: Never,删除旧的pod,重启创建Pod

[root@k8s-master yaml]# kubectl delete pod liveness-exec-pod
pod "liveness-exec-pod" deleted
[root@k8s-master yaml]# kubectl apply -f liveness-exec-pod.yaml 
pod/liveness-exec-pod created

执行查看详情指令,探针重试三次识别后,停止了容器,Pod不会重启。

 可以再将重启策略修改为restartPolicy: OnFailure看下,其结果与Always一致,Pod重启。

 由此可知,对于重启策略的影响,存活探针的结果,等同与容器的状态。

(2)ReadinessProbe(就绪探针)

      存活探针主要探针业务是否正常,就绪探针主要探测业务是否准备好提供服务了,如果探测失败,表明该业务未就绪,不会向该Pod的分发流量,当探测成功后,才认为是就绪状态,开始向 Pod 发送流量,一般与service配合使用。其探测机制与参数与LivenessProbe(存活探针)一致。其探测过程如下图描述:

下面我们采用httpGet模式示例演示下。

创建readiness-http-pod.yaml,其内容如下:

[root@k8s-master yaml]# cat readiness-http-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: readiness-httpname: readiness-http
spec:containers:- name: readiness-httpimage: tcy83/readiness:v1.0ports:- containerPort: 8080readinessProbe:httpGet:path: /health port: 8080initialDelaySeconds: 5periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:name: readiness-http-svc
spec:ports:- port: 8080targetPort: 8080selector:app: readiness-http

      该内容包含一个Pod对象和一个Service对象(关于service内容,K8S初级入门系列之八-Service核心概念将介绍,现在可以理解成pod网关代理,类似nginx)

       我们来分析下Pod对象,其包含一个名为readiness-http容器,镜像tcy83/readiness:v1.0(已上传DockerHub)代码片段如下

        请求/health的接口不超过5次,返回200状态码,超过5次则抛出异常,返回500状态码。

         该容器定义readinessProbe探针,其模式为httpGet,容器启动后延迟5s,开始第一次探测,请求/health接口,后续每隔5s探测一次。

       理论分析得知,readiness探针将在请求5次后失败,将无法通过service访问。我们来实验下:

(1)创建pod,并查看下pod的运行状态

[root@k8s-master yaml]# kubectl apply -f readiness-http-pod.yaml
[root@k8s-master yaml]# kubectl get pod -o wide
NAME                        READY   STATUS      RESTARTS         AGE     IP              NODE        NOMINATED NODE   READINESS GATES
readiness-http              0/1     Running     0                6m33s   10.244.36.68    k8s-node1   <none>           <none>

      Pod运行后,我们查看下端点(endpoint),可以看到该容器的ip地址已经加入到端点的地址,说明已经处于就绪状态。

[root@k8s-master yaml]# kubectl get ep
NAME                 ENDPOINTS           AGE
kubernetes           192.168.16.4:6443   28d
readiness-http-svc   10.244.36.68:8080   8d

查看service的地址

[root@k8s-master yaml]# kubectl get svc
NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes           ClusterIP   10.96.0.1      <none>        443/TCP    28d
readiness-http-svc   ClusterIP   10.99.142.12   <none>        8080/TCP   8d

 通过service地址访问应用

[root@k8s-master yaml]# curl http://10.99.142.12:8080/health
OK

就绪状态下,通过service 的ip可以正确访问应用接口。

(2)探测失败

一段时间后(大于30s后),我们再来查看下pod的状态,探测失败

 查看下端点的列表,此时容器的ip地址已经从端点列表中删除

[root@k8s-master yaml]# kubectl get ep
NAME                 ENDPOINTS           AGE
kubernetes           192.168.16.4:6443   28d
readiness-http-svc                       8d

再用service地址访问下应用,已经无法访问。但是pod还是处于运行状态,仅无法对外提供服务。

[root@k8s-master yaml]# curl http://10.99.142.12:8080/health
curl: (7) Failed to connect to 10.99.142.12 port 8080: Connection refused
[root@k8s-master yaml]# kubectl get pod
NAME                        READY   STATUS             RESTARTS         AGE
readiness-http              0/1     Running            0                4m43s

    一般情况下,存活探针和就绪探针采用同样的探针接口,也就是业务失败后,就立即从service中摘除,阻止业务的继续访问,放置异常的进一步扩算。

(3)StartupProbe(启动探针)

      启动探针主要指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。

     如果没有启动探针,那么存活探针和就绪探针都是在容器启动后就开始计时,initialDelaySeconds到期后,开始探测,如果应用的启动时间较长,而相关的参数设置不合理,就会出现以下情况。

      应用的启动时间超过了initialDelaySeconds+failureThreshold*periodSeconds的时间,导致探测失败后的容器重启,重启后又探测失败,进入死循环。

     要解决这个问题,一种方式就是将initialDelaySeconds设置的足够大,当然更优雅的方式就是使用启动探针,如下图所示:

     当启动探针探测成功后,存活探针和就绪探针才会接管生效,从而避免了上述问题。

     我们先来演示应用启动时间过长,没有使用启动探针的场景。创建startup-exec-pod.yaml,内容如下:

[root@k8s-master yaml]# cat startup-exec-pod.yaml
apiVersion: v1
kind: Pod
metadata:labels:app: startup-exec-podname: startup-exec-pod
spec:containers:- name: startupimage: busyboxargs:- /bin/sh- -c- sleep 60;echo ok > /tmp/healthy;sleep 600livenessProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 5failureThreshold: 3periodSeconds: 5

      该容器中通过指令sleep 60s,然后才创建/tmp/healthy文件,而存活探针根据是否存在该文件确认应用启动成功,其探测的最大时间initialDelaySeconds+failureThreshold*periodSeconds=5+3*5=20s,要小于60s,探测失败,容器重启,重启后,再探测失败重启,进入不断重启的死循环的状态。

我们创建pod后,跟踪一段时间看下(本例重启了6次)

 下面我们修改下yaml文件,增加startupProbe

[root@k8s-master yaml]# cat startup-exec-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: startup-exec-podname: startup-exec-pod
spec:containers:- name: startupimage: busyboxargs:- /bin/sh- -c- sleep 60;echo ok > /tmp/healthy;sleep 600startupProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 5failureThreshold: 30periodSeconds: 5livenessProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 5failureThreshold: 3periodSeconds: 5

     启动探测的最大探测时间:initialDelaySeconds+failureThreshold*periodSeconds=155s,大于60s,探测成功后,存活探针才生效。所以不会导致重启。我们看下:

 可以看到,这次容器没有重启。

三、Pod调度

     在接受到Pod的创建指令,Pod处于pending状态,接下来需要将Pod调度到Node上执行,由K8S初级入门系列之一-概述可知这个工作是由schedule模块负责的,它根据指定的一系列规则选择合适的Node进行调度,其过程大致如下:

(1)过滤,此阶段将所有满足要求的Node选择出来,形成一个节点列表,称之为可调度节点列表。如果这个列表为空,则调度失败。

(2)打分,对每个可调度节点打分,从中选择一个最合适的,即分数最高的节点,如果有多个最高分,则随机选择一个。

在这两个阶段,我们都可以设置些规则协助schedule判定,下面我们看下有哪些规则。

1、指定Node

nodeSelector是最简单的指定形式,通过指定node的标签来选择,如下图所示

 下面我们来实验下,首先创建nodeselector-pod.yaml文件,其内容如下:

[root@k8s-master yaml]# cat nodeselector-pod.yaml
apiVersion: v1
kind: Pod
metadata:name: nodeselector-pod
spec:containers:- name: nginximage: nginx:1.8nodeSelector:disk: ssd

该pod需要调度到标签disk值为ssd的node上。我们来创建这个pod

[root@k8s-master yaml]# kubectl apply -f nodeselector-pod.yaml 
pod/nodeselector-pod created
[root@k8s-master yaml]# kubectl get pod
NAME                        READY   STATUS             RESTARTS           AGE
nodeselector-pod            0/1     Pending            0                  5s

可以看到此时pod一直处于pending状态,查看下详情可知没有找到合适的pod。

 因为此时我们的k8s-master,k8s-node1两个节点都没有disk=ssd的标签。

接下来,我们给k8s-node1增加标签,可以通过kubectl label node <nodename> <labelname=labelvalue>指令

[root@k8s-master yaml]# kubectl label node k8s-node1 disk=ssd
node/k8s-node1 labeled

再看下pod的状态,此时已经匹配到节点,正确调度到k8s-node1上运行。

[root@k8s-master yaml]# kubectl get pod -o wide
NAME                        READY   STATUS             RESTARTS           AGE     IP              NODE        NOMINATED NODE   READINESS GATES
nodeselector-pod            1/1     Running            0                  8m41s   10.244.36.75    k8s-node1   <none>           <none>               7m34s

2、Node亲和性和反亲和性

     指定Node的方式虽然简单,但是很难满足复杂的调度场景要求,此时就需要使用节点的亲和性和反亲和性配置。比如以下场景:

     pod期望调度到ZoneA区域,磁盘类型优选ssd的节点,同时,又不希望调度到DB类型的节点上。最终的调度结果示意图如下:

 下面我们通过实例node-affinity-pod.yaml实现调度策略的配置。

[root@k8s-master yaml]# cat node-affinity-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: node-affinity name: node-affinity
spec:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: zoneoperator: Invalues:- ZoneA- key: appoperator: NotInvalues:- dbpreferredDuringSchedulingIgnoredDuringExecution:- weight: 1preference:matchExpressions:- key: diskoperator: Invalues:- ssdcontainers:- name: node-affinityimage: nginx:1.8ports:- containerPort: 80

      nodeAffinity支持requiredDuringSchedulingIgnoredDuringExecution(硬亲和)以及preferredDuringSchedulingIgnoredDuringExecution(软亲和)两种模式。

       requiredDuringSchedulingIgnoredDuringExecution(硬亲和),表示是必须满足的调度条件,支持多组matchExpressions,使用matchExpressions实现复杂的标签选择机制,由key,operator,values三个字段组成一个表达式,key为选择的标签名称,value表示标签的可选值列表,operator为操作符,有以下几种类型:

  • In:label的值在某个列表中
  • NotIn:label的值不在某个列表中
  • Gt:label的值大于某个值
  • Lt:label的值小于某个值
  • Exists:某个label存在
  • DoesNotExist:某个label不存在

其中NotIn,DoesNotExist可以用来表示反亲和性。

      从本例看,pod需要调度到zone为ZoneA,但是不能调度到app为db的节点,所以对于前者为亲和性策略,采用In,对于后者采用反亲和性策略,采用NotIn。

      preferredDuringSchedulingIgnoredDuringExecution(软亲和)是优选策略,表示在多个节点都满足硬亲和情况下,优选其中一个。由多组不同weight(权重),同样也是使用matchExpressions进行标签选择。如本例中,优选磁盘类型为ssd的节点,采用是软亲和,权重weight为1,策略为disk=ssd。

需要主要的是以下两点:

  • 对于硬亲和,matchExpressions为多组时,只要一个满足即认为满足,是"or"的关系;每个matchExpressions下可以有多组选择策略,必须要同时满足才认为满足,是"and"的关系。
  • 如果node的标签进行更改,对于已运行其上的pod不受影响。

3、Pod的亲和性和反亲和性

      上面都是基于Node的标签属性进行判定和调度,实际上,Pod之间也存在亲疏性,比如下面的例子中,对于多有个副本的web server的pod,首先从高可用考虑,要避免多个副本调度到同一个节点上;其次,为了提升资源的利用率,需要与大数据离线处理任务的pod部署在一起;最后,避免延迟大,希望能与redis部署在一个节点上(优选策略)。如下图所示:

 编写实例pod-affinity-pod.yaml来实现下

[root@k8s-master yaml]# cat pod-affinity-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: web  name: pod-affinity
spec:affinity:podAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues:- bigdatatopologyKey: kubernetes.io/hostnamepreferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues:- redistopologyKey: kubernetes.io/hostnamepodAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues:- webtopologyKey: kubernetes.io/hostnamecontainers:- name: node-affinityimage: nginx:1.8ports:- containerPort: 80

其结构和属性与Node的类似,但也有以下几点差别:

(1)matchExpressions表达式中的选择的是pod的标签。

(1)pod的反亲和性可以独立设置,采用podAntiAffinity属性配置,内容和结构与podAffinity类似。

(2)增加了topologyKey字段,表示拓扑域,理论上节点,机架,区域都是拓扑范围,比如本例的kubernetes.io/hostname表示的是节点拓扑,实际上topologyKey可以是任何合法的标签建,当然我们也可以自行定义。K8S会给节点打上一些默认的标签,可以使用如下指令查看:

[root@k8s-master ~]# kubectl get node --show-labels
NAME         STATUS   ROLES                  AGE   VERSION   LABELS
k8s-master   Ready    control-plane,master   36d   v1.23.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
k8s-node1    Ready    <none>                 35d   v1.23.0   app=db,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node1,kubernetes.io/os=linux,zone=ZoneA

出于性能和安全原因,topologyKey有一些限制:

  • 对于 Pod 亲和性而言,在 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution 中,topologyKey 不允许为空
  • 对于 requiredDuringSchedulingIgnoredDuringExecution 要求的 Pod 反亲和性, 准入控制器 LimitPodHardAntiAffinityTopology 要求 topologyKey 只能是 kubernetes.io/hostname。如果你希望使用其他定制拓扑逻辑, 你可以更改准入控制器或者禁用之。

4、污点与容忍度

      亲和性表示是相吸性,即pod被吸引到同一类的节点上,而污点(traint)恰好相反,它使得pod排斥某一类型的节点,如果想要不排斥,就需要配置这个污点的容忍度(Toleration)。污点和容忍度相互配合使用。

我们先来看下示例,首先在k8s-node1节点上加个污点

[root@k8s-master ~]# kubectl taint nodes k8s-node1 key1=value1:NoSchedule
node/k8s-node1 tainted

      其中key1是污点的键名,键值是value1,效果是NoSchedule,即对该污点没有容忍度的pod无法调度到该节点。

创建pod,其yaml文件如下,暂不配置容忍度看下结果。

[root@k8s-master yaml]# cat taint-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: web  name: taint-pod
spec:containers:- name: taint-podimage: nginx:1.8ports:- containerPort: 80

执行创建命令,并看下状态

[root@k8s-master yaml]# kubectl get pod
NAME                        READY   STATUS             RESTARTS           AGE
taint-pod                   0/1     Pending            0                  41m

       该pod一直pending状态,再查看该pod的详情

        可以看到,目前的两个node(k8s-master,k8s-node1)都存在污点,而该pod没有容忍度,所以无法调度成功。我们再来修改下yaml文件,配置上述污点的容忍度。

[root@k8s-master yaml]# cat taint-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: web  name: taint-pod
spec:tolerations:- key: "key1"operator: "Equal"value: "value1"effect: "NoSchedule"containers:- name: taint-podimage: nginx:1.8ports:- containerPort: 80

      这样,该pod对于键名为key1,键值为value1,效果为NoSchedule的污点存在容忍度,也就是可以调度具有该污点的节点(k8s-node1)上。接下来,我们重新创建该pod,查看下运行状态

[root@k8s-master yaml]# kubectl get pod -o wide
NAME                        READY   STATUS             RESTARTS           AGE     IP              NODE        NOMINATED NODE   READINESS GATES
taint-pod                   1/1     Running            0                  4m3s    10.244.36.76    k8s-node1   <none>           <none>

结果也如图所示,正确调度到k8s-node1上运行。

     综上所述,当一个节点配置了污点,所有的pod都无法调度到该节点,除非该pod配置了该污点的容忍度,相当于配置了一些白名单pod。那么污点和容忍度有哪些使用场景呢?

(1)专用的节点,这些节点专门给一些特点的应用使用。

(2)具有特殊硬件设备的节点,比如金融领域的加解密都是通过物理加密卡,对于带有类加密卡的节点,是不希望运行其他的应用的。

(3)Pod驱逐行为,节点出现问题时,就会自动增加相应类型的污点,从而驱逐其上运行的但没有配置相应容忍度的pod。比如网络不可用,节点自动增加node.kubernetes.io/network-unavailable污点。

      不过要达到对其上pod达到驱逐目的,其效果值不能是NoSchedule,而应该是NoExecute。NoSchedule仅对于还未调度的pod有效,而对于前期已经调度到节点的Pod,不会产生驱逐效果。NoExecute对于没有配置其污点容忍度的所有运行pod立即驱逐,当前也可以通过配置tolerationSeconds时间,来延迟驱逐的时间。

   事实上,K8S会给一些特点节点加上污点标识,比如我们实验环境的master节点k8s-master。

[root@k8s-master ~]# kubectl describe node k8s-master
....
Taints:             node-role.kubernetes.io/master:NoSchedule
....

      一般情况下,pod不允许调度到master节点,除非能容忍这个污点。比如将上面例子中的pod调度到master节点,我们修改下容忍度配置。

[root@k8s-master yaml]# cat taint-pod.yaml 
apiVersion: v1
kind: Pod
metadata:labels:app: web  name: taint-pod
spec:tolerations:- key: "node-role.kubernetes.io/master"operator: "Exists"effect: "NoSchedule"containers:- name: taint-podimage: nginx:1.8ports:- containerPort: 80

执行下该文件,创建pod,看下运行的节点

[root@k8s-master yaml]# kubectl get pod -o wide
NAME                        READY   STATUS             RESTARTS          AGE     IP               
taint-pod                   1/1     Running            0                 77s     10.244.235.196   k8s-master   <none>           <none>

可以看到,确实调度到k8s-master节点上了。

当然,我们也可以通过指令把主节点上的污点给删了,kubectl taint node <nodename> <key>-

比如:

[root@k8s-master yaml]# kubectl taint node k8s-master node-role.kubernetes.io/master-

四、总结

本章节的内容较多,我们来总结下:

1、Pod的生命周期包括挂起(Pending),容器创建(ContainerCreating),运行(Running),失败(Failed),成功(Succeed),未知(Unknown)几种状态,在容器创建后以及销毁前,可以使用postStart,preStop两个钩子使用诸如环境准备,优雅停机等工作。

2、容器的重启策略有三种类型,Always,OnFailure和Never。对于Always无论异常还是正常退出,Pod都会重启;对于OnFailure仅异常退出时,Pod才会重启;对于Never无论异常还是正常退出,Pod都不会重启

3、探针类型包括LivenessProbe(存活探针),ReadinessProbe(就绪探针),StartupProbe(启动探针),存活探针主要探测业务是否异常,就绪探针主要探测业务是否就绪,能对外提供服务,启动探针主要探测业务是否启动。

4、Pod调度的策略,包括指定node,node亲和性和反亲和性,pod的亲和性和反亲和性,污点与容忍度。

 附:

K8S初级入门系列之一-概述

K8S初级入门系列之二-集群搭建

K8S初级入门系列之三-Pod的基本概念和操作

K8S初级入门系列之四-Namespace/ConfigMap/Secret

K8S初级入门系列之五-Pod的高级特性

K8S初级入门系列之六-控制器(RC/RS/Deployment)

K8S初级入门系列之七-控制器(Job/CronJob/Daemonset)

K8S初级入门系列之八-网络

K8S初级入门系列之九-共享存储

K8S初级入门系列之十-控制器(StatefulSet)

K8S初级入门系列之十一-安全

K8S初级入门系列之十二-计算资源管理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C进阶】指针进阶(1)_二次复习版

目录 1. 字符指针 1.1常量字符串的修改 加上const解决问题 打印常量字符串 1.2数组存放的字符串 1.3例题:数组创建与常量池的区别 2. 指针数组 2.1字符指针数组 2.2整型指针数组 2.3使用3个一维数组,模拟实现一个二维数组 2.4例题: 3.数组指针 3.1 数组指针的定义…

老年公寓人员定位管理系统:提升安全与关怀的智能解决方案

老年公寓作为提供安全居住环境和关怀服务的重要场所&#xff0c;面临着人员管理和安全控制的挑战。为了解决这些问题&#xff0c;老年公寓人员定位管理系统应运而生。基于为提供全面的安全管理和个性化关怀服务&#xff0c;华安联大便通过老年公寓人员定位管理系统的技术原理、…

数字孪生和 GIS 系统融合将为水利领域带来哪些变化?

随着科技的不断进步&#xff0c;数字孪生和 GIS 系统的融合应用逐渐成为了水利领域的新趋势。数字孪生是指通过数字化技术模拟物理实体和过程&#xff0c;将现实世界与虚拟世界相结合的技术&#xff0c;而 GIS 系统则是地理信息系统&#xff0c;用于收集、存储、管理和分析地理…

网工内推 | 售前、售后工程师,IE认证优先

01 广州佳杰科技有限公司 招聘岗位&#xff1a;IT售前工程师 职责描述&#xff1a; 1、负责所在区域 IT 产品的售前技术支持工作,包括客户交流、方案编写、配置报价、投标应标、测试、赋能等; 2、与厂商相关人员建立和保持良好的关系,相互配合,提高项目成功率和厂商满意度; 3、…

Python:使用openpyxl读取Excel文件转为json数据

文档 https://openpyxl.readthedocs.io/en/stable/https://pypi.org/project/openpyxl/ 安装 pip install openpyxl环境 $ python --version Python 3.7.0读取文件示例&#xff1a;将Excel文件读取为json数据 有如下一个文件 data.xlsx 实现代码 # -*- coding: utf-8 -…

如何恢复损坏/删除的 Word 文件

有关如何修复不可读的 Microsoft Word 文件或 Rich Text 文件中的文本的分步说明。这些说明有助于从损坏的*.doc、*.docx、*.dot、*.dotx、*.rtf文件&#xff08;任何版本和大小&#xff09;中提取文本&#xff0c;只需单击几下&#xff1a; 从此处下载奇客数据恢复 &#xff…

如何在Linux系统中安装ActiveMQ

1、环境 ActiveMQ是一个纯Java程序&#xff0c;这里安装5.18.2版ActiveMQ&#xff0c;该版MQ运行在JDK 11环境内&#xff0c;为此需要先搭建JDK 11环境&#xff0c;这里安装JDK 15。 1.1、卸载 卸载开源JDK软件包&#xff0c;如下所示&#xff1a; [rootlocalhost ~]# rpm -…

如何将表格中的状态数据转换为Tag标签显示

考虑到系统前端页面的美观程度&#xff0c;通常通过Tag标签来代替某条数据中的状态信息。仅通过一点操作&#xff0c;便能够使得页面美观程度得到较大提升&#xff0c;前后对比如下所示。代码基于Vue以及Element-ui组件实现。 修改前&#xff1a; 修改后&#xff1a; 修改前…

1.Flink概述

1.1 技术架构 应用框架层: 在API层之上构建的满足特定应用场景的计算框架&#xff0c;总体上分为流计算和批处理两类应用框架。API 层&#xff1a; Flink对外提供能力的接口 &#xff0c;实现了面向流计算的DataStream API和面向批处理的DataSet API。运行时层&#xff1a;Flin…

Visio文件编辑查看工具Visio Viewer for Mac

Visio Viewer for Mac可以打开和查看Visio文件&#xff08;.vsd、.vdx和.vsdm文件&#xff09;。它具有简单易用的用户界面&#xff0c;可以快速加载和显示Visio文件。此外&#xff0c;它还支持导出文件为PDF、PNG、JPEG等格式&#xff0c;方便用户进行文件转换和共享。 Visio…

《零基础入门学习Python》第053讲:论一只爬虫的自我修养

0. 请写下这一节课你学习到的内容&#xff1a;格式不限&#xff0c;回忆并复述是加强记忆的好方式&#xff01; 马上我们的教学就要进入最后一个章节&#xff0c;Pygame 嗨爆引爆全场&#xff0c;但由于发生了一个小插曲&#xff0c;所以这里决定追加一个章节&#xff0c;因为…

Qt Core学习日记——第四天QMetaEnum(下)

类定义&#xff1a; 成员变量就只有QMetaObject *mobj和uint handle&#xff0c;handle同样用于计算在qt_meta_stringdata_XTest的位置 成员函数&#xff1a; 接下以test类进行函数讲解 test.h #pragma once #include <qobject.h> #include <QFlags> class X…

有些能力,是工作中学不来的,看看这篇超过90%同行

俗话说:360行&#xff0c;行行转IT。 在就业形势压力巨大的今天&#xff0c;不仅仅是计算机专业的毕业生&#xff0c;很多其他专业的大学生都选择转行从事计算机行业。 尤其是软件测试行业&#xff0c;远远超出其他行业的薪水和广阔的就业前景&#xff0c;吸引了大批应届毕业…

QT DAY1

1.思维导体 2.作业 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {qDebug()<<this->size();qDebug()<<this->rect().size();qDebug()<<this->geometry().size();qDebug()<<this->frameGeometry().siz…

视频增强技术-对比度增强

在图像处理中&#xff0c;由于获取的图像质量不好&#xff0c;需要通过对比度增强来提升图片质量&#xff0c;主要解决的是由于图像灰度级范围较小造成的对比度较低的问题&#xff0c;作用是使图像的灰度级范围放大&#xff0c;从而让图像更加清晰。主要对比度增强方法包括线性…

macbook 软件iMovie for Mac(专业视频剪辑工具)中文版

iMovie mac中文版是一款针对Mac平台量身定做的视频编辑工具&#xff0c;软件凭借流线型设计和直观的编辑功能&#xff0c;可以让您感受前所未有的方式制作好莱坞风格的预告片和精美电影&#xff0c;并且还可以浏览视频资料库&#xff0c;快速共享挚爱瞬间&#xff0c;创建精美的…

DevOps自动化平台开发之 Shell脚本执行的封装

基础知识 基于如下技术栈开发DevOps平台 Spring Boot Shell Ansible Git Gitlab Docker K8S Vue 1、spring boot starter的封装使用 2、Shell脚本的编写 3、Ansible 脚本的编写 4、Docker 的使用与封装设计 本篇介绍如何使用Java封装Linux命令和Shell脚本的使用 将其设计成…

文件上传漏洞

什么是文件上传漏洞&#xff1f; 攻击者上传了一个可执行文件如木马&#xff0c;病毒&#xff0c;恶意脚本&#xff0c;WebShell等到服务器执行&#xff0c;并最终获得网站控制权限的高危漏洞。 webshell 非法用户可利用上传的恶意脚本文件控制整个网站&#xff0c;甚至控制服…

直线导轨的主要功能

直线导轨是一种常见的机械结构&#xff0c;用于工业机器人、数控机床和其他自动化装置中。它的作用是提供一个准确的直线运动轨道&#xff0c;使得设备能够在预定的路径上进行精确的移动。 直线导轨作为一种重要的机械基础件&#xff0c;在现代工业中得到了广泛的应用。它主要的…

min_free_kbytes

转自&#xff1a;技术分享 | MemAvailable 是怎么计算的-腾讯云开发者社区-腾讯云 背景 前两天安装 OceanBase 时遇到一个小问题&#xff1a; 很明显&#xff0c;安装OB时要求服务器可用内存至少 8G&#xff0c;不达标就无法安装。为了凑这3台10G内存的服务器我已经费了不少劲…