kubernetes | init 容器 & 探针 & 钩子

2022年3月17日 · 4056 字 · 9 分钟 · Kubernetes

一、概述

1、当一个 pod 被创建的时候,会启动第一个容器 pause。
作用:挂载可能存在的存储卷,初始化网络栈,后来僵尸进程的杀死。维护。
2、pause 容器启动后,就会启动另一个部分,不是 mainC,而是 initC(初始化容器,initC >= 0)

initC 初始化容器的特性:

  • 线性启动 阻塞特性,前一个 initC-1 必须退出,后面的 initC-2 才会运行。执行退出命令的返回码为 0,代表成功退出。如果 initC 启动过程中失败,出现错误,那么 pause 会重新开始创建 initC-1,initC-2,initC-3 等等。只有所有 initC 全部成功启动退出,才会继续往下运行。
  • 独立于应用容器 Init 容器可以有自己的独立镜像,这与应用容器使用的镜像可以完全不同。因此,它们具有其特殊的依赖和工具,不会影响应用容器的镜像构成。
  • 运行到完成 Init 容器必须在终止前运行到完成。它们不是用来启动长期运行的进程的。一旦它们成功执行了启动命令,它们就会终止。
  • 重新启动策略 如果 Init 容器失败,Kubernetes 默认的重新启动策略是 ‘Always’,它会一直重试直到容器成功为止。这与应用容器的启动失败策略是独立的。

二、init 容器

2.1、概述

Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。 Init 容器与普通的容器非常像,除了如下两点: 1、Init 容器总是运行到成功完成为止。 2、每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。 如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 restartPolicy 为 Never,它不会重新启动。

2.2、init 容器作用

因为 Init 容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势: 1、可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这些实用工具的。 2、应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。 3、Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。
4、它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init 容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。

2.3、InitC 容器示例

点击查看完整的 initC-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.35.0
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.35.0
    command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox:1.35.0
    command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

串行与阻塞

主应用 myapp-container 必须等待两个“前置条件”完成后才会启动。
Init Containers 的运行规则如下:
按序运行:它们会按照你在 YAML 中写的顺序(先 init-myservice,再 init-mydb)一个接一个启动。
成功才继续:只有前一个 Init 容器成功退出(Exit Code 0),下一个才会开始。
阻塞主容器:在所有 Init 容器运行成功之前,主容器 myapp-container 永远不会启动,Pod 状态会一直卡在 Init:0/2。

脚本功能拆解

  • init-myservice:使用 nslookup 检查集群 DNS 中是否存在名为 myservice 的服务。如果找不到,它会每隔 2 秒打印一次 “waiting for myservice” 并循环,直到该服务被创建。
  • init-mydb:同理,它在等一个名为 mydb 的服务。
  • myapp-container:只有当上述两个域名都能解析出 IP 时,它才会启动并输出 “The app is running!"。

操作

kubectl apply -f initC-pod.yaml
 
kubectl get pod
 
kubectl describe pod myapp-pod

Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  24s   default-scheduler  Successfully assigned default/myapp-pod to k8s-node02
  Normal  Pulled     23s   kubelet, k8s-node02  Container image "busybox:1.35.0" already present on machine
  Normal  Created    23s   kubelet, k8s-node02  Created container init-myservice

查看日志

# 因为这里无法解析 myservice 的 dns 地址,所以才会报错
kubectl logs myapp-pod -c init-myservice

** server can't find myservice.default.svc.cluster.local: NXDOMAIN

*** Can't find myservice.svc.cluster.local: No answer
*** Can't find myservice.cluster.local: No answer
*** Can't find myservice.default.svc.cluster.local: No answer
*** Can't find myservice.svc.cluster.local: No answer
*** Can't find myservice.cluster.local: No answer

waiting for myservice

创建 myservice

kubectl create svc clusterip myservice --tcp=80:80
 
kubectl get svc

# 这里只加载好一个是因为,只解析了 myservice 的 DNS,找不到 mydb 的 DNS,所以无法加载
kubectl get pod

kubectl create svc clusterip mydb --tcp=80:80
 
# 稍等一会再次查看,就会加载完成
kubectl get pod

三、容器探针

3.1、探针概述

探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。有三种类型的处理程序:

  • ExecAction 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
  • TCPSocketAction 对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
  • HTTPGetAction 对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的

3.2、探针类型

K8s 中存在两种类型的探针:liveness probe 和 readiness probe。

  • livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其重启策略的影响。如果容器不提供存活探针,则默认状态为 Success。
  • readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success。

四、liveness probe(存活探针)

用于判断容器是否存活,即 Pod 是否为 running 状态,如果 LivenessProbe 探针探测到容 器不健康,则 kubelet 将 kill 掉容器,并根据容器的重启策略是否重启。如果一个容器不包含 LivenessProbe 探针,则 Kubelet 认为容器的 LivenessProbe 探针的返回值永远成功。有时应用程序可能因为某些原因(后端服务故障等)导致暂时无法对外提供服务,但应用软件没有终止,导致 K8S 无法隔离有故障的 pod,调用者可能会访问到有故障的 pod,导致业务不稳定。K8S 提供 livenessProbe 来检测应用程序是否正常运行,并且对相应状况进行相应的补救措施。

五、readiness probe(就绪探针)

用于判断容器是否启动完成,即容器的 Ready 是否为 True,可以接收请求,如果 ReadinessProbe 探测失败,则容器的 Ready 将为 False,控制器将此 Pod 的 Endpoint 从对 应的 service 的 Endpoint 列表中移除,从此不再将任何请求调度此 Pod 上,直到下次探测成功。通过使用 Readiness 探针,Kubernetes 能够等待应用程序完全启动,然后才允许服务将流量发送到新副本。
比如使用 tomcat 的应用程序来说,并不是简单地说 tomcat 启动成功就可以对外提供服务的,还需要等待 spring 容器初始化,数据库连接没连上等等。对于 spring boot 应用,默认的 actuator 带有 /health 接口,可以用来进行启动成功的判断。

六、每类探针都支持三种探测方法:

  • exec 通过执行命令来检查服务是否正常,针对复杂检测或无 HTTP 接口的服务,命令返回值为 0 则表示容器健康。
  • httpGet 通过发送 http 请求检查服务是否正常,返回 200-399 状态码则表明容器健康。
  • tcpSocket 通过容器的 IP 和 Port 执行 TCP 检查,如果能够建立 TCP 连接,则表明容器健康。

七、探针探测的结果

  • Success Container 通过了检查。
  • Failure Container 未通过检查。
  • Unknown 未能执行检查,因此不采取任何措施。

八、Pod 重启策略:

  • Always 总是重启
  • OnFailure 如果失败就重启
  • Never 永远不重启

七、示例

7.1、readinessProbe - 就绪检测示例

点击查看完整的 readiness-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: readiness-httpget-pod
  namespace: default
  labels:
    app: myapp
    env: test
spec:
  containers:
  - name: readiness-httpget-container
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    readinessProbe:
      httpGet:
        port: 80
        path: /index1.html
      initialDelaySeconds: 1	# 延迟的间隔是一秒
      periodSeconds: 3		# 检测的间隔时间是 3 秒	
      timeoutSeconds: 3		# 超时时间是 3 秒

操作

kubectl create -f readiness-pod.yaml

# 未就绪状态,因为请求不到 index1.html 文件
kubectl get pod

# 进入 pod 容器
kubectl exec -it readiness-httpget-pod -- /bin/sh

#pod 中添加 index1.html 文件
cd /usr/share/nginx/html/
 
echo "123" > index1.html
 
exit

# 就绪状态,可以正常对外提供访问
kubectl get pod

核心逻辑

与 Liveness Probe(存活检测,失败会重启容器)不同,Readiness Probe 失败时:

  • 不会重启容器。
  • 切断流量:Kubernetes 会将该 Pod 的 IP 从关联 Service 的 Endpoints 列表中移除。
  • 保护用户:确保用户请求不会被发送到尚未准备好或发生故障的 Pod 上。

配置细节

  • 检测方式:每隔 3 秒 (periodSeconds) 向容器的 80 端口发起一个 HTTP GET 请求,访问 /index1.html。
  • 等待期:容器启动后,先等 1 秒 (initialDelaySeconds) 再开始第一次检测。
  • 判定标准:
    • 如果返回状态码在 200-399 之间,认为成功,Pod 状态变为 Ready,Service 开始接入流量。
    • 如果请求超过 3 秒 (timeoutSeconds) 没反应,或者返回 404/500,认为失败。

7.2、livenessProbe - 存活检测示例

点击查看完整的 livenessProbe-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec-pod
  namespace: default
spec:
  restartPolicy: Never            # 由于设置了 Never,Pod 不会重启,而是会直接进入 Completed 或 Error 状态。
  containers:
  - name: liveness-exec-container
    image: busybox:1.35.0
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","touch /tmp/live ; sleep 60; rm -rf /tmp/live; sleep 3600"]
    livenessProbe:
      exec:
        command: ["test","-e","/tmp/live"]	
      initialDelaySeconds: 1	      # 延迟的间隔是一秒
      periodSeconds: 3		      # 检测的间隔时间是 3 秒	
      timeoutSeconds: 3		      # 超时时间是 3 秒

操作

kubectl apply -f livenessProbe-pod.yaml
 
kubectl get pod

# 再次查看,已处于 error 状态。
# 文件被删除,存活探测失败
kubectl get pod

7.3、livenessProbe-tcp– 检测端口模板

apiVersion: v1
kind: Pod
metadata:
  name: probe-tcp
spec:
  containers:
  - name: nginx
    image: nginx:latest
    livenessProbe:		# 存活探测
      initialDelaySeconds: 5	# 初始化间隔时间时 5 秒
      timeoutSeconds: 1		# 超时时间是 1 秒
      periodSeconds: 3		# 检测间隔是 3 秒
      tcpSocket:
        port: 80                # 检测的端口是 80

说明:这种方式不太好,检测端口是否存在,不代表服务能正常响应。

7.4、其他示例

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
    - name: goproxy
      image: k8s.gcr.io/goproxy:0.1
      ports:
        - containerPort: 8080
      readinessProbe:
        tcpSocket:
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 10
      livenessProbe:
        tcpSocket:
          port: 8080
        initialDelaySeconds: 15
        periodSeconds: 20

探针 (Probe) 有许多可选字段,可以用来更加精确的控制 Liveness 和 Readiness 两种探针的行为。这些参数包括:

  • initialDelaySeconds 容器启动后第一次执行探测是需要等待多少秒。
  • periodSeconds 执行探测的频率。默认是 10 秒,最小 1 秒。
  • timeoutSeconds 探测超时时间。默认 1 秒,最小 1 秒。
  • successThreshold 探测失败后,最少连续探测成功多少次才被认定为成功。默认是 1。对于 liveness 必须是 1。最小值是 1。
  • failureThreshold 探测成功后,最少连续探测失败多少次才被认定为失败。默认是 3。最小值是 1。

八、钩子

8.1、概述

Pod hook(钩子)是由 Kubernetes 管理的 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。可以同时为 Pod 中的所有容器都配置 hook。

Hook 的类型包括两种:

  • exec:执行一段命令。
  • HTTP:发送 HTTP 请求。

启动后钩子可以编译软件,挂载存储。关闭前钩子可以保存文件,解除存储。

8.2、yaml 模板

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: nginx:latest
    lifecycle:
      postStart:		# 启动后
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:			# 关闭前
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the preStop handler > /usr/share/message"]

8.3、示例

点击查看完整的 lifecycle-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: nginx:latest
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the preStop handler > /usr/share/message"]

操作

kubectl apply -f lifecycle-pod.yaml
 
kubectl get pod

# 进入容器
kubectl exec -it lifecycle-demo -- /bin/sh

# pod 中执行
while 2>1;do cat /usr/share/message;done

# 再开一个终端删除 pod,然后观察原终端的返回信息
kubectl delete pod --all

[root@k8s-master01 4]# kubectl delete pod --all
pod "lifecycle-demo" deleted
pod "liveness-exec-pod" deleted
pod "liveness-httpget-pod" deleted
pod "pod-1" deleted
pod "pod-2" deleted
pod "pod-3" deleted
pod "readiness-httpget-pod" deleted

Hello from the postStart handler
Hello from the postStart handler
Hello from the preStop handler