Docker 容器化

Docker 让应用的部署变得标准化和可移植。无论在本地开发环境还是生产服务器,容器里的应用行为都是一致的。对于 Go 应用来说,Docker 化更是简单——一个静态编译的二进制文件,几乎不需要什么依赖。

基础 Dockerfile

最简单的 Dockerfile:

FROM golang:1.21

WORKDIR /app

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

COPY . .

RUN go build -o myapp

EXPOSE 8080

CMD ["./myapp"]

这个 Dockerfile 可以工作,但有几个问题:

  • 镜像很大(golang 基础镜像约 1GB)
  • 包含编译工具,不安全
  • 没有优化构建缓存

多阶段构建

推荐使用多阶段构建,分离编译环境和运行环境:

# 构建阶段
FROM golang:1.21-alpine AS builder

WORKDIR /app

# 安装依赖
RUN apk add --no-cache git

# 复制 go.mod 和 go.sum,利用缓存
COPY go.mod go.sum ./
RUN go mod download

# 复制源代码
COPY . .

# 编译
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp

# 运行阶段
FROM alpine:latest

WORKDIR /app

# 安装 ca-certificates(HTTPS 需要)
RUN apk --no-cache add ca-certificates tzdata

# 从构建阶段复制二进制文件
COPY --from=builder /app/myapp .

# 设置时区
ENV TZ=Asia/Shanghai

EXPOSE 8080

CMD ["./myapp"]

这样构建出来的镜像只有 10-20MB。

Scratch 镜像

如果不需要时区数据和 CA 证书,可以用更小的 scratch 镜像:

FROM golang:1.21-alpine AS builder

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

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp

FROM scratch

COPY --from=builder /app/myapp /myapp

EXPOSE 8080

ENTRYPOINT ["/myapp"]

scratch 是空镜像,只有你放进去的文件,大小通常在 10MB 以内。

Docker Compose

开发环境用 Docker Compose 编排多个服务:

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - GIN_MODE=debug
      - DATABASE_URL=postgres://postgres:postgres@db:5432/myapp?sslmode=disable
      - REDIS_URL=redis:6379
    depends_on:
      - db
      - redis
    volumes:
      - ./uploads:/app/uploads

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app

volumes:
  postgres_data:

启动:

docker-compose up -d

生产环境配置

健康检查

FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp

FROM alpine:latest

RUN apk --no-cache add ca-certificates curl

WORKDIR /app
COPY --from=builder /app/myapp .

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

EXPOSE 8080

CMD ["./myapp"]

非 root 用户

FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp

FROM alpine:latest

RUN apk --no-cache add ca-certificates && \
    addgroup -g 1000 -S appgroup && \
    adduser -u 1000 -S appuser -G appgroup

WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/myapp .

USER appuser

EXPOSE 8080

CMD ["./myapp"]

环境变量配置

FROM alpine:latest

WORKDIR /app

# 默认环境变量
ENV GIN_MODE=release \
    PORT=8080

COPY --from=builder /app/myapp .

EXPOSE 8080

CMD ["./myapp"]

运行时覆盖:

docker run -e GIN_MODE=debug -e PORT=3000 -p 3000:3000 myapp

镜像优化技巧

1. 利用构建缓存

把不常变化的层放前面:

# 好的做法
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build

# 不好的做法
COPY . .
RUN go mod download
RUN go build

2. 减少层数

# 不好的做法
RUN apk add --no-cache git
RUN apk add --no-cache curl
RUN apk add --no-cache tzdata

# 好的做法
RUN apk add --no-cache git curl tzdata

3. 使用 .dockerignore

创建 .dockerignore 文件:

.git
.gitignore
.env
.env.*
*.md
Makefile
docker-compose*.yml
Dockerfile*
.dockerignore
tmp/
uploads/
*.test
*.out

Kubernetes 部署

Deployment

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:latest
          ports:
            - containerPort: 8080
          env:
            - name: GIN_MODE
              value: "release"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: database-url
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5

Service

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

Ingress

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - example.com
      secretName: myapp-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp
                port:
                  number: 80

镜像仓库

Docker Hub

# 登录
docker login

# 打标签
docker tag myapp username/myapp:v1.0.0

# 推送
docker push username/myapp:v1.0.0

私有仓库

# 推送到私有仓库
docker tag myapp registry.example.com/myapp:v1.0.0
docker push registry.example.com/myapp:v1.0.0

小结

Docker 容器化部署的关键点:

  • 使用多阶段构建减小镜像体积
  • 用 alpine 或 scratch 作为基础镜像
  • 添加健康检查
  • 使用非 root 用户运行
  • 利用 .dockerignore 排除不必要的文件
  • 用 Docker Compose 编排开发环境
  • 用 Kubernetes 编排生产环境

容器化让部署变得标准化,也便于扩展和迁移。掌握 Docker 是现代后端开发的必备技能。