K8S 启动探测、就绪探测、存活探测

先来思考一个问题:

在 Deployment 执行滚动更新 web 应用的时候,总会出现一段时间,Pod 对外提供网络访问,但是页面访问却发生404,这个问题要如何解决呢?学完今天的内容,相信你会有自己的答案。

一、理论介绍

1.1、探测类型(探针类型)

官网介绍如下

探测或者探针都可以,英文都是 probe:

1、启动探测 startupProbe

  • 检查容器内的应用是否已启动。
  • 如果配置了启动探测,它会禁用存活探测和就绪探测,直到启动探测成功为止。
  • 如果启动探测失败,kubelet 会将容器杀死,并依据容器重启策略进行重启。
  • 如果容器没有提供启动探测,则默认状态为 Success(成功)。
  • 这类探针仅在启动时执行,不像存活探针和就绪探针那样周期性地运行。

2、就绪探测 readinessProbe

  • 决定何时容器准备好开始接受流量。
  • 如果就绪探测失败,kubelet 会将该 Pod 从所有对应服务的端点(endpoint)中移除。
  • 如果容器没有提供就绪探测,则默认状态为 Success(成功)。

3、存活探测 livenessProbe

  • 检测容器是否正常运行。
  • 如果一个容器的存活探针失败多次,kubelet 将依据容器重启策略进行重启。
  • 如果容器没有提供存活探测,则默认状态为 Success(成功)。

注意的点:

  • 如果三个都配置,启动顺序:
  • 就绪探测 和 存活探测的区别

readinessProbe 当检测失败后,将 Pod 的 IP:Port 从对应的 EndPoint 列表中删除。 livenessProbe 当检测失败后,将杀死容器并根据 Pod 的重启策略来决定作出对应的措施。

1.2、探测结果

1.3、检查机制(探测模式)

检查机制三种探针都一样,都有四种:

(因为三种探针都是 go 语言 Probe类型,所以它们探针类型都一样)

1、exec

  • 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。

2、grpc

  • 使用 gRPC 执行一个远程过程调用。

3、httpGet

  • 对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。

4、tcpSocket

  • 对容器的 IP 地址上的指定端口执行 TCP 检查。如果能够建立 TCP 连接,则表明容器健康。

1.4、探针属性介绍

探针主要属性介绍:

(因为三种探针都是 go 语言 Probe 类型,所以它们属性都一样)

bash 复制代码
kubectl explain pod.spec.containers.startupProbe

其中,exec、grpc、httpGet、tcpSocket 是检查机制,就不多说了。

1、failureThreshold

  • 连续探测失败多少次,会被认为是失败。默认值是 3,最小值为 1**。**

2、initialDelaySeconds

  • 容器启动多少秒,探针才开始工作。

3、periodSeconds

  • 执行探测的时间间隔(单位是秒),默认为 10s,单位"秒",最小值是1

4、successThreshold

  • 连续探测几次成功,才认为探测成功,默认为 1,在启动探针和存活探针中必须为1,最小值为1。

5、timeoutSeconds

  • 探针执行检测请求后,等待响应的超时时间,默认为1,单位"秒"。

1.5、环境准备

假设有如下三个节点的 K8S 集群:

k8s31master 是控制节点

k8s31node1、k8s31node2 是工作节点

容器运行时是 containerd

1.5.1、镜像准备

bash 复制代码
docker pull tomcat:8.5-jre8-alpine
docker pull busybox:1.28

1.5.2、镜像导出

bash 复制代码
docker save -o tomcat-8.5-jre8-alpine.tar.gz docker.io/library/tomcat:8.5-jre8-alpine
docker save -o busybox-1.28.tar.gz docker.io/library/busybox:1.28

15.3、镜像导入工作节点 containerd

bash 复制代码
# k8s31node1 执行
[root@k8s31node1 ~]# ctr -n=k8s.io images import tomcat-8.5-jre8-alpine.tar.gz
[root@k8s31node1 ~]# ctr -n=k8s.io images ls|grep tomcat
[root@k8s31node1 ~]# ctr -n=k8s.io images import busybox-1.28.tar.gz
[root@k8s31node1 ~]# ctr -n=k8s.io images ls|grep busybox
 
# k8s31node2 执行
[root@k8s31node2 ~]# ctr -n=k8s.io images import tomcat-8.5-jre8-alpine.tar.gz
[root@k8s31node2 ~]# ctr -n=k8s.io images ls|grep tomcat
[root@k8s31node2 ~]# ctr -n=k8s.io images import busybox-1.28.tar.gz
[root@k8s31node2 ~]# ctr -n=k8s.io images ls|grep busybox

说明:

  • ctr 是 containerd 命令
  • ctr images import:导入镜像
  • -n=k8s.io:K8S 镜像存储命名空间

三、启动探测 startupProbe

3.1、exec 模式

3.1.1、探测成功

  • pod-startupprobe-exec.yaml
bash 复制代码
apiVersion: v1
kind: Pod
metadata: 
  name: pod-startupprobe-exec
spec:
  containers:
  - name: tomcat
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080
    startupProbe:
      exec:
        command:
        - "/bin/sh"
        - "-c"
        - "ps -ef|grep tomcat"
      initialDelaySeconds: 20
      periodSeconds: 20
      timeoutSeconds: 10
      successThreshold: 1
      failureThreshold: 3

initialDelaySeconds:容器启动后多久才开始探测,这里为了演示方便,故意调成 20 秒。实际业务,需要根据服务启动的平均时间来设置。假设容器启动要 10 秒,initialDelaySeconds 设置为 5 秒,太早探测,容易多次探测失败。

initialDelaySeconds 设置为 20 秒,则容易空等,不利于服务的吞吐。

periodSeconds:执行探测的时间间隔为 20 秒。

timeoutSeconds:探针执行检测请求后,等待响应的超时时间为 10 秒。

successThreshold:连续探测 1 次成功,才认为探测成功。所以如果成功,会执行 2 次探测。

failureThreshold:连续探测失败 3 次,才认为是失败。

command:要在容器里面执行的命令行。

  • /bin/sh:因为有 | 管道操作,所以需要显示地调起 shell 解释器。
  • -c:command 的意思,后接命令行字符串。
  • ps -ef|grep tomcat:打印出 tomcat 进程信息。
  • 整个命令其实是在容器中执行 /bin/sh -c "ps -ef|grep tomcat"
  • 执行并监控
bash 复制代码
kubectl apply -f pod-startupprobe-exec.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思

运行 5s 的时候,K8S 已经确认并创建 Pod,但是这个时候,启动探测还没通过,Pod 没有一个容器 READY。20s 的时候,第一次启动探测,因为需要连续两次成功,才算成功,所以间隔 20s 又探测一次,整个 Pod 启动,用时 40s。

  • 查看 pod 事件
bash 复制代码
kubectl describe pod pod-startupprobe-exec

会发现启动探测的信息在这里。

3.1.2、探测失败(容器重启)

  • 删除原来 pod 并修改 yaml 文件
bash 复制代码
kubectl delete -f pod-startupprobe-exec.yaml
vim pod-startupprobe-exec.yaml
bash 复制代码
apiVersion: v1
kind: Pod
metadata: 
  name: pod-startupprobe-exec
spec:
  containers:
  - name: tomcat
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080
    startupProbe:
      exec:
        command:
        - "false"
      initialDelaySeconds: 20
      periodSeconds: 20
      timeoutSeconds: 10
      successThreshold: 1
      failureThreshold: 3

command:

  • "false" # 这一行返回非0的状态,启动探测会失败。
  • 执行并监控
bash 复制代码
kubectl apply -f pod-startupprobe-exec.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思

可以看到容器在不停地重启,RESTARTS 在不停增加。

这是因为我们容器 pod.spec.containers.restartPolicy 默认为 Always。

倘若大家配置为 Never,那它就不重启了,这个大家可以试一下。

  • 查看 pod 事件
bash 复制代码
kubectl describe pod pod-startupprobe-exec

可以看到,K8S 会提示,启动探测失败。

  • 删除 pod
bash 复制代码
kubectl delete -f pod-startupprobe-exec.yaml

3.2、tcpSocket 模式

  • pod-startupprobe-tcpsocket.yaml
bash 复制代码
apiVersion: v1
kind: Pod
metadata: 
  name: pod-startupprobe-tcpsocket
spec:
  containers:
  - name: tomcat
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080
    startupProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 20
      periodSeconds: 20
      timeoutSeconds: 10
      successThreshold: 1
      failureThreshold: 3

tcpSocket 有两个参数:

  • host:默认 pod IP。也就是下图 10.244.165.44
  • port:必填,容器端口。

tcpSocket 类似于执行一个 telnet 10.244.165.44 8080

tomcat 也是能接收 tcp 请求的,因为 tomcat 底层用的 socket 连接。

bash 复制代码
kubectl explain pod.spec.containers.startupProbe.tcpSocket
  • 执行并监控
bash 复制代码
kubectl apply -f pod-startupprobe-tcpsocket.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思
  • 删除 pod
bash 复制代码
kubectl delete -f pod-startupprobe-tcpsocket.yaml

3.3、httpGet

  • pod-startupprobe-httpget.yaml
bash 复制代码
apiVersion: v1
kind: Pod
metadata: 
  name: pod-startupprobe-httpget
spec:
  containers:
  - name: tomcat
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080
    startupProbe:
      httpGet:
        path: /
        port: 8080
      initialDelaySeconds: 20
      periodSeconds: 20
      timeoutSeconds: 10
      successThreshold: 1
      failureThreshold: 3

httpGet 有三个重要参数:

  • host:默认 pod IP。也就是下图 10.244.165.45
  • path:我们服务定义的路径,像 tomcat 就是根路径 /
  • port:必填,容器端口。

httpGet 类似于执行一个 curl http://10.244.165.45:8080/

bash 复制代码
kubectl explain pod.spec.containers.startupProbe.httpGet
  • 执行并监控
bash 复制代码
kubectl apply -f pod-startupprobe-httpget.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思
  • 删除 pod
bash 复制代码
kubectl delete -f pod-startupprobe-httpget.yaml

四、存活探测 livenessProbe

4.1、exec 模式(容器重启)

我们来看一个 K8S 官网的例子 pod-livenessprobe-exec.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-livenessprobe-exec
spec:
  containers:
  - name: liveness
    image: busybox:1.28
    imagePullPolicy: IfNotPresent
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

args:容器启动的时候执行命令

  • /bin/sh -c "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"
  • 容器启动的时候创建一个 /tmp/healthy 文件,然后睡眠 30 秒,到时间后删除文件 /tmp/healthy。接着睡眠 600 秒,防止容器退出。

command:

  • kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
  • 这个容器生命的前 30 秒,/tmp/healthy 文件是存在的。 所以在这最开始的 30 秒内,执行命令 cat /tmp/healthy 会返回成功代码。 30 秒之后,执行命令 cat /tmp/healthy 就会返回失败代码。
  • 执行并监控
bash 复制代码
kubectl apply -f pod-livenessprobe-exec.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思

前 30 秒因为文件 /tmp/healthy 存在,存活探针探测成功。我们设置的探测间隔 periodSeconds 是 5 秒,所以在第 35 秒的时候,存活探针探测失败,又因为 failureThreshold 失败阈值我们没有设置,默认是 3,所以又要再探测 2 次,再加 10 秒,差不多 45 秒,这个时候 Pod 开始重启。

  • 在 30 秒内,查看 Pod 的事件
bash 复制代码
kubectl describe pod pod-livenessprobe-exec

探测成功。

  • 35 秒之后,再来看 Pod 的事件
bash 复制代码
kubectl describe pod pod-livenessprobe-exec

探测失败。45 秒的时候,容器开始重启。从这边也能看出,存活探针是周期性启动的

  • 删除 pod
bash 复制代码
kubectl delete -f pod-livenessprobe-exec.yaml

4.2、httpGet 模式

我们来看一个官方的例子。

4.2.1、镜像拉取

由于国内无法访问 registry.k8s.io,所以我找了一个镜像中转站

bash 复制代码
docker pull myifeng/registry.k8s.io_e2e-test-images_agnhost:2.40
bash 复制代码
# 查看是否下载完成
docker images | grep agnhost

4.2.2、镜像导出

bash 复制代码
docker save -o agnhost-2.40.tar.gz myifeng/registry.k8s.io_e2e-test-images_agnhost:2.40

4.2.3、镜像导入工作节点 containerd

bash 复制代码
# k8s31node1 执行
[root@k8s31node1 ~]# ctr -n=k8s.io images import agnhost-2.40.tar.gz
[root@k8s31node1 ~]# ctr -n=k8s.io images ls|grep agnhost
 
# k8s31node2 执行
[root@k8s31node2 ~]# ctr -n=k8s.io images import agnhost-2.40.tar.gz
[root@k8s31node2 ~]# ctr -n=k8s.io images ls|grep agnhost

4.2.4、Demo 演示

pod-livenessprobe-httpget.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-livenessprobe-http
spec:
  containers:
  - name: liveness
    image: myifeng/registry.k8s.io_e2e-test-images_agnhost:2.40
    imagePullPolicy: IfNotPresent
    args:
    - liveness
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 2
      periodSeconds: 2
  • args:容器启动时设置参数 liveness。
  • initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 2 秒。
  • periodSeconds 字段指定了 kubelet 每隔 2 秒执行一次存活探测。
  • kubelet 会向容器内运行的服务(服务在监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并将其重启。
  • 返回大于或等于 200 并且小于 400 的任何代码都标示成功,其它返回代码都标示失败。

容器内服务的源码:容器存活期间的最开始 10 秒钟,/healthz 处理程序返回 200 的状态码。之后处理程序返回 500 的状态码。

Go 复制代码
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
})

kubelet 在容器启动之后 2 秒开始执行健康检查。所以前几次健康检查都是成功的。 但是 10 秒之后,健康检查会失败,并且 kubelet 会杀死容器再重新启动容器。

  • 执行并监控
bash 复制代码
kubectl apply -f pod-livenessprobe-httpget.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思
  • 在 10 秒内,查看 Pod 的事件
bash 复制代码
kubectl describe pod pod-livenessprobe-http
  • 10 秒之后,再来看 Pod 的事件
bash 复制代码
kubectl describe pod pod-livenessprobe-http

存活探针已经失败,并且容器被重新启动了。

4.2.5、删除 pod

bash 复制代码
kubectl delete -f pod-livenessprobe-httpget.yaml

4.3、tcpSocket 模式

pod-livenessprobe-tcpsocket.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-livenessprobe-tcp
spec:
  containers:
  - name: tomcat
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 5
  • 执行并监控
bash 复制代码
kubectl apply -f pod-livenessprobe-tcpsocket.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思
  • 进入容器,关闭 tomcat
bash 复制代码
[root@k8s31master ~]# kubectl exec -it pod-livenessprobe-tcp -- /bin/bash
# 关闭 tomcat
bash-4.4# /usr/local/tomcat/bin/shutdown.sh

关闭 tomcat 之后,容器状态为 Completed,然后 kubelet 检测到容器 8080 端口没有存活,重启容器。

  • 删除 pod
bash 复制代码
kubectl delete -f pod-livenessprobe-tcpsocket.yaml

五、就绪探测 readinessProbe

5.1、exec 模式(容器不会重启)

pod-readinessprobe-exec.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-readinessprobe-exec
spec:
  containers:
  - name: readiness
    image: busybox:1.28
    imagePullPolicy: IfNotPresent
    args:
    - /bin/sh
    - -c
    - sleep 20; touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 600
    readinessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

args:容器启动的时候执行命令

  • /bin/sh -c "sleep 20; touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 600"
  • 容器启动的时候先睡眠 20 秒,到时间后创建一个 /tmp/healthy 文件,然后睡眠 10 秒,到时间后删除文件 /tmp/healthy。接着睡眠 600 秒,防止容器退出。

command:

  • kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是就绪的,可以对外提供服务。 如果这个命令返回非 0 值,kubelet 会隔 5 秒重新探测。
  • 这个容器生命的前 20 秒,/tmp/healthy 文件是不存在的。所以在这最开始的 20 秒内,执行命令 cat /tmp/healthy 会返回失败代码,容器 READY 个数为 0 。20 秒之后,执行命令 cat /tmp/healthy 就会返回成功代码,这个时候容器 READY 个数就会变为 1。再过了 10 秒,/tmp/healthy文件被删除,就绪探测失败,容器 READY 个数重新变为 0,然后隔 5 秒重新探测一次。在整个过程中,容器不会重启
  • 执行并监控
bash 复制代码
kubectl apply -f pod-readinessprobe-exec.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思

因为 readinessProbe 不会重启,所以经常要跟 livenessProbe 搭配使用。

  • 在 20 秒内,查看 Pod 的事件
bash 复制代码
kubectl describe pod pod-readinessprobe-exec
  • 20 秒到 30 秒,查看 Pod 的事件
bash 复制代码
kubectl describe pod pod-readinessprobe-exec
  • 30 秒以后,查看 Pod 的事件
bash 复制代码
kubectl describe pod pod-readinessprobe-exec

实际的 Age 时间与我们预期的会有一些出入。不过监控 -w 里面的时间,跟我们分析的是吻合的。

  • 删除 pod
bash 复制代码
kubectl apply -f pod-readinessprobe-exec.yaml

5.2、httpGet 模式

pod-readinessprobe-http.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-readinessprobe-http
spec:
  containers:
  - name: readiness
    image: myifeng/registry.k8s.io_e2e-test-images_agnhost:2.40
    imagePullPolicy: IfNotPresent
    args:
    - liveness
    readinessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 5
      periodSeconds: 3
  • 执行并监控
bash 复制代码
kubectl apply -f pod-readinessprobe-http.yaml
kubectl get pod -owide -w
# -w watch 持续监控的意思

就绪探测 5 秒之后开始,容器存活期间的最开始 10 秒钟,/healthz 处理程序返回 200 的状态码,容器 READY 变为 1/1。之后容器内处理程序返回 500 的状态码,容器 READY 变为 0/1,之后每隔 3 秒重新探测一次。

  • 删除 pod
bash 复制代码
kubectl delete -f pod-readinessprobe-http.yaml

六、开头问题解答

在 Deployment 执行滚动更新的时候,总会出现一段时间,Pod 对外提供网络访问,但是访问却发生404,这个问题要如何解决呢?

问题的原因,是因为 Pod 已经成功启动,但是 Pod 内的容器中应用程序还在启动中导致。

可以使用就绪探针,去检测系统中,页面元素适中、接口响应时间在平均时间的页面或接口,使用 httpGet 模式去探测它,成功了才加到系统 EndPoint 中。

相关推荐
元气满满的热码式3 小时前
K8S中Service详解(一)
云原生·容器·kubernetes
元气满满的热码式7 小时前
K8S中ingress详解
云原生·容器·kubernetes
jcrose25807 小时前
Ubuntu二进制部署K8S 1.29.2
linux·ubuntu·kubernetes
lozhyf7 小时前
基于 JFinal 的国产微服务框架
微服务·云原生·架构
Dusk_橙子7 小时前
在K8S中,如何使用EFK实现日志的统一管理?
云原生·容器·kubernetes
Tony11548 小时前
Kubernetes v1.28.0安装dashboard v2.6.1(k8s图形化操作界面)
云原生·容器·kubernetes
龙胖不下锅8 小时前
k8s资源预留
云原生·容器·kubernetes
超级阿飞8 小时前
利用Kubespray安装生产环境的k8s集群-排错篇
docker·容器·kubernetes
喝醉酒的小白8 小时前
在 Kubernetes 上快速安装 KubeSphere v4.1.2
云原生·容器·kubernetes