# Kubernetes 存活检测的危险性

原文：[LIVENESS PROBES ARE DANGEROUS](https://srcco.de/posts/kubernetes-liveness-probes-are-dangerous.html)

Kubernetes 的 `livenessProbe` 是有一定危险性的。建议在用例清晰，并且理解足够深刻的情况下才使用这个功能。本文会涉及到存活检测以及就绪检测，并做出一些应该或者不该的建议。

我的同事 Sandor 最近说了一下他看到的问题，其中包括了错误的存活检测和就绪检测的内容：

%[https://x.com/sszuecs/status/1175655221382529025] 

* 没有就绪检测
    
* 错误的就绪检测
    
* 混淆了就绪检测和存活检测
    
* 不优雅的退出
    
* 不够优雅的优雅关闭，最好使用[生命周期 Hook](https://twitter.com/sszuecs/status/1175377157343907840)
    
* Fork 模式
    

错误的存活检测过程可能加重负载问题（雪崩式故障加上延长容器应用启动时间的风险），会引发其他负面问题，例如破坏依赖（参见我另一篇关于 [K3s 和 ACME 速率限制的文章](https://srcco.de/posts/k3s-outage-traefik-acme-lets-encrypt-local-path.html)）。存活检测和外部数据健康检查的依赖是最差的情况：**数据库的一点小问题会重启你的所有应用**。

在喊出“不要使用存活检测”口号之前，还是先看看存活检测和就绪检测的用途。

> 注意下文很多来自 Zalando 的内部文档。

## 就绪和存活检测

Kubernetes 提供了两个很棒的功能，分别是[就绪检测和存活检测](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)。这两个功能会周期性的执行一个动作（比如说发出 HTTP 请求，打开一个 TCP 连接或者在容器中运行一个命令），从而确认你的应用正在如常运行。

Kubernetes 使用**就绪检测**来探测容器是否准备好开始接收流量。如果 Pod 中所有的容器都准备就绪，这个 Pod 就被当做是就绪状态。这种信号的一个用途就是来控制 Kubernetes 服务的后端 Pod（尤其是 Ingress）。

Kubernetes 使用**存活检测**来确定是否需要重启容器。例如存活检测能够检查到运行中应用的死锁，这种应用正在运行，但是不会有任何进展。重启这种容器能够在有 Bug 的情况下提高应用的可用性，然而也可能会引起级联故障（见后）。

如果一个应用的存活或者就绪检测失败了，在尝试对其进行更新时，滚动更新的过程可能会挂死——K8s 会想要等待你的 Pod 进入就绪状态。

## 举个栗子

就绪检测会使用 HTTP 协议，检查 `/health` 路径（缺省行为：10 秒钟间隔、1 秒钟超时、成功阈值 1，失败阈值：3）：

```yaml
...
podTemplate:
  spec:
    containers:
    - name: my-container
      # ...
      readinessProbe:
        httpGet:
          path: /health
          port: 8080
...
```

### 建议

* 对于使用 HTTP 端点提供服务（尤其是 REST 服务）的微服务来说，严重建议**定义一个就绪检测**，用来检测你的应用（Pod）是否准备好接收请求。
    
* 确认你的就绪检测设置覆盖了 Web 服务器的真实端口。
    
    * 如果你的就绪检测中使用了管理员端口（比如说 9090），如果主要 HTTP 端口（例如 8080）准备就绪，务必要确认该端点仅返回 OK。
        
    * 为就绪检测使用专属端口可能在主端口线程池的拥塞时报告错误的状态（主服务池已满，然而健康检测依然能够通过）。
        
* 确保就绪检测包含了数据库的初始化和迁移过程。
    
    * 最简单的方式就是仅在[初始化](https://flywaydb.org/)完成之后才打开 HTTP 端口，也就是说，不设置健康状态，只是不启动 Web 服务器，直到数据库迁移完成。
        
* 使用 http Get 访问知名的健康检查端点（例如 `/health`）来完成就绪检测。
    
* 理解缺省行为（缺省行为：10 秒钟间隔、1 秒钟超时、成功阈值 1，失败阈值 3）：
    
    * 在大概 30 秒（3 次失败的检测）后，这个 Pod 会成为未就绪状态。
        
* 如果你的技术栈（例如 Java/Spring）允许的话，可以使用不同的管理端口，把监控、检测端口和业务流量分开。
    
    * 注意前面提到的线程问题。
        
* 可以使用就绪检测来对应用进行缓存和预热，在容器完成预热之前，返回 503.
    
    * 建议阅读 [1.6 中新增的 `stateupProbe`](https://sysdig.com/blog/whats-new-kubernetes-1-16/)
        

### 反对

* **不要依赖外部因素，以免发生雪崩**
    
    * 例如有个有状态的 REST 服务，其中包含了 10 个依赖一个 Postgres 数据库的 Pod：如果数据库和网络不稳定，会导致 10 个 Pod 关闭，很显然这种反应会让结果变得更差。
        
    * Spring Data 的缺省行为是检查数据库连接。
        
    * 这里所说的外部因素，还包含本集群中的其它 Pod，也就是说你的检测过程不应该依赖其它 Pod 的状态，以防止雪崩：
        
        * 对于具有分布式状态的应用(例如跨 Pod 的内存缓存)，可能会有所不同。
            
* **在清楚为什么要使用存活检测，了解其后果之前，不用使用存活检测**
    
    * 存活检测能够帮助你恢复“卡死”的容器，但是如果你的应用够成熟，是不应该出现这种情况的——更好的对策是有目的地崩溃，以恢复正常状态。
        
    * 失败的存活检测会导致容器重启，可能会让性能问题更加恶化：容器重启是有停机时间的（损失时间至少是你的应用的启动时间，例如 30 秒），这样就会造成更多错误，让其它容器承受更多压力，可能引起更多容器的崩溃。
        
* 如果使用存活检测，**不要让存活检测和就绪检测使用同样的条件**
    
    * 可以让存活检测使用同样的健康检测方法，但是设置更高的 `failureThreshold`（例如 3 次失败之后设置为未就绪，10 次失败后才让存活检测失败）
        
* **不要使用** `exec` 检测：这是一个已知问题，会导致僵尸进程。
    
    * 参考 [Datadog 的故障经历](https://www.youtube.com/watch?v=QKI-JRs2RIE)。
        

## 总结

* 在 Web App 中使用就绪检测来确定该 Pod 可以接受流量。
    
* 仅在的确需要时候使用存活检测。
    
* 不恰当的检测方法可能会损失可用性甚至有引发雪崩的危险。
    

%[https://x.com/sszuecs/status/1175655221382529025]
