生产环境禁用gin.default()和r.run(),必须用gin.new()+自定义中间件、gin.releasemode、http.server+shutdown优雅关闭、非root镜像及分离健康检查探针。

直接上生产环境跑 gin.Default() 或 r.Run(":8080") 是高危操作,服务会因 panic 崩溃、无健康检查、不支持优雅关闭、日志不可控——这不是部署,是裸奔。
用 gin.ReleaseMode 关闭调试能力,但别只靠它
Gin 默认在 debug 模式下会输出彩色日志、堆栈、404/500 页面详情,这些在生产中全是攻击面。必须显式切换:
-
gin.SetMode(gin.ReleaseMode)要在gin.Default()之前调用,否则中间件(如Recovery)行为可能不一致 - 仅设 mode 不够:
gin.Default()自带的Logger中间件仍会打印每条请求,需替换为结构化日志(如zerolog或zap),并禁用控制台输出 - 别依赖
GIN_MODE=release环境变量——它只影响启动时的 mode 判断,无法覆盖代码中硬编码的gin.DebugMode
必须实现优雅关闭,否则滚动更新必丢请求
Kubernetes 的 preStop 钩子或 docker stop 发送 SIGTERM 后,进程若立刻退出,正在处理的 HTTP 连接会被强制断开。Gin 本身不处理信号,得自己加:
- 监听
os.Interrupt和syscall.SIGTERM,触发srv.Shutdown()(不是srv.Close()) -
Shutdown()会等待活跃连接完成,但有超时——建议设30s,太短丢请求,太长拖慢发布 - 务必在
Shutdown()后才退出,否则defer srv.Close()可能被跳过 - 示例关键片段:
srv := &http.Server{Addr: ":8080", Handler: r} go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } }() // ... 接收信号后: if err := srv.Shutdown(context.WithTimeout(context.Background(), 30*time.Second)); err != nil { log.Fatal(err) }
Docker 镜像里不能留 go build 环境,也别用 root 运行
镜像体积大、漏洞多、权限过高,三者叠加就是生产事故温床:
- 必须用多阶段构建:build 阶段用
golang:1.21-alpine,runtime 阶段切到alpine:3.18或scratch(后者需静态编译) - 编译时加
CGO_ENABLED=0和-ldflags="-w -s",否则二进制依赖 libc,scratch镜像跑不起来 -
USER指令必须指定非 root 用户,且该用户对运行目录有读写权(比如/app);别用root+chmod 777应急 - 配置文件(
config.yaml)不要 COPY 进镜像,改用ConfigMap或Secret挂载,避免敏感信息泄露
K8s 里健康检查要区分就绪(readiness)和存活(liveness)
用同一个 /healthz 端点配两种探针,等于把数据库挂掉和服务进程卡死混为一谈:
-
readinessProbe应检查依赖是否就绪(如 Redis 连通、DB 连接池可用),失败则从 Service Endpoint 摘流,但不重启容器 -
livenessProbe应只检查进程是否存活(如端口可连、HTTP 返回 200),失败才触发重启;若它也查 DB,DB 故障会导致全量重启,雪崩风险拉满 - Gin 路由里别用
c.AbortWithStatusJSON(503, ...)响应健康检查——K8s 只看 HTTP 状态码,内容体无关,503 就是“不健康” - 建议 readiness 用独立路由(如
GET /readyz),liveness 用更轻量的(如GET /livez),两者逻辑分离
真正卡住团队的往往不是怎么写路由,而是 SIGTERM 信号有没有被正确转发、Shutdown() 超时是否和 K8s 的 terminationGracePeriodSeconds 对齐、非 root 用户对挂载卷的权限是否被 SELinux 拦截——这些细节不验证,上线当天凌晨三点你还在改 preStop 脚本。











