Liveness, Readiness与Startup Probes

一、探针的种类

  • livenessProbe:健康状态检查,周期性检查服务是否存活,检查结果失败,将重启容器;

  • readinessProbe:可用性检查,周期性检查服务是否可用,不可用将从service的endpoints中移除;

  • startupProbe:启动探针,首次初始化时需要额外启动时间的应用程序,超过设定的启动时间,将被杀死;

kubelet使用活跃度探针知道什么时候重新启动的容器。例如,活动性探针可能会陷入僵局,而应用程序正在运行,但无法取得进展。在这种状态下重新启动容器可以帮助使应用程序尽管存在错误也更可用。

Kubelet使用就绪性探测器来了解何时Container准备开始接受流量。当Pod的所有容器都准备就绪时,即视为准备就绪。此信号的一种用法是控制将哪些Pod用作服务的后端。当Pod尚未就绪时,会将其从服务负载平衡器中删除。

kubelet使用启动探针来了解何时启动Container应用程序。如果配置了这样的探针,它将禁用活动性和就绪性检查,直到成功为止,以确保这些探针不会干扰应用程序的启动。这可用于对启动缓慢的容器进行活动检查,避免它们在启动和运行之前被kubelet杀死。

二、探针的检测方法

  • exec:执行一段命令;

  • httpGet:检测某个 http 请求的返回状态码;

  • tcpSocket:测试某个端口是否能够连接;

2.1、探针常用参数

  • initialDelaySeconds:启动容器后,启动活动或就绪探针之前的秒数。

  • periodSeconds:执行探测的频率(以秒为单位)。默认为10秒。最小值为1。

  • timeoutSeconds:探测超时的秒数。默认为1秒。最小值为1。

  • successThreshold:探测失败后,连续最小成功探测为成功。默认值为1。为保持活力,必须为1。最小值为1。

  • failureThreshold:当Pod启动并且探测失败时,Kubernetes会尝试尝试failureThreshold多次,然后放弃。放弃活动探针意味着重新启动容器。如果准备就绪,则将Pod标记为“未就绪”。默认值为3。最小值为1。

2.2、exec的使用

# vim liveness_exec.yml
  containers:
  - name: liveness
    image: harbor.ui.com/library/busybox:latest
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5  # 执行第一次探测之前应等待时间,生产环境应该设长一点
      periodSeconds: 5        # 没5秒执行一次活动性探测

说明:

在容器寿命的前30秒中,有一个/tmp/healthy文件。因此,在前30秒内,该命令cat /tmp/healthy将返回成功代码。30秒后,cat /tmp/healthy返回失败代码,则为检测不健康,执行杀死容器并重新创建

查看30秒是否判定不健康:

# kubectl describe pod liveness
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  51s               default-scheduler  Successfully assigned default/liveness-exec to node02
  Normal   Pulling    50s               kubelet, node02    Pulling image "harbor.ui.com/library/busybox:latest"
  Normal   Pulled     50s               kubelet, node02    Successfully pulled image "harbor.ui.com/library/busybox:latest"
  Normal   Created    50s               kubelet, node02    Created container liveness
  Normal   Started    49s               kubelet, node02    Started container liveness
  Warning  Unhealthy  6s (x3 over 16s)  kubelet, node02    Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    6s                kubelet, node02    Container liveness failed liveness probe, will be restarted

查看是否创建新的pod:

# kubectl describe pod liveness
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  2m50s                default-scheduler  Successfully assigned default/liveness-exec to node02
  Warning  Unhealthy  50s (x6 over 2m15s)  kubelet, node02    Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    50s (x2 over 2m5s)   kubelet, node02    Container liveness failed liveness probe, will be restarted
  Normal   Pulling    20s (x3 over 2m49s)  kubelet, node02    Pulling image "harbor.ui.com/library/busybox:latest"
  Normal   Pulled     20s (x3 over 2m49s)  kubelet, node02    Successfully pulled image "harbor.ui.com/library/busybox:latest"
  Normal   Created    20s (x3 over 2m49s)  kubelet, node02    Created container liveness
  Normal   Started    19s (x3 over 2m48s)  kubelet, node02    Started container liveness

2.3、httpGet的使用

# vim liveness_httpGet.yml
apiVersion: v1
kind: Pod
metadata:
  name: httpget
spec:
  containers:
    - name: nginx
      image: harbor.ui.com/library/nginx:latest
      ports:
        - containerPort: 80
      livenessProbe:
        httpGet:
          path: /index.html
          port: 80
        initialDelaySeconds: 3
        periodSeconds: 3

说明:

为了执行探测,kubelet将HTTP GET请求发送到在Container中运行并在端口80上侦听的服务器。如果服务器/index.html路径的处理程序返回成功代码,则kubelet认为Container处于活动状态且运行状况良好。如果处理程序返回失败代码,则kubelet将杀死Container并重新启动它

删除index.html文件:

# kubectl exec -it httpget -- rm -rf /usr/share/nginx/html/index.html

查看是否不健康:

# kubectl describe pod httpget
Events:
  Type     Reason     Age                 From               Message
  ----     ------     ----                ----               -------
  Normal   Scheduled  7m44s               default-scheduler  Successfully assigned default/httpget to node02
  Normal   Pulling    2s (x2 over 7m43s)  kubelet, node02    Pulling image "harbor.ui.com/library/nginx:latest"
  Normal   Pulled     2s (x2 over 7m42s)  kubelet, node02    Successfully pulled image "harbor.ui.com/library/nginx:latest"
  Normal   Created    2s (x2 over 7m42s)  kubelet, node02    Created container nginx
  Warning  Unhealthy  2s (x3 over 8s)     kubelet, node02    Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing    2s                  kubelet, node02    Container nginx failed liveness probe, will be restarted
  Normal   Started    1s (x2 over 7m40s)  kubelet, node02    Started container nginx

2.4、tcpSocket的使用

# vim liveness_tcpSocket.yml
apiVersion: v1
kind: Pod
metadata:
  name: tcpsocket
spec:
  containers:
    - name: nginx
      image: harbor.ui.com/library/nginx:latest
      ports:
        - containerPort: 80
      readinessProbe:
        tcpSocket:
          port: 81
        initialDelaySeconds: 5
        periodSeconds: 10
      livenessProbe:
        tcpSocket:
          port: 81  
        initialDelaySeconds: 15  
        periodSeconds: 20

说明:

TCP检查的配置与HTTP检查非常相似。此示例同时使用了就绪和活跃度探针(可用性)。容器启动后5秒钟内,kubelet将发送第一个就绪探测器。这将尝试连接到nginx端口81上的容器。如果探测成功,则容器将标记为就绪。kubelet将继续每10秒运行一次此检查。

除了就绪探针之外,此配置还包括活动探针。容器启动后15秒钟,kubelet将运行第一个活动探针。就像就绪探针一样,这将尝试nginx在端口81上连接到容器。如果活动探针失败,则将重新启动容器;

# kubectl describe pod tcpsocket
Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  19s   default-scheduler  Successfully assigned default/tcpsocket to node02
  Normal   Pulling    18s   kubelet, node02    Pulling image "harbor.ui.com/library/nginx:latest"
  Normal   Pulled     18s   kubelet, node02    Successfully pulled image "harbor.ui.com/library/nginx:latest"
  Normal   Created    18s   kubelet, node02    Created container nginx
  Normal   Started    17s   kubelet, node02    Started container nginx
  Warning  Unhealthy  9s    kubelet, node02    Readiness probe failed: dial tcp 10.244.3.46:81: connect: connection refused
  Warning  Unhealthy  2s    kubelet, node02    Liveness probe failed: dial tcp 10.244.3.46:81: connect: connection refused

Liveness与Readiness都是K8S的HealthCheck机制,Liveness探测是重启容器,而Readiness探测则是将容器设置为不可用,不让其再接受Service转发的请求。

Liveness与Readiness是独立执行的,二者无依赖,可以单独使用也可以同时使用。

三、Health Check在K8S中的应用

3.1、在Scale Up中的应用

对于多副本应用,当执行ScaleUp操作时,新的副本会作为后端服务加入到Service的负载均衡列表中。但是,很多时候应用的启动都需要一定的时间做准备(比如加载缓存、连接数据库等等),这时我们可以通过Readiness探测判断容器是否真正就绪,从而避免将请求发送到还未真正就绪的后端服务。

下面一个示例YAML配置文件定义了Readiness探测,重点关注readinessProbe部分:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: edc-webapi-deployment
  namespace: ns
spec:
  replicas: 2
  selector:
    matchLabels:
      name: edc-webapi
  template:
    metadata:
      labels:
        name: edc-webapi
    spec:
      containers:
      - name: edc-webapi-container
        image: edisonsaonian/k8s-demo:1.2
        ports:
        - containerPort: 80
        imagePullPolicy: IfNotPresent
        readinessProbe:
          httpGet:
            scheme: HTTP
            path: /api/health
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5

---

apiVersion: v1
kind: Service
metadata:
  name: edc-webapi-service
  namespace: ns
spec:
  type: NodePort
  ports:
    - nodePort: 31000 
      port: 8080
      targetPort: 80
  selector:
    name: edc-webapi
对于readinessProbe部分:

(1)schema指定了协议,这里是HTTP协议,也可以是HTTPS协议;

(2)path指定访问路径,这里是我们自定义的一个Controller中的接口:简单地返回一个状态码为200的响应;

[Produces("application/json")]
    [Route("api/Health")]
    public class HealthController : Controller
    {
        [HttpGet]
        public IActionResult Get() => Ok("ok");
    }

(3)port指定端口,这里是容器的端口80;

(4)initialDelaySeconds和periodSeconds指定了容器启动10秒之后开始探测,然后每隔5秒执行探测,如果发生3次以上探测失败,则该容器会从Service的负载均衡中移除,直到下次探测成功后才会重新加入。

3.2、在Rolling Update中的应用

假设现在有一个正常运行的多副本应用,我们要对其进行滚动更新即Rolling Update,K8S会逐步用新Pod替换旧Pod,结果就有可能发生这样的一个场景:当所有旧副本被替换之后,而新的Pod由于人为配置错误一直无法启动,因此整个应用将无法处理请求,无法对外提供服务,后果很严重!!!

因此,Readiness探测还提供了用于避免滚动更新中出现这种情况的一些解决办法,比如maxSurge和maxUnavailable两个参数,用来控制副本替换的数量。

继续以上面的YAML配置文件为例,重点关注strategy部分:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: edc-webapi-deployment
  namespace: ns
spec:
  strategy:
    rollingupdate:
      maxSurge: 25%
      maxUnavailable: 25%
  replicas: 10
  selector:
    matchLabels:
      name: edc-webapi
  template:
    metadata:
      labels:
        name: edc-webapi
    spec:
      containers:
      - name: edc-webapi-container
        image: edisonsaonian/k8s-demo:1.2
        ports:
        - containerPort: 80
        imagePullPolicy: IfNotPresent
        readinessProbe:
          httpGet:
            scheme: HTTP
            path: /api/health
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5

---

apiVersion: v1
kind: Service
metadata:
  name: edc-webapi-service
  namespace: ns
spec:
  type: NodePort
  ports:
    - nodePort: 31000 
      port: 8080
      targetPort: 80
  selector:
    name: edc-webapi

(1)maxSurge : 25% => 控制滚动更新过程中副本总数超过预期(这里预期是10个副本 replicas: 10)的上限,可以是数值也可以是百分比,然后向上取整。这里写的百分比,默认值是25%;

如果预期副本数为10,那么副本总数的最大值为RoundUp(10 + 10 * 25%)=13个。

(2)maxUnavailable : 25% => 控制滚动更新过程中不可用的副本(这里预期是10个副本 replicas: 10)占预期的最大比例,可以是数值也可以是百分比,然后向下取整,同样地默认值也是25%;

如果预期副本总数为10,那么可用的副本数至少要为10-roundDown(10 * 25%)=10-2=8个。

综上看来,maxSurge的值越大,初始创建的新副本数量就越多;maxUnavaliable值越大,初始销毁的旧副本数量就越多;