最稳妥的是多阶段构建:编译用golang:1.22-alpine并设CGO_ENABLED=0、GOOS=linux,运行用scratch或distroless/static;必须静态链接、非root运行、监听0.0.0.0、配置GOMEMLIMIT及真实readiness探针。

直接用 FROM golang:alpine 构建并运行,大概率在 Kubernetes 里被 OOMKilled 或健康检查失败——这不是配置错了,是镜像构建逻辑没对齐容器生命周期。
Go 编译必须显式关掉 CGO
默认 go build 启用 CGO_ENABLED=1,生成的二进制依赖系统 libc;而 Alpine 用 musl,scratch 甚至没有 libc,一启动就报错:standard_init_linux.go:228: exec user process caused: no such file or directory。
-
CGO_ENABLED=0是硬性要求,不是可选项 - 加
GOOS=linux确保交叉编译目标正确 - 老项目若用了 SQLite、某些 DNS resolver 或自定义 C 代码,就得换纯 Go 实现,或改用
debian:slim基础镜像 - 验证方式:
ldd ./main输出应为not a dynamic executable
Dockerfile 必须用多阶段构建
单阶段镜像(比如直接 FROM golang:alpine 然后 CMD ["./main"])会把 Go 编译器、/usr/lib、调试符号全打包进去,镜像体积翻倍,攻击面扩大,且无法跑在 scratch 上。
- 第一阶段用
golang:1.22-alpine,只做编译:先COPY go.mod go.sum,再RUN go mod download,最后RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags="-s -w -buildmode=pie" -o /app/main . - 第二阶段用
scratch或gcr.io/distroless/static:nonroot,只COPY --from=builder /app/main / - 别写
CMD ["./main"],改用ENTRYPOINT ["/main"],确保 PID 1 是你的服务进程,否则SIGTERM转发失效 - 如果服务要发 HTTPS 请求(连 DB、调其他服务),
scratch里缺 CA 证书,得手动挂载或退选alpine:latest并RUN apk --no-cache add ca-certificates
HTTP Server 启动顺序必须改
http.ListenAndServe(":8080", handler) 是阻塞调用,但 Kubernetes 的 readinessProbe 在容器启动后立刻发请求——此时端口还没真正 bind 完,probe 就失败,Pod 卡在 ContainerCreating 或反复重启。
立即学习“go语言免费学习笔记(深入)”;
- 不能靠
time.Sleep等,不可靠也不符合 readiness 语义 - 正确做法:先
lis, err := net.Listen("tcp", ":8080"),再srv := &http.Server{Handler: router},最后srv.Serve(lis) -
/readyz端点必须检查真实依赖:DB 连接池是否 ready、gRPC client 是否已 dial、配置是否已从 ConfigMap 加载完毕 -
livenessProbe用/healthz,只返回 200;initialDelaySeconds至少设为 5,failureThreshold别小于 3,防 GC STW 误杀
Kubernetes 内存限制必须配 GOMEMLIMIT
即使你写了 resources.limits.memory: 512Mi,Go runtime 仍可能申请超过该值的堆内存,触发 OOMKilled——因为 Go 的 GC 不严格遵守 cgroup limit。
- 启动前读取
/sys/fs/cgroup/memory/memory.limit_in_bytes(K8s 1.20+ 改为memory.max),设环境变量GOMEMLIMIT为它的 80% - 同时配
resources.requests.memory,且requests ≤ limits,否则调度器可能把 Pod 分到内存不足节点 -
Service的targetPort必须和 Go 程序监听端口一致(比如代码里是:8080,就不能写成targetPort: 80) - Pod 必须以非 root 用户运行:
securityContext.runAsNonRoot: true,配合USER 1001(Dockerfile 里创建)
最常被跳过的其实是 readiness 检查项的真实性和 GOMEMLIMIT 的动态计算——这两点不落实,服务看着跑起来了,其实随时可能因 probe 失败或内存超限被 K8s 杀掉。


















