下载工作台
Gin Web 开发

Docker 多阶段构建

试读上半部分 · 解锁后可读全文

第 15 章 · Docker 多阶段构建

本章目标:编写 api-go-demo 多阶段 Dockerfile非 root 运行;配置 HEALTHCHECK;编写 docker-compose 编排 MySQL + Redis + API(+ Worker 选修);.dockerignore 与十二要素配置;对照 fastapi-web ch19paas 部署;为 ch16 毕业交付做准备。

学时建议:4~5 小时(含 2 小时跟练)

前置:完成 gin-web ch14;本机 Docker Desktop。


15.1 场景说明:单二进制容器化

Go 编译为静态二进制,runtime 镜像可极小:

源码 ──multi-stage build──► registry.example.com/api-go-demo:1.0.0 ──► compose / K8s
对比Python svc-demoGo api-go-demo
运行时venv + 解释器单二进制
典型镜像200MB+< 30MB(distroless)
启动秒级毫秒级
用户app nonrootnonroot
api-go-demo/
├── Dockerfile
├── Dockerfile.worker      # 选修
├── docker-compose.yml
├── docker-compose.prod.yml # 选修
└── .dockerignore
虚构 registry.example.comapi.example.com严禁真实密钥进镜像或 Git。

15.2 多阶段 Dockerfile(完整)

# syntax=docker/dockerfile:1

FROM golang:1.22-alpine AS builder
WORKDIR /src

RUN apk add --no-cache git ca-certificates tzdata

COPY go.mod go.sum ./
RUN go mod download

COPY . .

ARG VERSION=dev
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
    -ldflags="-s -w -X main.version=${VERSION}" \
    -o /out/server ./cmd/server

# 选修:同一阶段编译 worker
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/worker ./cmd/worker

FROM gcr.io/distroless/static-debian12:nonroot AS runtime
WORKDIR /app

COPY --from=builder /out/server /app/server
COPY --from=builder /src/configs /app/configs
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

ENV TZ=UTC
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD ["/app/server", "-health-check"] 

# 若 distroless 无 wget,改用 binary 内置 -health-check 或 k8s probe 代替
# 见 15.6 备选 HEALTHCHECK

USER nonroot:nonroot
ENTRYPOINT ["/app/server"]
阶段作用
buildertoolchain、编译、go mod cache
runtime仅二进制 + CA + 配置

CGO_ENABLED=0:纯静态链接,distroless 无 glibc 依赖。


15.3 内置 health-check 模式(distroless 友好)

distroless 无 shell/curl,建议在 cmd/server 支持:

// cmd/server/main.go
func main() {
    if len(os.Args) > 1 && os.Args[1] == "-health-check" {
        resp, err := http.Get("http://127.0.0.1:8080/health")
        if err != nil || resp.StatusCode != 200 {
            os.Exit(1)
        }
        os.Exit(0)
    }
    // 正常启动...
}

不在 Dockerfile HEALTHCHECK,仅依赖 compose/K8s probe(见 15.5)。


15.4 .dockerignore

.git
.gitignore
.env
.env.*
!.env.example
bin/
logs/
tmp/
**/*_test.go
tests/
coverage.out
*.md
!README.md
docker-compose*.yml
Dockerfile*
.vscode
.idea
docs/swagger.json.bak

减小 context,避免 .env 进镜像


15.5 docker-compose.yml(开发全栈)

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: pass
      MYSQL_DATABASE: api_demo
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-ppass"]
      interval: 5s
      timeout: 3s
      retries: 10

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s

  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      APP_ENV: prod
      APP_PORT: "8080"
      DATABASE_URL: "root:pass@tcp(mysql:3306)/api_demo?charset=utf8mb4&parseTime=True&loc=Local"
      REDIS_ADDR: "redis:6379"
      JWT_SECRET: "docker-dev-secret-change-in-prod-min-32-chars"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "/app/server", "-health-check"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  worker:
    build:
      context: .
      dockerfile: Dockerfile
    entrypoint: ["/app/worker"]
    environment:
      REDIS_ADDR: "redis:6379"
    depends_on:
      - redis
      - api

volumes:
  mysql_data:
docker compose up --build -d
docker compose ps
curl -sf http://127.0.0.1:8080/health
curl -sf http://127.0.0.1:8080/api/v1/products/go-handbook

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。