在 Kubernetes (K8s) 的世界里,很多开发者都遇到过这种尴尬:kubectl get pods 显示 Running,但用户访问却全是 502 或超时。
这通常是因为你没有正确配置 探针 (Probes)。K8s 就像一个医生,通过三种不同的“体检”方式来判断你的容器状态:
一、三大探针:各自解决什么问题?
| 探针类型 | 解决的问题 | 失败后的动作 |
| Startup (启动探针) | 专门针对启动慢的服务。如果它没过,其他探针都不会开始。 | 杀死容器,重新启动。 |
| Liveness (存活探针) | 服务是不是“卡死”了(如死锁、OOM)? | 杀死容器,重新启动。 |
| Readiness (就绪探针) | 服务现在能接活吗(如正在加载缓存、数据库连不上)? | 不重启容器,但把 Pod 从 Service 的负载均衡中剔除。 |
二、为什么会“活着却不可用”?
如果只配置了 Liveness 而没配置 Readiness,就会出现以下情况:
1. 启动阶段:
你的 Go 程序刚启动,正在连接数据库或预热几百兆的缓存。此时程序进程在,Liveness 认为是好的,于是流量涌入,用户看到一堆错误。
2. 运行阶段:
数据库突然压力过大,响应极慢。Liveness 检查程序进程还在,不重启;但因为没有 Readiness,流量依然往这个“慢 Pod”上发,导致整体链路拖慢。
三、代码示例
下面是一个用 Gin 实现的典型例子,展示了如何区分这三种状态。
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 使用原子操作保证线程安全
var isReady int32 = 0
// 模拟异步初始化任务(如加载大型配置、预热缓存、建立 DB 连接)
go func() {
log.Println("正在初始化服务组件...")
time.Sleep(10 * time.Second) // 模拟耗时操作
atomic.StoreInt32(&isReady, 1)
log.Println("服务初始化完成,可以接收流量!")
}()
// 健康检查路由组
health := router.Group("/health")
{
// 1. Startup: 只要 Gin 跑起来了就返回 200
health.GET("/startup", func(c *gin.Context) {
c.String(http.StatusOK, "started")
})
// 2. Liveness: 检查进程是否还在正常循环,是否死锁
health.GET("/live", func(c *gin.Context) {
c.String(http.StatusOK, "alive")
})
// 3. Readiness: 核心逻辑!检查依赖是否就绪
health.GET("/ready", func(c *gin.Context) {
if atomic.LoadInt32(&isReady) == 1 {
c.String(http.StatusOK, "ready")
} else {
// 关键:返回 503,K8s 会将此 Pod 从 Service 摘除
c.String(http.StatusServiceUnavailable, "initializing")
}
})
}
// --- 优雅关闭 (Graceful Shutdown) ---
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务...")
// 技巧:收到关闭信号后,可以立即将 isReady 设为 0
// 这样在 Pod 还没被彻底杀掉前,Readiness 探针会先失败,确保新流量不再进来
atomic.StoreInt32(&isReady, 0)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("服务器强制关闭:", err)
}
log.Println("服务器已退出")
}
四、对应的K8s配置YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: golang-per-day-67-deployment
labels:
app: golang-per-day-67
spec:
replicas: 3 # 部署3个副本
selector:
matchLabels:
app: golang-per-day-67
template:
metadata:
labels:
app: golang-per-day-67
spec:
containers:
- name: gin-server
image: golang-per-day:day67
ports:
- containerPort: 8080
# --- 1. 启动探针 (Startup Probe) ---
# 作用:保护期。如果它没成功,K8s 不会杀掉容器,也不会启动 Liveness 和 Readiness。
startupProbe:
httpGet:
path: /health/startup
port: 8080
# 30 * 5 = 150s。如果 150 秒还没启动成功,K8s 才会重启容器。
failureThreshold: 30
periodSeconds: 5
# --- 2. 存活探针 (Liveness Probe) ---
# 作用:判断容器是否“没气了”(如死锁)。失败则重启容器。
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 5 # startup 成功后,等待 5s 开始检查
periodSeconds: 15 # 每 15s 检查一次
timeoutSeconds: 3 # 响应超时时间
failureThreshold: 3 # 连续 3 次失败才重启
# --- 3. 就绪探针 (Readiness Probe) ---
# 作用:判断容器是否“能接活”(如数据库已连上)。失败则切断流量(Service 摘除)。
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 2
periodSeconds: 5 # 检查频率可以高点,为了快速发现故障
successThreshold: 1 # 只要 1 次成功就认为就绪
failureThreshold: 2 # 连续 2 次失败就踢出负载均衡
五、总结建议
-
不要在 Liveness 中检查数据库: **如果数据库挂了,你所有 Pod 都会被 K8s 重启,造成“雪崩”。**数据库检查应该放在 Readiness 中。
-
Startup 必须配:特别是 Java 或需要加载大型模型的服务,防止被 Liveness 杀在摇篮里。
-
**优雅关闭:**当收到
SIGTERM信号时,你应该手动将 Readiness 设为失败,让 K8s 提前切断流量,实现无损发布。
友情链接:加班费计算器(vx小程序搜索“加班计”)
*源码地址*
1、公众号“Codee君”回复“源码”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!