k8s基础篇 pod(十)Pod容器探测和钩子

时间:2023-03-27 20:56:16

10.1 容器钩子:postStart和preStop

postStart:容器创建成功后,运行前的任务,用于资源部署、环境准备等。 preStop:在容器被终止前的任务,用于优雅关闭应用程序、通知其他系统等。

演示postStart和preStop用法
......
containers:
- image: sample:v2  
     name: war
     lifecycle:
      postStart:
       exec:
         command:
          - “cp”
          - “/sample.war”
          - “/app”
      prestop:
       httpGet:
        host: monitor.com
        path: /waring
        port: 8080
        scheme: HTTP
......

以上示例中,定义了一个Pod,包含一个JAVA的web应用容器,其中设置了PostStart和PreStop回调函数。即在容器创建成功后,复制
/sample.war到/app文件夹中。而在容器终止之前,发送HTTP请求到http://monitor.com:8080/waring,即向监控系统发送警告。

优雅的删除资源对象
当用户请求删除含有pod的资源对象时(如RC、deployment等),K8S为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件)
,K8S提供两种信息通知:
1)、默认:K8S通知node执行docker stop命令,docker会先向容器中PID为1的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,
如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送SIGKILL的系统信号强行kill掉进程。

2)、使用pod生命周期(利用PreStop回调函数),它执行在发送终止信号之前。
默认情况下,所有的删除操作的优雅退出时间都在30秒以内。kubectl delete命令支持--grace-period=的选项,以运行用户来修改默认值。0表示
删除立即执行,并且立即从API中删除pod。在节点上,被设置了立即结束的的pod,仍然会给一个很短的优雅退出时间段,才会开始被强制杀死。如下:

    spec:
      containers:
      - name: nginx-demo
        image: centos:nginx
        lifecycle:
          preStop:
            exec:
              # nginx -s quit gracefully terminate while SIGTERM triggers a quick exit
              command: ["/usr/local/nginx/sbin/nginx","-s","quit"]
        ports:
          - name: http
            containerPort: 80

10.2 存活性探测livenessProbe和就绪性探测readinessProbe

livenessProbe:存活性探测
  许多应用程序经过长时间运行,最终过渡到无法运行的状态,除了重启,无法恢复。通常情况下,K8S会发现应用程序已经终止,然后重启应用程序
  pod。有时应用程序可能因为某些原因(后端服务故障等)导致暂时无法对外提供服务,但应用软件没有终止,导致K8S无法隔离有故障的pod,调用
  者可能会访问到有故障的pod,导致业务不稳定。K8S提供livenessProbe来检测容器是否正常运行,并且对相应状况进行相应的补救措施。 

readinessProbe:就绪性探测
在没有配置readinessProbe的资源对象中,pod中的容器启动完成后,就认为pod中的应用程序可以对外提供服务,该pod就会加入相对应的service
,对外提供服务。但有时一些应用程序启动后,需要较长时间的加载才能对外服务,如果这时对外提供服务,执行结果必然无法达到预期效果,影响用
户体验。比如使用tomcat的应用程序来说,并不是简单地说tomcat启动成功就可以对外提供服务的,还需要等待spring容器初始化,数据库连接上等
等。

目前LivenessProbe和ReadinessProbe两种探针都支持下面三种探测方法:
1、ExecAction:在容器中执行指定的命令,如果执行成功,退出码为 0 则探测成功。
2、TCPSocketAction:通过容器的 IP 地址和端口号执行 TCP 检 查,如果能够建立 TCP 连接,则表明容器健康。
3、HTTPGetAction:通过容器的IP地址、端口号及路径调用 HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康

探针探测结果有以下值:
1、Success:表示通过检测。
2、Failure:表示未通过检测。
3、Unknown:表示检测没有正常进行。

Pod探针相关的属性:
探针(Probe)有许多可选字段,可以用来更加精确的控制Liveness和Readiness两种探针的行为
    initialDelaySeconds: Pod启动后首次进行检查的等待时间,单位“秒”。
    periodSeconds: 检查的间隔时间,默认为10s,单位“秒”。
    timeoutSeconds: 探针执行检测请求后,等待响应的超时时间,默认为1s,单位“秒”。
successThreshold:连续探测几次成功,才认为探测成功,默认为 1,在 Liveness 探针中必须为1,最小值为1。
 
failureThreshold: 探测失败的重试次数,重试一定次数后将认为失败,在 readiness 探针中,Pod会被标记为未就绪,默认为 3,最小值为 1

两种探针区别:
ReadinessProbe 和 livenessProbe 可以使用相同探测方式,只是对 Pod 的处置方式不同:
readinessProbe 当检测失败后,将 Pod 的 IP:Port 从对应的 EndPoint 列表中删除。
livenessProbe 当检测失败后,将杀死容器并根据 Pod 的重启策略来决定作出对应的措施。

1、LivenessProbe 探针使用示例

(1)、通过exec方式做健康探测

[root@k8s01 test1]# cat liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
  labels:
    app: liveness
spec:
  containers:
  - name: liveness
    image: busybox:1.28
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","touch /tmp/healthy && sleep 30 && rm -f /tmp/healthy && sleep 600"]
    livenessProbe:
      initialDelaySeconds: 10 #延迟加载时间
      periodSeconds: 5        #重试时间间隔
      exec:
        command:
        - cat
        - /tmp/healthy

kubectl apply -f liveness-exec.yaml
[root@k8s01 test1]# kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP               NODE    NOMINATED NODE   READINESS GATES
liveness-exec   1/1     Running   1          81s   10.244.236.132   k8s02   <none>           <none>

容器启动设置执行的命令:
 /bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"
容器在初始化后,首先创建一个 /tmp/healthy 文件,然后执行睡眠命令,睡眠 30 秒,到时间后执行删除 /tmp/healthy 文件命令。而设置的
存活探针检检测方式为执行 shell 命令,用 cat 命令输出 healthy 文件的内容,如果能成功执行这条命令,存活探针就认为探测成功,否则探测
失败。在前 30 秒内,由于文件存在,所以存活探针探测时执行 cat /tmp/healthy 命令成功执行。30 秒后 healthy 文件被删除,所以执行命
令失败,Kubernetes 会根据 Pod 设置的重启策略来判断,是否重启 Pod。

(2)、通过HTTP方式做健康探测

示例文件 liveness-http.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-http
  labels:
    test: liveness
spec:
  containers:
  - name: liveness
    image: mydlqclub/springboot-helloworld:0.0.1
    livenessProbe:
      initialDelaySeconds: 20   #延迟加载时间
      periodSeconds: 5          #重试时间间隔
      timeoutSeconds: 10        #超时时间设置
      httpGet:
        scheme: HTTP
        port: 8081
        path: /actuator/health

上面 Pod 中启动的容器是一个 SpringBoot 应用,其中引用了 Actuator 组件,提供了 /actuator/health 健康检查地址,存活探针可以使用
HTTPGet 方式向服务发起请求,请求 8081 端口的 /actuator/health 路径来进行存活判断:

任何大于或等于200且小于400的代码表示探测成功。
任何其他代码表示失败。

如果探测失败,则会杀死 Pod 进行重启操作。

httpGet探测方式有如下可选的控制字段:
scheme: 用于连接host的协议,默认为HTTP。
host:要连接的主机名,默认为Pod IP,可以在http request head中设置host头部。
port:容器上要访问端口号或名称。
path:http服务器上的访问URI。
httpHeaders:自定义HTTP请求headers,HTTP允许重复headers。

(3)、通过TCP方式做健康探测

示例文件 liveness-tcp.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-tcp
  labels:
    app: liveness
spec:
  containers:
  - name: liveness
    image: nginx
    livenessProbe:
      initialDelaySeconds: 15
      periodSeconds: 20
      tcpSocket:
        port: 80
TCP 检查方式和 HTTP 检查方式非常相似,在容器启动 initialDelaySeconds 参数设定的时间后,kubelet 将发送第一个 livenessProbe 
探针,尝试连接容器的 80 端口,如果连接失败则将杀死 Pod 重启容器。

2、ReadinessProbe 探针使用示例

Pod 的ReadinessProbe 探针使用方式和 LivenessProbe 探针探测方法一样,也是支持三种,只是一个是用于探测应用的存活,一个是判断是否
对外提供流量的条件。这里用一个 Springboot 项目,设置 ReadinessProbe 探测 SpringBoot 项目的 8081 端口下的 /actuator/health 
接口,如果探测成功则代表内部程序以及启动,就开放对外提供接口访问,否则内部应用没有成功启动,暂不对外提供访问,直到就绪探针探测成功。
示例文件 readiness-exec.yaml
apiVersion: v1
kind: Service
metadata:
  name: springboot
  labels:
    app: springboot
spec:
  type: NodePort
  ports:
  - name: server
    port: 8080
    targetPort: 8080
    nodePort: 31180
  - name: management
    port: 8081
    targetPort: 8081
    nodePort: 31181
  selector:
    app: springboot
---
apiVersion: v1
kind: Pod
metadata:
  name: springboot
  labels:
    app: springboot
spec:
  containers:
  - name: springboot
    image: mydlqclub/springboot-helloworld:0.0.1
    ports:
    - name: server
      containerPort: 8080
    - name: management
      containerPort: 8081
    readinessProbe:
      initialDelaySeconds: 20   
      periodSeconds: 5          
      timeoutSeconds: 10   
      httpGet:
        scheme: HTTP
        port: 8081
        path: /actuator/health

3、ReadinessProbe + LivenessProbe 配合使用示例

一般程序中需要设置两种探针结合使用,并且也要结合实际情况,来配置初始化检查时间和检测间隔,下面列一个简单的 SpringBoot 项目的 Deployment 例子。

apiVersion: v1
kind: Service
metadata:
  name: springboot
  labels:
    app: springboot
spec:
  type: NodePort
  ports:
  - name: server
    port: 8080
    targetPort: 8080
    nodePort: 31180
  - name: management
    port: 8081
    targetPort: 8081
    nodePort: 31181
  selector:
    app: springboot
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: springboot
  labels:
    app: springboot
spec:
  replicas: 1
  selector:
    matchLabels:
      app: springboot
  template:
    metadata:
      name: springboot
      labels:
        app: springboot
    spec:
      containers:
      - name: readiness
        image: mydlqclub/springboot-helloworld:0.0.1
        ports:
        - name: server 
          containerPort: 8080
        - name: management
          containerPort: 8081
        readinessProbe:
          initialDelaySeconds: 20 
          periodSeconds: 5      
          timeoutSeconds: 10        
          httpGet:
            scheme: HTTP
            port: 8081
            path: /actuator/health
         livenessProbe:
            initialDelaySeconds: 30 
            periodSeconds: 10 
            timeoutSeconds: 5 
            httpGet:
              scheme: HTTP
              port: 8081
              path: /actuator/health
         
#kubectl taint nodes k8s01 node-type=production:NoSchedule
以后试验方便镜像扔一个k8s02就好

4、startupProbe启动探测

1、三种探针
livenessProbe:用于探测容器是否运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其重启策略的影响决定是否重启。如果容器
不提供存活探针,则默认状态为 Success。 
readinessProbe:一般用于探测容器内的程序是否健康,容器是否准备好服务请求。如果就绪探测失败,endpoint 将从与 Pod 匹配的所有 
Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success。 
startupProbe: 探测容器中的应用是否已经启动。如果提供了启动探测(startup probe),则禁用所有其他探测,直到它成功为止。如果启动探测失
败,kubelet 将杀死容器,容器服从其重启策略进行重启。
如果容器没有提供启动探测,则默认状态为成功 Success。 

可以自定义在 pod 启动时是否执行这些检测,如果不设置,则检测结果均默认为通过,如果设置,则顺序为 startupProbe>readinessProbe>
livenessProbe。
 
2、为什么要用 startupProbe? 
在 k8s 中,通过控制器管理 pod,如果更新 pod 的时候,会创建新的 pod,删除老的 pod,但是如果新的 pod 创建了,pod 里的容器还没完成
初始化,老的 pod 就被删除了,会导致访问 service 或者ingress 时候,访问到的 pod 是有问题的,所以 k8s 就加入了一些存活性探针:
livenessProbe、就绪性探针 readinessProbe 以及这节课要介绍的启动探针 startupProbe。 

startupProbe 是在 k8s v1.16 加入了 alpha 版,官方对其作用的解释是: 
Indicates whether the application within the Container is started. All other probes are 
disabled if a startup probe is provided, until it succeeds. If the startup probe fails, the kubelet kills the 
Container, and the Container is subjected to its restart policy. If a Container does not provide a startup probe, 
the default state is Success 
翻译:判断容器内的应用程序是否已启动。如果提供了启动探测,则禁用所有其他探测,直到它成功为止。如果启动探测失败,kubelet 将杀死容器,
容器将服从其重启策略。如果容器没有提供启动探测,则默认状态为成功。 
注意:不要将 startupProbe 和 readinessProbe 混淆。

3、什么时候会用 startupProbe 呢?
正常情况下,我们会在 pod template 中配置 livenessProbe 来探测容器是否正常运行,如果异常则会触发restartPolicy 重启容器(因为默
认情况下 restartPolicy 设置的是 always)。
如下:
livenessProbe:
  httpGet:
    path: /test
    port: 80
  failureThreshold: 1
  initialDelaySeconds: 10
  periodSeconds: 10
#上面配置的意思是容器启动 10s 后每 10s 检查一次,允许失败的次数是 1 次。如果失败次数超过 1 则会触发 restartPolicy。 
#但是有时候会存在特殊情况,比如服务 A 启动时间很慢,需要 60s。这个时候如果还是用上面的探针就会进入死循环,因为上面的探针 10s 后就开
始探测,这时候我们服务并没有起来,发现探测失败就会触发restartPolicy。这时候有的朋友可能会想到把 initialDelay 调成 60s 不就可以了
?但是我们并不能保证这个服务每次起来都是 60s,假如新的版本起来要 70s,甚至更多的时间,我们就不好控制了。有的朋友可能还会想到把失败
次数增加,比如下面配置: 
livenessProbe:
  httpGet:
    path: /test
    port: 80
  failureThreshold: 5
  initialDelaySeconds: 60
  periodSeconds: 10

#这在启动的时候是可以解决我们目前的问题,但是如果这个服务挂了呢?如果 failureThreshold=1 则10s 后就会报警通知服务挂了,如果设置了
failureThreshold=5,那么就需要 5*10s=50s 的时间,在现在大家追求快速发现、快速定位、快速响应的时代是不被允许的。 在这时候我们把
startupProbe 和 livenessProbe 结合起来使用就可以很大程度上解决我们的问题。 如下: 
startupProbe:
  httpGet:
    path: /test
    port: 80
  failureThreshold: 10
  initialDelaySeconds: 10
  periodSeconds: 10
#上面的配置是只有 startupProbe 探测成功后再交给 livenessProbe。我们 startupProbe 配置的是10*10s,也就是说只要应用在 100s 内
启动都是 OK 的,而且应用挂掉了 10s 就会发现问题。 其实这种还是不能确定具体时间,只能给出一个大概的范围。我个人认为对服务启动时间的
影响因素太多了,有可能是应用本身,有可能是外部因素,比如主机性能等等。我们只有在最大程度上追求高效、稳定,但是我们不能保证 100%稳定
,像阿里这样的大企业对外宣称的也是 5 个 9,6 个 9 的稳定率,如果出问题了,不好意思你恰恰不在那几个 9 里面,所以我们自己要做好监控有
效性,告警的及时性,响应的快速性,处理的高效性

4、K8s 的 LivenessProbe 和ReadinessProbe 的启动顺序问题
LivenessProbe 会导致 pod 重启,ReadinessProbe 只是不提供服务 
我们最初的理解是 LivenessProbe 会在 ReadinessProbe 成功后开始检查,但事实并非如此。 kubelet 使用存活探测器来知道什么时候要重启容
器。 例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。 这样的情况下重启容器有助于让应用程序在有问题的情
况下可用。 
kubelet 使用就绪探测器可以知道容器什么时候准备好了并可以开始接受请求流量, 当一个 Pod 内的所有容器都准备好了,才能把这个 Pod 看作
就绪了。这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。 在 Pod 还没有准备好的时候,会从 Service 的负载均衡器中被剔除的。 

kubelet 使用启动探测器(startupProbe)可以知道应用程序容器什么时候启动了。 如果配置了这类探测器,就可以控制容器在启动成功后再进行存
活性和就绪检查, 确保这些存活、就绪探测器不会影响应用程序的启动。 这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀
掉。 
真正的启动顺序 

https://github.com/kubernetes/kubernetes/issues/60647 
https://github.com/kubernetes/kubernetes/issues/27114 
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes 

官方文档:Caution: Liveness probes do not wait for readiness probes to succeed. If you want to wait before executing
a liveness probe you should use initialDelaySeconds or a startupProbe. 
也就是 Liveness probes 并不会等到 Readiness probes 成功之后才运行根据上面的官方文档,Liveness 和 readiness 应该是某种并发的
关系