Go Docker 部署
Go 服务容器化部署的最佳实践,涵盖多阶段构建、镜像优化、健康检查与优雅关闭
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
Go Docker 部署
一句话定义
Go 服务的容器化部署利用多阶段构建和最小基础镜像(scratch/alpine)实现极小的镜像体积,配合健康检查和优雅关闭机制保证生产环境可靠性。
核心机制 / 工作原理
多阶段构建模式:
Go 的多阶段 Dockerfile 是业界标准做法——第一阶段编译,第二阶段只复制二进制:
# ---- 构建阶段 ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./cmd/myapp
# ---- 运行阶段 ----
FROM scratch
COPY --from=builder /app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/app"]
scratch vs alpine:
| 维度 | scratch | alpine |
|---|---|---|
| 基础大小 | 0 MB | ~7 MB |
| Shell | 无 | 有(ash) |
| TLS 证书 | 需手动复制 | 预装 |
| 调试能力 | 无法 exec | 可安装工具 |
| 适用场景 | 生产最终镜像 | 需要 shell 调试 |
CGO 在容器中的影响:
如果代码依赖 CGO(如 SQLite、图像处理),不能用 scratch:
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache gcc musl-dev
RUN CGO_ENABLED=1 go build -tags "netgo" -ldflags="-s -w" -o /app ./cmd/myapp
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app /app
.dockerignore 配置:
.git
.github
*.md
Dockerfile
docker-compose.yml
tmp/
vendor/
减少构建上下文大小,加速 docker build,避免缓存失效。
最小例子 / 最小场景
Go 服务中的优雅关闭:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8080"}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx)
}
Docker 健康检查:
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD ["/app", "-healthcheck"] || exit 1
为什么重要
- 极小镜像体积:scratch 镜像只有二进制本身大小(通常 10-20MB),相比 Java/Python 的数百 MB 镜像有数量级优势,拉取和启动速度极快。
- 安全性:最小镜像没有 shell、没有包管理器、没有多余用户,攻击面极小。
- 构建缓存优化:先复制
go.mod/go.sum再go mod download,利用 Docker 层缓存,源码变动不会重新下载依赖。 - 生产就绪:健康检查保证容器编排器(K8s)能正确判断服务状态,优雅关闭保证零请求丢失。
边界与易混淆点
- 信号处理:Docker 默认发送
SIGTERM,等待 10 秒后发送SIGKILL。Go 程序必须监听SIGTERM并实现 graceful shutdown,否则会被强制终止。 - PID 1 问题:Go 二进制作为 PID 1 运行时,默认不会处理信号转发。使用
tini或在代码中显式监听信号可以解决。 - 时区问题:scratch 镜像没有时区数据,如果用到
time.LoadLocation,需要复制/usr/share/zoneinfo或使用tzdata包。 - DNS 解析:scratch 镜像默认使用 Go 的纯 DNS 解析器(netgo),行为可能与 glibc 不同。遇到 DNS 问题可以尝试
-tags=netgo。 - 调试策略:scratch 镜像无法
docker exec,生产调试依赖日志和可观测性。开发环境建议用 alpine 或 dlv 远程调试镜像。 COPY --from=builder的路径:必须使用绝对路径,且目标路径不存在时会自动创建。