蓝绿发布是通过两套独立环境(blue/green)并行部署、service selector原子切换流量实现零停机更新与快速回滚的k8s部署策略,核心依赖就绪探针真实性、优雅下线机制及数据库兼容性保障。

Go 服务本身不实现蓝绿部署,只负责让 Kubernetes 能准确判断它“能不能接流量”和“能不能安全下线”。真正的流量切换动作在 K8s Service selector、Ingress 或网关层完成,Go 的任务是暴露真实就绪状态、扛住 SIGTERM、干净退出。
为什么 /readyz 返回 200 了,K8s 还是把流量导进来就 panic?
因为 /readyz 没检查真实依赖,只是返回了固定 {"status": "up"}。K8s 看到 200 就往 Pod 写流量,但此时 DB 连接池还没建好、Redis client 还在重试、gRPC downstream 还没连上。
- 必须用后台 goroutine 定期调用
database/sql.DB.PingContext()、redis.Client.Ping()、grpc.ClientConn.WaitForStateChange(),结果缓存在本地var isReady bool -
/readyzhandler 只读这个变量,不现场探测——避免阻塞或超时拖垮探针 - K8s
readinessProbe.initialDelaySeconds设为 5 秒,但你的 DB 初始化实际要 8 秒?那就得配minReadySeconds: 10+ 启动后time.Sleep(2 * time.Second),或者更稳妥地用var ready = make(chan struct{}),所有初始化完成后close(ready),/readyz 阻塞等待该 channel
切流后旧 Pod 请求丢失、gRPC 流被截断,怎么避免?
根本原因是没正确处理 SIGTERM:直接调 srv.Close() 或忽略信号,导致正在写的 HTTP 响应、gRPC stream、数据库事务全中断。
- 必须监听
os.Interrupt和syscall.SIGTERM,收到后先调srv.Close()关 listener,再调srv.Shutdown(ctx),ctx带 15–30s 超时 - 所有后台 goroutine(Kafka 消费、指标上报、定时任务)必须接收
context.Context,并在ctx.Done()时 clean exit;别用全局 flag 或无缓冲 channel 控制停止 - K8s
preStopHook里写sleep 15是掩盖问题,不是解决方案——shutdown 逻辑没覆盖全,加 sleep 只是拖延崩溃时间
Kubernetes Service selector 切换为什么有时还丢请求?
Service selector 修改本身是原子的,但 Endpoint 更新有延迟、客户端长连接未中断、DNS 缓存未刷新,三者叠加就导致“切了但旧 Pod 还在收包”。
- Kube-proxy 刷新 iptables/ipvs 规则不是瞬时的,高并发集群中可能有几百毫秒滞后
- 已有 TCP 连接不会自动断开,HTTP/2 多路复用更明显;需在 Go 服务端配合
srv.SetKeepAlivesEnabled(false)(可选),或依赖客户端主动重连 - 确保新 Pod 的
readinessProbe真实生效后再切——否则 K8s 会把流量导到空 Endpoints,等于没切 - 不要用
kubectl apply -f覆盖整个 Service YAML,容易冲掉 CI 注入的git-commitlabel;改用kubectl patch service myapp -p '{"spec":{"selector":{"version":"green"}}}'
数据库 schema 变更为什么是蓝绿期间最致命的坑?
蓝绿不是“新旧版本不共存”,而是“新旧版本同时在线、共享同一套数据库”。如果绿色版本写了新字段、蓝色版本读不到,或者绿色删了旧字段、蓝色还在 SELECT,立刻报错。
- 所有 DB 变更必须向前兼容:新增字段设默认值或允许 NULL,删字段前先停写、等蓝色版本下线,改类型必须确保旧代码能解析新值
- 上线前跑一次兼容性验证脚本:用蓝色版本代码连绿色 DB,执行关键查询;用绿色版本代码连蓝色 DB,确认无 panic
- 别指望“先切一半流量测试”,DB 兼容性问题在全量读写时才暴露——它不看 QPS,只看 SQL 是否合法
最容易被忽略的是:就绪探针缓存更新时机与 shutdown 信号处理的竞态。比如后台 goroutine 刚把 isReady 设为 true,SIGTERM 就到了,而 srv.Shutdown() 还没等完 pending 请求,这时新来的健康检查可能读到 false,K8s 就把它从 Endpoints 删了——但旧连接还在写数据。这种边界情况必须靠日志 + traceID 对齐来定位,不能靠猜测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











