Pod存活性探测
有不少应用程序长时间持续运行后会逐渐转为不可用状态,并且仅能通过重启操作回复,K8S的容器存活性探测机制可以发现诸如此类的问题,并依据探测结果结合重启策略触发后续的行为。存活性探测是隶属于容器级别的配置,用来指示容器是否还在运行,如果存活探测失败,kubelet可基于它判定何时需要重启一个容器,并且容器将受到容器策略的影响。如果容器不公存活探针,则默认状态为success Pod spec为容器列表中的相应容器定义其专用的探针(存活性探测机制)即可启用存活性探测。目前K8S的容器支持存活性探测的方法包含以下三种,ExecAction,TCPSocketAction和HTTPGetAction
设置exec探针
exec类型的探针通过在目标容器中执行由用户自定义的命令来判定容器的健康状态,若命令状态返回值为0则表示“成功”通过检测,其它的值均为“失败”状态。
spec.containers.livenessProbe.exec字段用于定义此类检测,它只有一个可用属性command,用于指定要执行的命令,下面是示例 yaml文件 在containers字段里面增加了livenessProbe字段,该模板定义了一个busybox:0.1的镜像,在镜像启动前做了一个postStart的钩子往/data/html/的目录生成一个test.html的文件,并且往里面输出了一段信息
apiVersion: v1kind: Podmetadata:annotations:created-by: cluster adminname: test-nodeselector1labels:env: prespec:containers:- name: busyboximage: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1ports:- name: httpcontainerPort: 80protocol: TCPlivenessProbe:exec:command: ["test","-e","/data/html/test.html"]lifecycle:postStart:exec:command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]nodeSelector:disktype: ssd
shell执行
先把之前创建的Pod删掉,然后再创建这个Pod[root@k8s-master01 nginx]# kubectl delete -f initcontainer.yamlpod "test-nodeselector1" deleted[root@k8s-master01 nginx]# kubectl apply -f initcontainer.yamlpod/test-nodeselector1 created[root@k8s-master01 nginx]#[root@k8s-master01 nginx]# kubectl get pods -l "env=pre" -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATEStest-nodeselector1 1/1 Running 0 71s 10.244.50.8 192.168.1.17 <none> <none>[root@k8s-master01 nginx]#可以看到Pod正常运行了,我们再开一个终端,使用`kubectl get pods -w`命令监控Pod运行状态。之后我们进入到刚刚创建的这个Pod里面,手动删除 /data/html/test.html文件第二个终端[root@k8s-master01 ~]# kubectl get pods -wNAME READY STATUS RESTARTS AGEbusybox 1/1 Running 12 9dmy-nginx-854bbd7557-44wpn 1/1 Running 2 9dmy-nginx-854bbd7557-7xnxz 1/1 Running 2 9dmy-nginx-854bbd7557-cmtzp 1/1 Running 2 9dmy-nginx-854bbd7557-fcpmp 1/1 Running 1 3d7hmy-nginx-854bbd7557-gz8tr 1/1 Running 1 3d7hmy-nginx-854bbd7557-jhmkd 1/1 Running 2 9dmy-nginx-854bbd7557-l9rgd 1/1 Running 2 9dmy-nginx-854bbd7557-lv87f 1/1 Running 1 3d7hmy-nginx-854bbd7557-m67c6 1/1 Running 1 3d7hmy-nginx-854bbd7557-whrfc 1/1 Running 1 3d7hmybox 1/1 Running 2 3d4htest-nodeselector1 1/1 Running 0 5m57s然后再第一个终端里面执行删除操作[root@k8s-master01 nginx]# kubectl exec -ti test-nodeselector1 -- /bin/sh/ # rm -f /data/html/test.html/ # exit[root@k8s-master01 nginx]#第二个终端可以看到Pod被重启过一次[root@k8s-master01 ~]# kubectl get pods -wNAME READY STATUS RESTARTS AGEbusybox 1/1 Running 12 9dmy-nginx-854bbd7557-44wpn 1/1 Running 2 9dmy-nginx-854bbd7557-7xnxz 1/1 Running 2 9dmy-nginx-854bbd7557-cmtzp 1/1 Running 2 9dmy-nginx-854bbd7557-fcpmp 1/1 Running 1 3d7hmy-nginx-854bbd7557-gz8tr 1/1 Running 1 3d7hmy-nginx-854bbd7557-jhmkd 1/1 Running 2 9dmy-nginx-854bbd7557-l9rgd 1/1 Running 2 9dmy-nginx-854bbd7557-lv87f 1/1 Running 1 3d7hmy-nginx-854bbd7557-m67c6 1/1 Running 1 3d7hmy-nginx-854bbd7557-whrfc 1/1 Running 1 3d7hmybox 1/1 Running 2 3d4htest-nodeselector1 1/1 Running 0 5m57stest-nodeselector1 1/1 Running 1 7m55s可以看到RESTART字段变成了1[root@k8s-master01 nginx]# kubectl get pods -l "env=pre"NAME READY STATUS RESTARTS AGEtest-nodeselector1 1/1 Running 1 10m[root@k8s-master01 nginx]#也可以使用`kubectl describe`命令来查看[root@k8s-master01 nginx]# kubectl describe pods test-nodeselector1Name: test-nodeselector1Namespace: defaultPriority: 0Node: 192.168.1.17/192.168.1.17Start Time: Wed, 10 Jun 2020 20:27:27 +0600Labels: env=preAnnotations: created-by: cluster adminkubectl.kubernetes.io/last-applied-configuration:{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{"created-by":"cluster admin"},"labels":{"env":"pre"},"name":"test-nodeselector1...Status: RunningIP: 10.244.50.8IPs:IP: 10.244.50.8Containers:busybox:Container ID: docker://84c89d85f7f331e695c04738704f69d8988c7f5bd03e079df5965cdeba701374Image: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1Image ID: docker-pullable://registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox@sha256:0fc4899e1c59495b980bea7ba1e92c6493f4cdf420edaa5887b1789387b86ac3Port: 80/TCPHost Port: 0/TCPState: RunningStarted: Wed, 10 Jun 2020 20:35:21 +0600Last State: TerminatedReason: ErrorExit Code: 137Started: Wed, 10 Jun 2020 20:27:28 +0600Finished: Wed, 10 Jun 2020 20:35:21 +0600Ready: TrueRestart Count: 1Liveness: exec [test -e /data/html/test.html] delay=0s timeout=1s period=10s #success=1 #failure=3........Conditions:Type StatusInitialized TrueReady TrueContainersReady TruePodScheduled True........Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled <unknown> default-scheduler Successfully assigned default/test-nodeselector1 to 192.168.1.17Warning Unhealthy 123m (x3 over 123m) kubelet, 192.168.1.17 Liveness probe failed:Normal Killing 123m kubelet, 192.168.1.17 Container busybox failed liveness probe, will be restartedNormal Pulled 123m (x2 over 130m) kubelet, 192.168.1.17 Container image "registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1" already present on machineNormal Created 123m (x2 over 130m) kubelet, 192.168.1.17 Created container busyboxNormal Started 123m (x2 over 130m) kubelet, 192.168.1.17 Started container busybox[root@k8s-master01 nginx]#
通过上面的输出 “Containers” 一段中也清晰的显示了容器健康状态及状态变化的相关信息,容器当前处于”Running状态”,但前一次的状态是”Terminating”,原因是退出码为137的错误信息,它表示进程是被外部信号所终止的。137事实上是由两部分数字之和生成的,128+signum,其中signum是导致进程终止的信号的数字表示,9表示SIGKILL,这意味着进程是被强制终止的
Containers:busybox:............State: Running......Last State: TerminatedReason: ErrorExit Code: 137......Ready: TrueRestart Count: 1Liveness: exec [test -e /data/html/test.html] delay=0s timeout=1s period=10s #success=1 #failure=3
待容器重启完成之后再次查看,Pod已经处于正常运行状态,我们可以进入到Pod里面再次查看,该文件又会被重新生成
[root@k8s-master01 nginx]# kubectl exec -ti test-nodeselector1 -- /bin/sh/ # ls /data/html/index.html test.html/ # exit[root@k8s-master01 nginx]
我们也可以把上面的yaml文件进行改动一下,让容器每次重启之后,每过30秒就自动删除test.html文件 下面这个清单的意思就是,在容器启动前生成一个/data/html/test.html,因为定义了postStart,然后容器启动完成之后过30秒删除/data/html/test.html,并使容器进入睡眠状态,睡眠时间600秒,如果不设置睡眠时间,删除完成之后容器就会终止(docker里面的知识),然后定义了一个容器存活性探测的探针exec,检测/data/html/test.html文件是否存在 这里有一个知识点,细心的朋友会发现这里定义了三个
command,那么他们的执行顺序是怎么样的呢?
- 首先执行的是postStart里面定义的command
- 然后执行的是containers里面定义的command
- 最后执行的是livenessProbe里面定义的command
apiVersion: v1kind: Podmetadata:annotations:created-by: cluster adminname: test-nodeselector1labels:env: prespec:containers:- name: busyboximage: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1ports:- name: httpcontainerPort: 80protocol: TCPcommand: ["/bin/sh","-c","sleep 30; rm -f /data/html/test.html; sleep 600"]livenessProbe:exec:command: ["test","-e","/data/html/test.html"]lifecycle:postStart:exec:command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]nodeSelector:disktype: ssd
[root@k8s-master01 nginx]# kubectl get pods -l "env=pre"NAME READY STATUS RESTARTS AGEtest-nodeselector1 1/1 Running 4 7m23s[root@k8s-master01 nginx]#可用看到Pod已经被重启过4次了,并且还会继续重启,只要你的命令没有修改,也可以使用`kubectl describe`来查看,这里就不再演示了
需要特别说明的是,exec指令的命令运行于容器中。会消耗容器的可用资源配额,另外,考虑到探针操作的效率本身等因素,探针操作的指令应该尽可能简单和轻量
设置HTTP探针
基于HTTP的探测(HTTPGetAction)向目标容器发起一个HTTP请求,根据其响应码进行结果判定,响应码为2XX或3XX时表示检测通过。
spec.containers.livenessProbe.httpGet字段用于定义此类检测,它的可用配置段包括如下几个
- host:请求的主机地址,默认为PodIP,也可以在httpHeaders中使用”Host”来定义
- port:请求的端口,必选字段
- httpHeaders<[]object>:自定义的请求报文首部
- path: 请求的http资源路径,即URL 路径
- scheme:建立连接使用的协议,仅可以为http或者https,默认为http
下面是一个资源清单的示例,它通过lifecycle中的postStart钩子创建了一个test.html的面,用于生成网站的页面,任何顶一个livenessPorbe使用httpGet的探针方式进行健康状态检测
apiVersion: v1kind: Podmetadata:annotations:created-by: cluster adminname: test-nodeselector1labels:env: prespec:containers:- name: busyboximage: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1ports:- name: httpcontainerPort: 80protocol: TCPlivenessProbe:httpGet:path: /test.htmlport: 80scheme: HTTPlifecycle:postStart:exec:command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]nodeSelector:disktype: ssd
shell操作示例
1.我们先删除之前创建的pod[root@k8s-master01 nginx]# kubectl delete -f nodeSelector.yamlpod "test-nodeselector1" deleted[root@k8s-master01 nginx]#2.然后创建Pod[root@k8s-master01 nginx]# kubectl apply -f nodeSelector.yamlpod/test-nodeselector1 created[root@k8s-master01 nginx]#3.使用`kubectl describe pods`查看探针[root@k8s-master01 nginx]# kubectl describe pods test-nodeselector1.........Containers:busybox:Port: 80/TCPHost Port: 0/TCPState: RunningStarted: Thu, 11 Jun 2020 11:22:48 +0800Ready: TrueRestart Count: 0Liveness: http-get http://:80/test.html delay=0s timeout=1s period=10s #success=1 #failure=3.........4.使用exec命令删除/data/html/test.html文件[root@k8s-master01 nginx]# kubectl exec test-nodeselector1 rm /data/html/test.html[root@k8s-master01 nginx]#5.然后再次使用`kubectl describe pods`命令查看其Pod的详细信息[root@k8s-master01 nginx]# kubectl describe pods test-nodeselector1............Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled <unknown> default-scheduler Successfully assigned default/test-nodeselector1 to 172.18.15.113Normal Pulled 3m53s kubelet, 172.18.15.113 Container image "registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1" already present on machineNormal Created 3m53s kubelet, 172.18.15.113 Created container busyboxNormal Started 3m53s kubelet, 172.18.15.113 Started container busyboxWarning Unhealthy 1s kubelet, 172.18.15.113 Liveness probe failed: HTTP probe failed with statuscode: 404[root@k8s-master01 nginx]#
一般来说,HTTP类型的探测操作应该针对专用的URL路径进行,例如,前面的示例中特别为其准备的test.html。另外,此URL路径对应的web资源应该以轻量化的方式在内部对应程序的各关键组件进行全面检测,以确保它们可以正常向客户端提供完整的服务 需要注意的是,这种检测方式仅对分层结构架构中的当前一层有效,例如,它能检测应用程序工作状态是否正常,但重启操作却无法解决其后端服务导致的故障。此时,容器可能会被一次次的重启,直到后端服务恢复正常为止。其他两种检测方式也存在类似的问题
设置TCP探针
基于TCP的存活性探测(TCPSocketAction)用于向容器的特定端口发起TCP请求尝试建立连接进行结果判定,连接建立成功则为通过检测。相比较来说,它比基于HTTP的探测更高效,更节约资源,但精准度略低,毕竟连接建立成功未必意味着页面资源可用。
spec.containers.livenessProbe.tcpSocket字段用于定义此类检测,它主要包含两个可用的属性
- host:请求连接的目标IP地址,默认为PodIP
- port:请求连接的目标端口,必须字段
下面是基于tcpSocket的资源清单的示例文件, 它向PodIP的80端口发起连接请求,根据连接状态判断测试结果
apiVersion: v1kind: Podmetadata:annotations:created-by: cluster adminname: test-nodeselector1labels:env: prespec:containers:- name: busyboximage: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1ports:- name: httpcontainerPort: 80protocol: TCPlivenessProbe:tcpSocket:port: 80lifecycle:postStart:exec:command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]nodeSelector:disktype: ssd
shell操作
1.我们先删除之前创建的Pod[root@k8s-master01 nginx]# kubectl delete pods test-nodeselector1pod "test-nodeselector1" deleted[root@k8s-master01 nginx]#2.基于清单创建创建一个Pod[root@k8s-master01 nginx]# kubectl apply -f nodeSelector.yamlpod/test-nodeselector1 created[root@k8s-master01 nginx]#3.进入Pod查看80端口是否监听[root@k8s-master01 nginx]# kubectl exec -ti test-nodeselector1 /bin/sh/ # psPID USER TIME COMMAND1 root 0:00 /bin/httpd -f -h /data/html44 root 0:00 /bin/sh49 root 0:00 ps/ #/ #/ # exit[root@k8s-master01 nginx]#4.测试如果我们将PID为1的进程杀死的话,其实Pod也会被终止,并被重建。所以这里我就不给测结果了
存活性探测行为属性
使用
kubectl describe命令查看配置了存活性探测的Pod对象的详细信息时,相关容器中会输出类似如下一行的内容
Liveness: tcp-socket :80 delay=0s timeout=1s period=10s #success=1 #failure=3
它给出了探测方式机器额外的配置属性delay、timeout、period、success和failure及其各自的相关属性值。用户没有明确定义这些属性字段时,它们会使用各自的默认值,例如上面显示出的设定。这些属性值可用通过
spec.containers.livenessProbe的如下属性字段来给出
- initDelaySeconds:存活性探测延迟时长,即容器启动多久之后再开始执行第一次探测操作。显示为delay属性,默认为0秒,即容器启动后立刻开始进行探测
- timeoutSeconds:存活性探测的超时时长,显示为timeout属性,默认为1s,最小值也为1s,健康检测发送请求以后等待响应的超时时间,单位为s,当超时发生时,kubelet会认为容器已经无法提供服务
- periodSeconds:存活性探测到频度,多长时间进行一次探测,显示为period属性,默认为10s,最小值为1s,过高的频率会对Pod对象带来额外的开销,而过低的频率又会使得对错误的反映不及时
- successThreshold:处于失败状态时,探测操作至少连续多少次的成功才能认为是通过检测,显示为success属性。默认值为1,最小值也为1。大致的意思就是,当出现失败状态,则探针需要连续探测
successThreshold定义的次数的状态才能认为是通过,其值必须为1- failureThreshold:处于成功状态时,探测操作至少连续多少次的失败才被视为检测不通过,显示为failure属性,默认值为3,最小值为1
例如,下面的示例的配置清单中添加相关属性
apiVersion: v1kind: Podmetadata:annotations:created-by: cluster adminname: test-nodeselector1labels:env: prespec:containers:- name: busyboximage: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1ports:- name: httpcontainerPort: 80protocol: TCPlivenessProbe:tcpSocket:port: 80timeoutSeconds: 5periodSeconds: 10initialDelaySeconds: 10failureThreshold: 3lifecycle:postStart:exec:command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]nodeSelector:disktype: ssd
shell操作
1.删除之前的pod[root@k8s-master01 nginx]# kubectl delete -f nodeSelector.yamlpod "test-nodeselector1" deleted[root@k8s-master01 nginx]#2.创建Pod[root@k8s-master01 nginx]# kubectl apply -f nodeSelector.yamlpod/test-nodeselector1 created[root@k8s-master01 nginx]#3.使用describe查看[root@k8s-master01 nginx]# kubectl describe pods test-nodeselector1.........Liveness: tcp-socket :80 delay=10s timeout=5s period=10s #success=1 #failure=3..........
什么时候能用到存活探测(livenessProbe)呢
- 如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针,kubelet将根据Pod的”restartPolicy(重启策略)”自动执行正确的操作,如果你希望容器子啊探测失败时被杀死并重新启动,那么就需要指定一个存活探针,并指定”restartPolicy(重启策略)”为Alwsys活OnFailure
Pod就绪性探测
Pod对象启动以后,容器应用通常需要一段时间才能完成其初始化过程,例如加载配置活数据,甚至有些应用程序需要某些的预热过程,若在此阶段完成之前就接入客户端的请求,势必会因为等待太久而影响用户体验,因此,应该避免于Pod对象启动后立即让其处理客户端请求,而是等待容器初始化工作执行完成并转为“就绪”状态。尤其是存在其他提供相同服务的Pod对象的场景更是如此 与存活性探测机制类似,就绪探测用是来判断容器就绪与否的周期性(默认周期为10秒钟)操作,它用于探测容器是否已经初始化完成并可以对外提供服务,探测操作返回Success状态时,即为传递容器已经就绪的信息,如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success 与存活探测机制相同,就绪探测也支持Exec,HTTPGet和TCPSocket三种探测方式,且各自定义的机制也都相同。但与存活探测处罚的操作不同的是,探测失败时,就绪探测不会杀死活重启容器以保证其减抗性,而是通知其尚未就绪,并出发依赖于其就绪状态的操作以确保不会有客户端请求接入Pod对象。不过,即便是在运行过程中,Pod就绪探测依然有它的存在价值,例如Pod A依赖到Pod B因网络故障等原因而不可用时,Pod A上的服务应该转为未就绪状态,以免无法向客户端提供完整的响应 将容器定义中的livenessProbe字段替换为readinessProbe即可定义出就绪探测的配置,一个简单的示例如下,这里我们先把livenessProbe注释掉,或者将livenessProbe换一种探针,因为我们在containers字段定义了command,它默认会把docker容器里面定义的command替换,导致http服务启动的命令被替换成这个循环语句,80端口就没办法监听,这个时候存活探针会探测80端口导致失败而重启容器 定义了一个启动前钩子,生成一个test.html的文件。然后container字段里面定义了一个command,循环执行删除test.html文件,然后30秒之后再生成test.html文件。
apiVersion: v1kind: Podmetadata:annotations:created-by: cluster adminname: test-nodeselector1labels:env: prespec:containers:- name: busyboximage: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1ports:- name: httpcontainerPort: 80protocol: TCPcommand: ["/bin/sh","-c","while true; do rm -f /data/html/test.html; sleep 30; echo 'readinessProbe' > /data/html/test.html; sleep 300; done"]# livenessProbe:# tcpSocket:# port: 80# timeoutSeconds: 5# periodSeconds: 10# initialDelaySeconds: 10# failureThreshold: 3readinessProbe:exec:command: ["test","-e","/data/html/test.html"]initialDelaySeconds: 5periodSeconds: 5lifecycle:postStart:exec:command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]nodeSelector:disktype: ssd
shell操作
1.删除之前的创建的Pod[root@k8s-master01 nginx]# kubectl delete -f nodeSelector.yamlpod "test-nodeselector1" deleted[root@k8s-master01 nginx]#2.重新创建Pod[root@k8s-master01 nginx]# kubectl apply -f nodeSelector.yamlpod/test-nodeselector1 created[root@k8s-master01 nginx]#3.接着运行kubectl get pods -l "env=pre" -w[root@k8s-master01 nginx]# kubectl get pods -l "env=pre" -wNAME READY STATUS RESTARTS AGEtest-nodeselector1 0/1 Pending 0 0stest-nodeselector1 0/1 Pending 0 0stest-nodeselector1 0/1 ContainerCreating 0 0stest-nodeselector1 0/1 Running 0 1stest-nodeselector1 1/1 Running 0 6s尽管Pod处于running状态,但直到就绪探测命令执行成功之后,Pod资源才转为running状态
或者使用
kubectl describe命令查看
State: RunningStarted: Thu, 11 Jun 2020 15:30:02 +0800Ready: TrueRestart Count: 0Readiness: exec [test -e /data/html/test.html] delay=5s timeout=1s period=5s #success=1 #failure=3Environment: <none>
需要注意的是,未定义就绪探测的Pod对象在Pod进入“Running”状态后将立即就绪,在容器需要时间初始化的场景中,在应用真正就绪之前必然无法正常响应客户端请求,因此,生产实践中,必须为关键性Pod资源中的容器定义就绪探测机制 在这种情况下,就绪探针可能存活探针相同,但是spec中的就绪探针的存在意味着Pod将在没有接收到任何流量的情况下启动,并且只有在探针探测成功后才开始接收流量,如果你希望容器能够自行维护,可用指定一个就绪探针,该探针检测与存活探针不同的端点
