3-4-3-Pod生命周期、健康、就绪状态探测以及资源限制

熊 0 阅读 容器

1. Pod生命周期

pod生命周期是指在pod开始创建到pod退出所消耗的时间范围 , 我们把开始到结束的这段时间范围就叫做pod的生命周期 ; 其大概过程如下图所示

kubernetes-pod-lifecycle-1

上图主要描述了一个pod从创建到退出 , 中间这段时间经历的过程 ; 从大的方向上看 , pod生命周期分两个阶段 , 第一阶段是初始化容器 , 第二阶段是主容器的整个生命周期 ; 其中对于主容器来来说 , 它的生命周期有分了三个阶段 , 第一阶段是运行post start hook , 这个阶段是主容器运行之后立即需要做的事 ; 第二阶段是主容器正常运行的阶段 , 在这个阶段中 , 我们可以定义对容器的健康状态检查和就绪状态检查 ; 第三阶段是运行pre stop hook , 这个阶段主要做容器即将退出前需要做的事 ; 这里需要注意对于初始化容器来说 , 一个pod中可以定义多个初始化容器 , 他们必须是串行执行 , 只有当所有的初始化容器执行完后 , 对应的主容器才会启动 ; 对于对容器的健康状态检查和就绪状态检查 , 我们也可以定义开始检查的延迟时长 ; 因为有些容器存在容器显示running状态 , 但内部程序还没有初始化 , 如果立即做健康状态检查 , 可能存在健康状态为不健康 , 从而导致容器重启的状况 ;

2. Pod创建过程

kubernetes-pods-creating-1

首先用户通过客户端工具将请求提交给apiserver , apiserver收到用户的请求以后 , 它会尝试将用户提交的请求内容存进etcd中 , etcd存入完成后就反馈给apiserver写入数据完成 , 此时apiserver就返回客户端 , 说某某资源已经创建 ; 随后apiserver要发送一个watch信号给scheduler , 说要创建一个新pod , 请你看看在那个节点上创建合适 , scheduler收到信号以后 , 就开始做调度 , 并把调度后端结果反馈给apiserver , apiserver收到调度器的调度信息以后 , 它就把对应调度信息保存到etcd中 , 随后apiServer要发送一个watch信号给对应被调度的主机上的kubelet , 对应主机上的kubelet收到消息后 , 立刻调用docker , 并把对应容器跑起来 ; 当容器运行起来以后 , docker会向kubelet返回容器的状体 ; 随后kubelet把容器的状态反馈给apiserver,由apiserver把容器的状态信息保存到etcd中 ; 最后当etcd中的容器状态信息更新完成后 , 随后apiserver把容器状态信息更新完成的消息发送给对应主机的kubelet ;

3. 在资源配置清单中定义初始化容器

root@k8s-master01:/opt/k8s-data/yaml/pod# cat pod-demo1.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-demo6
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    ports:
      - containerPort: 80
        hostPort: 8080
        name: web
        protocol: TCP
  initContainers:
  - name: init-something
    image: busybox
    command:
    - /bin/sh
    - -c
    - "sleep 60"
​

在资源配置清单中定义初始化容器需要在spec字段下 , 使用initContainers字段来定义 , 这个字段的值是一个列表对象 ; 初始化容器的定义和主容器的定义方式很类似 ; 上面初始化容器中主要干了一件事 , 就是sleep 60 , 意思是在启动主容器前 , 首先要让初始化容器中的操作执行完以后 , 对应的主容器才会开始运行 ; 如果定义的初始化容器有多个 , 则要等待所有初始化容器中的操作执行完以后 , 对应主容器才会开始启动 ;

4. Pod生命周期的两个函数钩子的使用

postStart : 这个函数钩子主要用来定义在主容器启动之后 , 立即需要做的事 , 比如执行一个命令 , 创建一个文件等等 ; 这里需要注意的是 , postStart这个函数钩子说定义的操作 , 都是针对主容器的 , 所以执行命令或其他操作的前提都是主容器上能够正常执行的操作 ;

示例 : 定义运行一个nginx容器 , 在容器启动之后立即在对应html目录下创建一个文件 , 作为用户自定义测试页面

root@k8s-master01:/opt/k8s-data/yaml/pod# cat pod-demo2.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-demo7
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    ports:
      - containerPort: 80
        hostPort: 8080
        name: web
        protocol: TCP
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/sh
          - -c
          - "echo 'this is test page' > /usr/share/nginx/html/test.html"
​

在资源配置清单中定义主容器启动之后需要做的事 , 需要在对应主容器下用lifecycle字段来定义 ; 其中postStart字段使用用来指定主容器启动之后要做到事 , 这个字段的值是一个对象 ; 其中exec是用来定义使用exec来执行命令 , command字段用来指定要执行的命令 ; 除了可以用exec来定义执行命令 , 还可以使用httpGet来向当前容器中的url发起http请求 , 或者使用tcpSocket来向某个主机的某个端口套接字发起请求 , 默认不指定host表示向当前podip发起请求 ;

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl apply -f pod-demo2.yaml 
pod/nginx-pod-demo7 unchanged
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods 
NAME              READY   STATUS    RESTARTS   AGE
​
nginx-pod-demo7   1/1     Running   0          49s
​
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pod -o wide
NAME              READY   STATUS    RESTARTS   AGE     IP            NODE                   NOMINATED NODE   READINESS GATES
​
nginx-pod-demo7   1/1     Running   0          3m1s    172.20.5.45   k8s-node03.cbear.top   <none>           <none>
​
​

访问对应pod看看test.html是否能够访问到

root@k8s-master01:/opt/k8s-data/yaml/pod# curl 172.20.5.45/test.html
this is test page

可以看到访问对应的pod的ip地址 , 能够访问到我们刚才定义容器启动之后创建的文件内容 ;

preStop: 这个函数钩子主要用来定义在容器结束之前需要做的事情 , 使用方式和postStart一样 , 都是在对应主容器里的lifesycle字段下定义 ; 它也可以使用exec来执行命令或者httpGet来向容器的某个url发起请求 , 或者使用tcpSocket向某个套接字发起请求 ;

示例: 在容器结束前执行echo 命令

root@k8s-master01:/opt/k8s-data/yaml/pod# cat pod-demo3.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-demo3
​
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/sh
          - -c
          - "echo 'this is test page' > /usr/share/nginx/html/test.html"
      preStop:
        exec:
          command: ["/bin/sh","-c","echo goodbye.."]

5. pod终止过程

kubernetes-pod-lifecycle-2

用户通过客户端工具想APIserver发送删除pod的指令 , 在APIserver收到用户发来的删除指令后 , 首先APIserver会把对应的操作写到etcd中 , 并设置其宽限期 , 然后etcd把对应数据写好以后 , 响应APIserver , 随后APIserver响应客户端说对应容器已经标记为terminating状态 ; 随后APIserver会发送一个把对应pod标记为terminating状态的消息给endpoint端点控制 , 让其删除与当前要删除pod相关的所有service , ( 其实在k8s上我们创建service关联pod不是直接关联pod , 是现关联endpoint端点控制器 , 然后端点控制器再关联pod ) , 随后APIserver会向对应要删除pod所在主机上的kubelet发送将pod标记为terminating状态的消息 , 当对应主机收到APIserver发送的标记pod为terminating状态消息后 , 对应主机上的kubelet会向对应pod里运行的容器发送TERM信号 , 随后再执行preStop中定义的操作 ; 随后等待宽限期超时 , 如果对应的pod还没有被删除 , 此时APIserver就会向对应pod所在主机上的kubelet发送宽限期超时的消息 , 此时对应kubelet会向对应容器发送SIGKILL信号来强制删除对应的容器 , 随后docker把对应容器删除后 , 把删除完容器的消息响应给APIserver , 此时APIserver会向etcd发送删除对应pod在etcd中的所有信息 ;

6. 状态探测

6.1 pod健康状态探测

pod健康状态探测是指检查对应pod是否健康 , 如果不健康就把对应的pod重启 ; 健康状态探测是一个周期性的工作 ; 只要发现对应pod不健康 , 就重启对应pod ; 在k8s上对pod的健康状态探测的方式有三种

  1. 第一种上执行命令 , 只有对应命令执退出码为0就表示对应pod是处于健康状态 , 否则就不健康 ;

  2. 第二种是用httpGet来探测对应pod里的容器的某个url是否可以访问 , 只有请求对应的url状态码为200才表示对应pod是健康的 , 否则就不健康 ;

  3. 第三种是使用tcpSocket的方式来对某个套接字发送请求 , 只有套接字正常响应就表示对应pod是处于健康的 , 否则就是不健康 ; 至于我们要使用那种方式来判断pod的健康与否 , 这取决与pod里的服务和业务逻辑 ;

6.1.1 exec

示例 : 使用exec执行命令的方式来探测pod的健康状态

root@k8s-master01:/opt/k8s-data/yaml/pod# cat pod-demo4.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/sh
          - -c
          - "echo 'this is test page' > /usr/share/nginx/html/test.html"
      preStop:
        exec:
          command: ["/bin/sh","-c","echo goodbay.."]
    livenessProbe:
      exec:
        command: ["/usr/bin/test","-e","/usr/share/nginx/html/test.html"]

使用配置清单定义pod的健康状态监测需要用到livenessProbe这个字段 , 这个字段的值是一个对象 ; 以上配置表示判断/usr/share/nginx/html/test.html这个文件是否存在 , 存在就表示对应pod健康 , 否则就不健康 ;

应用配置清单

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl apply -f pod-demo4.yaml 
pod/liveness-exec created
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods 
NAME              READY   STATUS    RESTARTS   AGE
liveness-exec     1/1     Running   0          5s

可以看到对应pod现在已经正常运行着 , 并且重启次数为0 ;

测试 : 进入对应pod把test.html文件删除 , 看看对应pod还会正常处于健康状态吗?重启次数还是0吗?

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl exec liveness-exec -- rm -f /usr/share/nginx/html/test.html

查看对应pod状态

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods 
NAME              READY   STATUS    RESTARTS      AGE
liveness-exec     1/1     Running   1 (33s ago)   3m33s

可以看到对应pod重启数已经变为1了 , 说明pod发生了重启 ;

查看pod的详细信息

image-20240604203745681

从上面的截图可以看到 , pod健康状态检查不通过 , 就把容器给重启了 ; 重启以后对应的文件又回重新创建 , 所以再次健康状态监测就通过了 , 所以pod处于健康状态 ;

6.1.2 httpGet

示例: 使用httpGet探测对应pod是否健康

root@k8s-master01:/opt/k8s-data/yaml/pod# cat liveness-httpget.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    ports:
    - name: http
      containerPort: 80
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/sh
          - -c
          - "echo 'this is test page' > /usr/share/nginx/html/test.html"
      preStop:
        exec:
          command: ["/bin/sh","-c","echo goodbay.."]
    livenessProbe:
      httpGet:
        path: /test.html
        port: http
        scheme: HTTP
      failureThreshold: 2
      initialDelaySeconds: 2
      periodSeconds: 3

failureThreshold字段用于指定失败阈值 , 即多少次失败就把对应pod标记为不健康 ; 默认是3次 ;

initialDelaySeconds字段用于指定初始化后延迟多少时间再做健康状态监测 ;

periodSeconds字段用于指定监测频率 , 默认是10秒一次 ; 最小为1秒一次 ;

以上配置清单表示对pod容器里的/test.html这个url发起请求 , 如果响应码为200就表示pod健康 , 否则就不健康 ; httpGet中必须指定端口 , 端口信息可以应用上面容器中定义的端口名称 ;

应用配置清单

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl apply -f liveness-httpget.yaml 
pod/liveness-httpget created

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
liveness-httpget   1/1     Running   0          3m19s
nginx-demo         1/1     Running   0          6h29m
nginx-demo2        1/1     Running   0          5h52m

验证: 进入对应pod , 把test.html文件删除 , 看看对应pod是否会重启

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl exec liveness-httpget -- rm -rf /usr/share/nginx/html/test.html

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods
NAME               READY   STATUS    RESTARTS     AGE
liveness-httpget   1/1     Running   1 (4s ago)   4m49s
nginx-demo         1/1     Running   0            6h31m
nginx-demo2        1/1     Running   0            5h54m

可以看到对应pod已经发生了重启 ;

查看pod详细信息

image-20240604204746364

可以看到对应pod健康状态探测失败 , 并重启的事件 ;

6.1.3 tcpsocket

示例: 使用tcpsocket方式来探测pod健康状态

root@k8s-master01:/opt/k8s-data/yaml/pod# cat liveness-tcpsocket.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: liveness-tcpsocket
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    ports:
    - name: http
      containerPort: 80
    livenessProbe:
      tcpSocket:
        port: http
      failureThreshold: 2
      initialDelaySeconds: 2
      periodSeconds: 3

使用tcpSocket方式来探测健康与否 , 默认不指定host字段表示探测对应podip ;

应用资源配置清单

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl apply -f liveness-tcpsocket.yaml 
pod/liveness-tcpsocket created
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pod
NAME                 READY   STATUS    RESTARTS        AGE
liveness-httpget     1/1     Running   1 (6m58s ago)   11m
liveness-tcpsocket   1/1     Running   0               48s
nginx-demo           1/1     Running   0               6h38m
nginx-demo2          1/1     Running   0               6h

测试: 进入pod里的容器 , 修改nginx的端口为81 , 看看对应pod是否会重启

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl exec liveness-tcpsocket -it -- /bin/sh
/ # netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      
/ # grep "listen" /etc/nginx/conf.d/default.conf
    listen       80;
    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
/ # sed -i 's@    listen.*@    listen 81;@g' /etc/nginx/conf.d/default.conf
/ # grep "listen" /etc/nginx/conf.d/default.conf
    listen 81;
    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
/ # nginx -s reload
2024/06/04 12:55:03 [notice] 18#18: signal process started
/ # command terminated with exit code 137

可以看到我们修改了配置文件让nginx监听81端口 , 没过几秒就退出了;

查看对应pod是否发生了重启

image-20240604205626272

可以看到对应pod里的事件信息说健康状态监测172.20.3.80:80连接失败 , 容器重启了 ;

6.2 pod就绪状态探测

所谓pod就绪状态探测是指探测对应pod是否就绪 , 主要用在service关联后端pod的一个重要依据 , 如果对应pod未就绪 , 对应service就不应该关联pod , 否则可能发生用户访问对应service , 响应服务不可用 ; pod就绪状态检查和健康状态检查两者最主要的区别是 , 健康状态检查 , 一旦对应pod不健康了 , 就会执行重启对应pod的操作 , 而就绪状态检查是没有权限去重启pod , 如果对应pod没有就绪 , 它不会做任何操作 ; 同样的对就绪状态检查在k8s上也有三种方式和健康状态检查的方式一摸一样 ;

示例 : 使用exec方式探测pod就绪状态

root@k8s-master01:/opt/k8s-data/yaml/pod# cat readiness-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: readiness-demo
  namespace: default
  labels:
    app: nginx
    env: testing
  annotations:
    descriptions: "this is test pod "
spec:
  containers:
  - image: nginx:1.14-alpine
    imagePullPolicy: IfNotPresent
    name: nginx
    ports:
    - name: http
      containerPort: 80
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh","-c","echo 'this is test page' > /usr/share/nginx/html/test.html"]
    readinessProbe:
      exec:
        command: ["/usr/bin/test","-e","/usr/share/nginx/html/test.html"]
      failureThreshold: 2
      initialDelaySeconds: 2
      periodSeconds: 3

以上清单表示如果/usr/share/nginx/html/test.html文件存在 , 则表示对应pod就绪 , 否则就表示为就绪 ;

应用配置清单

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl apply -f readiness-demo.yaml 
pod/readiness-demo created
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods
NAME                 READY   STATUS    RESTARTS      AGE
readiness-demo       1/1     Running   0             108s

测试: 删除pod容器中的test.html文件 , 看看对应pod是否会从就绪状态到未就绪状态

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl apply -f readiness-demo.yaml 
pod/readiness-demo created
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods
NAME                 READY   STATUS    RESTARTS      AGE
liveness-httpget     1/1     Running   1 (21m ago)   26m
liveness-tcpsocket   1/1     Running   1 (11m ago)   15m
nginx-demo           1/1     Running   0             6h52m
nginx-demo2          1/1     Running   0             6h15m
readiness-demo       1/1     Running   0             108s
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl exec readiness-demo -- rm -rf /usr/share/nginx/html/test.html

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods
NAME                 READY   STATUS    RESTARTS      AGE
liveness-httpget     1/1     Running   1 (29m ago)   34m
liveness-tcpsocket   1/1     Running   1 (20m ago)   23m
nginx-demo           1/1     Running   0             7h1m
nginx-demo2          1/1     Running   0             6h23m
readiness-demo       0/1     Running   0             10m

可以看到对应pod已经处于未就绪状态了 ;

查看对应pod的详细信息

image-20240604211704116

在对应pod的详细信息中也能看到对应的事件 , 不同于健康状态探测 , 就绪状态探测 , 它这里不会重启pod ;

测试: 创建test.html文件 , 看看对应pod是否会从未就绪状态到就绪状态

root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl exec readiness-demo -- touch /usr/share/nginx/html/test.html
root@k8s-master01:/opt/k8s-data/yaml/pod# kubectl get pods
NAME                 READY   STATUS    RESTARTS      AGE
liveness-httpget     1/1     Running   1 (33m ago)   38m
liveness-tcpsocket   1/1     Running   1 (23m ago)   27m
nginx-demo           1/1     Running   0             7h4m
nginx-demo2          1/1     Running   0             6h27m
readiness-demo       1/1     Running   0             13m

可以看到对应pod已经处于就绪状态 ;

6.3 启动探针

指示容器中的应用是否已经启动。如果提供了启动探针(startup probe), 则禁用所有其他探针, 直到它成功为止。如果启动探针失败, kubelet 将杀死容器, 容器服从其重启策略进行重启。如果容器没有提供启动探针, 则默认状态为成功Success。

启动探针 ( Startup Probe ) 用于Pod内程序告诉kubernetes, 其准备工作已经做好。这些准备工作主要是指业务运行前的前置条件, 比如资源文件下载完毕, 内置数据库文件下载完毕等。这步完成后存活和就绪探针才会开始工作。

存活和就绪探针之间没有关系, 所以它们没有优先级区别, 即在启动探针确定Success后, 它们两个同时开始检测。有任何一个失败就会执行其对应的失败处理动作。 存活探针用于表示程序是否活着。如果被认定不存活, 会依据设置要么重启容器或让Pod调度失败。 就绪探针表示程序是否可以提供服务。一般Pod内程序是通过Service对外提供服务, 如果就绪探针失败, Service会将该Pod摘除, 这样流量就不会打到这个不能工作的Pod上 ; 如果就绪探针成功了, 该Pod又会被加进Service。 似乎有存活和就绪探针就够了, 为什么还要启动探针呢?因为一些准备工作我们并不知道其需要花多长时间, 比如可能网络带宽问题导致资源文件下载很慢。这个时候设置存活或者就绪探针就可能不准确, 或者导致其不灵敏。所以设置启动探针可以提升其他探针的灵敏度。

6.3.1 启动和存活探针

有时候 , 会有一些现有的应用在启动时需要较长的初始化时间。 要这种情况下 , 若要不影响对死锁作出快速响应的探测 , 设置存活探测参数是要技巧的。 技巧就是使用相同的命令来设置启动探测 , 针对 HTTP 或 TCP 检测 , 可以通过将 failureThreshold * periodSeconds 参数设置为足够长的时间来应对最糟糕情况下的启动时间。

# startup_liveness.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: startup-liveness-deployment
spec:
  selector:
    matchLabels:
      app: startup-liveness
  template:
    metadata:
      labels:
        app: startup-liveness
    spec:
      containers:
      - name: startup-liveness-container
        image: busybox
        command: ["/bin/sh", "-c", "sleep 6; touch /tempdir/ready; sleep 3;touch /tempdir/keepalive; while true; do sleep 5;  done"]
        volumeMounts:
        - name:  probe-volume
          mountPath:  /tempdir
        startupProbe:
          exec:
            command:
            - cat
            - /tempdir/ready
          initialDelaySeconds: 3
          failureThreshold: 6
          periodSeconds: 1
          successThreshold: 1
        livenessProbe:
          exec:
            command:
            - cat
            - /tempdir/keepalive
          failureThreshold: 6
          periodSeconds: 1
          successThreshold: 1
      volumes:
      - name: probe-volume
        emptyDir: 
          medium: Memory
          sizeLimit: 1Gi
kubernetes-pods-creating-2

我们使用下面指令查看中间发生的事件

kubectl describe pod 

Volumes:
  probe-volume:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     Memory
    SizeLimit:  1Gi
  kube-api-access-54ggd:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  83s                default-scheduler  Successfully assigned default/startup-liveness-deployment-b98bc8df5-tvq89 to k8s-node02.cbear.top
  Normal   Pulling    83s                kubelet            Pulling image "busybox"
  Normal   Pulled     62s                kubelet            Successfully pulled image "busybox" in 20.477s (20.477s including waiting)
  Normal   Created    62s                kubelet            Created container startup-liveness-container
  Normal   Started    62s                kubelet            Started container startup-liveness-container
  Warning  Unhealthy  57s (x3 over 59s)  kubelet            Startup probe failed: cat: can't open '/tempdir/ready': No such file or directory
  Warning  Unhealthy  54s (x2 over 55s)  kubelet            Liveness probe failed: cat: can't open '/tempdir/keepalive': No such file or directory

可以看到Startup Probe在第3次检测时 , /tempdir/ready文件还没创建。但是第4次时 , 就检测到了它 , 于是进入Liveness Probe检测状态。这从最后两个行为经历的时间差57s-54s=3s可以印证Startup Probe执行的次数大概在3次(因为检测周期periodSeconds是1秒) 。 Liveness Probe在第2次检测时 , /tempdir/keepalive还没创建。到第三次检测时 , 这个标志文件就创建了。于是整个Pod进入了Running状态。

6.3.2 启动和就绪探针

# startup_readiness.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: startup-readiness-deployment
spec:
  selector:
    matchLabels:
      app: startup-readiness
  template:
    metadata:
      labels:
        app: startup-readiness
    spec:
      containers:
      - name: startup-readiness-container
        image: busybox
        command: ["/bin/sh", "-c", "sleep 6; touch /tempdir/ready; sleep 3;touch /tempdir/readiness; while true; do sleep 5; done"]
        volumeMounts:
        - name:  probe-volume
          mountPath:  /tempdir
        startupProbe:
          exec:
            command:
            - cat
            - /tempdir/ready
          initialDelaySeconds: 3
          failureThreshold: 6
          periodSeconds: 1
          successThreshold: 1
        readinessProbe:
          exec:
            command:
            - cat
            - /tempdir/readiness
          failureThreshold: 6
          periodSeconds: 1
          successThreshold: 1
      volumes:
      - name: probe-volume
        emptyDir: 
          medium: Memory
          sizeLimit: 1Gi
kubernetes-pods-creating-3

其执行事件如下:

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  31s                default-scheduler  Successfully assigned default/startup-readiness-deployment-7bcb4b6bb5-ht6k9 to k8s-node02.cbear.top
  Normal   Pulling    31s                kubelet            Pulling image "busybox"
  Normal   Pulled     30s                kubelet            Successfully pulled image "busybox" in 443ms (443ms including waiting)
  Normal   Created    30s                kubelet            Created container startup-readiness-container
  Normal   Started    30s                kubelet            Started container startup-readiness-container
  Warning  Unhealthy  25s (x3 over 27s)  kubelet            Startup probe failed: cat: can't open '/tempdir/ready': No such file or directory
  Warning  Unhealthy  22s (x3 over 24s)  kubelet            Readiness probe failed: cat: can't open '/tempdir/readiness': No such file or directory

这次readiness检测到第4次时才认定状态为success。

上述两个实验可以证明:启动探针 ( Startup Probe ) 检测状态是success后 , 存活 ( Liveness Probe ) 和就绪探针(Readiness Probe)才开始检测。

kubernetes-pods-creating-4

6.3.3 存活和就绪探针

# liveness_readiness.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-readiness-deployment
spec:
  selector:
    matchLabels:
      app: liveness-readiness
  template:
    metadata:
      labels:
        app: liveness-readiness
    spec:
      containers:
      - name: liveness-readiness-container
        image: busybox
        command: ["/bin/sh", "-c", "sleep 3; touch /tempdir/keepalive; sleep 3;touch /tempdir/readiness; while true; do sleep 5; done"]
        volumeMounts:
        - name:  probe-volume
          mountPath:  /tempdir
        livenessProbe:
          exec:
            command:
            - cat
            - /tempdir/keepalive
          initialDelaySeconds: 3
          failureThreshold: 6
          periodSeconds: 1
          successThreshold: 1
        readinessProbe:
          exec:
            command:
            - cat
            - /tempdir/readiness
          failureThreshold: 6
          periodSeconds: 1
          successThreshold: 1
      volumes:
      - name: probe-volume
        emptyDir: 
          medium: Memory
          sizeLimit: 1Gi

通过Pod的Event可以看到 , Liveness和Readiness Probe的生命长度一致(如下图都是6秒)。

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  44s                default-scheduler  Successfully assigned default/liveness-readiness-deployment-6b8cf98b6b-ksqb5 to k8s-node01.cbear.top
  Normal   Pulling    44s                kubelet            Pulling image "busybox"
  Normal   Pulled     39s                kubelet            Successfully pulled image "busybox" in 5.426s (5.426s including waiting)
  Normal   Created    39s                kubelet            Created container liveness-readiness-container
  Normal   Started    38s                kubelet            Started container liveness-readiness-container
  Warning  Unhealthy  33s (x7 over 38s)  kubelet            Readiness probe failed: cat: can't open '/tempdir/readiness': No such file or directory
kubernetes-pods-creating-5

7. pod资源限制

所谓pod资源限制就是指限制对应pod里容器的cpu和内存使用量 ; 我们知道如果一个容器不限制其资源的使用大小 , 很有可能发生一个容器将宿主机上的内存耗尽的情况 , 如果一旦发生内存耗尽 , 内核很有可能向容器进程发起oom(out of memory),这样一来运行在docker上的其他容器也会相继退出 ; 所以为了不让类似的情况发生 , 我们有必要给pod里的容器做资源限定 ;

资源计量方式

对于cpu来讲 , 它是可压缩资源 , 所谓可以压缩资源就是表示cpu不够用时 , 它并不会报错 , pod可以等待 ; 对于内存来讲 , 它是不可压缩资源 , 不可压缩就是指如果内存不够用对应程序就会崩溃 , 从而导致容器退出 ; cpu的计量方式是m , 即1核心=1000m , 0.5个核心就等于500m ; 内存的计量方式默认单位是字节 , 我们在指定内存资源 , 直接加上单位即可 。 可以使用E、P、T、G、M、K为后缀单位 , 也可以使用Ei、Pi、Ti、Gi、Mi、Ki作为单位 。

示例 : 在资源清单中限制pod资源

[root@master01 ~]# cat resource.yaml
apiVersion: v1
kind: Pod
metadata:
  name: stress-pod
spec:
  containers:
  - name: stress
    image: ikubernetes/stress-ng
    command: ["/usr/bin/stress-ng", "-c 1", "-m 1", "--metrics-brief"]
    resources:
      requests:
        memory: "128Mi"
        cpu: "200m"
      limits:
        memory: "512Mi"
        cpu: "400m"
[root@master01 ~]#

提示 : 定义pod的资源限制 , 需要用到resources这个字段 , 这个字段的值为一个对象;其中requests字段用于指定下限 , limits指定资源的上限。

应用资源清单

[root@master01 ~]# kubectl apply -f resource.yaml
pod/stress-pod created
[root@master01 ~]# kubectl get pod -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP            NODE             NOMINATED NODE   READINESS GATES
liveness-exec                1/1     Running   2          147m    10.244.3.21   node03.k8s.org   <none>           <none>
liveness-httpget             1/1     Running   1          118m    10.244.2.14   node02.k8s.org   <none>           <none>
liveness-tcpsocket           1/1     Running   1          105m    10.244.3.22   node03.k8s.org   <none>           <none>
myapp-dep-5bc4d8cc74-cvkbc   1/1     Running   3          8d      10.244.1.16   node01.k8s.org   <none>           <none>
myapp-dep-5bc4d8cc74-gmt7w   1/1     Running   4          8d      10.244.3.17   node03.k8s.org   <none>           <none>
myapp-dep-5bc4d8cc74-gqhh5   1/1     Running   3          8d      10.244.2.11   node02.k8s.org   <none>           <none>
nginx-pod-demo7              1/1     Running   1          7h12m   10.244.1.14   node01.k8s.org   <none>           <none>
ngx-dep-5c8d96d457-w6nss     1/1     Running   3          8d      10.244.2.12   node02.k8s.org   <none>           <none>
readiness-demo               1/1     Running   0          82m     10.244.3.23   node03.k8s.org   <none>           <none>
stress-pod                   1/1     Running   0          13s     10.244.2.16   node02.k8s.org   <none>           <none>
[root@master01 ~]#

提示 : 可以看到stress-pod被调度到node02上运行了。

测试 : 在node02上使用docker stats命令查看对应stress-pod容器占用资源情况

1503305-20201216212844762-967922449

提示 : 可以看到在node02上跑的k8s_stress_stress-pod_default容器占有cpu和内存都是我们在资源清单中定义的量。

示例 : 当pod里的容器资源不够用时 , 对应pod是否会发生oom呢?

[root@master01 ~]# cat memleak-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: memleak-pod
spec:
  containers:
  - name: simmemleak
    image: saadali/simmemleak
    resources:
      requests:
        memory: "64Mi"
        cpu: "1"
      limits:
        memory: "1Gi"
        cpu: "1"
[root@master01 ~]#

提示 : 以上配置清单主要限制了容器最大内存为1G , 最小内存为64M , cpu上下限都为1核心。

应用配置清单

[root@master01 ~]# kubectl apply -f memleak-pod.yaml
pod/memleak-pod created
[root@master01 ~]# kubectl get pod
NAME                         READY   STATUS              RESTARTS   AGE
liveness-exec                1/1     Running             2          155m
liveness-httpget             1/1     Running             1          126m
liveness-tcpsocket           1/1     Running             1          113m
memleak-pod                  0/1     ContainerCreating   0          2s
myapp-dep-5bc4d8cc74-cvkbc   1/1     Running             3          8d
myapp-dep-5bc4d8cc74-gmt7w   1/1     Running             4          8d
myapp-dep-5bc4d8cc74-gqhh5   1/1     Running             3          8d
nginx-pod-demo7              1/1     Running             1          7h21m
ngx-dep-5c8d96d457-w6nss     1/1     Running             3          8d
readiness-demo               1/1     Running             0          90m
stress-pod                   1/1     Running             0          8m46s
[root@master01 ~]# kubectl get pod
NAME                         READY   STATUS      RESTARTS   AGE
liveness-exec                1/1     Running     2          156m
liveness-httpget             1/1     Running     1          126m
liveness-tcpsocket           1/1     Running     1          114m
memleak-pod                  0/1     OOMKilled   0          21s
myapp-dep-5bc4d8cc74-cvkbc   1/1     Running     3          8d
myapp-dep-5bc4d8cc74-gmt7w   1/1     Running     4          8d
myapp-dep-5bc4d8cc74-gqhh5   1/1     Running     3          8d
nginx-pod-demo7              1/1     Running     1          7h21m
ngx-dep-5c8d96d457-w6nss     1/1     Running     3          8d
readiness-demo               1/1     Running     0          91m
stress-pod                   1/1     Running     0          9m5s
[root@master01 ~]#

提示 : 可以看到应用资源清单以后 , 对应的pod处于OOMKilled状态。原因是我们运行的镜像里面的程序一直申请内存 , 超出了最大限制。

查看pod详细信息

1503305-20201216213948456-529852692

提示 : 可以看到当前pod状态为terminated状态,原因是OOMKilled 。上一次状态为terminated , 原因也是OOMKilled。


参考链接

研发工程师玩转Kubernetes——启动、存活和就绪探针-腾讯云开发者社区-腾讯云 (tencent.com)

容器编排系统k8s之Pod生命周期、健康/就绪状态探测以及资源限制 - Linux-1874 - 博客园 (cnblogs.com)