每日一Go-67、K8s 探针(Liveness / Readiness / Startup)——为什么你的服务“活着却不可用”?

9 阅读4分钟

在 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君”回复“源码”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!