
手写实现扫描探针,面试官当场问懵?
面试时被问到 K8s 探针原理,90% 的人只能背出 Liveness 和 Readiness 的定义。面试官追问:“如果我要手写实现一个扫描探针,核心逻辑是什么?” 瞬间大脑空白。这种场景太常见了。很多应届生背了概念,却不懂底层如何执行检查,导致在系统设计题或故障排查题中彻底卡壳。
今天不聊虚的,直接上干货。我们将通过手写实现的方式,拆解扫描探针的核心机制。对比三种主流探针类型:HTTP Get、TCP Socket 和 Exec。你会发现,所谓探针,本质就是一个定时任务加一个判定逻辑。掌握这个底层逻辑,面试时你不再是背书机器,而是能讲清“为什么这么设计”的工程师。
探针的核心定位与面试陷阱
很多新人混淆了“健康检查”和“探针”。在 Kubernetes 语境下,探针是 kubelet 组件执行的周期性操作。根据 Kubernetes 官方文档,探针分为三种:Liveness Probe(存活探针):判断容器是否运行正常。如果失败,kubelet 会杀死容器并重启策略决定后续动作。
Readiness Probe(就绪探针):判断容器是否准备好接收流量。如果失败,Service 不会将流量转发给该 Pod。
Startup Probe(启动探针):针对启动缓慢的应用,在成功之前禁用其他探针。面试陷阱在于:面试官往往不问“是什么”,而是问“区别是什么”以及“失败后果是什么”。Liveness 失败 = 重启容器。这可能导致数据丢失或雪崩效应,所以配置要保守,容忍度高。
Readiness 失败 = 摘除流量。这比重启好,但可能导致负载均衡不均。
Startup 失败 = 一直等待直到超时。这是保护慢启动应用的关键。关键点:不要死记硬背。你要理解,探针是 K8s 与容器内部应用之间的“握手协议”。如果你手写一个监控脚本,你需要决定:检查什么?多久检查一次?失败几次算死?这就是探针的本质。
核心差异对比:HTTP vs TCP vs Exec
为了让你一目了然,我们用一张表格对比三种探针实现方式的核心差异。这也是面试中展示结构化思维的好机会。特性
HTTP Get
TCP Socket
Exec检查层级
应用层 (L7)
传输层 (L4)
进程层 (L0)依赖
需要应用提供 HTTP 接口
需要端口监听
需要容器内有执行权限开销
高 (建立连接+解析响应)
低 (仅三次握手)
中 (fork 子进程)准确性
最高 (能检查业务逻辑)
低 (端口通不代表服务正常)
取决于脚本质量适用场景
Web 服务、微服务
数据库、中间件
无 HTTP 接口的后台任务调试难度
中等
简单
复杂 (需进容器看日志)深度解析:HTTP Get 是最推荐的默认选择。因为它能检查到应用是否真的能处理请求。比如,Tomcat 启动了,但数据库连接池满了,HTTP 探针返回 500,K8s 就会摘除流量。这是 TCP 探针做不到的。
TCP Socket 性能最好,但最“骗人”。MySQL 端口 3306 通了,不代表你能查询成功。它只保证网络层是通的。
Exec 最灵活,但也最危险。如果你在 Exec 里跑了个 sleep 10,探针就会超时。而且每次检查都要 fork 进程,在资源受限的 Pod 里要谨慎。代码实战:手写三种探针逻辑
光说不练假把式。我们用 Python 和 Go 分别手写实现这三种探针的核心判定逻辑。注意,这不是完整的 K8s 控制器,而是探针执行器(Executor)的核心代码。理解这段代码,你就懂了 K8s 底层是怎么工作的。
1. HTTP Get 探针实现 (Python)
模拟 kubelet 发送 HTTP 请求并判断状态码。
import requests
import timedef http_probe(host, port, path, timeout=3):模拟 HTTP Get 探针返回: True 表示健康, False 表示不健康url = fhttp://{host}:{port}{path}try:# 设置超时,防止探针阻塞response = requests.get(url, timeout=timeout)# K8s 默认认为 200-399 为成功if 200 = response.status_code 400:return Trueelse:# 记录日志,方便调试print(fHTTP Probe Failed: {response.status_code})return Falseexcept requests.exceptions.RequestException as e:# 连接超时、DNS 解析失败等都算失败print(fHTTP Probe Exception: {e})return False# 模拟循环检查
if __name__ == __main__:while True:is_healthy = http_probe(localhost, 8080, /healthz)print(fStatus: {'Healthy' if is_healthy else 'Unhealthy'})time.sleep(10) # 模拟 periodSeconds逐行讲解:timeout=3:对应 K8s 配置中的 timeoutSeconds。如果应用响应慢,这里必须调大,否则误判。
200 = response.status_code 400:这是 K8s 的默认判定逻辑。注意,4xx 错误通常也被视为“服务可达但业务错误”,具体取决于你的应用设计。有些团队会将 404 视为不健康,这需要在业务层做特殊处理。2. TCP Socket 探针实现 (Go)
Go 语言在并发网络编程上有天然优势,适合模拟高频率的 TCP 检查。
package mainimport (fmtnettime
)func tcpProbe(host string, port int, timeout time.Duration) bool {// 构造地址address := fmt.Sprintf(%s:%d, host, port)// 建立 TCP 连接conn, err := net.DialTimeout(tcp, address, timeout)if err != nil {fmt.Printf(TCP Probe Failed: %v\n, err)return false}// 关键点:必须关闭连接,否则资源泄漏conn.Close()return true
}func main() {for {// 模拟 periodSeconds = 10s, timeoutSeconds = 3sisHealthy := tcpProbe(localhost, 3306, 3*time.Second)fmt.Printf(Status: %v\n, isHealthy)time.Sleep(10 * time.Second)}
}避坑指南:资源泄漏:很多新手手写探针时忘记 conn.Close()。在高频检查下,这会导致 too many open files 错误,进而导致节点故障。
防火墙干扰:TCP 探针只检查端口连通性。如果安全组或 iptables 规则变更,TCP 探针会立刻失败,但这不代表应用挂了。3. Exec 探针实现 (Bash/Shell)
Exec 探针在容器内执行命令。这里我们展示如何判定命令退出码。
#!/bin/bash
# exec_probe.sh# 检查进程是否存在
if ! pgrep -f my-app-server /dev/null; thenecho Process not foundexit 1 # 非 0 退出码表示失败
fi# 检查内存使用是否超过阈值 (示例)
MEM_LIMIT=512 # MB
CURRENT_MEM=$(ps -o %m -p $(pgrep -f my-app-server) | awk '{print int($1 * 100)}')
if [ $CURRENT_MEM -gt $MEM_LIMIT ]; thenecho Memory limit exceeded: ${CURRENT_MEM}%exit 1
fi# 所有检查通过
exit 0注意:退出码:K8s 规定,只有退出码为 0 才表示成功。任何非 0 值都视为失败。
脚本性能:Exec 探针会 fork 子进程。如果脚本复杂(如运行 Python 脚本),启动开销大。建议保持脚本轻量。进阶技巧与避坑指南
掌握了基础代码,还需要知道生产环境中的“坑”。初始延迟 (initialDelaySeconds):
应用启动需要时间。如果你配置 initialDelaySeconds: 0,探针会在容器刚启动、应用还没监听端口时就开始检查,导致立刻失败并重启,陷入死循环。建议:根据应用冷启动时间设置合理的初始延迟,或者使用 Startup Probe。失败阈值 (failureThreshold):
网络抖动是常态。如果 failureThreshold: 1,一次网络波动就会重启容器。建议:至少设置为 3。给应用和系统恢复的时间。Liveness 与 Readiness 共用端口的陷阱:
很多应用只有一个 /health 端点。如果 Liveness 和 Readiness 都指向它,当依赖的下游服务(如 DB)短暂不可用时,Liveness 失败会导致 Pod 重启,加剧故障。最佳实践:Liveness 只检查自身进程是否存活;Readiness 检查依赖服务是否可用。资源限制:
探针本身也消耗资源。在 CPU 限制很低的 Pod 中,频繁的 HTTP 检查可能导致 CPU 争抢,反而影响应用性能。监控探针的执行耗时,必要时降低检查频率。选型建议与面试应答策略
回到面试场景。当面试官问“你怎么选择探针类型?”时,不要只说“看应用”。你要给出决策树:应用是否提供 HTTP 接口?是 → 优先使用 HTTP Get。检查 /healthz 端点,确保业务逻辑正常。
否 → 进入下一步。应用是否监听 TCP 端口?是 → 使用 TCP Socket。适用于数据库、消息队列等。
否 → 进入下一步。是否有特定的健康检查脚本?是 → 使用 Exec。确保脚本轻量且退出码正确。
否 → 考虑是否真的需要探针。对于无网络服务的后台批处理任务,可能只需要 Liveness 检查进程存在即可。面试金句:
“探针不是越严格越好,而是要匹配应用的启动特性和故障模式。Liveness 要‘宽容’,避免误杀;Readiness 要‘敏感’,快速摘除坏节点。手写实现让我明白,探针本质上是一个带重试机制的 HTTP/TCP 客户端,理解这一点就能灵活配置参数。”
总结与互动
通过手写实现扫描探针,我们剥开了 K8s 的神秘面纱。它不是魔法,而是简单的网络请求加逻辑判断。HTTP 最准确,适合 Web 服务。
TCP 最轻量,适合中间件。
Exec 最灵活,适合特殊场景。面试中,能讲出“探针执行器”的底层逻辑,并对比三种方式的优缺点,足以让面试官眼前一亮。这比背诵 YAML 配置强大得多。
互动话题:
这个知识点你面试被问过吗?你在实际项目中遇到过因为探针配置不当导致的“雪崩重启”吗?留言说说你的排查过程,我们一起拆解。