环境:centos7.9 docker-ce-20.10.9 kubernetes-version v1.22.6
在前面我们介绍过,可以通过配置restartPolicy字段来对容器退出后执行3种不同的重启策略,但这并不能解决我们所有的问题,比如容器中的Java应用程序抛出OutOfMemoryErrors,但JVM进程会一致存在,容器并没有退出,再比如,Java停止响应或死锁,容器也没有终止等等,这时如果有一种机制来告诉kubernetes来重启容器那就最好了,在k8s中,提供了一种存活探针的机制来实现上诉的问题。
livenessProbe,叫做存活探针,是为了检测容器是否正在运行,是否活着;
readinessProbe,叫做就绪探针,是为了检测容器是否准备就绪,是否能接受客户端请求;
startupProbe,叫做启动探针,用于判断容器进程是否已经启动。
存活探针是将检查失败的容器杀死,创建新的启动容器来保持pod正常工作;
就绪探针是,当就绪探针检查失败,并不重启容器,而是将pod移出服务,就绪探针确保服务中的pod都是可用的,确保客户端只与正常的pod交互并且客户端永远不会知道系统存在问题。
kubernetes可以通过存活探针检查容器是否还在运行,可以为pod中的每个容器单独定义存活探针,kubernetes将定期执行探针,如果探测失败,将杀死容器,并根据restartPolicy策略来决定是否重启容器。
kubernetes提供了3种探测容器的存活探针,如下:
exec:在容器内执行shell命令,根据命令退出状态码是否为0判定成功失败;
httpGet:根据容器IP地址、端口号、路径发送http get请求,返回200-400范围内的状态码表示成功;(生产环境建议使用这种)
tcpSocket:与容器IP地址、端口建立TCP Socket链接,能建立则表示成功;
存活探针的附加属性有以下几个:
initialDelaySeconds:表示在容器启动后延时多久秒才开始探测;
periodSeconds:表示执行探测的频率,即间隔多少秒探测一次,默认间隔周期是10秒,最小1秒;
timeoutSeconds:表示探测超时时间,默认1秒,最小1秒,表示容器必须在超时时间范围内做出响应,否则视为本次探测失败;
successThreshold:表示最少连续探测成功多少次才被认定为成功,默认是1,对于liveness必须是1,最小值是1;
failureThreshold:表示连续探测失败多少次才被认定为失败,默认是3,连续3次失败,k8s 将根据pod重启策略对容器做出决定;
注意:定义存活探针时,一定要设置initialDelaySeconds
属性,该属性为初始延时,如果不设置,默认容器启动时探针就开始探测了,这样可能会存在应用程序还未启动就绪,就会导致探针检测失败,k8s就会根据pod重启策略杀掉容器然后再重新创建容器的莫名其妙的问题。
在生产环境中,一定要定义一个存活探针。
exec探针是通过在容器内执行shell命令,根据命令退出状态码是否为0判定成功失败;
[root@master ~]# cat rs_nginx.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-nginx-http
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx-rs
matchExpressions:
- {key: env, operator: In, values: [dev]}
template:
metadata:
labels:
app: nginx-rs
env: dev
spec:
containers:
- image: nginx:1.7.9
name: nginx-container
ports:
- containerPort: 80
livenessProbe: #定义探针
exec: #探针类型为exec探针
command:
- cat #表示cat /usr/share/nginx/html/index.html文件,根据命令执行
- /usr/share/nginx/html/index.html #结果返回状态是否非0来表示探针状态
initialDelaySeconds: 8 #初始延时,表示容器启动15秒后才开始探测
periodSeconds: 3 #探测周期,3秒探测一次
timeoutSeconds: 2 #超时时间为2秒,表示必须2秒内做出回应,否则将视本次探测失败
failureThreshold: 1 #连续失败次数,表示连续失败1次就定义为失败,k8s将根据pod重启机制
#做出是否重启容器的就决定
#下面将进入容器内部删除 /usr/share/nginx/html/index.html文件,看探针是否生效
[root@master ~]# kubectl exec rs-nginx-http-2rng2 -it -- bash #进入容器
root@rs-nginx-http-2rng2:/# rm -rf /usr/share/nginx/html/index.html #删除/usr/share/nginx/html/index.html
root@rs-nginx-http-2rng2:/# exit #退出容器
[root@master ~]# kubectl get pods #查看pod
NAME READY STATUS RESTARTS AGE
pod/rs-nginx-http-2rng2 1/1 Running 1 (11s ago) 74s #发现pod的容器重启过一次
pod/rs-nginx-http-bgr4m 1/1 Running 0 74s
pod/rs-nginx-http-cv9bv 1/1 Running 0 74s
[root@master ~]# kubectl describe pod/rs-nginx-http-2rng2 #查看pod信息,可以看出探针探测失败,容器被重新启动了
................
Restart Count: 1
Liveness: exec [cat /usr/share/nginx/html/index.html] delay=8s timeout=2s period=3s #success=1 #failure=1
................................
Normal Pulled 9m32s (x2 over 10m) kubelet Container image "nginx:1.7.9" already present on machine
Normal Created 9m32s (x2 over 10m) kubelet Created container nginx-container
Warning Unhealthy 9m32s kubelet Liveness probe failed: cat: /usr/share/nginx/html/index.html: No such file or directory
Normal Killing 9m32s kubelet Container nginx-container failed liveness probe, will be restarted
Normal Started 9m31s (x2 over 10m) kubelet Started container nginx-container
[root@master ~]#
httpGet探针是根据容器的IP地址、端口、路径发送http get 请求,返回200-400范围内的状态码表示成功;
[root@master ~]# cat rs_nginx.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-nginx-http
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx-rs
matchExpressions:
- {key: env, operator: In, values: [dev]}
template:
metadata:
labels:
app: nginx-rs
env: dev
spec:
containers:
- image: nginx:1.7.9
name: nginx-container
ports:
- containerPort: 80
livenessProbe: #为容器定义一个存活探针
httpGet: #探针类型为httpGet探针
# host: 10.244.0.49 #指定主机,默认是pod的ip,一般省略不写
path: / #路径
port: 80 #端口
initialDelaySeconds: 15 #初始延时时间为15s
periodSeconds: 5 #探测周期
timeoutSeconds: 2 #超时时间
failureThreshold: 9 #失败次数
[root@master ~]#
tcpSocket探针,根据容器的IP地址、端口发起tcp Socket链接,探测是否能成功与容器的端口建立tcp Socket链接;
[root@master ~]# cat rs_nginx.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-nginx-http
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx-rs
matchExpressions:
- {key: env, operator: In, values: [dev]}
template:
metadata:
labels:
app: nginx-rs
env: dev
spec:
containers:
- image: nginx:1.7.9
name: nginx-container
ports:
- containerPort: 80
livenessProbe: #定义存活探针
tcpSocket: #探针类型为tcpSocket探针
# host: 10.244.0.49 #指定主机,默认是pod的ip,一般省略不写
port: 80 #探测与80端口是否能成功建立tcp链接
initialDelaySeconds: 8
periodSeconds: 3
timeoutSeconds: 2
failureThreshold: 1
我们知道,当一个pod启动后,就会立即加入service的endpoint ip列表中,并开始接收到客户端的链接请求,假若此时pod中的容器的业务进程还没有初始化完毕,那么这些客户端链接请求就会失败,为了解决这个问题,kubernetes提供了就绪探针来解决这个问题的。
在pod中的容器定义一个就绪探针,就绪探针周期性检查容器,如果就绪探针检查失败了,说明该pod还未准备就绪,不能接受客户端链接,则该pod将从endpoint列表中移除,被剔除了service就不会把请求分发给该pod,然后就绪探针继续检查,如果随后容器就绪,则再重新把pod加回endpoint列表。
就绪探针也有三种类型,exec,httpGet和tcpSocket。
exec:执行容器中的命令并检查命令退出的状态码,如果状态码为0则说明容器已经准备就绪;
httpGet:向容器的ip,端口、路径发送http get请求,通过响应的http状态码是否位于200-400之间判断容器是否准备就绪;
tcpSocke:向容器的IP地址、端口发起tcp socket链接,如果能正常建立链接,则认为容器已经准备就绪。
就绪探针的附加属性有以下几个:
initialDelaySeconds:延时秒数,即容器启动多少秒后才开始探测,不写默认容器启动就探测;
periodSeconds :执行探测的频率(秒),默认为10秒,最低值为1;
timeoutSeconds :超时时间,表示探测时在超时时间内必须得到响应,负责视为本次探测失败,默认为1秒,最小值为1;
failureThreshold :连续探测失败的次数,视为本次探测失败,默认为3次,最小值为1次;
successThreshold :连续探测成功的次数,视为本次探测成功,默认为1次,最小值为1次;
exec存活探针,执行容器中的命令并检查命令退出的状态码,如果状态码为0则说明容器已经准备就绪;
下面使用deployment部署3个有exec就绪探针的pod(service已经存在了),如下所示:
[root@master ~]# cat deplyment_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-nginx
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
name: nginx-container
readinessProbe: #定义exec探针
initialDelaySeconds: 5 #容器启动5秒后才开始探测
periodSeconds: 10 #探针周期为10s探测一次
timeoutSeconds: 2 #超时时间,2秒得不到响应则视为探测失败
failureThreshold: 3 #连续探测失败3次,视为本次探测失败
successThreshold: 1 #探测成功1次则视为本次探测成功
exec: #探针类型为exec
command: #执行的命令是ls /var/ready
- ls
- /var/ready
ports:
- name: http
containerPort: 80
[root@master ~]# kubectl get pods #查看pod,发现没有一个是ready状态的
NAME READY STATUS RESTARTS AGE
deployment-nginx-68bb45dd46-5rj7c 0/1 Running 0 3m1s
deployment-nginx-68bb45dd46-78ld2 0/1 Running 0 3m1s
deployment-nginx-68bb45dd46-gnnhn 0/1 Running 0 3m1
[root@master ~]# kubectl describe pods deployment-nginx-68bb45dd46-5rj7c |tail -n 3 #查看某个pod的详细信息,显示存活探针检测失败了
Normal Created 5m7s kubelet Created container nginx-container
Normal Started 5m7s kubelet Started container nginx-container
Warning Unhealthy 2m18s (x22 over 5m6s) kubelet Readiness probe failed: ls: cannot access /var/ready: No such file or directory
[root@master ~]#
[root@master ~]# kubectl get ep svc-rc-nginx-nodeport #查看service的endpoint列表,显示没有任何pod ip列表可用
NAME ENDPOINTS AGE
svc-rc-nginx-nodeport 6h32m
# 现在,我们手动在某个pod中创建一个/var/ready文件,这样pod就会处于准备就绪状态,service就能成功添加该pod
[root@master ~]# kubectl exec deployment-nginx-68bb45dd46-5rj7c -- touch /var/ready #为该pod创建/var/ready文件
[root@master ~]# kubectl get pods,ep -o wide #查看该pod,发现pod已处于就绪状态
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/deployment-nginx-68bb45dd46-5rj7c 1/1 Running 0 8m52s 10.244.1.77 node1 <none>
pod/deployment-nginx-68bb45dd46-78ld2 0/1 Running 0 8m52s 10.244.1.76 node1 <none>
pod/deployment-nginx-68bb45dd46-gnnhn 0/1 Running 0 8m52s 10.244.2.38 node2 <none>
NAME ENDPOINTS AGE #endpoint已经有该pod的ip端口
endpoints/svc-rc-nginx-nodeport 10.244.1.77:80 6h36m
[root@master ~]#
httpGet存活探针,向容器发送http get请求,通过响应的http状态码判断容器是否准备就绪;
下面使用deployment部署3个有httpGet就绪探针的pod,如下所示:
..........
containers:
- image: nginx:1.7.9
name: nginx-container
readinessProbe: #定义探针
httpGet: # 探针类型为httpGet
# host: 10.244.0.49 #指定主机,默认是pod的ip,一般省略不写
path: /read
port: 80
...........
tcpSocket存活探针,打开一个tcp连接到容器的指定端口,如果连接已建立,则认为容器已经准备就绪。
下面使用deployment部署有tcpSocket就绪探针的pod,如下所示:
..........
containers:
- image: nginx:1.7.9
name: nginx-container
readinessProbe: #定义探针
tcpSocket: # 探针类型为tcpSocket
# host: 10.244.0.49 #指定主机,默认是pod的ip,一般省略不写
port: 80
...........
startupProbe 启动探针,startupProbe探针是1.16版本加入的探针,用于判断容器进程是否已经启动,是为了解决程序启动时间很长,启动慢等问题的,当配置了startupProbe启动探针,会先禁用其他探针,直到startupProbe探针成功,成功后将退出不在进行探测,如果startupProbe探针探测失败,pod将会重启。
startupProbe 启动探针也是使用exec 、httpGet、tcpSocket
等三种方法进行探针,这里不在陈诉。
首先,如果没有定义就绪探针,那么新创建的pod就会立即加入到service的endpoint列表,如果容器里的业务程序需要很长时间才能开始处理传入的链接,而此时service又将客户端连接转发给了该pod,那么客户端就会看到“连接被拒绝”等类型的错误,所以,为每个容器定义一个就绪探针是很有必要的,即使是定义一个简单的http get 探针。
在生产环境中,一般会让开发提供容器的健康检查接口,在spring boot已经有原生的健康检查接口,所有然开发定义健康检查接口后我们再使用http get方法来定义我们的探针。