# 生命周期钩子

Pod 中的 **每个容器** 都支持定义两种生命周期钩子：

* 启动后钩子（Post-start）：容器启动后，与容器中的主进程 **并行执行**，只有该钩子执行成功，容器才会被判定 Running；
* 停止前钩子（Pre-stop）：在容器开始终止进程之前，执行钩子，在容器被停止之前尽可能完成（因为可能容器被要求立即结束，那么钩子还没有来得及被执行）；

钩子与探针类似的，有三种执行机制：

* HTTP GET针对设定的URL执行请求，根据响应状态码判断执行状态（2xx或3xx为成功，其他或不响应为失败）；
* TCP套接字：与指定端口建立TCP连接，建立失败则探测失败；
* Exec：在容器内执行任意命令，根据命令返回的状态码判断执行状态（0为成功，其他为失败）；

## 启动后钩子

启动后钩子可以让开发者在不改动应用的情况下，执行一些额外的工作。包括向外部监控服务发送应用已启动的信号，或是初始化应用以使得应用能够顺利运行。

在钩子执行完毕之前，容器会一直停留在 Waiting 状态，Pod 的状态停留在 ContainerCreating 状态。

🚀 **如果钩子执行失败，那么容器会被重新创建。**

如果钩子执行失败，在 Events 中会看到一个 **FailedPostStartHook** 警告。

钩子进程若将日志输出至 stdin，那么是无法看到的，解决方法是将其输出至临时文件中，然后从该文件中查看输出的日志信息。

### 案例

下面创建一个带启动后钩子容器的 Pod，在 `spec.containers.lifecycle.postStart` 中定义，如下。在该案例中，共创建了三种启动后钩子：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-poststart-hook
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
      postStart:    # 启动后钩子
        exec:    # Exec 机制；沉睡5s后，以15为状态码退出
          command: 
          - sh
          - -c
          - "echo 'hook will fail with exit code 15'; sleep 5 ; exit 15"
        httpGet:    # http GET 机制；向本容器的9090端口并以postStart为path发送GET请求
          port: 9090
          path: postStart
        tcpSocket:    # TCP 套接字机制；向本容器的8081建立TCP连接
          port: 8081
```

{% hint style="info" %}
🍓 `lifecycle.httpGet` 中还包括下面几个字段：

* host: 这里的 host 默认是 Pod 的 IP 地址，因为钩子程序的执行者是 **Kubelet**
* httpHeaders
* path
* port
* scheme
  {% endhint %}

## 停止前钩子

停止前钩子是在容器停止之前执行的，在容器需要被终止时，Kubelet 会执行该钩子，即 Pod 的状态称为 Terminating 后，立刻开始执行该钩子，在执行完该钩子后，再向容器发送 SIGTERM 信号。

🌝 **和启动前钩子不同的是，无论停止前钩子是否成功，容器都会被终止。**

如果钩子执行失败，在 Events 中会看到一个 **FailedPreStopHook** 警告。

### 案例

下面创建一个带停止前钩子容器的 Pod，在 `spec.containers.lifecycle.preStop` 中定义，如下。在该案例中，共创建了三种停止前钩子：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-prestop-hook
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
      preStop:    # 停止前钩子
        exec:    # Exec 机制；沉睡5s后，以15为状态码退出
          command: 
          - sh
          - -c
          - "echo 'hook will fail with exit code 15'; exit 15"
        httpGet:    # http GET 机制；向本容器的8080端口并以shutdown为path发送GET请求
          port: 8080
          path: shutdown
        tcpSocket:    # TCP 套接字机制；向本容器的8081建立TCP连接
          port: 8081
```

{% hint style="info" %}
🍓 `lifecycle.httpGet` 中还包括下面几个字段：

* host：这里的 host 默认是 Pod 的 IP 地址，因为钩子程序的执行者是 **Kubelet**
* httpHeaders
* path
* port
* scheme
  {% endhint %}

### Pod 关闭流程

当 API 服务器收到 HTTP 的 DELETE 请求后，会给指定的 Pod 设置一个 `deletionTimeStamp` 值。当 Kubelet 发现需要终止的 Pod 时，它会开始终止该 Pod 中的所有容器。

可以为 Pod 设置一个终止宽限时间 `pod.spec.terminationGracePeriodSeconds`，给每个容器一定的时间来优雅地停止。

{% hint style="warning" %}
⚠️ **提示** 应该尽可能将终止宽限时间设置的足够长，保证容器进程可以在这个时间段内完成清理工作。
{% endhint %}

{% hint style="info" %}
或者也可以使用 `--grace-period` 参数覆盖在 `pod.spec` 中指定的终止宽限时间：

```bash
kubectl delete pod mypod --grace-period 5
```

强制立即结束命令：

```bash
kubectl delete pod mypod --grace-period 0 --force
```

> 该命令尽量不要使用，因为比如在 StatefulSet 中，会避免在同一时间运行两个完全一样的 Pod（具有相同的序号、名称、挂载相同的 PV），使用该命令删除时，可能导致原容器还未退出，就有新容器创建，即两个容器同时存在，可能会造成无法预知的错误
> {% endhint %}

在终止进程开始后，计时器就开始计时，接着开始按照下述流程执行以下事件：

1. 执行停止前钩子进程（如果配置了的话），然后 **等它执行完毕**；
2. 向容器主进程发送 SIGTERM 信号；
3. 等待容器优雅地关闭或者等待终止宽限期超时；
4. 如果容器主进程还在运行，则发送 SIGKILL 信号强制终止进程；

![容器终止流程](https://2906552408-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6Ub8CloS5kJszh6xSR%2Fsync%2F8f7b85c70dc8a3cf1114a1430b7bfdee4958d49c.png?generation=1592324947354437\&alt=media)
