再战 k8s:Pod Volume存储卷健康检查

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再战 k8s:Pod Volume存储卷健康检查相关的知识,希望对你有一定的参考价值。

文章目录

Volume

我们经常会说:容器和 Pod 是短暂的。
其含义是它们的生命周期可能很短,会被频繁地销毁和创建。容器销毁时,保存在容器内部文件系统中的数据都会被清除。

为了持久化保存容器的数据,可以使用 Kubernetes Volume。

Volume 的生命周期独立于容器,Pod 中的容器可能被销毁和重建,但 Volume 会被保留。

本质上,Kubernetes Volume 是一个目录,这一点与 Docker Volume 类似。当 Volume 被 mount 到 Pod,Pod 中的所有容器都可以访问这个 Volume。Kubernetes Volume 也支持多种 backend 类型,完整列表可参考 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

Volume 提供了对各种 backend 的抽象,容器在使用 Volume 读写数据的时候不需要关心数据到底是存放在本地节点的文件系统中呢还是云硬盘上。对它来说,所有类型的 Volume 都只是一个目录。

我们将从最简单的 emptyDir 开始学习 Kubernetes Volume。


为什么需要存储卷

容器部署过程中一般有以下三种数据:
• 启动时需要的初始数据,例如配置文件
• 启动过程中产生的临时数据,该临时数据需要多个容器间共享
• 启动过程中产生的持久化数据,例如mysql的data目录


数据卷概述

• Kubernetes中的Volume提供了在容器中挂载外部存储的能力
• Pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后才可以使用相应的Volume


数据卷类型大致分类

• 本地(hostPath,emptyDir等)
• 网络(NFS,Ceph,GlusterFS等)
• 公有云(AWS EBS等)
• K8S资源(configmap,secret等)

Kubernetes支持存储卷类型中,emptyDir存储卷的生命周期与其所属的Pod对象相同,它无法脱离Pod对象的生命周期提供数据存储功能,因此emptyDir通常仅用于数据缓存或临时存储。不过基于emptyDir构建的gitRepo存储卷可以在Pod对象的生命周期起始时从响应的Git仓库中复制相应的数据文件到底层的emptyDir中,从而使得它具有了一定意义上的持久性。


emptyDir存储卷

Kubernetes支持存储卷类型中,emptyDir存储卷的生命周期与其所属的Pod对象相同,它无法脱离Pod对象的生命周期提供数据存储功能,因此emptyDir通常仅用于数据缓存或临时存储。不过基于emptyDir构建的gitRepo存储卷可以在Pod对象的生命周期起始时从响应的Git仓库中复制相应的数据文件到底层的emptyDir中,从而使得它具有了一定意义上的持久性。

emptyDir存储卷是Pod对象生命周期中的一个临时目录,类似于Docker上的docker挂载卷,在Pod对象启动时即被创建,而在Pod对象被移除时会被一并删除。不具有持久能力的emptyDir存储卷只能用于某些特殊场景中,例如,用一Pod内的多个容器间文件的共享,或者作为容器数据的临时存储目录用于数据缓存系统等。

emptyDir存储卷则定义于.spec.volumes.emptyDir嵌套字段中,可用字段主要包含两个,具体如下:

•medium:此目录所在存储介质的类型,可取值为default或Memory,默认为default,表示使用节点的默认存储介质:Memory 表示基于RAM的临时文件系统tmpfs,空间受于内存,但性能非常好,通常用于为容器中的应用提供缓存空间

•sizeLimit:当前存储卷的空间限额,默认值为 nil,表示不限制;不过在 medium 字段为 Memory时,建议定义此限额。

Pod 中的所有容器都可以共享 Volume,它们可以指定各自的 mount 路径。下面通过例子来实践 emptyDir,配置文件如下:

[root@k8s-master ~]# cat emptyDir.yml 
apiVersion: v1
kind: Pod
metadata: 
  name: producer-consumer
spec:
  containers:
    - name: producer
      image: busybox
      volumeMounts: 
      - name: shared-volume
        mountPath: /producer_dir
      args:
      - /bin/sh
      - -c
      - echo "hello this is producer" > /producer_dir/hello ; sleep 3600
    - name: consumer
      image: busybox
      volumeMounts:
      - name: shared-volume
        mountPath: /consumer_dir
      args:
      - /bin/sh
      - -c
      - cat  /consumer_dir/hello ; sleep 3600
  volumes:
  - name: shared-volume
    emptyDir: 
 
[root@k8s-master ~]# kubectl apply -f emptyDir.yml 
pod/producer-consumer created

这里我们模拟了一个 producer-consumer 场景。Pod 有两个容器 producer和 consumer,它们共享一个 Volume。producer 负责往 Volume 中写数据,consumer 则是从 Volume 读取数据。

文件最底部 volumes 定义了一个 emptyDir 类型的 Volume shared-volume。
 producer 容器将 shared-volume mount 到 /producer_dir 目录。
producer 通过 echo 将数据写到文件 hello 里。
consumer 容器将 shared-volume mount 到 /consumer_dir 目录。
consumer 通过 cat 从文件 hello 读数据。

[root@k8s-master ~]# kubectl logs producer-consumer consumer
hello this is producer

kubectl logs 显示容器 consumer 成功读到了 producer 写入的数据,验证了两个容器共享 emptyDir Volume。

因为 emptyDir 是 Docker Host 文件系统里的目录,其效果相当于执行了 docker run -v /producer_dir 和 docker run -v /consumer_dir。通过 docker inspect 查看容器的详细配置信息,我们发现两个容器都 mount 了同一个目录:

            "Mounts": [
                
                    "Type": "bind",
                    "Source": "/var/lib/kubelet/pods/620cd011-1d40-47af-8a1a-1beb131e135f/volumes/kubernetes.io~empty-dir/shared-volume",
                    "Destination": "/consumer_dir",
                    "Mode": "Z",
                    "RW": true,
                    "Propagation": "rprivate"
                ,


​     
​                
​                    "Type": "bind",
​                    "Source": "/var/lib/kubelet/pods/620cd011-1d40-47af-8a1a-1beb131e135f/volumes/kubernetes.io~empty-dir/shared-volume",
​                    "Destination": "/producer_dir",
​                    "Mode": "Z",
​                    "RW": true,
​                    "Propagation": "rprivate",

这里"Source" 就是 emptyDir 在 Host 上的真正路径。

emptyDir 是 Host 上创建的临时目录,其优点是能够方便地为 Pod 中的容器提供共享存储,不需要额外的配置。但它不具备持久性,如果 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器需要临时共享存储空间的场景,比如前面的生产者消费者用例。

[root@k8s-master ~]# kubectl describe pod producer-consumer 
Volumes:
  shared-volume:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)

下面是一个使用了emptyDir存储卷的简单示例

1.创建Pod对象配置清单

apiVersion: v1
kind: Pod
metadata:
  name: vol-emptydir-pod
spec:
  volumes:
  - name: html
    emptyDir:  
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  - name: pagegen
    image: alpine
    volumeMounts:
    - name: html
      mountPath: /html
    command: [ "/bin/sh", "-c" ]
    args:                        #定义循环,每10秒向/html/文件中追加写入当前主机名和时间
    - while true; do
        echo $(hostname) $(date) >> /html/index.html;
        sleep 10;
      done

上面示例中定义的存储卷名称为html,挂载于容器nginx的 /usr/share/nginx/html目录,以及容器pagegen的/html目录。容器pagegen每隔10秒向存储卷上的index.html文件中追加一行信息,而容器nginx中的nginx进程则以其站点主页。如下图所示:

2.创建Pod对象

kubectl apply -f vol-emptydir.yaml

3.查看Pod状态 Pod对象的详细信息中会显示存储卷的相关状态,包括其是否创建成功(在Events字段中输出)、相关的类型及参数(在Volumes字段中输出)以及容器中挂载状态等信息(在Containers字段中输出),如下面命令所示:

kubectl describe pods/vol-emptydir-pod
...

4.访问Pod中Nginx pagegen容器每隔10秒向 html/index.html 追加写入信息,Nginx容器挂载的也是此临时存储,所以Nginx的网页文件也是从这里获取。

5.进入容器 以下分别进入Nginx容器以及pagegen容器查看其挂载

通过 -c 来指定容器名称进入指定容器

kubectl exec -it pods/vol-emptydir-pod -c nginx -- /bin/sh
# ls /usr/share/nginx/html
index.html
# head -3 /usr/share/nginx/html/index.html
...

进入pagegen容器

    kubectl exec -it pods/vol-emptydir-pod -c pagegen -- /bin/sh
    / # ls /html/
    index.html
    / # head -3 /html/index.html
...
    / # ps aux
    PID   USER     TIME  COMMAND
        1 root      0:00 /bin/sh -c while true; do echo $(hostname) $(date) >> /html/index.html; sleep 10; done
      286 root      0:00 /bin/sh
      303 root      0:00 sleep 10
      304 root      0:00 ps aux

作为边车 (sidecar)的容器pagegen,其每隔10秒生成一行信息追加到存储卷上的index.html文件中,因此,通过主容器nginx的应用访问到文件内存也会处理不停的变动中。另外,emptyDir存储卷也可以基于RAM创建tmpfs文件系统的存储卷,常用于为容器的应用提高高性能缓存,下面是一个配置示例:

cat vol-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: vol-emptydir-pod
spec:
  volumes:
  - name: html
    emptyDir:
      medium: Memory                #指定临时存储到内存
      sizeLimit: 256Mi              #给予的内存空间大小
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  - name: pagegen
    image: alpine
    volumeMounts:
    - name: html
      mountPath: /html
    command: [ "/bin/sh", "-c" ]
    args:
    - while true; do
        echo $(hostname) $(date) >> /html/index.html;
        sleep 10;
      done

emptyDir卷简单易用,但仅能用于临时存储,另外还存在一些类型的存储卷构建在emptyDir之上,并额外提供了emptyDir没有的功能。

如果还是不懂可以看下面这个例子

定义一个emptyDir存储大小为1G,将其挂载到redis的/data目录中

[root@k8s-master emptydir]# cat emptydir-redis.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-redis
  labels:
    volume: emptydir
  annotations:
    kubernetes.io/storage: emptyDir
spec:
  containers:
  - name: emptydir-redis
    image: redis:latest
    imagePullPolicy: IfNotPresent
    ports:
    - name: redis-6379-port
      protocol: TCP
      containerPort: 6379
    volumeMounts:   #将定义的驱动emptydir-redis挂载到容器的/data目录,通过名字方式关联
    - name: emptydir-redis
      mountPath: /data
  volumes:          #定义一个存储,驱动类型为emptyDir,大小1G
  - name: emptydir-redis
    emptyDir:
      sizeLimit: 1Gi

生成redis pod,并查看describe pod的详情信息

[root@k8s-master emptydir]# kubectl apply -f emptydir-redis.yaml 
pod/emptydir-redis created
[root@k8s-master emptydir]# kubectl describe pod emptydir-redis #执行kubectl describe pods emptydir-redis查看容器的存储挂载信息

向redis中写入数据

[root@k8s-master emptydir]# kubectl get pod emptydir-redis -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
emptydir-redis   1/1     Running   0          11m   10.244.0.15   k8s-master   <none>           <none>
 
#安装客户端redis-cli
[root@k8s-master emptydir]# yum install redis -y
 
[root@k8s-master emptydir]# redis-cli -h 10.244.0.15 -p 6379
10.244.0.15:6379> 
#向redis中写入两个key
10.244.0.15:6379> set volume emptydir
OK
10.244.0.15:6379> set username happylu
OK
10.244.0.15:6379> get volume
"emptydir"
10.244.0.15:6379> get username
"happylu"

登陆到pod中,可以直接kill redis-server进程,进程一般为1,进程被kill后kubelet会自动将进程重启

[root@k8s-master emptydir]# kubectl exec -it  emptydir-redis -- /bin/sh
# kill 1
# command terminated with exit code 137

pod异常重启后,再次登录redis并查看redis中的数据内容,发现数据没有丢失。

[root@k8s-master ~]# kubectl get pod
NAME                     READY   STATUS      RESTARTS   AGE
emptydir-redis           0/1     Completed   1          27m
 
[root@k8s-master ~]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
emptydir-redis           1/1     Running   2          30m
 
#pod重启后,再次登录redis并查看redis中的数据内容,发现数据没有丢失。
10.244.0.15:6379> keys *
1) "volume"
2) "username"

emptyDir实际是宿主机上创建的一个目录,将目录以bind mount的形势挂载到容器中,跟随容器的生命周期。查看存储内容如下:

[root@k8s-master ~]# docker inspect  d64ee8367b49
        "Mounts": [
            
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/28800e94-3474-4a0e-9e35-d5585b60a133/volumes/kubernetes.io~empty-dir/emptydir-redis",
                "Destination": "/data",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            ,

查看目录的信息:

[root@k8s-master ~]# cd /var/lib/kubelet/pods/28800e94-3474-4a0e-9e35-d5585b60a133/volumes/kubernetes.io~empty-dir/emptydir-redis
[root@k8s-master emptydir-redis]# ls
dump.rdb

Pod删除后,volume的信息也随之删除

[root@k8s-master emptydir]# kubectl delete -f emptydir-redis.yaml 
pod "emptydir-redis" deleted
 
[root@k8s-master emptydir]# cd /var/lib/kubelet/pods/28800e94-3474-4a0e-9e35-d5585b60a133/volumes/kubernetes.io~empty-dir/emptydir-redis
-bash: cd: /var/lib/kubelet/pods/28800e94-3474-4a0e-9e35-d5585b60a133/volumes/kubernetes.io~empty-dir/emptydir-redis: No such file or directory

Pod健康检查介绍

默认情况下,kubelet根据容器运行状态作为健康依据,不能监控容器中应用程序状态,例如程序假死。这就会导致无法提供服务,丢失流量。因此引入健康检查机制确保容器健康存活。

Pod通过两类探针来检查容器的健康状态。分别是LivenessProbe(存活探测)和 ReadinessProbe(就绪探测)。

livenessProbe(存活探测)

存活探测将通过http、shell命令或者tcp等方式去检测容器中的应用是否健康,然后将检查结果返回给kubelet,如果检查容器中应用为不健康状态提交给kubelet后,kubelet将根据Pod配置清单中定义的重启策略restartPolicy来对Pod进行重启。

readinessProbe(就绪探测)

就绪探测也是通过http、shell命令或者tcp等方式去检测容器中的应用是否健康或则是否能够正常对外提供服务,如果能够正常对外提供服务,则认为该容器为(Ready状态),达到(Ready状态)的Pod才可以接收请求。

对于被Service所管理的Pod,Service与被管理Pod的关联关系也将基于Pod是否Ready进行设置,Pod对象启动后,容器应用通常需要一段时间才能完成其初始化的过程,例如加载配置或数据,甚至有些程序需要运行某类的预热过程,若在此阶段完成之前就接收客户端的请求,那么客户端返回时间肯定非常慢,严重影响了体验,所以因为避免Pod对象启动后立即让其处理客户端请求,而是等待容器初始化工作执行完成并转为Ready状态后再接收客户端请求。

如果容器或则Pod状态为(NoReady)状态,Kubernetes则会把该Pod从Service的后端endpoints Pod中去剔除。


健康检测实现方式

以上介绍了两种探测类型livenessProbe(存活探测),readinessProbe(就绪探测),这两种探测都支持以下方式对容器进行健康检查

  1. ExecAction:在容器中执行命令,命令执行后返回的状态为0则成功,表示我们探测结果正常
  2. HTTPGetAction:根据容器IP、端口以及路径发送HTTP请求,返回码如果是200-400之间表示成功
  3. TCPSocketAction:根据容器IP地址及特定的端口进行TCP检查,端口开放表示成功

以上每种检查动作都可能有以下三种返回状态

  1. Success,表示通过了健康检查
  2. Failure,表示没有通过健康检查
  3. Unknown,表示检查动作失败

通过在目标容器中执行由用户自定义的命令来判定容器的健康状态,即在容器内部执行一个命令,如果改命令的返回码为0,则表明容器健康。spec.containers.LivenessProbe字段用于定义此类检测,它只有一个可用属性command,用于指定要执行的命令,下面是在资源清单文件中使用liveness-exec方式的示例:

1.创建资源配置清单

创建一个Pod——》运行Nginx容器——》首先启动nginx——》然后沉睡60秒后——〉删除nginx.pid 通过livenessProbe存活探测的exec命令判断nginx.pid文件是否存在,如果探测返回结果非0,则按照重启策略进行重启。 预期是容器真正(Ready)状态60s后,删除nginx.pid,exec命令探测生效,按照重启策略进行重启

cat ngx-health.yaml
apiVersion: v1
kind: Pod
metadata:
  name: ngx-health
spec:
  containers:
  - name: ngx-liveness
    image: nginx:latest
    command:
    - /bin/sh
    - -c
    - /usr/sbin/nginx; sleep 60; rm -rf /run/nginx.pid
    livenessProbe:
      exec:
        command: [ "/bin/sh", "-c", "test", "-e", "/run/nginx.pid" ]
  restartPolicy: Always

2.创建Pod资源

kubectl apply -f ngx-health.yaml

等待Pod Ready

3.查看Pod的详细信息

#第一次查看,Pod中的容器启动成功,事件正常
kubectl describe pods/ngx-health | grep -A 10 Events
Events:
  Type    Reason     Age        From                 Message
  ----    ------     ----       ----                 -------
  Normal  Scheduled  <unknown>  default-scheduler    Successfully assigned default/ngx-health to k8s-node03
  Normal  Pulling    12s        kubelet, k8s-node03  Pulling image "nginx:latest"
  Normal  Pulled     6s         kubelet, k8s-node03  Successfully pulled image "nginx:latest"
  Normal  Created    6s         kubelet, k8s-node03  Created container ngx-liveness
  Normal  Started    5s         kubelet, k8s-node03  Started container ngx-liveness

#第二次查看,容器的livenessProbe探测失败,
kubectl describe pods/ngx-health | grep -A 10 Events
Events:
  Type     Reason     Age                From                 Message
  ----     ------     ----               ----                 -------
  Normal   Scheduled  <unknown>          default-scheduler    Successfully assigned default/ngx-health to k8s-node03
  Normal   Pulling    52s                kubelet, k8s-node03  Pulling image "nginx:latest"
  Normal   Pulled     46s                kubelet, k8s-node03  Successfully pulled image "nginx:latest"
  Normal   Created    46s                kubelet, k8s-node03  Created container ngx-liveness
  Normal   Started    45s                kubelet, k8s-node03  Started container ngx-liveness
  Warning  Unhealthy  20s (x3 over 40s)  kubelet, k8s-node03  Liveness probe failed:
  Normal   Killing    20s                kubelet, k8s-node03  Container ngx-liveness failed liveness probe, will be restarted

#第三次查看,已经重新拉取镜像,然后创建容器再启动容器
kubectl describe pods/ngx-health | grep -A 10 Events
Events:
  Type     Reason     Age                From                 Message
  ----     ------     ----               ----                 -------
  Normal   Scheduled  <unknown>          default-scheduler    Successfully assigned default/ngx-health to k8s-node03
  Warning  Unhealthy  35s (x3 over 55s)  kubelet, k8s-node03  Liveness probe failed:
  Normal   Killing    35s                kubelet, k8s-node03  Container ngx-liveness failed liveness probe, will be restarted
  Normal   Pulling    4s (x2 over 67s)   kubelet, k8s-node03  Pulling image "nginx:latest"
  Normal   Pulled     2s (x2 over 61s)   kubelet, k8s-node03  Successfully pulled image "nginx:latest"
  Normal   Created    2s (x2 over 61s)   kubelet, k8s-node03  Created container ngx-liveness
  Normal   Started    2s (x2 over 60s)   kubelet, k8s-node03  Started container ngx-liveness

通过长格式输出可以看到如下,第一次长格式输出Pod运行时间22s,重启次数为0。

第二次长格式输出,运行时间是76s,Pod已经完成一次重启

kubectl get pods -o wide | grep ngx-health
ngx-health                          1/1     Running            0          22s     10.244.5.44   k8s-node03   <none>           <none>

kubectl get pods -o wide | grep ngx-health
ngx-health                          1/1     Running            1          76s     10.244.5.44   k8s-node03   <none>           <none>

第二次健康探测失败及第二次重启

kubectl describe pods/ngx-health | grep -A 10 Events
Events:
  Type     Reason     Age                 From                 Message
  ----     ------     ----                ----                 -------
  Normal   Scheduled  <unknown>           default-scheduler    Successfully assigned default/ngx-health to k8s-node03
  Normal   Pulled     58s (x2 over 117s)  kubelet, k8s-node03  Successfully pulled image "nginx:latest"
  Normal   Created    58s (x2 over 117s)  kubelet, k8s-node03  Created container ngx-liveness
  Normal   Started    58s (x2 over 116s)  kubelet, k8s-node03  Started container ngx-liveness
  Warning  Unhealthy  31s (x6 over 111s)  kubelet, k8s-node03  Liveness probe failed:
  Normal   Killing    31s (x2 over 91s)   kubelet, k8s-node03  Container ngx-liveness failed liveness probe, will be restarted
  Normal   Pulling    0s (x3 over 2m3s)   kubelet, k8s-node03  Pulling image "nginx:latest"

kubectl get pods -o wide | grep ngx-health
ngx-health                          1/1     Running            2          2m13s   10.244.5.44   k8s-node03   <none>           <none>

livenessProbe for HTTPGetAction示例

通过容器的ip地址,端口号及路径调用HTTPGet方法,如果响应的状态码大于等于200且小于400,则认为容器健康,spec.containers.livenessProbe.httpGet字段用于定义此类检测,它的可用配置字段包括如下几个:

  • host :请求的主机地址,默认为Pod IP;也可以在httpHeaders中使用 Host: 来定义
  • port :请求的端口,必选字段,端口范围1-65535
  • httpHeaders <[]Object>:自定义的请求报文首部
  • path :请求的HTTP资源路径,即URL path
  • scheme:建立连接使用的协议,仅可为HTTP或HTTPS,默认为HTTP

1.创建资源配置清单

创建一个Pod——》运行Nginx容器——》首先启动nginx——》然后沉睡60秒后——〉删除nginx.pid 通过livenessProbe存活探测的httpGet方式请求nginx项目根目录下的index.html文件,访问端口为80,访问地址默认为Pod IP,请求协议为HTTP,如果请求失败则按照重启策略进行重启。

cat ngx-health.yaml
apiVersion: v1
kind: Pod
metadata:
  name: ngx-health
spec:
  containers:
  - name: ngx-liveness
    image: nginx:latest
    command:
    - /bin/sh
    - -c
    - /usr/sbin/nginx; sleep 60; rm -rf /run/nginx.pid
    livenessProbe:
      httpGet:
        path: /index.html
        port: 80
        scheme: HTTP
  restartPolicy: Always

2.创建Pod资源对象

kubectl apply -f ngx-health.yaml

3.查看Pod运行状态

#容器创建
kubectl get pods -o wide | grep ngx-health
ngx-health                          0/1     ContainerCreating   0          7s      <none>        k8s-node02   <none>           <none>

#容器运行成功
kubectl get pods -o wide | grep ngx-health
ngx-health                          1/1     Running            0          19s     10.244.2.36   k8s-node02   <none>           <none>

4.查看Pod的详细事件信息

容器镜像拉取并启动成功

kubectl describe pods/ngx-health | grep -A 10 Events
Events:
  Type    Reason     Age        From                 Message
  ----    ------     ----       ----                 -------
  Normal  Scheduled  <unknown>  default-scheduler    Successfully assigned default/ngx-health to k8s-node02
  Normal  Pulling    30s        kubelet, k8s-node02  Pulling image "nginx:latest"
  Normal  Pulled     15s        kubelet, k8s-node02  Successfully pulled image "nginx:latest"
  Normal  Created    15s        kubelet, k8s-node02  Created container ngx-liveness
  Normal  Started    14s        kubelet, k8s-node02  Started container ngx-liveness

容器ready状态后运行60s左右livenessProbe健康检测,可以看到下面已经又开始拉取镜像

kubectl describe pods/ngx-health | grep -A 15 Events
Events:
  Type    Reason     Age               From                 Message
  ----    ------     ----              ----                 -------
  Normal  Scheduled  <unknown>         default-scheduler    Successfully assigned default/ngx-health to k8s-node02
  Normal  Pulled     63s               kubelet, k8s-node02  Successfully pulled image "nginx:latest"
  Normal  Created    63s               kubelet, k8s-node02  Created container ngx-liveness
  Normal  Started    62s               kubelet, k8s-node02  Started container ngx-liveness
  Normal  Pulling    1s (x2 over 78s)  kubelet, k8s-node02  Pulling image "nginx:latest"

镜像拉取完后再次重启创建并启动了一遍,可以看到 Age 列的时间已经重新计算

kubectl describe pods/ngx-health | grep -A 15 Events
Events:
  Type    Reason     Age                From                 Message
  ----    ------     ----               ----                 -------
  Normal  Scheduled  <unknown>          default-scheduler    Successfully assigned default/ngx-health to k8s-node02
  Normal  Pulling    18s (x2 over 95s)  kubelet, k8s-node02  Pulling image "nginx:latest"
  Normal  Pulled     2s (x2 over 80s)   kubelet, k8s-node02  Successfully pulled image "nginx:latest"
  Normal  Created    2s (x2 over 80s)   kubelet, k8s-node02  Created container ngx-liveness
  Normal  Started    1s (x2 over 79s)   kubelet, k8s-node02  Started container ngx-liveness

长格式输出Pod,可以看到Pod已经重启过一次

kubectl get pods -o wide | grep ngx-health
ngx-health                          0/1     Completed          0          96s     10.244.2.36   k8s-node02   <none>           <none>
k8sops@k8s-master01:~/manifests/pod$ kubectl get pods -o wide | grep ngx-health
ngx-health                          1/1     Running            1          104s    10.244.2.36   k8s-node02   <none>           <none>

通过查看容器日志,可以看到下面的探测日志,默认10秒探测一次

kubectl logs -f pods/ngx-health
10.244.2.1 - - [15/May/2020:03:01:13 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "kube-probe/1.18" "-"
10.244.2.1 - - [15/May/2020:03:01:23 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "kube-probe/1.18" "-"
10.244.2.1 - - [15/May/2020:03:01:33 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "kube-probe/1.18" "-"
10.244.2.1 - - [15/May/2020:03:01:43 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "kube-probe/1.18" "-"
10.244.2.1 - - [15/May/2020:03:01:53 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "kube-probe/1.18" "-"
10.244.2.1 - - [15/May/2020:03:02:03 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "kube-probe/1.18" "-"

livenessProbe for TCPSocketAction示例

通过容器的IP地址和端口号进行TCP检查,如果能够建立TCP连接,则表明容器健康。相比较来说,它比基于HTTP的探测要更高效,更节约资源,但精准度略低,毕竟建立连接成功未必意味着页面资源可用,spec.containers.livenessProbe.tcpSocket字段用于定义此类检测,它主要包含以下两个可用的属性:

  • host:请求连接的目标IP地址,默认为Pod IP
  • port:请求连接的目标端口,必选字段 下面是在资源清单文件中使用liveness-tcp方式的示例,它向Pod IP的80/tcp端口发起连接请求,并根据连接建立的状态判定测试结果:

1.创建资源配置清单

apiVersion: v1
kind: Pod
metadata:
  name: ngx-health
spec:
  containers:
  - name: ngx-liveness
    image: nginx:latest
    command:
    - /bin/sh
    - -c
    - /usr/sbin/nginx; sleep 60; rm -rf /run/nginx.pid
    livenessProbe:
      tcpSocket:
        port: 80
  restartPolicy: Always

2.创建资源对象

kubectl apply -f ngx-health.yaml

3.查看Pod创建属性信息

#容器创建并启动成功
kubectl describe pods/ngx-health | grep -A 15 Events
Events:
  Type    Reason     Age        From                 Message
  ----    ------     ----       ----                 -------
  Normal  Scheduled  <unknown>  default-scheduler    Successfully assigned default/ngx-health to k8s-node02
  Normal  Pulling    19s        kubelet, k8s-node02  Pulling image "nginx:latest"
  Normal  Pulled     9s         kubelet, k8s-node02  Successfully pulled image "nginx:latest"
  Normal  Created    8s         kubelet, k8s-node02  Created container ngx-liveness
  Normal  Started    8s         kubelet, k8s-node02  Started container ngx-liveness

#在容器ready状态后60s左右Pod已经有了再次拉取镜像的动作
kubectl describe pods/ngx-health | grep -A 15 Events
Events:
  Type    Reason     Age                From                 Message
  ----    ------     ----               ----                 -------
  Normal  Scheduled  <unknown>          default-scheduler    Successfully assigned default/ngx-health to k8s-node02
  Normal  Pulled     72s                kubelet, k8s-node02  Successfully pulled image "nginx:latest"
  Normal  Created    71s                kubelet, k8s-node02  Created container ngx-liveness
  Normal  Started    71s                kubelet, k8s-node02  Started container ngx-liveness
  Normal  Pulling    10s (x2 over 82s)  kubelet, k8s-node02  Pulling image "nginx:latest"

#通过长格式输出Pod,也可以看到当前Pod已经进入了完成的状态,接下来就是重启Pod
 kubectl get pods -o wide | grep ngx-health
ngx-health                          0/1     Completed          0          90s     10.244.2.37   k8s-node02   <none>           <none>

健康检测参数

上面介绍了两种在不同时间段的探测方式,以及两种探测方式所支持的探测方法,这里介绍几个辅助参数

  • initialDelaySeconds:检查开始执行的时间,以容器启动完成为起点计算
  • periodSeconds:检查执行的周期,默认为10秒,最小为1秒
  • successThreshold:从上次检查失败后重新认定检查成功的检查次数阈值(必须是连续成功),默认为1,也必须是1
  • timeoutSeconds:检查超时的时间,默认为1秒,最小为1秒
  • failureThreshold:从上次检查成功后认定检查失败的检查次数阈值(必须是连续失败),默认为1

健康检测实践

以下示例使用了就绪探测readinessProbe和存活探测livenessProbe 就绪探测配置解析:

  1. 容器在启动5秒initialDelaySeconds后进行第一次就绪探测,将通过http访问探测容器网站根目录下的index.html文件,如果探测成功,则Pod将被标记为(Ready)状态。
  2. 然后就绪检测通过periodSeconds参数所指定的间隔时间进行循环探测,下面我所指定的间隔时间是10秒钟,每隔10秒钟就绪探测一次。
  3. 每次探测超时时间为3秒,如果探测失败1次就将此Pod从Service的后端Pod中剔除,剔除后客户端请求将无法通过Service访问到其Pod。
  4. 就绪探测还会继续对其进行探测,那么如果发现此Pod探测成功1次,通过successThreshold参数设定的值,那么会将它再次加入后端Pod。

存活探测配置解析

  1. 容器在启动15秒initialDelaySeconds后进行第一次存活探测,将通过tcpSocket探测容器的80端口,如果探测返回值为0则成功。
  2. 每次存活探测间隔为3秒钟,每次探测超时时间为1秒,如果连续探测失败2次则通过重启策略重启Pod。
  3. 检测失败后的Pod,存活探测还会对其进行探测,如果再探测成功一次,那么将认为此Pod为健康状态

1.资源配置清单

cat nginx-health.yaml
#create namespace
apiVersion: v1
kind: Namespace
metadata:
  name: nginx-health-ns
  labels:
    resource: nginx-ns
spec:

---

#create deploy and pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-health-deploy
  namespace: nginx-health-ns
  labels:
    resource: nginx-deploy
spec:
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx-health
  template:
    metadata:
      namespace: nginx-health-ns
      labels:
        app: nginx-health
    spec:
      restartPolicy: Always
      containers:
      - name: nginx-health-containers
        image: nginx:1.17.1
        imagePullPolicy: IfNotPresent
        command:
        - /bin/sh
        - -c
        - /usr/sbin/nginx; sleep 60; rm -rf /run/nginx.pid
        readinessProbe:
          initialDelaySeconds: 5
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
          failureThreshold: 1
          httpGet:
            path: /index.html
            port: 80
            scheme: HTTP
        livenessProbe:
          initialDelaySeconds: 15
          periodSeconds: 3
          successThreshold: 1
          timeoutSeconds: 1
          failureThreshold: 2
          tcpSocket:
            port: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"

---

#create service
apiVersion: v1
kind: Service
metadata:
  name: nginx-health-svc
  namespace: nginx-health-ns
  labels:
    resource: nginx-svc
spec:
   clusterIP: 10.106.189.88
   ports:
   - port: 80
     protocol: TCP
     targetPort: 80
   selector:
     app: nginx-health
   sessionAffinity: ClientIP
   type: ClusterIP

2.创建资源对象

kubectl apply -f nginx-health.yaml
namespace/nginx-health-ns created
deployment.apps/nginx-health-deploy created
service/nginx-health-svc created

3.查看创建的资源对象

k8sops@k8s-master01:/$ kubectl get all -n nginx-health-ns -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
pod/nginx-health-deploy-6bcc8f7f74-6wc6t   1/1     Running   0          24s   10.244.3.50   k8s-node01   <none>           <none>
pod/nginx-health-deploy-6bcc8f7f74-cns27   1/1     Running   0          24s   10.244.5.52   k8s-node03   <none>           <none>
pod/nginx-health-deploy-6bcc8f7f74-rsxjj   1/1     Running   0          24s   10.244.2.42   k8s-node02   <none>           <none>

NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/nginx-health-svc   ClusterIP   10.106.189.88   <none>        80/TCP    25s   app=nginx-health

NAME                                  READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS                IMAGES         SELECTOR
deployment.apps/nginx-health-deploy   3/3     3            3           25s   nginx-health-containers   nginx:1.17.1   app=nginx-health

NAME                                             DESIRED   CURRENT   READY   AGE   CONTAINERS                IMAGES         SELECTOR
replicaset.apps/nginx-health-deploy-6bcc8f7f74   3         3         3       25s   nginx-health-containers   nginx:1.17.1   app=nginx-health,pod-template-hash=6bcc8f7f74

4.查看Pod状态,目前Pod状态都没有就绪并且完成状态,准备重启

k8sops@k8s-master01:/$ kubectl get pods -n nginx-health-ns -o wide
NAME                                   READY   STATUS      RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
nginx-health-deploy-6bcc8f7f74-6wc6t   0/1     Completed   0          64s   10.244.3.50   k8s-node01   <none>           <none>
nginx-health-deploy-6bcc8f7f74-cns27   0/1     Completed   0          64s   10.244.5.52   k8s-node03   <none>           <none>
nginx-health-deploy-6bcc8f7f74-rsxjj   0/1     Completed   0          64s   10.244.2.42   k8s-node02   <none>           <none>

5.目前已经有一台Pod完成重启,已准备就绪

kubectl get pods -n nginx-health-ns -o wide
NAME                                   READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
nginx-health-deploy-6bcc8f7f74-6wc6t   1/1     Running   1          73s   10.244.3.50   k8s-node01   <none>           <none>
nginx-health-deploy-6bcc8f7f74-cns27   0/1     Running   1          73s   10.244.5.52   k8s-node03   <none>           <none>
nginx-health-deploy-6bcc8f7f74-rsxjj   0/1     Running   1          73s   10.244.2.42   k8s-node02   <none>           <none>

6.三台Pod都均完成重启,已准备就绪

kubectl get pods -n nginx-health-ns -o wide
NAME                                   READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
nginx-health-deploy-6bcc8f7f74-6wc6t   1/1     Running   1          85s   10.244.3.50   k8s-node01   <none>           <none>
nginx-health-deploy-6bcc8f7f74-cns27   1/1     Running   1          85s   10.244.5.52   k8s-node03   <none>           <none>
nginx-health-deploy-6bcc8f7f74-rsxjj   1/1     Running   1          85s   10.244.2.42   k8s-node02   <none>           <none>

7.在Pod重启的时候,可以看到Service可以动态关联和取消相关的Pod

以上是关于再战 k8s:Pod Volume存储卷健康检查的主要内容,如果未能解决你的问题,请参考以下文章

7.存储卷(Volume)

K8S 之 使用NFS卷作为POD存储卷

Kubernetes核心概念之Volume存储数据卷详解

k8s存储卷+PV与PVC

k8s 存储卷之简单存储

k8s 存储卷之简单存储