k8s健康检查

  • 容器默认的健康检查
  • 健康检查的种类
    • 常用参数配置
    • 存活探测器
      • 命令检查
      • 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左右差不多。

你可能感兴趣的:(k8s健康检查)