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:

维度scratchalpine
基础大小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

为什么重要

  1. 极小镜像体积:scratch 镜像只有二进制本身大小(通常 10-20MB),相比 Java/Python 的数百 MB 镜像有数量级优势,拉取和启动速度极快。
  2. 安全性:最小镜像没有 shell、没有包管理器、没有多余用户,攻击面极小。
  3. 构建缓存优化:先复制 go.mod/go.sumgo mod download,利用 Docker 层缓存,源码变动不会重新下载依赖。
  4. 生产就绪:健康检查保证容器编排器(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 的路径:必须使用绝对路径,且目标路径不存在时会自动创建。
创建于 2026/6/25 更新于 2026/6/25