- 容器默认的健康检查
- 健康检查的种类
- 常用参数配置
- 存活探测器
- 命令检查
- http GET检查
- tcp检查
- 就绪探测器
- 启动探测器
- 延伸
以下这篇文章是我总结和理解的,具体的可参考官网健康检查
容器默认的健康检查
容器的主进程为pid为1
的进程,此进程为 Dockerfile 的 CMD 或 ENTRYPOINT 指定。如果进程退出时返回码非零,则认为容器发生故障,Kubernetes 就会根据 restartPolicy 重启容器。
容器的默认健康检查就是:只要主进程存在,则健康检查就属于通过,容器就处于running
状态,容器则不会退出。
以某个容器为例,查看所有进程:
[root@nginx-php7-backup-5b5644444f-hsrmt /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 07:22 ? 00:00:00 /bin/sh /entrypoint.sh
root 7 1 0 07:22 ? 00:00:00 php-fpm: master process
我们尝试kill pid为1的主进程,发现kill不掉
[root@nginx-php7-backup-5b5644444f-hsrmt /]# kill 1
[root@nginx-php7-backup-5b5644444f-hsrmt /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 07:29 ? 00:00:00 /bin/sh /entrypoint.sh
root 7 1 0 07:29 ? 00:00:00 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
看到pid为1的进程cmd为entrypoint.sh,然后我们查看下/entrypoint.sh文件,看见有一个php-fpm进程属于守护进程,还有一个nginx的前台进程,所以nginx的前台进程的就是该容器的主进程
。
......
# /usr/local/php/sbin/php-fpm -F
/usr/local/php/sbin/php-fpm -D
# /usr/local/nginx/sbin/nginx -g
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
然后查看nginx相关的进程,pid为10的进程正是/entrypoint.sh文件中执行的进程,10进程也就是主进程pid了。
[root@nginx-php7-585b7dc7d9-259rs /]# ps aux | grep nginx
root 10 0.0 0.0 46192 3528 ? S 07:20 0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
www 11 0.0 0.5 67004 22612 ? S 07:20 0:00 nginx: worker process
root 27 0.0 0.0 9092 664 pts/0 S+ 07:59 0:00 grep --color=auto nginx
也就是说只要pid为10的进程一直存在,则该pod的健康检查就是通过,只有该进程被kill或者因为某种原因被kill后且进程退出时返回码非零, 然后根据pod的 restartPolicy 策略决定是否重启。
如果php-fpm的进程被kill,而nginx的进程仍在,针对这种情况,容器认为主进程还在,健康检查也就通过,所以并不会重启容器。如果这是一个php的容器,此时访问php会报502。。
综上所述,容器默认的健康检查有时候并满足不了我们的需求,所以我们引入了下面的健康检查策略。
健康检查的种类
kubelet 使用存活探测器
来知道什么时候要重启容器。 例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。 这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。
kubelet 使用就绪探测器
可以知道容器什么时候准备好了并可以开始接受请求流量, 然后将该pod加入到server的endpoint列表中,同理知道容器什么时候没有准备好,此时就需要将该pod从endpoint列表中剔除,不在接受流量。 这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。
kubelet 使用启动探测器
可以知道应用程序容器什么时候启动了。 如果配置了这类探测器,就可以控制容器在启动成功后再进行存活性和就绪检查
, 确保这些存活、就绪探测器不会影响应用程序的启动。 这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
常用参数配置
很多配置字段,可以使用这些字段精确的控制存活和就绪检测的行为:
-
initialDelaySeconds:
容器启动后要等待多少秒
后开始进行存活探测器检测或者就绪探测器检查,启动探测器不使用该参数。默认是 0 秒,最小值是 0。下面这幅图的意思是,容器的status处于running状态后,且正在健康检查。其中分母表示该pod中的容器数,分子表示已经通过健康检查的容器数。
[root@master ~]# kubectl get pod -n laravel
NAME READY STATUS RESTARTS AGE
nginx-php7-ingress-787975547b-sqmf8 0/1 Running 0 23m
- periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。
- timeoutSeconds:
探测后等待响应的时间,如果在时间内得到响应则认为是成功,如果未响应则认为是失败,用于计数下面的successThreshold 和failureThreshold
。默认值是 1 秒。最小值是 1。 - successThreshold:探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
- failureThreshold:当探测失败时,Kubernetes 的重试次数。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
可以在 httpGet 上配置额外的字段:
- host:连接使用的主机名,默认是 Pod 的 IP。也可以在 httpHeaders中设置 “Host” 来代替。
- scheme :用于设置连接主机的方式(HTTP 还是 HTTPS)。默认是 HTTP。
- path:访问 HTTP 服务的路径。
- httpHeaders:请求中自定义的 HTTP 头。HTTP 头字段允许重复。
- port:访问容器的端口号或者端口名。如果数字必须在 1 ~ 65535 之间。
存活探测器
存活探测器如果没有通过健康检查,将会重启pod
。
存活探测器有以下3中检查方式:
命令检查
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: k8s.gcr.io/busybox
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds:2
successThreshold:1
failureThreshold:3
initialDelaySeconds 字段告诉 kubelet 应该是在容器启动后(处于running状态后) 5 秒执行第一次探测。
periodSeconds 字段指定了 kubelet 应该每 5 秒执行一次存活探测。
timeoutSeconds为2s, 意味着每次探测的响应时间不能超过2秒。如果当前处于是成功状态下且响应超过2秒则计失败次数+1。如果当前处于失败状态下且响应时间未超过2秒则成功次数+1。
successThreshold为1表示成功次数达到1时,表示检查正常。
failureThreshold为3表示,失败此时达到3时,该pod将会被重启。
如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 就会认为该容器是不健康的状态。
http GET检查
此方式肯定GET请求,所以需要注意检测中路由地址为GET请求方式。
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: k8s.gcr.io/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
健康检查的参数和上面的意思相同,不在赘述。
首先要知道,httpGET方式和tcp方式的检查连接都是在pod所造节点上进行连接测试的,所以要特别注意端口的监听地址。
默认使用http检测方式,检查的路由则为 http://host:8080/healthz,需要注意host默认使用的是pod的ip,所以需要保证容器内8080端口监听的地址不是127.0.0.1或者是localhost,监听地址应该为内网地址或者是0.0.0.0。
kubelet将会在pod所在节点
上执行类似cur lhost:8080/healthz的操作,返回的http code为任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。
如果host地址必须为127.0.0.1的话,可以在 httpGet 中的 host 字段中设置,否则 kubelet 默认是给 Pod 的 IP 地址发送探测,大多数情况下,不需要设置host 字段。 这里有个需要设置 host 字段的场景,假设容器监听 127.0.0.1,并且 Pod 的 hostNetwork 字段设置为了 true。那么 httpGet 中的 host 字段应该设置为 127.0.0.1。 可能更常见的情况是如果 Pod 依赖虚拟主机,你不应该设置 host 字段,而是应该在 httpHeaders 中设置 Host,大致如下
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
- name: Host
value: 127.0.0.1
initialDelaySeconds: 3
periodSeconds: 3
或者
livenessProbe:
httpGet:
path: /healthz
port: 8080
host: 127.0.0.1
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
tcp检查
这种方式常用检查4层代理服务,如mysql,redis等。
为什么php项目不使用tcp检查,而使用http检查有如下原因:访问php首先需要监听ngnix的端口,比如80端口,该端口遇到php文件会重定向于php-fpm进程,该进程使用9000端口监听。
需要俩个端口都处于监听状态时,php才可以被访问,如果使用tcp检测方式则只能监听一个端口,故不适合tcp检查方式,使用httpGet的方式则可以解决这问题。
对于一次 TCP 探测,kubelet 在节点
上建立探测连接, 这意味着你不能在 host 参数上配置服务名称,因为 kubelet 不能解析服务名称。
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: k8s.gcr.io/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
具体参数和上面一致,不在赘述。tcp和http方式一样,都是在pod所在的节点上使用podIp进行连接测试,如上面代码,将在节点进行如下测试。
telnet podIp 8080
tcp检查方式也可以设置host参数
livenessProbe:
tcpSocket:
port: 8080
host: 127.0.0.1
initialDelaySeconds: 3
periodSeconds: 3
就绪探测器
如果没有通过健康检查,就绪探测器将会把该pod从service的endpoit列表中剔除,该pod则不会对外提供服务
。
小技巧:就绪检查应该在存活检查之前,也就是说就绪检查的initialDelaySeconds要小于存活检查的initialDelaySeconds。
相反,就绪检查如果在存活检查之后,则有可能,在开始就绪检查时,存活检查还没有成功,还处于重试状态,那么之前的那段时间该pod是可以对外提供服务的,但是该服务是不可用的。
理想的状态是服务可用时pod加入service的endpiont列表,服务不可用时将pod从endpoint列表中删除。
可能到这里你还是对这个探测器有点,接下来我们通过一个例子来说明,这个pod起的是一个php,前面也说了需要一个nginx端口80和php-fpm端口9000来配合使用,php才可以被使用,大致yaml如下,我们配置了livenessProbe。
.......
.......
- name: nginx-php7-backup
image: harbor.maigengduo.com/laravel/nginx-php7:latest
livenessProbe:
httpGet:
path: /api/health
port: 80
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 2
......
......
然后查看下该镜像的entrypoint.sh,查看下主进程,
......
......
# /usr/local/php/sbin/php-fpm -F
/usr/local/php/sbin/php-fpm -D
# /usr/local/nginx/sbin/nginx -g
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
查看进程,发现entrypoint.sh文件中的php-fpm进程pid为7,nginx进程pid为10,也就是说该容器的主进程pid为10。
[root@nginx-php7-backup-565b4f5589-rqcx9 /]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 11688 1372 ? Ss 07:58 0:00 /bin/sh /entrypoint.sh
root 7 0.0 0.1 116604 4428 ? Ss 07:58 0:00 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
www 8 1.5 0.5 129256 20600 ? S 07:58 0:13 php-fpm: pool www
www 9 1.4 0.5 129256 20692 ? S 07:58 0:13 php-fpm: pool www
root 10 0.0 0.0 46192 3532 ? S 07:58 0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
www 11 0.0 0.5 67004 22608 ? S 07:58 0:00 nginx: worker process
www 12 0.0 0.5 67004 22608 ? S 07:58 0:00 nginx: worker process
root 13 0.0 0.0 11828 1920 pts/0 Ss 08:11 0:00 bash
root 26 0.0 0.0 51752 1716 pts/0 R+ 08:12 0:00 ps aux
然后查看下端口的监听状态,发现nginx监听的是80端口,监听地址为0.0.0.0,而php-fpm监听的是9000端口,监听地址为127.0.0.1,看到此处就知道了,如果使用http或者tcp做健康检查时,只有80端口可以当做被监听端口,因为127.0.0.1是容器内部,而进行检查测试都是在节点上进行,所以9000当作是检测端口时,将检测不通过。
[root@nginx-php7-backup-565b4f5589-rqcx9 /]# netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 7/php-fpm: master p
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 10/nginx: master pr
上面做了这么多铺垫,接下来开始说重点。
如果kill掉pid为10的主进程,整个容器将会重启,所以我们选择kill掉pid为7的php-fpm进程,这是livenessProbe检测将会失败,此时用户继续访问php,将会报502 Bad Gateway,这个现象将会一直持续到lpod重启之前,这对于用户来说是个很不友好的现象啊,一直报502。。。
由于service selector的pod的80端口,只要该80端口一直存在,service就会一直将流量转发到该pod上。
此时我们的就绪探测器就派上用场了,它的作用就是既不想杀死应用程序,也不想给它发送请求,只是单纯的将该pod从service的endpoint列表中剔除,当就绪探测器检测成功状态时,在将该pod重新加入到endpoint中。
所以重新定义yaml文件如下
- name: nginx-php7-ingress
image: harbor.maigengduo.com/laravel/nginx-php7:latest
livenessProbe:
httpGet:
path: /api/health
port: 80
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 2
readinessProbe:
httpGet:
path: /api/health
port: 80
initialDelaySeconds: 5
periodSeconds: 10
这样的话大概在10s之后,就绪探测器就会将该pod剔除出endpoint列表,将会对该pod停止流量转发。
启动探测器
有时候,会有一些应用程序在启动时需要较多的初始化时间,为例避免它们在启动运行之前被存活探测器杀掉,可以使用启动探测器
来实现这个功能。
启动探测器检查成功之后才会进行存活检查和就绪检查
,这点需要注意。
ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 1
periodSeconds: 10
startupProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 30
periodSeconds: 10
如果该应用程序员启动时间很长,设置存活探测参数是要技巧的,可以通过设置 failureThreshold * periodSeconds 参数来保证有足够长的时间应对糟糕情况下的启动时间。
该应用程序将会有最多 5 分钟(30 * 10 = 300s) 的时间来完成它的启动。 一旦启动探测成功一次,存活探测任务就会接管对容器的探测。如果启动探测一直没有成功,容器会在 300 秒后被杀死,并且根据 restartPolicy 来设置 Pod 状态。
延伸
设置了一个container的存活探针和就绪探针,且保证监听端口正常,但是该pod一直在restart,describe pod查看如下
Normal Started 2m8s kubelet Started container nginx-php7-backup
Warning Unhealthy 53s kubelet Readiness probe failed: HTTP probe failed with statuscode: 429
Warning Unhealthy 52s kubelet Liveness probe failed: HTTP probe failed with statuscode: 429
看见存活探针和就绪探针都失败了,且返回一个状态码为429
,后经查阅资料,返回该http code表示请求过于频繁。。。。。也就是设置的periodSeconds参数值太小了。。。
设置太小会频繁请求,会造成一些无谓的资源消耗,设置太大,又会对故障发现不及时,所以这个值在10s左右差不多。