先来思考一个问题:
在 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 连接。
bashkubectl 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/
bashkubectl 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 中。