Docker 生产化补充手册

1 阅读7分钟

适用:自建大型集群配套的镜像构建 / 节点 Docker 守护进程 假设你已读过 Docker操作手册.mdDockerCompose操作手册.md 镜像供应链单独成篇,见 镜像供应链安全手册.md


目录

  1. 生产前提:你真的需要 Docker 吗
  2. 守护进程生产配置
  3. 存储驱动与磁盘
  4. 日志驱动
  5. 资源限制与隔离
  6. Buildx 多架构构建流水线
  7. 构建缓存最佳实践
  8. Dockerfile 生产规约
  9. Rootless / Podman 兼容
  10. Docker Compose 在生产的边界
  11. 节点 Docker 故障处理
  12. 生产化 Checklist

1. 生产前提:你真的需要 Docker 吗

K8s 1.24+ 默认运行时是 containerd,不再是 dockershim。50+ 节点集群强烈建议:

场景建议运行时
K8s worker 节点containerd(轻、官方主推)
构建节点 / CIdocker(buildx 体验好)或 buildkit 独立部署
开发机docker(生态最熟)

下文"Docker 守护进程"主要针对构建机器仍用 docker 作运行时的节点


2. 守护进程生产配置

/etc/docker/daemon.json

{
  "data-root": "/var/lib/docker",
  "storage-driver": "overlay2",

  "live-restore": true,

  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5",
    "compress": "true",
    "labels": "service,env"
  },

  "default-ulimits": {
    "nofile": { "Name": "nofile", "Hard": 1048576, "Soft": 1048576 },
    "nproc":  { "Name": "nproc",  "Hard": 65535,   "Soft": 65535 }
  },

  "default-address-pools": [
    { "base": "172.30.0.0/16", "size": 24 }
  ],

  "registry-mirrors": [
    "https://harbor.internal.example.com"
  ],
  "insecure-registries": [],
  "allow-nondistributable-artifacts": [],

  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 5,
  "max-download-attempts": 5,

  "no-new-privileges": true,
  "userland-proxy": false,

  "metrics-addr": "127.0.0.1:9323",
  "experimental": false,

  "default-runtime": "runc",

  "exec-opts": ["native.cgroupdriver=systemd"]
}

关键项解释

  • live-restore: true:升级 dockerd 不杀容器,生产必开
  • default-address-pools:避免默认 172.17.0.0/16 与企业内网冲突(这是常见事故源)
  • exec-opts: native.cgroupdriver=systemd:与 K8s/kubelet 一致,避免 cgroup 驱动错配
  • userland-proxy: false:高 QPS 下用 iptables NAT 性能更好
  • no-new-privileges: true:默认安全基线
  • metrics-addr:暴露 Prometheus 指标
  • data-root:默认 /var/lib/docker,数据多时换大盘 独立 LV

修改后:

sudo systemctl daemon-reload
sudo systemctl restart docker
docker info | grep -E "Storage Driver|Cgroup|Live Restore"

3. 存储驱动与磁盘

3.1 选 overlay2

90% 场景用 overlay2(默认)。要求:

  • 内核 ≥ 4.0(推荐 ≥ 5.x)
  • 后端文件系统 ext4 或 xfs(xfs 必须 ftype=1
mkfs.xfs -n ftype=1 /dev/sdb1

3.2 数据目录独立挂载

/var/lib/docker 单独挂一块大盘(推荐 SSD),LVM 方便扩容:

pvcreate /dev/sdb
vgcreate vg-docker /dev/sdb
lvcreate -L 500G -n lv-docker vg-docker
mkfs.xfs -n ftype=1 /dev/vg-docker/lv-docker
mkdir -p /var/lib/docker
echo "/dev/vg-docker/lv-docker /var/lib/docker xfs defaults 0 0" >> /etc/fstab
mount -a

3.3 监控 & 清理

定期清理(cron):

# /etc/cron.d/docker-prune
30 3 * * * root docker system prune -af --filter "until=168h" >> /var/log/docker-prune.log 2>&1

构建节点不要乱清,会丢 buildx 缓存。专门隔离构建机和运行机。

监控指标:

docker_state_total{state="running"}
node_filesystem_avail_bytes{mountpoint="/var/lib/docker"} / node_filesystem_size_bytes{mountpoint="/var/lib/docker"}

4. 日志驱动

4.1 json-file(默认,最稳)

容器化生产 99% 用这个,必须配 max-sizemax-file,否则会撑爆磁盘。已在第 2 节配过。

4.2 集中日志(推荐两种方式)

方式 A:journald → Vector / Fluent Bit

{ "log-driver": "journald" }

宿主机 journald 由 Vector 转发到 Loki / ES。

方式 B:保持 json-file,sidecar 收集

K8s 默认这样,DaemonSet 跑 Fluent Bit 收 /var/log/containers/*.log

不推荐 gelf / fluentd 直连 driver:网络抖动时 docker 写日志会阻塞,进而阻塞容器 stdout,曾导致重大事故。


5. 资源限制与隔离

K8s 场景由 K8s 处理;裸 docker 场景:

docker run -d --name app \
  --cpus=2 \
  --memory=2g --memory-swap=2g \
  --pids-limit=1000 \
  --ulimit nofile=65535:65535 \
  --read-only \
  --tmpfs /tmp:rw,size=64m,mode=1777 \
  --cap-drop=ALL --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  --user 10001:10001 \
  myapp:1.0

6. Buildx 多架构构建流水线

6.1 启用 buildx + 容器 builder

# 创建专用 builder(脱离默认 docker driver,能用所有 buildkit 特性)
docker buildx create --name multi --driver docker-container --use \
  --driver-opt network=host \
  --buildkitd-flags '--allow-insecure-entitlement security.insecure'
docker buildx inspect --bootstrap

6.2 远程共享 builder(CI 多机加速)

CI 集群跑一个 BuildKit 服务,所有 runner 共享缓存:

# 单独跑 buildkitd
docker run -d --name buildkitd --privileged \
  -p 1234:1234 \
  moby/buildkit:latest --addr tcp://0.0.0.0:1234

# 各 runner 用 remote driver
docker buildx create --name remote --driver remote tcp://buildkit.ci.internal:1234 --use

6.3 多架构构建(amd64 + arm64)

# 注册 binfmt(每台机器一次)
docker run --privileged --rm tonistiigi/binfmt --install all

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t harbor.internal/app/myapp:1.2.3-${GIT_SHA::8} \
  --push .

6.4 Cache 远程化(CI 必备)

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --cache-from=type=registry,ref=harbor.internal/app/myapp:buildcache \
  --cache-to=type=registry,ref=harbor.internal/app/myapp:buildcache,mode=max \
  -t harbor.internal/app/myapp:1.2.3 --push .

模式:

  • mode=min:只缓存最终层
  • mode=max:缓存所有中间层(推荐,命中率高,但占用大)

GitHub Actions 用 cache-from=type=gha,cache-to=type=gha,mode=max


7. 构建缓存最佳实践

7.1 分层缓存命中规则

层缓存命中条件:指令 + 上下文文件 hash 相同。任何上层失效会传染下层。

错误示例:

COPY . /app
RUN npm ci                # 改任何源码都会重装依赖

正确:

COPY package.json package-lock.json ./
RUN npm ci                # 仅 package.json 改时重装
COPY . .
RUN npm run build

7.2 BuildKit 高级特性

# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
# Cache mount:跨构建复用 npm 缓存
RUN --mount=type=cache,target=/root/.npm \
    npm ci

FROM deps AS build
COPY . .
RUN --mount=type=cache,target=/app/node_modules/.cache \
    npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=deps  /app/node_modules ./node_modules
USER node
CMD ["node","dist/server.js"]

Go 项目类似:

# syntax=docker/dockerfile:1.7
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=cache,target=/go/pkg/mod \
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app

FROM gcr.io/distroless/static
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

7.3 secret mount(永远不要 ARG 传密钥

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
docker buildx build --secret id=npmrc,src=$HOME/.npmrc -t myapp:1.0 .

8. Dockerfile 生产规约

规则说明
基础镜像 pin 到 sha256FROM node:20-alpine@sha256:... 防供应链 tag 偷换
多阶段,最终镜像不带构建工具通用
用 distroless / alpine / scratch减少攻击面与体积
非 root 用户USER 10001 或 distroless 自带 nonroot
不写敏感信息用 secret mount / 运行时注入
显式 EXPOSE 和 ENTRYPOINT/CMD避免运行时歧义
加 HEALTHCHECK 或 K8s probe(推荐 K8s 侧)运行时健康
加 OCI labelsorg.opencontainers.image.source / revision / version / created
加 .dockerignore必须有

OCI labels 推荐:

ARG GIT_SHA
ARG VERSION
ARG BUILD_DATE
LABEL org.opencontainers.image.source="https://git.example.com/team/myapp" \
      org.opencontainers.image.revision="$GIT_SHA" \
      org.opencontainers.image.version="$VERSION" \
      org.opencontainers.image.created="$BUILD_DATE" \
      org.opencontainers.image.licenses="Apache-2.0" \
      org.opencontainers.image.vendor="Example Inc."

构建时:

docker buildx build \
  --build-arg GIT_SHA=$(git rev-parse HEAD) \
  --build-arg VERSION=$(git describe --tags --always) \
  --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
  -t harbor.internal/app/myapp:$(git rev-parse --short HEAD) \
  --push .

9. Rootless / Podman 兼容

9.1 Rootless Docker

合规要求高的环境用 rootless:

dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker

限制:

  • 不能直接绑 < 1024 端口(可改 net.ipv4.ip_unprivileged_port_start
  • 性能略低
  • 需要 newuidmap / subuid / subgid 配置

9.2 Podman 兼容

K8s containerd + 开发机 Podman 是越来越常见的组合。Podman CLI 兼容 docker:

alias docker=podman
podman build -t myapp:1.0 .

差异:

  • 没有守护进程(fork 模型)
  • 默认 rootless
  • podman generate kube 可输出 K8s YAML

10. Docker Compose 在生产的边界

10.1 结论先行

多机生产不要用 Compose。 Compose 设计目标是"单机栈"。

允许使用的场景:

  • 单机内部工具(CI runner / 跳板机服务)
  • 边缘节点(断网环境,单台跑应用)
  • 临时演示 / PoC

不要用于:

  • 多机生产应用集群(用 K8s)
  • 关键业务数据库(要么 K8s + Operator,要么纯 VM + 配置管理)

10.2 单机生产 Compose 的最低要求

services:
  app:
    image: harbor.internal/app/myapp:1.2.3-abc1234   # 不用 latest,含 sha 摘要更佳
    pull_policy: always
    restart: unless-stopped
    init: true                         # 防 zombie
    read_only: true
    tmpfs: ["/tmp:size=64m"]
    user: "10001:10001"
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    deploy:
      resources:
        limits:   { cpus: "2", memory: 2G }
        reservations: { memory: 512M }
    healthcheck:
      test: ["CMD","wget","-qO-","http://localhost:8080/healthz"]
      interval: 15s
      timeout: 3s
      retries: 5
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "5"
        labels: "service,env"
        tag: "{{.Name}}/{{.ID}}"
    labels:
      service: myapp
      env: prod
    secrets:
      - source: db_password
        target: /run/secrets/db_password
        mode: 0400

secrets:
  db_password:
    file: /etc/secrets/db_password     # 文件权限 0600,root only

10.3 secret 的正确用法

secrets: 字段把 secret 作为文件挂进容器(/run/secrets/<name>),比环境变量安全(不会出现在 docker inspect、ps 输出、子进程环境)。

应用读:

with open("/run/secrets/db_password") as f:
    password = f.read().strip()

10.4 单机近似零宕机

Compose 没有滚动语义,但可以:

# 双 Compose 项目 + 前置 nginx 切流
docker compose -p app-blue  -f compose.yaml --env-file .env.blue  up -d
docker compose -p app-green -f compose.yaml --env-file .env.green up -d

# 验证 green 健康后,nginx upstream 切到 green
# 然后停 blue
docker compose -p app-blue down

10.5 备份

# 备份命名卷
docker run --rm \
  -v db-data:/data:ro \
  -v $(pwd)/backup:/backup \
  alpine tar czf /backup/db-$(date +%F).tar.gz -C / data

数据库类必须用应用级备份(mysqldump / pg_dump),文件级 tar 不保证一致性。


11. 节点 Docker 故障处理

11.1 常见现象与排查

现象排查
dockerd 卡死,容器无法操作journalctl -u docker --since "1 hour ago";看 goroutine kill -SIGUSR1 $(pidof dockerd) 后看日志;多数是日志驱动阻塞或 containerd 异常
磁盘满docker system dfdu -sh /var/lib/docker/containers/*/*.log(日志没限大小);du -sh /var/lib/docker/overlay2
failed to register layeroverlay2 损坏,停 docker、备份 /var/lib/docker、清理或换盘
网络不通 / IP 冲突检查 default-address-pools 是否与企业网段冲突;iptables -t nat -L -n
高 CPU sys 占用老内核的 cgroup v1 + 大量短任务;升内核或开 cgroup v2
容器频繁 OOM`docker inspect jq '.[0].State';查 dmesggrep -i kill`;调 limits

11.2 关键运维命令

# 守护进程信息
docker info
docker version

# dockerd debug 数据
kill -SIGUSR1 $(pidof dockerd)        # 输出 goroutine stack 到日志
journalctl -u docker --since "30 min ago"

# 容器级
docker stats --no-stream
docker top <container>
docker inspect <container> | jq '.[0].State'
docker events --since 1h              # 守护进程事件

# 强清理(小心!只在确认无误时)
docker container prune
docker image prune -a
docker volume prune
docker system prune -a --volumes      # 全清,谨慎

11.3 保留现场

故障时先保留再清理

# 容器内 core dump / 文件
docker cp <id>:/path/to/log /tmp/

# 容器配置快照
docker inspect <id> > /tmp/inspect.json

# 整个守护进程数据目录(大!)
tar czf /tmp/docker-state.tgz /var/lib/docker/containers /var/lib/docker/overlay2/<layer-id>

12. 生产化 Checklist

守护进程

  • daemon.json 配齐:live-restore、log-opts、ulimits、address-pools、storage-driver
  • cgroup 驱动 = systemd(与 K8s 一致)
  • /var/lib/docker 独立大盘 SSD
  • dockerd 监控接 Prometheus(metrics-addr)
  • 审计 / 合规要求 → rootless 或 Podman

镜像

  • 所有 FROM 用 alpine / distroless / 公司基础镜像
  • 多阶段,最终镜像最小化
  • 非 root 用户
  • Pin 到 sha256(高安全要求场景)
  • OCI labels 完整
  • 永不 latest,tag 含 git-sha
  • .dockerignore 完整

构建

  • Buildx + container/remote driver
  • 多架构(amd64 + arm64,按需)
  • cache-to/cache-from 远程缓存
  • secret 用 --mount=type=secret,绝不 ARG
  • 构建机器与运行机器分开
  • 镜像签名 + SBOM + 漏洞扫描(见 镜像供应链安全手册.md

运行

  • 资源 limit 必填(CPU / mem / pids)
  • cap drop ALL,按需 add
  • no-new-privileges
  • read-only + tmpfs /tmp
  • healthcheck(K8s 场景由 probe 替代)
  • 日志大小限制
  • 重启策略明确

Compose(如使用)

  • 仅限单机场景
  • secrets 用文件方式
  • 镜像 tag 不变更(immutable)
  • 数据卷有应用级备份
  • healthcheck + restart 策略

故障预案

  • dockerd 卡死的 SIGUSR1 步骤进 runbook
  • 磁盘清理脚本 + 报警
  • 节点 IP 段冲突自检
  • 升级 docker 演练(live-restore 验证)

配套阅读:Kubernetes生产化手册.mdNginx生产化手册.md镜像供应链安全手册.md