深入理解Pod-Pod Hook

1、什么是Pod Hook

我们知道 Pod 是 Kubernetes 集群中的最小单元,而Pod 是有容器组组成的,一个Pod可以有多个容器组成。所以在讨论 Pod 的⽣命周期的时候我们可以先来讨论下容器的生命周期。

实际上 Kubernetes 为我们的容器提供了生命周期钩⼦函数,就是我们说的 Pod Hook ,Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运⾏,这是包含在容器的生命周期之中,我们可以同时为 Pod 中的所有容器都配置 hook。

Kubernetes 为我们提供了两种钩子函数:

  • PostStart:这个钩⼦在容器创建后立即执⾏。但是,并不能保证钩子将在容器 ENTRYPOINT 之前运
    ⾏,因为没有参数传递给处理程序。主要⽤于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以⾄于不能运⾏或者挂起, 容器将不能达到 running 状态。
  • PreStop:这个钩子在容器终止之前立即被调⽤。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调⽤发出之前完成。主要⽤于优雅关闭应⽤程序、通知其他系统等。如果钩⼦在执⾏期间挂起, Pod阶段将停留在 running 状态并且永不会达到 failed 状态。

如果 PostStart 或者 PreStop 钩子失败, 它会杀死容器。所以我们应该让钩⼦函数尽可能的轻量。当然有些情况下,长时间运⾏命令是合理的, ⽐如在停⽌容器之前预先保存状态。

另外我们有两种⽅式来实现上⾯的钩⼦函数:

  • Exec – ⽤于执⾏⼀段特定的命令,不过要注意的是该命令消耗的资源会被计⼊容器。
  • HTTP – 对容器上的特定的端点执⾏ HTTP 请求。

2、环境准备

下面我们一起来看一个示例,我们定义了⼀个Nginx Pod,其中设置了 PostStart 钩子函数,即在容器创建成功后,写⼊⼀句话到 /usr/share/message ⽂件中,示例代码如下:

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo1
spec:
  containers:
  - name: hook-demo1
    image: nginx
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]

通过kubectl apply -f pod-hook.yaml 命令我们创建一个Pod:

img

注:我们通过上面的文件给容器里面写入一段话:Hello from the postStart handler,容器启动过以后我们就可以去容器里面验证一下。通过 kubectl exec hook-demo1 -i -t /bin/bash 命令我们进入到hook-demo1容器里面去,然后查看 cat /usr/share/message 文件、发现我们之前写入的那句话,验证了postStart钩子函数:

img

注:kubectl exec 的详细用法可以通过 kubectl exec –help 命令查看。

3、优雅删除资源对象

当⽤户请求删除含有 Pod 的资源对象时(如Deployment等),K8S 为了让应⽤程序优雅关闭(即让应⽤程序完成正在处理的请求后,再关闭软件),K8S提供两种信息通知:

  • 默认:K8S 通知 node 执⾏ docker stop 命令,docker 会先向容器中 PID 为1的进程发送系统信号 SIGTERM ,然后等待容器中的应⽤程序终止执⾏,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送 SIGKILL 的系统信号强行 kill 掉进程。
  • 使⽤ pod ⽣命周期(利⽤ PreStop 回调函数),它执⾏在发送终止信号之前。

默认所有的优雅退出时间都在30秒内。kubectl delete 命令⽀持 –grace-period= 选项,这个选项允许⽤户⽤他们⾃⼰指定的值覆盖默认值。值’0’代表 强制删除 pod. 在 kubectl 1.5 及以上的版本⾥,执⾏强制删除时必须同时指定 –force –grace-period=0 。

例如、我们通过 kubectl delete pod hook-demo1 –grace-period=0 –force 命令强制删除hook-demo1 容器:

img

通过Wainning信息我们可以看到:强制删除⼀个 pod 是从集群状态还有 etcd ⾥⽴刻删除这个 pod。 当 Pod 被强制删除时, api 服务器不会等待来⾃ Pod 所在节点上的 kubelet 的确认信息:pod 已经被终⽌。在 API ⾥ pod 会被⽴刻删除,在节点上, pods 被设置成⽴刻终⽌后,在强⾏杀掉前还会有⼀个很⼩的宽限期。

我们我们还是通过一个示例来看看如何优雅的关闭容器,首先我们定义⼀个Nginx Pod,其中设置了 PreStop 钩⼦函数,即在容器退出之前优雅的关闭Nginx:

---
apiVersion: v1
kind: Pod
metadata:
  name: hook-demo2
  labels:
    app: hook
spec:
  containers:
  - name: hook-demo2
    image: nginx
    ports:
    - name: webport
      containerPort: 80
    volumeMounts:
    - name: message
      mountPath: /usr/share/
    lifecycle:
      preStop:
        exec:
          command: ['/bin/sh', '-c', 'echo Hello from the preStop Handler > /usr/share/message']
  volumes:
    - name: message
      hostPath:
        path: /tmp

通过上面的Yaml文件、我们在删除Pod之前会去执行preStop钩子函数、把文件通过volumes写入到我们挂载的宿主机tmp文件目录下面。然后我们通过 kubectl apply -f hook-demo2.yaml 命令创建Pod,再通过 kubectl delete -f hook-demo2.yaml 命令删除Pod。在宿主机的tmp文件目录下、我们看到了我们创建的message文件、以及里面写入的内容: Hello from the preStop Handler 。那么就代表钩子函数preStop执行成功。

img

当然,在实际生产环境中,优雅的关闭资源对象最多的方式还是采用nginx -s quit来实现的,例如:

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo2
spec:
  containers:
  - name: hook-demo2
    image: nginx
    lifecycle:
      preStop:
        exec:
          command: ["/usr/sbin/nginx","-s","quit"]

另外 Hook 调⽤的⽇志没有暴露个给 Pod 的 event,所以只能通过 describe 命令来获取,如果有错误将可以看到 FailedPostStartHook 或 FailedPreStopHook 这样的 event。

推荐文章