K8s实践练习2_健康检查的探针

官网链接:配置存活、就绪和启动探针

前置准备

搭建好一套k8s集群,可以参考我写的这篇教程:搭建k8s集群

k8s官方的镜像站在国内是拉不下来的,有几种方法解决:

  1. 在拉取镜像的虚拟机/服务器上科学上网
  2. 配置k8s的镜像源,目前国内只有阿里云支持改版后的k8s镜像源(registry.k8s.io)。
  3. 需要拉取镜像的时候,指定拉取策略为本地拉取(imagePullPolicy:Never),每次需要拉取镜像前都手动拉取/上传一份镜像到服务器上再导入镜像

这里给出阿里云镜像源的配置教程: 旧版的k8s直接修改/etc/containerd/config.toml里的mirror信息,添加上阿里云的镜像站就行。但是新版的不支持inline或者说暂时兼容,未来不支持。所以这里就只给出新版k8s镜像源配置教程。

修改/etc/containerd/config.yaml,填入下列信息(如果你已经有了config.yaml且这个配置文件是从containerd默认配置里生成的,那直接备份,然后使用下面的内容)。sudo vim /etc/containerd/config.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version = 2

[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
    runtime_type = "io.containerd.runc.v2"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
      SystemdCgroup = true
  [plugins."io.containerd.grpc.v1.cri".registry]
    config_path = "/etc/containerd/certs.d"

创建/etc/containerd/certs.d目录,在这个目录填入docker.io和registry.k8s.io的镜像源。

注意:k8s里修改镜像源之后,使用kubectl describe pod <pod_name> 查看时还是显示的docker.ioregistry.k8s.io。配置镜像源只物理修改从哪里修改,不改镜像拉取的逻辑源。所以改好镜像源之后也不太好验证成功,随便拉个镜像sudo crictl pull nginx:1.14.2,能拉下来就是成了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Docker Hub 加速
sudo mkdir -p /etc/containerd/certs.d/docker.io
sudo tee /etc/containerd/certs.d/docker.io/hosts.toml << 'EOF'
server = "https://registry-1.docker.io"

[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF

# K8s 镜像加速
sudo mkdir -p /etc/containerd/certs.d/registry.k8s.io
sudo tee /etc/containerd/certs.d/registry.k8s.io/hosts.toml << 'EOF'
server = "https://registry.k8s.io"

[host."https://registry.cn-hangzhou.aliyuncs.com/v2/google_containers"]
  capabilities = ["pull", "resolve"]
  override_path = true
EOF

到这里,镜像源就配置好了,如果不出意外,文件目录应该是下面这样:

1
2
3
4
5
6
7
rust@k8s1:/etc/containerd$ ll
总计 28
drwxr-xr-x   3 root root  4096  2月  4 10:31 ./
drwxr-xr-x 144 root root 12288  2月  2 17:01 ../
drwxr-xr-x   4 root root  4096  2月  2 16:44 certs.d/
-rw-r--r--   1 root root   423  2月  2 19:02 config.toml
-rw-r--r--   1 root root   886 12月 19 02:48 config.toml.dpkg-dist

修改完配置文件后需要重启containerd:

1
2
sudo systemctl restart containerd
sudo systemctl status containerd

探针原理简介

k8s用各种类型的探针(存活态探针、就绪态探针、启动态探针)来检测容器状态,根据容器的状态执行对应的策略,例如重启(restartPolicy)、从EndpointSlice中删除pod的ip地址(使该pod不再对外提供服务)。

k8s探针有如下四种检测方式:

  1. exec:在容器内执行指定命令。如果命令推出时返回码为0,则认为诊断成功
  2. grpc:使用grpc执行一个远程调用。目标应该实现grpc健康检查。如果响应状态是serving,则认为诊断成功。
  3. httpGet:对容器的IP地址上指定端口和路径执行HTTP Get请求。如果响应的状态码处于[200,400)区间,则认为诊断是成功的
  4. tcpSocket:对容器的IP地址上的指定端口执行TCP检查。如果端口打开,则认为诊断成功。如果远程系统(容器)在打开连接后立刻将其关闭,也算作是健康的。

k8s探针根据检测状态或者时机可以分为三种类型:

  1. livenessProbe(存活态探针):指示容器是否正在运行。如果存活态探针检测失败,则kubelet会杀死容器,并且容器将根据其重启策略决定未来。
  2. readinessProbe(就绪态探针):指示容器是否准备好为请求提供服务。如果就绪态探测失败,EndpointSlice控制器将从与该Pod匹配的所有Service的EndpointSlice中删除该Pod的IP地址。初始延迟之前的就绪态的状态默认为Failure。
  3. startupProbe(启动态探针):指示容器中的应用是否已经启动。如果提供了启动探针,则直到此探针成功为止所有其他探针都会被禁用。如果启动探测失败,kubelet将杀死容器,而容器将依照其重启策略进行重启。

关于探针的配置字段:

  • initialDelaySeconds:容器启动后,等待多少秒才开始第一次探针检查。给应用流出初始化时间(比如加载配置、连接数据库等),避免在还没准备好时就被探针误判为失败。
  • periodSeconds:每隔多少秒进行一次探针检查,默认10s。
  • failureThreshold:探针连续失败多少次后,才将容器标记为不健康。对存活态探针来说会触发重启,对就绪态探针来说会从Service移除。第一次失败就算一次,不是“容忍失败次数”,默认为3。
  • timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
  • successThreshold:探针在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
  • terminationGracePeriodSeconds:为 kubelet 配置从为失败的容器触发终止操作到强制容器运行时停止该容器之前等待的宽限时长。 默认值是继承 Pod 级别的 terminationGracePeriodSeconds 值(如果不设置则为 30 秒),最小值为 1。
  • 补充:initialDelaySeconds+failureThreshold*periodSeconds:从**容器启动到被判断为“不健康”**所需的最短时间。(在探针连续失败的情况下)

存活探针练习

exec

下面的yaml定义一个执行命令的存活态探针。pod里只有一个容器,periodSeconds 字段指定了 kubelet 应该每 5 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 5 秒。 kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。

当容器启动时,容器执行命令:/bin/sh -c "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"

这个容器生命的前 30 秒,/tmp/healthy 文件是存在的。 所以在这最开始的 30 秒内,执行命令 cat /tmp/healthy 会返回成功代码。 30 秒之后,执行命令 cat /tmp/healthy 就会返回失败代码。存活态探针检测失败,就会触发重启。

1
2
vim exec-liveness.yaml
kubectl apply -f exec-liveness.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: busybox:1.27.2
    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

查看这个pod的信息,发现前30s内正常,35s时存活态探针检测到不健康,将重启容器。再查看pod,发现liveness-exec已经重启过(restarts次数不为0,失败的容器恢复为运行状态,RESTARTS 计数器就会增加 1)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  63s                default-scheduler  Successfully assigned default/liveness-exec to k8s2
  Normal   Pulled     63s                kubelet            Container image "busybox:1.27.2" already present on machine
  Normal   Created    63s                kubelet            Created container: liveness
  Normal   Started    63s                kubelet            Started container liveness
  Warning  Unhealthy  18s (x3 over 28s)  kubelet            Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    18s                kubelet            Container liveness failed liveness probe, will be restarted
rust@k8s1:~$ kubectl get pods
NAME            READY   STATUS    RESTARTS      AGE
liveness-exec   1/1     Running   2 (45s ago)   3m16s

http

k8s官网的agnhost镜像(特别是 e2e-test-images 这个仓库)在阿里云等国内镜像源上的同步存在问题。这里使用nginx镜像和一段启停脚本来模拟。

0-15 秒,脚本在后台启动了 Nginx,index.html 存在,HTTP 探测返回 200。状态为 Running,RESTARTS 为 0。 15 秒左右,脚本执行了rm命令,删除了网页文件。kubelet每3秒探测一次,发现 /index.html 返回 404。kubelet 连续失败几次后,判定容器不健康,杀死容器。最终RESTARTS 变成了 1,状态变回 Running,然后循环往复

1
2
vim http-liveness.yaml
kubectl apply -f http-liveness.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: nginx:1.16.1
    # 这里我们覆盖 Nginx 的默认启动命令,用脚本来模拟 "先健康,后故障" 的过程
    command: ["/bin/sh", "-c"]
    args:
    - |
      # 1. 启动 Nginx (放在后台运行)
      nginx -g "daemon off;" &
      
      # 2. 等待 10 秒 (这期间 /index.html 是正常的,探测会成功)
      echo "App is running healthy..."
      sleep 10
      
      # 3. 10 秒后,删除被探测的文件,制造 HTTP 404 错误
      echo "Simulating failure now..."
      rm /usr/share/nginx/html/index.html
      
      # 4. 保持容器主进程不退出 (挂起),否则 k8s 会认为容器 Crash 了,而不是因为 Liveness 失败
      # 我们只需要等待 Kubelet 的探针发现 404 并杀死我们即可
      sleep infinity
            
    livenessProbe:
      httpGet:
        path: /index.html
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 3

kubectl describe pod liveness-http,查看事件,能看见探针检测失败,容器重启。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  2m44s                default-scheduler  Successfully assigned default/liveness-http to k8s3
  Normal   Killing    50s (x3 over 2m26s)  kubelet            Container liveness failed liveness probe, will be restarted
  Normal   Pulled     20s (x4 over 2m44s)  kubelet            Container image "nginx:1.16.1" already present on machine
  Normal   Created    20s (x4 over 2m44s)  kubelet            Created container: liveness
  Normal   Started    20s (x4 over 2m44s)  kubelet            Started container liveness
  Warning  Unhealthy  8s (x10 over 2m32s)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 404

rust@k8s1:~$ kubectl get pods
NAME            READY   STATUS             RESTARTS        AGE
liveness-exec   0/1     CrashLoopBackOff   11 (4m8s ago)   30m
liveness-http   1/1     Running            4 (11s ago)     3m23s

TCP

k8s官网的goproxy国内拉不下来,依旧用nginx模拟。TCP探针的原理是kubelet 尝试连接容器的指定端口。如果连接成功,健康;如果连接被拒绝(端口关闭),不健康。

模拟逻辑:启动 Nginx(打开 80 端口) -> 10秒后停止 Nginx(关闭 80 端口) -> 探测失败 -> 重启。

1
2
vim tcp-liveness.yaml
kubectl apply -f tcp-liveness.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Pod
metadata:
  name: liveness-tcp
  labels:
    test: liveness
spec:
  containers:
  - name: liveness
    image: nginx:1.16.1
    command: ["/bin/sh", "-c"]
    args:
    - |
      # 1. 启动 Nginx (端口 80 打开)
      nginx -g "daemon off;" &
      
      # 2. 等待 10 秒,此时 tcpSocket 探测会成功
      sleep 10
      
      # 3. 停止 Nginx (端口 80 关闭),模拟服务挂掉
      nginx -s stop
      
      # 4. 保持容器主进程存活,等待 Kubelet 判定失败并重启我们
      sleep infinity
            
    livenessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 3
1
2
3
4
5
6
7
8
Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  15s   default-scheduler  Successfully assigned default/liveness-tcp to k8s2
  Normal   Pulled     15s   kubelet            Container image "nginx:1.16.1" already present on machine
  Normal   Created    15s   kubelet            Created container: liveness
  Normal   Started    14s   kubelet            Started container liveness
  Warning  Unhealthy  3s    kubelet            Liveness probe failed: dial tcp 172.16.109.84:80: connect: connection refused

grpc

创建grpc-liveness.yaml,发起一个远程调用,调用成功,状态正常。

1
2
vim grpc-liveness.yaml
kubectl apply -f grpc-liveness.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
  name: liveness-grpc
spec:
  containers:
  - name: etcd
    image: registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.4.13-0
    command: [ "/usr/local/bin/etcd", "--data-dir",  "/var/lib/etcd", "--listen-client-urls", "http://0.0.0.0:2379", "--advertise-client-urls", "http://127.0.0.1:2379", "--log-level", "debug"]
    ports:
    - containerPort: 2379
    livenessProbe:
      grpc:
        port: 2379
      initialDelaySeconds: 10

grpc不像前面三种那样容易制造错误,不过还是能从pod事件中看出探针运行情况。查看pod情况,在liveness一栏能看见:

1
2
rust@k8s1:~$ kubectl describe pod liveness-grpc|grep Liveness:
Liveness:       grpc <pod>:2379  delay=10s timeout=1s period=10s #success=1 #failure=3

补充

对于 HTTP 和 TCP 存活检测可以使用命名的 port (gRPC 探针不支持使用命名端口)。

例如:

1
2
3
4
5
6
7
8
ports:
- name: liveness-port
  containerPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port

有时候,会有一些现有的应用在启动时需要较长的初始化时间。可以将 failureThreshold * periodSeconds 参数设置为足够长的时间来应对最坏情况下的启动时间。

这样,前面的例子就变成了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ports:
- name: liveness-port
  containerPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 1
  periodSeconds: 10

startupProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 30
  periodSeconds: 10

就绪探针练习

就绪探针的配置和存活探针的配置相似。 唯一区别就是要使用 readinessProbe 字段,而不是 livenessProbe 字段。

就绪探针在容器的整个生命周期中保持运行状态。

存活探针与就绪性探针相互间不等待对方成功。 如果要在执行就绪性探针之前等待,应该使用 initialDelaySeconds 或 startupProbe。

网站总访客数:Loading
网站总访问量:Loading
使用 Hugo 构建
主题 StackJimmy 设计