# K8s 里多容器 Pod 的健康检查探针工作机制分析


## 1. 开篇

米娜桑，宝子们，ladies and 砖头们…… 总之，我回来了！

你看这标题，没错，K8s 的。兜转两载，我还是决定从“DevOps 工程师”变回“机器学习平台研发工程师”。直观一点讲，就是“云平台开发”那点事配上 GPU 那点料，是不是很好理解？

Anyway，以后又有机会玩 K8s 了，所以接下来我会继续更新和 K8s 或者“机器学习平台”相关的内容。总之总之，你们蹲了那么久的更新，来了！

## 2. 聊啥

今天有个同事问我：在1个 Pod 中跑多个 Container，如果其中一个挂了，另外一个会怎样？

嗯…… 我记得大概，不过没有确切的结论，这个取决于 probes 是怎么工作的，于是我实测了一下，发现和预期不是完全一致。

于是乎，今天和大伙分享下这个知识点。

## 3. 结论（TL;DR）

对，结论在开头。毕竟，我知道你们都很忙。

---

**一番操作猛如虎，然后我发现：**

当1个 Pod 中包含2个 Containers 的时候，如果2个 Containers 分别对外暴露不同的端口（http 服务为例），当其中有1个 Container 的：

1. Liveness probe 失败，则该 Container 会被 Kubelet 干掉，然后该 Container 重启/重建（当然，你的重启策略得是 Always），另外一个 Container 不会重启（也就是不会发生 Pod 级别的重启/重建，因此 Pod IP 也不会变化）；
2. Readiness probe 失败，这时候 Pod 状态里的 Ready 列自然是1/2，关键是 Service 会怎样工作呢？
    1. 当使用1个 Service 负载流量到这个 Pod 的2个端口时，Service 对应的 Endpoint 会变成 NotReady，导致 Pod 中 ready 的那个 Container 也不能通过 Service 地址访问到；
    2. 当使用2个不同的 Service 分别负载流量到这个 Pod 的2个端口时，很遗憾，对应的2个 Endpoint 均会变成 NotReady，而不是1个 Ready，一个 NotReady。（这是和我最开始的猜测不相符的）

## 4. 测试过程

你居然看到了这里，宝子，你是个求知欲很强的孩子啊！

### 4.1 准备测试用镜像

我想用 NGINX 镜像来完成这次 probes 特性测试，但是要让2个 containers 在1个 Pod 里监听不同的端口，那就得重新打下镜像，包一层了。

**1. 首先，准备一个配置文件**

- default.conf

```txt
server {
    listen 8080;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}
```

**2. 然后准备一个 Dockerfile**

- Dockerfile

```dockerfile
FROM nginx

RUN rm /etc/nginx/conf.d/default.conf

COPY default.conf /etc/nginx/conf.d/

EXPOSE 8080
```

注意到这里我们将端口号指定成了8080。

**3. 接着 build 一下**

```bash
docker build -t my-nginx-8080:1.0 .
```

很酷，第一个镜像有了。然后咱需要继续搞一个监听8081端口的新镜像。

**4. 更新配置文件**

- default.conf

```txt
server {
    listen 8081;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}
```

**5. 更新 Dockerfile**

```dockerfile
FROM nginx

RUN rm /etc/nginx/conf.d/default.conf

COPY default.conf /etc/nginx/conf.d/

EXPOSE 8081
```

**6. build 第二个镜像**

```bash
docker build -t my-nginx-8081:1.0 .
```

OK，到这里2个镜像就准备完成了。接着如何将镜像丢到 K8s worker 节点，大家就各显神通吧，通过镜像仓库也行，手动拷贝也罢。

### 4.2 准备 Deployment YAML

首先跑一个 probe 能过的版本，确保“1 Pod 2 Container”启起来。

- deploy.yaml

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx-container1
        image: my-nginx-8080:1.0
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
      - name: nginx-container2
        image: my-nginx-8081:1.0
        ports:
        - containerPort: 8081
        livenessProbe:
          httpGet:
            path: /
            port: 8081
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: 8081
          initialDelaySeconds: 5
          periodSeconds: 5
```

### 4.3 准备 Service YAML

然后准备一个 Service，用来测试 readinessProbe 相关行为。

- svc.yaml

```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-service
spec:
  selector:
    app: nginx
  ports:
    - name: port1
      protocol: TCP
      port: 8080
      targetPort: 8080
    - name: port2
      protocol: TCP
      port: 8081
      targetPort: 8081
```

### 4.4 准备第二个 Service YAML

如果是分开的2个 Services 去转发流量到 Pod 内的2个 Containers 呢？也试一下：

- svc-2.yaml

```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-service-1
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-service-2
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 8081
      targetPort: 8081
```

### 4.5 测试过程

Apply YAML 后，依次将 Deployment 配置里的 `livenessProbe.httpGet.path` 和 `readinessProbe.httpGet.path` 从正确的 `/` 改成会引发404的 `/hehe`，然后观察 Pod 的状态变化，Service/Endpoint 的状态变化，就可以啦。

（对，不放截图，显得冗长，不是懒，真的不是懒！）

## 5. 结论

前面贴过结论了，复制粘贴一波：

> 当1个 Pod 中包含2个 Containers 的时候，如果2个 Containers 分别对外暴露不同的端口（http 服务为例），当其中有1个 Container 的：
>
> 1. Liveness probe 失败，则该 Container 会被 Kubelet 干掉，然后该 Container 重启/重建（当然，你的重启策略得是 Always），另外一个 Container 不会重启（也就是不会发生 Pod 级别的重启/重建，因此 Pod IP 也不会变化）；
> 2. Readiness probe 失败，这时候 Pod 状态里的 Ready 列自然是1/2，关键是 Service 会怎样工作呢？
>     1. 当使用1个 Service 负载流量到这个 Pod 的2个端口时，Service 对应的 Endpoint 会变成 NotReady，导致 Pod 中 ready 的那个 Container 也不能通过 Service 地址访问到；
>     2. 当使用2个不同的 Service 分别负载流量到这个 Pod 的2个端口时，很遗憾，对应的2个 Endpoint 均会变成 NotReady，而不是1个 Ready，一个 NotReady。（这是和我最开始的猜测不相符的）

## 6. 结尾

没看够？别急嘛，关注微信公众号“胡说云原生”，来日方长，see you tomorrow。

