K8s详细学习笔记 第六章:应用的健康检查与自愈能力
第六章:应用的健康检查与自愈能力
在传统的运维模式中,当一个应用程序崩溃、死锁或变得无响应时,通常需要人工介入——接收告警、登录服务器、检查日志、最后手动重启服务。这个过程不仅耗时,而且在夜间或节假日会成为运维人员的噩夢。Kubernetes从根本上改变了这一现状,它通过内置的、自动化的健康检查和自愈机制,将系统可靠性提升到了一个全新的水平。
本章的核心是探针(Probe),它是Kubernetes赋予kubelet的“听诊器”,让kubelet能够深入了解容器内部应用的真实健康状况。基于探针反馈的信息,Kubernetes的控制器(如Deployment)就能做出智能决策,实现应用的自动“治愈”。
6.1 探针(Probe):K8s的“听诊器”
探针是由kubelet在容器上定期执行的一种诊断操作,用于检查容器的状态。kubelet根据探针的返回结果来决定是否需要对容器采取行动。Kubernetes提供了三种类型的探针,它们有不同的目的和触发的后果,理解它们的区别至关重要。
探针的三种实现方式
在配置任何一种探针之前,你需要先决定K8s应该如何去检查你的应用。它提供了三种机制:
-
httpGet:kubelet向容器的指定IP、端口和路径发送一个HTTP GET请求。如果收到的响应状态码在200到399之间,则认为探针成功;否则为失败。这是最常用的方式,适用于Web服务和API。httpGet: path: /healthz # 检查的HTTP端点 port: 8080 # 容器的目标端口 scheme: HTTP # 协议,可以是 HTTP 或 HTTPS -
tcpSocket:kubelet尝试在指定端口上与容器建立一个TCP连接。如果连接能够成功建立,则认为探针成功;否则为失败。这种方式适用于非HTTP的TCP服务,如数据库、缓存服务等。tcpSocket: port: 5432 # 容器的目标端口,例如PostgreSQL -
exec:kubelet在容器内部执行一个指定的命令。如果命令的退出状态码(Exit Code)为0,则认为探针成功;否则为失败。这是一种最灵活的方式,你可以编写任何脚本来定义复杂的健康检查逻辑。exec: command: - cat - /tmp/healthy # 或者执行一个健康检查脚本 # command: ["/usr/bin/check-health.sh"]
Liveness Probe(存活探针):判断容器是否需要重启
目的:回答“这个容器是否还活着?”
Liveness Probe用于检测应用程序是否已经陷入了无法恢复的僵尸状态,例如死锁、无限循环导致的CPU耗尽等。在这种状态下,应用程序虽然进程仍在,但已无法正常工作,并且无法通过自身逻辑恢复。
触发的后果:如果Liveness Probe连续多次失败(达到failureThreshold设定的阈值),kubelet会杀死这个容器,然后根据Pod的restartPolicy(重启策略,通常是Always)来决定是否重启它。
可以把它比作一个心跳监视器。只要心跳正常,就一切安好。一旦心跳停止,监视器就会发出警报,系统会立即采取“电击除颤”(重启容器)的措施,希望能让它恢复正常。
示例YAML配置:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15 # Pod启动后,等待15秒再开始第一次探测
periodSeconds: 20 # 每20秒探测一次
timeoutSeconds: 1 # 探测超时时间为1秒
failureThreshold: 3 # 连续3次失败后,才认为容器不存活
successThreshold: 1 # 探测成功1次,就认为容器恢复存活
Readiness Probe(就绪探针):判断容器是否准备好接收流量
目的:回答“这个容器是否已准备好对外提供服务?”
Readiness Probe用于检测应用程序是否已经完成了所有启动步骤,并且能够处理新的请求。很多应用在启动时需要执行一些耗时的初始化操作,比如加载大量数据到内存、预热缓存、建立与下游服务的连接等。在这些操作完成之前,应用虽然是“存活”的,但如果此时将流量转发给它,会导致请求失败。
触发的后果:如果Readiness Probe失败,kubelet不会杀死或重启容器。相反,它会将这个Pod的状态从Service的端点(Endpoints)列表中移除。这意味着,所有指向该Service的流量都不会再被转发到这个“未就绪”的Pod上。当后续的Readiness Probe再次成功时,kubelet会重新将该Pod添加回Service的端点列表,使其可以继续接收流量。
可以把它比作餐厅门口的“营业中/休息中”的牌子。厨房(容器)可能已经在运转了(存活),但如果厨师还没准备好食材(应用未就绪),前台(Service)就不会引导顾客(流量)进去。
示例YAML配置:```yaml
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
#### Startup Probe(启动探针):针对启动缓慢的应用
**目的**:为启动时间不固定的应用提供一个**宽容的启动窗口**。
Startup Probe是较新版本Kubernetes中引入的,它解决了Liveness和Readiness探针的一个痛点:如何处理那些启动时间特别长或不固定的应用(例如,需要进行大量数据初始化的Java应用)。如果这类应用的启动时间超过了Liveness Probe的`initialDelaySeconds + failureThreshold * periodSeconds`的总时间,`kubelet`会错误地认为应用启动失败并将其杀死,导致应用陷入无限的重启循环。
**工作机制**:
1. 如果定义了Startup Probe,那么在它成功之前,**Liveness和Readiness探针都会被禁用**。
2. `kubelet`会根据Startup Probe的配置(通常会设置一个很长的`failureThreshold`和较短的`periodSeconds`)来持续检查应用是否启动完成。
3. 一旦Startup Probe**首次成功**,`kubelet`就会认为应用已启动,然后**立即切换到Liveness和Readiness探针**的常规检查模式。
4. 如果Startup Probe在设定的总时间内(`failureThreshold * periodSeconds`)一直没有成功,`kubelet`就会杀死容器,和Liveness Probe失败的后果一样。
**可以把它比作火箭发射前的倒计时检查程序**。在主引擎(Liveness/Readiness)点火之前,有一个专门的、更耐心的程序(Startup Probe)来检查所有子系统是否都已准备就绪。
**示例YAML配置**:
```yaml
startupProbe:
httpGet:
path: /startupz
port: 8080
# 给予应用最多 5分钟 (30 * 10s) 的启动时间
failureThreshold: 30
periodSeconds: 10
6.2 K8s的自愈机制: 如何通过Controller和探针自动恢复故障应用
Kubernetes的自愈能力并非由单一组件实现,而是多个组件协同工作的结果,形成了一个强大的闭环控制系统。
核心参与者:
- Kubelet: 运行在每个Node上的代理,是健康检查的执行者。它负责根据Pod Spec中的探针配置,定期对容器进行探测,并根据结果执行本地操作(重启容器、更新Pod状态)。
- Controller Manager: 运行在Master节点上,是集群状态的决策者。其中包含了多个控制器,如Deployment Controller, ReplicaSet Controller等。
- API Server & etcd: 集群的状态中心。Kubelet将探测到的Pod状态更新到API Server,API Server再将其存入etcd。Controller则通过监控API Server来获取集群的期望状态和实际状态。
自愈场景一:容器级故障(应用死锁)
这是一个本地自愈的例子,主要由kubelet完成。
- 稳定运行: 一个由Deployment管理的Pod正在Node A上正常运行。
kubelet定期执行其Liveness Probe,每次都成功。 - 故障发生: Pod内的应用程序因代码bug陷入死锁,无法再响应任何请求。
- 探针失败: Node A上的
kubelet执行下一次Liveness Probe(例如,访问/healthz),请求超时。这被记为一次失败。 - 达到阈值:
kubelet按照periodSeconds继续探测,连续失败次数达到了failureThreshold(例如3次)。 - 执行重启:
kubelet判定该容器不健康,于是向容器运行时(如Docker)发送指令,强行终止该容器。 - 恢复: 根据Pod的
restartPolicy: Always,kubelet立即又向容器运行时发送指令,使用相同的镜像重新创建并启动一个新的容器。 - 恢复正常: 新的容器启动后恢复正常服务。整个过程对用户是透明的,Service的流量可能只中断了很短的时间。
自愈场景二:Pod/Node级故障(节点宕机)
这是一个集群级自愈的例子,需要Controller Manager的参与。
- 稳定运行: 一个Deployment期望拥有3个副本,这3个Pod分别运行在Node A, B, C上。
- 故障发生: Node C突然因为硬件故障或网络中断而宕机。
- 心跳超时: Master节点上的Node Controller(Controller Manager的一部分)通过
kubelet的心跳机制,发现Node C在规定时间内没有上报状态。 - 标记节点: Node Controller将Node C的状态标记为
NotReady或Unknown。同时,它会给Node C上的所有Pod添加一个污点(Taint),如node.kubernetes.io/unreachable,并设置一个驱逐超时时间(默认为5分钟)。 - 驱逐Pod: 如果Node C在超时时间内没有恢复,Node Controller就会将该节点上的所有Pod从etcd中删除。
- 控制器响应: Deployment Controller一直在监控其管理的Pod。它通过API Server发现,其实际运行的Pod数量现在变成了2,而期望是3。
- 创建新Pod: 为了弥补这个差距,Deployment Controller会立即创建一个新的Pod。
- 调度与运行: Scheduler会为这个新的Pod选择一个健康的节点(例如Node D),然后该节点上的
kubelet会启动Pod。 - 恢复正常: 集群的Pod副本数恢复到3,应用的高可用性得到了保障。
【重难点】 合理配置探针避免“误杀”和流量中断,理解不当的就绪检测可能导致服务雪崩
探针是保证系统稳定性的利器,但如果配置不当,它也可能成为导致系统混乱的“猪队友”。以下是配置探针时必须深入思考的重难点。
1. 合理配置探针参数,避免“误杀”(False Positives)
“误杀”指的是,应用程序本身没有死锁,只是因为某些暂时性的原因(如临时的网络抖动、高负载下的GC停顿、依赖服务短暂不可用)导致探针失败,而被kubelet错误地杀死重启。频繁的“误杀”会严重影响服务的稳定性。
避免策略:
-
宽容的超时和阈值:
timeoutSeconds: 应该设置为一个合理的值,要能覆盖应用在正常负载下99%的响应时间。不要设置得过于激进(比如1秒)。failureThreshold: 这是最重要的“缓冲垫”。如果你的应用偶尔会有一次响应超时(比如Java应用的Full GC),将failureThreshold设置为3或更高,可以有效避免因单次抖动就被重启。- 总容忍时间 =
failureThreshold*periodSeconds。你需要估算应用从暂时性问题中恢复所需的时间,并确保总容忍时间大于这个恢复时间。
-
使用Startup Probe: 对于启动缓慢的应用,必须使用Startup Probe。这是解决启动期被“误杀”问题的最佳实践。
-
轻量级的探针端点: 探针的HTTP端点(如
/healthz)或执行的脚本,其逻辑应该尽可能简单、快速、无外部依赖。- Liveness Probe的端点:绝对不应该检查与下游服务(数据库、缓存、其他微服务)的连接性。它只应该检查自身进程是否正常,例如,能否响应一个简单的HTTP请求,或者内部的关键线程是否存在。如果Liveness Probe依赖于数据库,那么数据库的一次抖动就可能导致整个服务的所有Pod被集体重启,造成灾难。
- Readiness Probe的端点: 可以(也应该)检查与关键下游服务的连接性。但需要小心处理,见下一点。
2. 理解不当的Readiness Probe可能导致的服务雪崩(Thundering Herd)
这是一个更隐蔽但更危险的问题,尤其是在大规模部署或滚动更新时。
雪崩场景复现:
- 你有一个
frontend服务,它依赖一个database服务。 - 你为
frontend的Pod配置了一个Readiness Probe,其逻辑是SELECT 1 FROM users来检查数据库连接。 - 现在,你对
frontend进行滚动更新,30个旧Pod被销毁,30个新Pod几乎同时启动。 - 这30个新Pod同时开始执行它们的Readiness Probe,也就是同时向数据库发起连接和查询。
- 数据库瞬间收到了大量的新连接和查询请求,CPU和连接池被打满,响应变得极度缓慢甚至无响应。
- 由于数据库无响应,所有
frontendPod的Readiness Probe都开始超时失败。 kubelet将所有30个新Pod都标记为NotReady,并将它们从frontend-service的端点列表中移除。- 结果:你的
frontend服务现在没有任何一个可用的后端Pod,所有用户请求都会失败,服务完全中断。这就是服务雪崩。
避免策略:
- 解耦就绪检查与依赖检查: 最理想的Readiness Probe应该只检查应用自身是否已完成初始化。应用内部应该使用更成熟的机制,如**断路器(Circuit Breaker)**模式来处理与下游服务的连接问题。当数据库不可用时,Readiness Probe仍然应该返回成功(因为
frontend应用本身是好的),但应用内部的断路器会打开,快速失败对数据库的调用,而不是让请求堆积。 - 如果必须检查依赖:
- 添加随机延迟: 在应用的启动脚本中或Readiness逻辑中加入一个小的随机延迟,避免所有Pod在同一时刻冲击下游。
- 使用连接池: 确保你的应用和探针逻辑都使用合理配置的数据库连接池,而不是每次探测都创建新连接。
- 独立的健康检查端点: 数据库自身也应该有轻量级的健康检查端点,
frontend的探针可以考虑调用这个端点,而不是执行实际的查询。
Liveness Probe vs. Readiness Probe - 最终抉择
| 场景 | 使用Liveness Probe? | 使用Readiness Probe? | 理由 |
|---|---|---|---|
| 应用启动需要加载大量数据 | 否 (或使用Startup Probe) | 是 | 在数据加载完成前,不应接收流量。 |
| 应用陷入死锁或无限循环 | 是 | 否 | 这是无法自愈的状态,唯一解法是重启。 |
| 依赖的数据库暂时不可用 | 绝对不要 | 可以,但需谨慎 (见雪崩问题) | 重启应用无法解决数据库问题,反而会加剧问题。更好的方式是让应用保持运行,等待数据库恢复。 |
| 进行后台批量任务,暂时不想处理API请求 | 否 | 是 | 应用是健康的,只是暂时“繁忙”,通过标记为NotReady来优雅地暂停接收新流量。 |
总结:
- 当你觉得**“重启是解决问题的唯一方法”时,使用Liveness Probe**。
- 当你觉得**“应用暂时不方便接客,请稍后再来”时,使用Readiness Probe**。
- 当你觉得**“应用启动太慢,请多给点耐心”时,使用Startup Probe**。
通过精心设计和配置这三种探针,你可以构建出真正具有弹性和自愈能力的系统,让Kubernetes为你自动化处理绝大多数常见的应用故障,从而极大地提升服务的可用性和运维效率。
K8s 详细学习笔记 目录
以下是整个系列的10章目录,点击章节标题即可跳转阅读:
- K8s详细学习笔记 第一章:K8s入门与核心概念
- K8s详细学习笔记 第二章:部署你的第一个应用:Pod与Deployment
- K8s详细学习笔记 第三章:应用访问与服务发现:Service与Ingress
- K8s详细学习笔记 第四章:应用配置管理:ConfigMap与Secret
- K8s详细学习笔记 第五章:数据持久化:Volume与Persistent Storage
- K8s详细学习笔记 第六章:应用的健康检查与自愈能力
- K8s详细学习笔记 第七章:资源的限制与调度
- K8s详细学习笔记 第八章:应用的扩缩容与更新策略
- K8s详细学习笔记 第九章:K8s安全机制:认证、授权与准入控制
- K8s详细学习笔记 第十章:集群运维与故障排查
更多推荐
所有评论(0)