> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Docker 高级指南
- URL: https://blog.vercanti.com/docker-gao-ji-zhi-nan/
- Published: 2026-08-28T14:34:23.000Z
- Updated: 2026-08-28T14:56:24.000Z
- Description: 1. Docker高级指南 · 镜像构建高级技巧(/docker-gao-ji-zhi-nan/#%E9%95%9C%E5%83%8F%E6%9E%84%E5%BB%BA%E9%AB%98%E7%BA%A7%E6%8A%80%E5%B7%A7) 2. Docker高级指南 · Docker 安全加固(/docker-gao-ji-zhi-nan/#docker-%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA) 3. Docker高级指南 · Docker Swarm 集群编排(/docker-gao-ji-zhi-nan/#do
- Author: yellowdog
- Tags: DevOps, Docker

> 官方文档：<https://docs.docker.com/>  
> 适用版本：Docker Engine 24+（2026-05-07 核实）

## 目录

1. [Docker高级指南 · 镜像构建高级技巧](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#%E9%95%9C%E5%83%8F%E6%9E%84%E5%BB%BA%E9%AB%98%E7%BA%A7%E6%8A%80%E5%B7%A7)
2. [Docker高级指南 · Docker 安全加固](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#docker-%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA)
3. [Docker高级指南 · Docker Swarm 集群编排](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#docker-swarm-%E9%9B%86%E7%BE%A4%E7%BC%96%E6%8E%92)
4. [Docker高级指南 · 生产环境部署方案](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2%E6%96%B9%E6%A1%88)
5. [Docker高级指南 · CI/CD 集成](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#ci%2Fcd-%E9%9B%86%E6%88%90)
6. [Docker高级指南 · 容器内应用最佳实践](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#%E5%AE%B9%E5%99%A8%E5%86%85%E5%BA%94%E7%94%A8%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)
7. [Docker高级指南 · 性能调优](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98)
8. [Docker高级指南 · Docker 网络高级](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#docker-%E7%BD%91%E7%BB%9C%E9%AB%98%E7%BA%A7)
9. [Docker高级指南 · 日常运维命令](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#%E6%97%A5%E5%B8%B8%E8%BF%90%E7%BB%B4%E5%91%BD%E4%BB%A4)
10. [Docker高级指南 · 踩坑与最佳实践清单](https://blog.vercanti.com/docker-gao-ji-zhi-nan/#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E6%B8%85%E5%8D%95)

---

## 镜像构建高级技巧

### BuildKit 概述

BuildKit 是 Docker 官方的下一代构建引擎，相比传统构建引擎具有以下优势：并行构建阶段、高效缓存、秘密挂载、SSH 转发、跨平台构建支持。

#### 启用 BuildKit

方式一：环境变量（临时生效）

```bash
DOCKER_BUILDKIT=1 docker build -t myapp .

```

方式二：Docker Desktop / dockerd 全局配置（永久生效）

编辑 `/etc/docker/daemon.json`：

```json
{
  "features": {
    "buildkit": true
  }
}

```

然后重启守护进程：

```bash
sudo systemctl restart docker

```

方式三：使用 `docker buildx`（BuildKit 的扩展命令行）

```bash
# 查看当前 builder
docker buildx ls

# 创建并使用新 builder（支持多平台）
docker buildx create --name mybuilder --use

# 查看 builder 详情
docker buildx inspect --bootstrap

```

### RUN --mount 全类型详解

`RUN --mount` 是 BuildKit 提供的挂载机制，允许在构建步骤中挂载各类资源，而不会将这些资源写入镜像层。

#### type=cache（构建缓存）

将包管理器缓存目录持久化到宿主机，避免每次构建重新下载依赖。

```dockerfile
# Node.js npm 缓存
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

```

```dockerfile
# Python pip 缓存
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

```

```dockerfile
# apt 包缓存（Debian/Ubuntu）
FROM ubuntu:22.04
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && apt-get install -y curl git

```

type=cache 参数说明：

| 参数       | 类型     | 默认值       | 说明                                       |
| -------- | ------ | --------- | ---------------------------------------- |
| target   | string | 必填        | 容器内挂载路径                                  |
| id       | string | target 路径 | 缓存标识符，相同 id 共享同一缓存                       |
| source   | string | 空         | 宿主机源路径（不填则由 BuildKit 管理）                 |
| from     | string | 空         | 从某个构建阶段的路径作为缓存源                          |
| sharing  | enum   | shared    | shared（多并发可读写）/ private（独占）/ locked（串行写） |
| readonly | bool   | false     | 设为 true 则只读挂载，不写回                        |
| mode     | string | 0755      | 缓存目录权限                                   |
| uid      | int    | 0         | 缓存目录 owner UID                           |
| gid      | int    | 0         | 缓存目录 owner GID                           |

#### type=bind（绑定挂载）

将构建上下文中的文件挂载进构建步骤，无需 COPY 即可使用，适合临时读取的文件（如依赖定义文件）。

```dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN --mount=type=bind,source=requirements.txt,target=/req.txt \
    pip install -r /req.txt
# requirements.txt 不会被复制进镜像
COPY src/ ./src/

```

```dockerfile
# 挂载整个目录（只读）
FROM node:20-alpine
RUN --mount=type=bind,source=.,target=/src,readonly \
    cd /src && npm run test

```

type=bind 参数说明：

| 参数       | 类型     | 默认值   | 说明                 |
| -------- | ------ | ----- | ------------------ |
| target   | string | 必填    | 容器内挂载路径            |
| source   | string | .     | 构建上下文中的源路径（相对路径）   |
| from     | string | 空     | 从某个构建阶段取文件，而非构建上下文 |
| readonly | bool   | false | 是否只读挂载             |
| rw       | bool   | false | readonly 的反义写法     |

#### type=secret（安全注入密钥）

在构建时注入敏感信息（如 npm token、SSH 密钥配置），密钥内容不会写入任何镜像层，`docker history` 中不可见。

```dockerfile
# 使用私有 npm registry
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
COPY . .

```

构建命令：

```bash
# 从文件注入
docker build --secret id=npmrc,src=$HOME/.npmrc .

# 从环境变量注入（通过 /dev/stdin 或临时文件）
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > /tmp/npmrc
docker build --secret id=npmrc,src=/tmp/npmrc .
rm /tmp/npmrc

```

```dockerfile
# 访问私有 pip 源
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN --mount=type=secret,id=pip_conf,target=/etc/pip.conf \
    pip install -r requirements.txt

```

type=secret 参数说明：

| 参数       | 类型     | 默认值           | 说明                           |
| -------- | ------ | ------------- | ---------------------------- |
| id       | string | 必填            | secret 标识符，与 --secret id= 对应 |
| target   | string | /run/secrets/ | 容器内挂载路径                      |
| required | bool   | false         | 设为 true 时 secret 不存在则构建失败    |
| mode     | string | 0400          | 文件权限                         |
| uid      | int    | 0             | 文件 owner UID                 |
| gid      | int    | 0             | 文件 owner GID                 |

#### type=ssh（SSH agent 转发）

将宿主机的 SSH agent 转发进构建步骤，用于拉取私有 Git 仓库，私钥不会写入镜像。

```dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y git openssh-client
RUN mkdir -p -m 0600 ~/.ssh && \
    ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh \
    git clone git@github.com:myorg/private-repo.git /app
WORKDIR /app
RUN pip install -r requirements.txt

```

构建命令：

```bash
# 确保 SSH agent 已加载密钥
eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa

# 构建时转发 SSH agent
docker build --ssh default .

# 指定特定 socket
docker build --ssh default=/run/user/1000/keyring/ssh .

```

type=ssh 参数说明：

| 参数       | 类型     | 默认值              | 说明                         |
| -------- | ------ | ---------------- | -------------------------- |
| id       | string | default          | SSH socket 标识符             |
| target   | string | $SSH\_AUTH\_SOCK | 容器内 socket 路径              |
| required | bool   | false            | 设为 true 时 SSH agent 不可用则失败 |
| mode     | string | 0600             | socket 文件权限                |
| uid      | int    | 0                | socket owner UID           |
| gid      | int    | 0                | socket owner GID           |

#### type=tmpfs（临时内存文件系统）

在构建步骤中挂载 tmpfs，适合需要临时写入但不希望写入镜像层的场景。

```dockerfile
FROM ubuntu:22.04
RUN --mount=type=tmpfs,target=/tmp/build \
    cd /tmp/build && \
    curl -o archive.tar.gz https://example.com/archive.tar.gz && \
    tar -xzf archive.tar.gz && \
    make install

```

type=tmpfs 参数说明：

| 参数     | 类型     | 默认值  | 说明                   |
| ------ | ------ | ---- | -------------------- |
| target | string | 必填   | 容器内挂载路径              |
| size   | int    | 系统默认 | tmpfs 大小（字节），0 表示不限制 |

### 多平台构建（Cross-Platform Build）

BuildKit 通过 QEMU 用户态模拟或交叉编译实现在单台机器上构建多平台镜像。

#### 环境准备

```bash
# 安装 QEMU 用户态模拟（Linux 宿主机）
docker run --privileged --rm tonistiigi/binfmt --install all

# 创建支持多平台的 builder
docker buildx create --name multiarch --driver docker-container --use
docker buildx inspect --bootstrap

```

#### 内置平台变量

BuildKit 在构建时自动注入以下 ARG，无需声明即可在 Dockerfile 中使用：

| 变量             | 说明       | 示例值           |
| -------------- | -------- | ------------- |
| BUILDPLATFORM  | 构建机器平台   | linux/amd64   |
| BUILDOS        | 构建机器操作系统 | linux         |
| BUILDARCH      | 构建机器架构   | amd64         |
| BUILDVARIANT   | 构建机器变体   | （arm 时为 v7 等） |
| TARGETPLATFORM | 目标平台     | linux/arm64   |
| TARGETOS       | 目标操作系统   | linux         |
| TARGETARCH     | 目标架构     | arm64         |
| TARGETVARIANT  | 目标变体     | v8            |

#### 多平台 Dockerfile 示例

```dockerfile
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
    go build -o /app/server ./cmd/server

FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

```

#### 多平台构建命令

```bash
# 构建并推送到 registry（必须推送，本地无法同时存储多平台）
docker buildx build \
    --platform linux/amd64,linux/arm64,linux/arm/v7 \
    -t myorg/myapp:latest \
    --push \
    .

# 仅构建不推送（测试用）
docker buildx build \
    --platform linux/amd64,linux/arm64 \
    -t myorg/myapp:latest \
    .

# 导出到本地 tar 文件
docker buildx build \
    --platform linux/amd64 \
    -t myorg/myapp:latest \
    -o type=docker,dest=./myapp.tar \
    .

```

#### CI 多平台发布完整示例（GitHub Actions）

```yaml
name: Multi-Platform Build

on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            myorg/myapp:latest
            myorg/myapp:${{ github.ref_name }}

```

### 构建缓存共享策略

#### \--cache-from 和 --cache-to

| 缓存类型     | \--cache-to 示例                           | 说明                         |
| -------- | ---------------------------------------- | -------------------------- |
| registry | type=registry,ref=myorg/myapp:cache      | 存储在 OCI registry           |
| inline   | type=inline                              | 嵌入到镜像本身（仅单平台）              |
| local    | type=local,dest=/tmp/cache               | 存储在本地目录                    |
| gha      | type=gha                                 | GitHub Actions Cache（自动鉴权） |
| s3       | type=s3,bucket=mybucket,region=us-east-1 | 存储在 S3                     |

```bash
# 使用 registry 缓存
docker buildx build \
    --cache-from type=registry,ref=myorg/myapp:cache \
    --cache-to type=registry,ref=myorg/myapp:cache,mode=max \
    -t myorg/myapp:latest \
    --push \
    .

# mode=max 缓存所有中间层（推荐）；mode=min 只缓存最终层

```

#### BuildKit 并行阶段执行

BuildKit 自动分析多阶段构建的依赖关系并行执行互不依赖的阶段：

```dockerfile
# syntax=docker/dockerfile:1
FROM node:20 AS frontend-builder
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build

# 与 frontend-builder 并行执行
FROM golang:1.22 AS backend-builder
WORKDIR /backend
COPY backend/go.* ./
RUN go mod download
COPY backend/ .
RUN go build -o server .

FROM ubuntu:22.04
COPY --from=frontend-builder /frontend/dist /var/www/html
COPY --from=backend-builder /backend/server /usr/local/bin/server
CMD ["/usr/local/bin/server"]

```

#### 使用 Heredoc 减少层数

```dockerfile
# syntax=docker/dockerfile:1
FROM ubuntu:22.04

# 传统写法：多个 RUN 命令多个层，或用 && 拼接可读性差
# BuildKit heredoc 写法：单层，可读性好
RUN <<EOF
apt-get update
apt-get install -y curl git vim
rm -rf /var/lib/apt/lists/*
useradd -m -u 1001 appuser
mkdir -p /app && chown appuser:appuser /app
EOF

USER appuser
WORKDIR /app

```

---

## Docker 安全加固

### 镜像安全

#### 使用非 root 用户

默认情况下容器以 root 身份运行，这是不必要的权限暴露。

```dockerfile
FROM node:20-alpine

# 方式一：使用基础镜像已有的非特权用户
WORKDIR /app
COPY --chown=node:node package*.json ./
RUN npm ci
COPY --chown=node:node . .
USER node

# 方式二：创建专用系统用户
FROM python:3.12-slim
RUN groupadd --gid 1001 appgroup && \
    useradd --uid 1001 --gid appgroup --shell /bin/sh --create-home appuser
WORKDIR /app
COPY --chown=appuser:appgroup requirements.txt ./
RUN pip install -r requirements.txt
COPY --chown=appuser:appgroup . .
USER appuser
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

```

useradd 关键参数：

| 参数                     | 说明                                          |
| ---------------------- | ------------------------------------------- |
| \--uid / -u            | 指定 UID（推荐 1000 以上，避免与系统用户冲突）                |
| \--gid / -g            | 指定主组 GID                                    |
| \--system / -r         | 创建系统用户（UID < 1000，无 home 目录）                |
| \--no-create-home / -M | 不创建 home 目录（服务进程不需要）                        |
| \--shell               | 登录 shell（服务用户设为 /bin/false 或 /sbin/nologin） |

#### Distroless 镜像

Distroless 镜像不包含 shell、包管理器等工具，大幅减少攻击面。

```dockerfile
# Go 应用（静态编译 + distroless）
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o server .

FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]

```

```dockerfile
# Node.js 应用
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY src/ ./src/
USER nonroot:nonroot
CMD ["src/index.js"]

```

常用 Distroless 镜像：

| 镜像                                  | 用途               |
| ----------------------------------- | ---------------- |
| gcr.io/distroless/static-debian12   | Go / Rust 静态编译   |
| gcr.io/distroless/base-debian12     | 需要 glibc 的应用     |
| gcr.io/distroless/cc-debian12       | 需要 libstdc++ 的应用 |
| gcr.io/distroless/python3-debian12  | Python 应用        |
| gcr.io/distroless/nodejs20-debian12 | Node.js 20 应用    |
| gcr.io/distroless/java21-debian12   | Java 21 应用       |

#### 镜像扫描

```bash
# Docker Scout（Docker 官方）
docker scout cves myorg/myapp:latest
docker scout recommendations myorg/myapp:latest

# Trivy（开源，功能全面）
trivy image myorg/myapp:latest
trivy image --severity HIGH,CRITICAL myorg/myapp:latest
trivy image --exit-code 1 --severity CRITICAL myorg/myapp:latest  # CI 中失败

# 扫描本地 Dockerfile
trivy config ./Dockerfile

# Snyk
snyk container test myorg/myapp:latest

```

#### 固定镜像 Digest

使用 tag 可能因为镜像更新而拉取不同内容；使用 digest 确保不变性：

```bash
# 获取镜像 digest
docker inspect --format='{{index .RepoDigests 0}}' node:20-alpine
# 输出: node@sha256:abc123...

# 在 Dockerfile 中固定 digest
FROM node:20-alpine@sha256:a0b943f9b0e2e6f3d8c1234567890abcdef1234567890abcdef1234567890ab

```

### 运行时安全

#### 安全运行参数综合示例

```bash
docker run \
    --read-only \
    --tmpfs /tmp:rw,size=64m,mode=1777 \
    --tmpfs /var/run:rw,size=8m \
    --cap-drop ALL \
    --cap-add NET_BIND_SERVICE \
    --security-opt no-new-privileges \
    --security-opt seccomp=./seccomp-profile.json \
    --user 1001:1001 \
    --pids-limit 100 \
    --memory 512m \
    --memory-swap 512m \
    --cpus 1.0 \
    -p 80:80 \
    myorg/myapp:latest

```

运行时安全参数说明：

| 参数                                | 说明                           |
| --------------------------------- | ---------------------------- |
| \--read-only                      | 容器根文件系统只读，防止运行时写入            |
| \--tmpfs path                     | 在指定路径挂载 tmpfs，允许临时写入         |
| \--cap-drop ALL                   | 删除所有 Linux Capabilities      |
| \--cap-add CAP                    | 仅添加所需的 Capability            |
| \--security-opt no-new-privileges | 禁止进程通过 setuid/setgid 获得新权限   |
| \--security-opt seccomp=file      | 使用自定义 seccomp 配置文件限制系统调用     |
| \--security-opt apparmor=profile  | 应用 AppArmor profile          |
| \--user uid:gid                   | 以指定 UID:GID 运行               |
| \--userns-remap=default           | 启用用户命名空间重映射（配置在 daemon.json） |
| \--pids-limit n                   | 限制容器内进程数，防止 fork bomb        |

#### Linux Capabilities 常用参考

| Capability         | 说明                | 常见用途             |
| ------------------ | ----------------- | ---------------- |
| NET\_BIND\_SERVICE | 绑定 1024 以下端口      | Web 服务器监听 80/443 |
| NET\_ADMIN         | 网络管理操作            | VPN 客户端、网络工具     |
| SYS\_PTRACE        | 进程跟踪（ptrace 系统调用） | 调试工具、性能分析        |
| SYS\_ADMIN         | 宽泛的系统管理操作（高危）     | 挂载文件系统等          |
| CHOWN              | 修改文件所有者           | 入口脚本调整文件权限       |
| SETUID / SETGID    | 修改进程 UID/GID      | sudo 类工具         |
| DAC\_OVERRIDE      | 绕过 DAC 权限检查       | 访问受限文件           |
| KILL               | 向任意进程发送信号         | 进程管理工具           |
| IPC\_LOCK          | 锁定内存（mlock）       | 数据库、内存缓存         |
| SYS\_NICE          | 修改进程优先级           | 实时应用             |

#### 自定义 Seccomp 配置文件

```json
{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"],
    "syscalls": [
        {
            "names": [
                "accept4", "access", "arch_prctl", "bind", "brk",
                "clock_gettime", "clone", "close", "connect",
                "epoll_create1", "epoll_ctl", "epoll_wait",
                "execve", "exit", "exit_group",
                "fcntl", "fstat", "futex", "getcwd", "getdents64",
                "getpid", "getppid", "getrandom", "getsockname",
                "getsockopt", "ioctl", "listen", "lseek", "mmap",
                "mprotect", "munmap", "nanosleep", "newfstatat",
                "openat", "pipe2", "poll", "prctl", "pread64",
                "read", "readlink", "recvfrom", "recvmsg", "rt_sigaction",
                "rt_sigprocmask", "rt_sigreturn", "sendmsg", "sendto",
                "set_robust_list", "setsockopt", "sigaltstack",
                "socket", "stat", "statfs", "tgkill", "write"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

```

```bash
docker run --security-opt seccomp=./seccomp-profile.json myapp

```

#### 用户命名空间重映射

在 `/etc/docker/daemon.json` 中配置：

```json
{
    "userns-remap": "default"
}

```

重启 Docker 后，容器内的 root（UID 0）会映射为宿主机上的高 UID（如 100000），即使容器逃逸也无法获得宿主机 root 权限。

### 敏感数据处理

#### 敏感信息传入方式安全对比

| 方式                                    | 写入镜像层 | docker history 可见 | 运行时环境可见            | 推荐程度     |
| ------------------------------------- | ----- | ----------------- | ------------------ | -------- |
| Dockerfile ENV/ARG 硬编码                | 是     | 是                 | 是                  | 禁止       |
| docker run -e KEY=VALUE               | 否     | 否                 | 是（/proc/1/environ） | 低风险场景可用  |
| \--env-file .env                      | 否     | 否                 | 是                  | 注意文件权限   |
| 构建时 --secret（RUN --mount=type=secret） | 否     | 否                 | 仅构建步骤内             | 推荐（构建阶段） |
| Docker Secrets（Swarm）                 | 否     | 否                 | 文件（/run/secrets/）  | 推荐（运行时）  |
| Vault / 外部 Secret Manager             | 否     | 否                 | 应用主动读取             | 最佳（大规模）  |

#### Docker Swarm Secrets 示例

```bash
# 创建 secret
echo "supersecretpassword" | docker secret create db_password -
docker secret create tls_cert ./cert.pem

# 查看 secrets
docker secret ls
docker secret inspect db_password

```

在 service 中使用：

```bash
docker service create \
    --name myapp \
    --secret db_password \
    --secret source=tls_cert,target=tls.pem,mode=0400 \
    myorg/myapp:latest

```

应用内读取（secret 挂载在 `/run/secrets/<name>`）：

```python
# Python 示例
import os

def get_secret(secret_name: str) -> str:
    secret_path = f"/run/secrets/{secret_name}"
    if os.path.exists(secret_path):
        with open(secret_path, 'r') as f:
            return f.read().strip()
    # 回退到环境变量（开发环境）
    return os.environ.get(secret_name.upper(), '')

db_password = get_secret("db_password")

```

### 网络安全

#### 自定义网络隔离

```yaml
# docker-compose.yml
services:
  frontend:
    image: nginx:alpine
    networks:
      - public
      - internal

  api:
    image: myorg/api:latest
    networks:
      - internal
      - database

  db:
    image: postgres:16
    networks:
      - database

networks:
  public:
    driver: bridge
  internal:
    driver: bridge
    internal: false
  database:
    driver: bridge
    internal: true  # 无外部连接，只有连接此网络的容器才能访问

```

#### 网络安全最佳实践

```yaml
services:
  api:
    image: myorg/api:latest
    # 不使用 ports（不对外暴露端口），只有同网络的容器通过服务名访问
    expose:
      - "8000"
    networks:
      - internal
    # 避免 network_mode: host，除非性能敏感且信任容器内代码

```

---

## Docker Swarm 集群编排

### 初始化集群

```bash
# 在 manager 节点执行
docker swarm init --advertise-addr 192.168.1.10

# 输出示例：
# Swarm initialized: current node (xxx) is now a manager.
# To add a worker to this swarm, run the following command:
#     docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377

# 在 worker 节点执行
docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377

# 查看集群节点
docker node ls

# 获取 manager/worker join token（忘记时）
docker swarm join-token manager
docker swarm join-token worker

# 提升 worker 为 manager
docker node promote <NODE-ID>

# 降级 manager 为 worker
docker node demote <NODE-ID>

```

docker swarm init 参数说明：

| 参数                   | 类型     | 默认值               | 说明                                |
| -------------------- | ------ | ----------------- | --------------------------------- |
| \--advertise-addr    | string | 自动检测              | 其他节点连接此 manager 的地址（IP:port 或接口名） |
| \--listen-addr       | string | 0.0.0.0:2377      | manager 监听地址                      |
| \--data-path-addr    | string | \--advertise-addr | VXLAN 数据平面地址（overlay 网络用）         |
| \--data-path-port    | uint32 | 4789              | VXLAN UDP 端口                      |
| \--force-new-cluster | bool   | false             | 从单个节点强制创建新集群（灾难恢复）                |
| \--availability      | enum   | active            | active / pause / drain            |

### 核心概念

| 概念              | 说明                                 |
| --------------- | ---------------------------------- |
| Node            | 集群中的 Docker 主机，分为 manager 和 worker |
| Manager         | 维护集群状态（Raft 一致性），响应 API 调用，调度任务    |
| Worker          | 执行任务（运行容器）                         |
| Service         | 期望状态声明（镜像、副本数、网络、约束等）              |
| Task            | 分配到节点的工作单元，包含一个容器实例                |
| Stack           | 通过 Compose 文件定义的一组相关 Service       |
| Overlay Network | 跨节点的虚拟网络，Swarm 服务默认使用              |

### Service 管理

```bash
# 创建 service
docker service create \
    --name web \
    --replicas 3 \
    -p 80:80 \
    --update-delay 10s \
    --update-parallelism 1 \
    --restart-condition on-failure \
    nginx:1.25

# 查看所有 service
docker service ls

# 查看 service 的 task 分布
docker service ps web

# 查看 service 详情
docker service inspect web
docker service inspect --pretty web

# 查看日志
docker service logs web
docker service logs -f --tail 100 web

# 更新镜像（滚动更新）
docker service update --image nginx:1.26 web

# 扩缩容
docker service scale web=5
docker service scale web=2 api=4

# 手动回滚
docker service rollback web

# 删除 service
docker service rm web

```

### docker service create 完整参数参考

| 参数                            | 类型       | 默认值        | 说明                                                |
| ----------------------------- | -------- | ---------- | ------------------------------------------------- |
| \--replicas                   | int      | 1          | 副本数（replicated 模式）                                |
| \--replicas-max-per-node      | int      | 0（不限）      | 每个节点最多副本数                                         |
| \--mode                       | enum     | replicated | replicated / global / replicated-job / global-job |
| \--update-delay               | duration | 0s         | 每个 task 更新之间的等待时间                                 |
| \--update-parallelism         | int      | 1          | 同时更新的 task 数量（0 = 全部同时）                           |
| \--update-failure-action      | enum     | pause      | pause / continue / rollback                       |
| \--update-max-failure-ratio   | float    | 0          | 允许失败的 task 比例（0\~1）                               |
| \--update-monitor             | duration | 5s         | 每个 task 更新后观察是否失败的时间窗口                            |
| \--update-order               | enum     | stop-first | stop-first（先停旧再起新）/ start-first（先起新再停旧，零停机）       |
| \--rollback-delay             | duration | 0s         | 回滚各 task 之间的等待时间                                  |
| \--rollback-parallelism       | int      | 1          | 并行回滚的 task 数量                                     |
| \--rollback-failure-action    | enum     | pause      | pause / continue                                  |
| \--rollback-max-failure-ratio | float    | 0          | 回滚允许失败比例                                          |
| \--rollback-monitor           | duration | 5s         | 回滚后监控时间窗口                                         |
| \--rollback-order             | enum     | stop-first | stop-first / start-first                          |
| \--restart-condition          | enum     | any        | none / on-failure / any                           |
| \--restart-delay              | duration | 5s         | 重启等待时间                                            |
| \--restart-max-attempts       | int      | 不限         | 最大重启次数                                            |
| \--restart-window             | duration | 0（不限）      | 判断重启次数的时间窗口                                       |
| \--constraint                 | string   | 无          | 节点约束（如 node.role==manager）                        |
| \--placement-pref             | string   | 无          | 分布偏好（如 spread=node.labels.zone）                   |
| \--limit-cpu                  | float    | 不限         | CPU 使用上限（核数）                                      |
| \--limit-memory               | bytes    | 不限         | 内存使用上限                                            |
| \--limit-pids                 | int      | 不限         | 进程数上限                                             |
| \--reserve-cpu                | float    | 0          | CPU 预留（调度时保证可用）                                   |
| \--reserve-memory             | bytes    | 0          | 内存预留                                              |
| \--network                    | string   | 无          | 连接的 overlay 网络名                                   |
| \--publish / -p               | port     | 无          | 端口映射（host:container）                              |
| \--secret                     | string   | 无          | 挂载 Docker Secret                                  |
| \--config                     | string   | 无          | 挂载 Docker Config                                  |
| \--env / -e                   | string   | 无          | 环境变量                                              |
| \--mount                      | string   | 无          | 卷挂载配置                                             |
| \--workdir / -w               | string   | 无          | 工作目录                                              |
| \--user / -u                  | string   | 无          | 运行用户                                              |
| \--hostname                   | string   | 无          | 容器 hostname                                       |
| \--log-driver                 | string   | 继承 daemon  | 日志驱动                                              |
| \--log-opt                    | string   | 无          | 日志驱动选项                                            |
| \--health-cmd                 | string   | 无          | 健康检查命令                                            |
| \--health-interval            | duration | 30s        | 健康检查间隔                                            |
| \--health-timeout             | duration | 30s        | 健康检查超时                                            |
| \--health-retries             | int      | 3          | 健康检查失败重试次数                                        |
| \--health-start-period        | duration | 0s         | 启动宽限期                                             |

### Stack 部署

Stack 使用 Compose 文件格式，支持 Swarm 特有的 `deploy` 配置块。

```yaml
# docker-compose.prod.yml
version: "3.9"

services:
  web:
    image: myorg/web:1.2.3
    networks:
      - frontend
      - backend
    ports:
      - "80:80"
      - "443:443"
    deploy:
      mode: replicated
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
        monitor: 15s
        order: start-first
      rollback_config:
        parallelism: 1
        delay: 5s
        failure_action: pause
        order: stop-first
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M
      placement:
        constraints:
          - node.role == worker
          - node.labels.zone == production
        preferences:
          - spread: node.labels.datacenter
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: postgres:16
    networks:
      - backend
    secrets:
      - db_password
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - db_data:/var/lib/postgresql/data
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.labels.storage == ssd

networks:
  frontend:
    driver: overlay
  backend:
    driver: overlay
    internal: true

volumes:
  db_data:
    driver: local

secrets:
  db_password:
    external: true

```

```bash
# 部署 stack
docker stack deploy -c docker-compose.prod.yml myapp

# 查看 stack 列表
docker stack ls

# 查看 stack 下的所有 service
docker stack services myapp

# 查看 stack 下所有 task
docker stack ps myapp

# 删除 stack（不删除 volumes）
docker stack rm myapp

```

### Overlay 网络

```bash
# 创建加密 overlay 网络
docker network create \
    --driver overlay \
    --opt encrypted \
    --attachable \
    --subnet 10.10.0.0/24 \
    myoverlay

# attachable 允许独立容器（非 service）连接此网络
# encrypted 启用 VXLAN 层的 AES-GCM 加密

```

overlay 网络参数说明：

| 参数                                   | 类型   | 默认值        | 说明                                 |
| ------------------------------------ | ---- | ---------- | ---------------------------------- |
| \--opt encrypted                     | bool | false      | 启用数据平面加密（AES-128-GCM）              |
| \--attachable                        | bool | false      | 允许独立容器连接（非 service 也可用）            |
| \--ingress                           | bool | false      | 创建 Swarm routing mesh 的 ingress 网络 |
| \--subnet                            | CIDR | 自动分配       | 网络子网                               |
| \--gateway                           | IP   | 子网第一个可用 IP | 网关 IP                              |
| \--ip-range                          | CIDR | 子网范围       | 容器 IP 分配范围                         |
| \--opt com.docker.network.driver.mtu | int  | 1450       | MTU 大小（overlay 默认降低以避免碎片）          |

### Secrets 和 Configs

```bash
# 创建 secret（不可修改，只能删除重建）
echo "mypassword" | docker secret create db_password -
docker secret create ssl_cert ./cert.pem
openssl rand -base64 32 | docker secret create jwt_secret -

# 列出 secrets（内容不可查看）
docker secret ls
docker secret inspect db_password

# 删除 secret（需先从所有 service 移除）
docker secret rm db_password

# 创建 config（存储非敏感配置，内容可查看）
docker config create nginx_conf ./nginx.conf
docker config ls
docker config inspect --pretty nginx_conf

```

在 service 中使用 secret 和 config：

```bash
docker service create \
    --name myapp \
    --secret db_password \
    --secret source=ssl_cert,target=/etc/ssl/app.pem,mode=0400 \
    --config source=nginx_conf,target=/etc/nginx/nginx.conf \
    myorg/myapp:latest

```

完整数据库密码示例：

```bash
# 1. 创建 secret
openssl rand -base64 32 | tr -d '\n' | docker secret create postgres_password -

# 2. 创建数据库 service
docker service create \
    --name postgres \
    --network myapp_backend \
    --secret postgres_password \
    --env POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password \
    --env POSTGRES_USER=myapp \
    --env POSTGRES_DB=myapp \
    --mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
    --constraint node.labels.storage==ssd \
    postgres:16

# 3. 应用读取 secret
docker service create \
    --name api \
    --network myapp_backend \
    --secret postgres_password \
    --env DB_PASSWORD_FILE=/run/secrets/postgres_password \
    myorg/api:latest

```

---

## 生产环境部署方案

### 健康检查与自动恢复

#### Dockerfile 中定义健康检查

```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD node healthcheck.js || exit 1

CMD ["node", "server.js"]

```

```javascript
// healthcheck.js
const http = require('http');
const options = {
    host: 'localhost',
    port: 3000,
    path: '/health',
    timeout: 5000
};

const req = http.request(options, (res) => {
    process.exit(res.statusCode === 200 ? 0 : 1);
});

req.on('error', () => process.exit(1));
req.on('timeout', () => { req.destroy(); process.exit(1); });
req.end();

```

HEALTHCHECK 参数说明：

| 参数                | 类型       | 默认值 | 说明                      |
| ----------------- | -------- | --- | ----------------------- |
| \--interval       | duration | 30s | 健康检查执行间隔                |
| \--timeout        | duration | 30s | 单次检查超时时间                |
| \--start-period   | duration | 0s  | 启动宽限期（期间失败不计入 retries）  |
| \--start-interval | duration | 5s  | 宽限期内的检查间隔（Docker 25.0+） |
| \--retries        | int      | 3   | 连续失败多少次后标记为 unhealthy   |

#### Compose 中的健康检查依赖链

```yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

  api:
    image: myorg/api:latest
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  web:
    image: myorg/web:latest
    depends_on:
      api:
        condition: service_healthy
    ports:
      - "80:80"

```

### 滚动更新与零停机部署

#### update\_config 完整配置

```yaml
services:
  api:
    image: myorg/api:latest
    deploy:
      replicas: 4
      update_config:
        parallelism: 1          # 每次更新 1 个副本
        delay: 10s              # 每个副本更新之间等待 10s
        failure_action: rollback  # 失败自动回滚
        monitor: 20s            # 每个副本更新后监控 20s
        max_failure_ratio: 0.1  # 允许 10% 失败率
        order: start-first      # 先启动新副本再停旧副本（零停机）
      rollback_config:
        parallelism: 2          # 并行回滚 2 个副本
        delay: 5s
        failure_action: pause
        monitor: 10s
        order: stop-first

```

#### 更新操作流程

```bash
# 更新镜像
docker service update --image myorg/api:1.3.0 myapp_api

# 查看更新进度
docker service ps myapp_api

# 查看更新失败原因
docker service ps myapp_api --filter desired-state=shutdown --no-trunc

# 手动回滚
docker service rollback myapp_api

# 强制重新部署（不更改镜像，用于重新拉取 latest）
docker service update --force myapp_api

```

### 日志集中收集

#### Fluentd 日志驱动

```yaml
# docker-compose.yml
services:
  api:
    image: myorg/api:latest
    logging:
      driver: fluentd
      options:
        fluentd-address: "localhost:24224"
        tag: "docker.{{.Name}}"
        fluentd-async: "true"    # 异步发送，避免阻塞容器
        fluentd-buffer-limit: "10485760"  # 10MB 缓冲区

  fluentd:
    image: fluent/fluentd:v1.16
    ports:
      - "24224:24224"
    volumes:
      - ./fluent.conf:/fluentd/etc/fluent.conf
      - fluentd_log:/var/log/fluentd

volumes:
  fluentd_log:

```

```xml
<!-- fluent.conf -->
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<filter docker.**>
  @type record_transformer
  <record>
    hostname "#{Socket.gethostname}"
    environment production
  </record>
</filter>

<match docker.**>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
  logstash_prefix docker
  include_timestamp true
  flush_interval 5s
</match>

```

#### Loki + Promtail（更轻量的选择）

```yaml
# docker-compose.yml
services:
  loki:
    image: grafana/loki:2.9.0
    ports:
      - "3100:3100"
    volumes:
      - loki_data:/loki
      - ./loki-config.yml:/etc/loki/local-config.yaml
    command: -config.file=/etc/loki/local-config.yaml

  promtail:
    image: grafana/promtail:2.9.0
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  loki_data:
  grafana_data:

```

```yaml
# promtail-config.yml
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        target_label: container
      - source_labels: [__meta_docker_container_log_stream]
        target_label: stream

```

### 监控方案

#### cAdvisor + Prometheus + Grafana

```yaml
# docker-compose.monitoring.yml
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.47.0
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    ports:
      - "8080:8080"
    privileged: true
    devices:
      - /dev/kmsg

  prometheus:
    image: prom/prometheus:v2.48.0
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'

  grafana:
    image: grafana/grafana:10.2.0
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/var/lib/grafana/dashboards
      - ./grafana/provisioning:/etc/grafana/provisioning
    depends_on:
      - prometheus

volumes:
  prometheus_data:
  grafana_data:

```

```yaml
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'docker-daemon'
    static_configs:
      - targets: ['host.docker.internal:9323']

```

关键监控指标：

| 指标名                                        | 说明               | 告警阈值建议             |
| ------------------------------------------ | ---------------- | ------------------ |
| container\_cpu\_usage\_seconds\_total      | 容器 CPU 使用量（累计秒数） | rate > limit 的 90% |
| container\_memory\_usage\_bytes            | 容器内存使用量（字节）      | \> limit 的 85%     |
| container\_memory\_working\_set\_bytes     | 工作集内存（不含缓存，更准确）  | \> limit 的 85%     |
| container\_network\_receive\_bytes\_total  | 容器网络接收字节数        | 异常突增               |
| container\_network\_transmit\_bytes\_total | 容器网络发送字节数        | 异常突增               |
| container\_fs\_usage\_bytes                | 容器文件系统使用量        | \> 80%             |
| container\_restart\_count                  | 容器重启次数           | \> 3 次/小时          |
| container\_oom\_events\_total              | OOM 事件数          | \> 0               |

---

## CI/CD 集成

### GitHub Actions 完整 Workflow

```yaml
# .github/workflows/docker-build-push.yml
name: Build and Deploy

on:
  push:
    branches: [main]
    tags: ['v*.*.*']
  pull_request:
    branches: [main]

env:
  REGISTRY_DOCKERHUB: docker.io
  REGISTRY_GHCR: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write

    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
      image-tag: ${{ steps.meta.outputs.tags }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up QEMU（多平台支持）
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY_DOCKERHUB }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Log in to GitHub Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY_GHCR }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata（生成 tag 和 label）
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ${{ env.REGISTRY_DOCKERHUB }}/${{ env.IMAGE_NAME }}
            ${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha,prefix=sha-,format=short
            type=ref,event=branch
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_DATE=${{ github.event.repository.updated_at }}
            VCS_REF=${{ github.sha }}
            VERSION=${{ steps.meta.outputs.version }}

      - name: Scan image with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

      - name: Upload Trivy scan results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production

    steps:
      - name: Deploy to production via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
            docker service update \
              --image ghcr.io/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image-digest }} \
              --update-order start-first \
              myapp_api

```

### GitLab CI 完整配置

```yaml
# .gitlab-ci.yml
stages:
  - build
  - scan
  - test
  - push
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest

.docker_login: &docker_login
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  <<: *docker_login
  script:
    - |
      docker buildx create --use --name mybuilder
      docker buildx build \
        --platform linux/amd64,linux/arm64 \
        --cache-from type=registry,ref=$CI_REGISTRY_IMAGE:cache \
        --cache-to type=registry,ref=$CI_REGISTRY_IMAGE:cache,mode=max \
        --tag $IMAGE_TAG \
        --push \
        .
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG

scan:
  stage: scan
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image --exit-code 1 --severity CRITICAL $IMAGE_TAG
  allow_failure: false

test:
  stage: test
  image: docker:24
  services:
    - docker:24-dind
  <<: *docker_login
  script:
    - docker pull $IMAGE_TAG
    - docker run --rm $IMAGE_TAG npm test

push-latest:
  stage: push
  image: docker:24
  services:
    - docker:24-dind
  <<: *docker_login
  script:
    - docker pull $IMAGE_TAG
    - docker tag $IMAGE_TAG $IMAGE_LATEST
    - docker push $IMAGE_LATEST
    - |
      if [ -n "$CI_COMMIT_TAG" ]; then
        docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
        docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
      fi
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG

deploy-production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$PROD_SSH_KEY" | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - echo "$PROD_HOST_KEY" >> ~/.ssh/known_hosts
  script:
    - |
      ssh $PROD_USER@$PROD_HOST "
        docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
        docker stack deploy -c /opt/myapp/docker-compose.prod.yml myapp
      "
  environment:
    name: production
    url: https://myapp.example.com
  rules:
    - if: $CI_COMMIT_TAG
      when: manual

```

### 镜像版本策略

```bash
# 在 CI 中同时推送多个 tag
IMAGE_BASE="myorg/myapp"
VERSION="1.2.3"
SHA=$(git rev-parse --short HEAD)

docker buildx build \
    --platform linux/amd64,linux/arm64 \
    -t ${IMAGE_BASE}:latest \
    -t ${IMAGE_BASE}:${VERSION} \
    -t ${IMAGE_BASE}:$(echo $VERSION | cut -d. -f1).$(echo $VERSION | cut -d. -f2) \
    -t ${IMAGE_BASE}:$(echo $VERSION | cut -d. -f1) \
    -t ${IMAGE_BASE}:${SHA} \
    --push \
    .

```

版本策略说明：

| Tag        | 示例          | 用途                   |
| ---------- | ----------- | -------------------- |
| latest     | latest      | 跟踪主分支最新版本（生产不推荐直接使用） |
| 完整语义化版本    | 1.2.3       | 精确锁定版本（生产推荐）         |
| 主次版本       | 1.2         | 接受补丁更新               |
| 主版本        | 1           | 接受次要更新               |
| Commit SHA | sha-a3f1b2c | 唯一标识构建产物，可追溯         |

---

## 容器内应用最佳实践

### 优雅关闭（Graceful Shutdown）

#### 为什么需要优雅关闭

Docker 停止容器时，先发送 `SIGTERM`，等待 `stop_grace_period`（默认 10s）后若进程未退出则发送 `SIGKILL`。应用应捕获 `SIGTERM` 完成清理工作（关闭连接、完成正在处理的请求等）再退出。

#### Node.js 优雅关闭

```javascript
// server.js
const http = require('http');
const app = require('./app');

const server = http.createServer(app);
let isShuttingDown = false;

server.listen(3000, () => {
    console.log('Server listening on port 3000');
});

function gracefulShutdown(signal) {
    console.log(`Received ${signal}, starting graceful shutdown`);
    isShuttingDown = true;

    server.close((err) => {
        if (err) {
            console.error('Error during server close:', err);
            process.exit(1);
        }
        // 关闭数据库连接等
        console.log('Server closed, exiting');
        process.exit(0);
    });

    // 超时强制退出
    setTimeout(() => {
        console.error('Graceful shutdown timeout, forcing exit');
        process.exit(1);
    }, 9000); // 留 1s 给 Docker 的 10s 宽限期
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

```

#### Python 优雅关闭

```python
# main.py
import signal
import sys
import asyncio
import uvicorn
from fastapi import FastAPI

app = FastAPI()
shutdown_event = asyncio.Event()

@app.get("/health")
async def health():
    return {"status": "ok"}

@app.on_event("shutdown")
async def shutdown():
    print("Application shutting down")
    # 清理资源

def handle_sigterm(signum, frame):
    print(f"Received signal {signum}")
    shutdown_event.set()

signal.signal(signal.SIGTERM, handle_sigterm)
signal.signal(signal.SIGINT, handle_sigterm)

if __name__ == "__main__":
    config = uvicorn.Config(
        "main:app",
        host="0.0.0.0",
        port=8000,
        timeout_graceful_shutdown=8
    )
    server = uvicorn.Server(config)
    server.run()

```

#### Go 优雅关闭

```go
// main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    srv := &http.Server{Addr: ":8080", Handler: setupRoutes()}

    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.SIGTERM, syscall.SIGINT)
    <-quit

    log.Println("Received shutdown signal, shutting down gracefully")

    ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited")
}

```

stop\_grace\_period 配置：

```yaml
services:
  api:
    image: myorg/api:latest
    stop_grace_period: 30s  # 给应用足够时间完成优雅关闭
    stop_signal: SIGTERM    # 发送的信号（默认 SIGTERM）

```

### 容器内进程管理与 PID 1

#### PID 1 的特殊性

Linux 内核对 PID 1 的处理与其他进程不同：

1. 未注册信号处理器的信号会被忽略（包括 SIGTERM），导致 `docker stop` 无法停止容器
2. PID 1 需要回收僵尸子进程（wait），否则系统进程表会被僵尸进程填满

#### 使用 tini 解决 PID 1 问题

```dockerfile
FROM ubuntu:22.04

# 方式一：安装 tini
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

# tini 作为 PID 1，管理应用进程
ENTRYPOINT ["tini", "--"]
CMD ["node", "server.js"]

```

```dockerfile
# 方式二：从官方镜像获取 tini（Go 静态编译镜像）
FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx
FROM ubuntu:22.04

COPY --from=xx /usr/bin/xx-info /usr/bin/xx-info
ARG TARGETPLATFORM
RUN ARCH=$(xx-info arch) && \
    curl -L https://github.com/krallin/tini/releases/download/v0.19.0/tini-${ARCH} \
    -o /tini && chmod +x /tini

ENTRYPOINT ["/tini", "--"]
CMD ["python", "app.py"]

```

```bash
# 方式三：运行时使用 --init 标志（Docker 自带 tini）
docker run --init myorg/myapp:latest

```

```yaml
# Compose 中启用 init
services:
  api:
    image: myorg/api:latest
    init: true

```

### 配置外部化（12-Factor App）

#### 环境变量 vs 配置文件 vs Secrets

| 配置类型                  | 推荐传递方式                     | 说明           |
| --------------------- | -------------------------- | ------------ |
| 非敏感配置（端口、日志级别）        | 环境变量                       | 简单直接         |
| 非敏感配置文件（nginx.conf）   | Docker Config / Bind Mount | 结构化配置        |
| 敏感信息（密码、API Key）      | Docker Secrets / Vault     | 不写入环境变量      |
| 平台信息（hostname、region） | 环境变量（运行时注入）                | 12-Factor 原则 |

```yaml
# 使用 Compose configs 管理非敏感配置文件
services:
  nginx:
    image: nginx:alpine
    configs:
      - source: nginx_config
        target: /etc/nginx/nginx.conf
        mode: 0444
    ports:
      - "80:80"

configs:
  nginx_config:
    file: ./nginx.conf   # Compose 模式

# Swarm 模式：
# configs:
#   nginx_config:
#     external: true

```

### 应用启动等待

#### 方式对比

| 方式                        | 适用场景            | 缺点         |
| ------------------------- | --------------- | ---------- |
| sleep 固定时间                | 演示 / 测试         | 不可靠，浪费时间   |
| wait-for-it.sh            | 等待 TCP 端口可达     | 不检查服务就绪状态  |
| depends\_on + healthcheck | Compose / Swarm | 推荐，等待真正就绪  |
| 应用内重试                     | 生产环境            | 最健壮，无需外部工具 |

#### wait-for-it.sh 用法

```dockerfile
FROM node:20-alpine
RUN apk add --no-cache bash
COPY --chmod=755 wait-for-it.sh /usr/local/bin/wait-for-it
WORKDIR /app
COPY . .
RUN npm ci

ENTRYPOINT ["wait-for-it", "db:5432", "--", "wait-for-it", "redis:6379", "--"]
CMD ["node", "server.js"]

```

#### 应用内重试（最健壮）

```python
# db.py
import time
import psycopg2

def get_connection(max_retries: int = 10, delay: float = 2.0):
    for attempt in range(max_retries):
        try:
            conn = psycopg2.connect(
                host="db",
                database="myapp",
                user="myapp",
                password=open("/run/secrets/db_password").read().strip()
            )
            print(f"Connected to database on attempt {attempt + 1}")
            return conn
        except psycopg2.OperationalError as e:
            if attempt == max_retries - 1:
                raise
            wait = delay * (2 ** attempt)  # 指数退避
            print(f"Database not ready (attempt {attempt + 1}), retrying in {wait:.1f}s: {e}")
            time.sleep(wait)

```

---

## 性能调优

### 容器存储驱动

| 驱动             | 说明                    | 推荐场景            | 性能特点           |
| -------------- | --------------------- | --------------- | -------------- |
| overlay2       | OverlayFS，内核原生支持      | 生产首选，Linux 4.0+ | 读写性能好，层缓存效率高   |
| fuse-overlayfs | 用户态 OverlayFS         | rootless Docker | 性能略低于 overlay2 |
| devicemapper   | 块设备，thin provisioning | RHEL/CentOS 旧版  | 随机写性能好，配置复杂    |
| btrfs          | B-tree 文件系统           | 需要快照等高级特性       | 碎片化后性能下降       |
| zfs            | ZFS 文件系统              | 高可靠性需求          | 内存消耗大          |
| vfs            | 直接复制，不共享层             | 测试环境 / CI       | 性能差，不推荐生产      |

查看和配置存储驱动：

```bash
# 查看当前存储驱动
docker info | grep "Storage Driver"

# 配置 overlay2（daemon.json）

```

```json
{
    "storage-driver": "overlay2",
    "storage-opts": [
        "overlay2.override_kernel_check=true"
    ]
}

```

### 网络性能

#### MTU 设置

在云环境（AWS、GCP、Azure）中，物理网络 MTU 通常为 1500 字节，但 overlay 网络添加了额外的 VXLAN 头部（约 50 字节），若不降低 MTU 会导致数据包碎片化或丢失。

```bash
# 创建时指定 MTU
docker network create \
    --opt com.docker.network.driver.mtu=1450 \
    myoverlay

# 在 daemon.json 中全局设置默认 MTU

```

```json
{
    "mtu": 1450
}

```

```yaml
# Compose 中配置网络 MTU
networks:
  mynet:
    driver: overlay
    driver_opts:
      com.docker.network.driver.mtu: "1450"

```

#### host 网络模式

```bash
# 延迟敏感的服务（如高频交易、游戏服务器）可使用 host 模式
docker run --network host myorg/latency-sensitive-app:latest

```

注意：host 网络模式下容器直接使用宿主机网络栈，无 NAT 开销，但失去网络隔离，不推荐普通 Web 服务使用。

### 构建速度优化

#### .dockerignore 示例

```
# .dockerignore
.git
.gitignore
.github
*.md
*.log
.DS_Store
Thumbs.db

# Node.js
node_modules
npm-debug.log
.npm

# Python
__pycache__
*.py[cod]
.venv
venv
*.egg-info
dist
.tox
.coverage

# 构建产物
build
dist
out
target

# IDE
.idea
.vscode
*.swp
*.swo

# 测试
coverage
.nyc_output
test-results

# Docker
Dockerfile*
docker-compose*.yml

```

#### 层缓存顺序优化

```dockerfile
# 错误顺序：频繁变化的文件在前，导致缓存经常失效
FROM node:20-alpine
COPY . .             # 代码变化 -> 后续所有步骤重新执行
RUN npm ci           # 每次都重新安装

# 正确顺序：变化频率低的步骤在前
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./    # 依赖定义很少变化
RUN npm ci               # 缓存此层
COPY . .                 # 代码变化，仅从这里开始重新执行
RUN npm run build

```

### 内存和 CPU 调优

#### 设置资源限制

```yaml
services:
  api:
    image: myorg/api:latest
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 256M

```

```bash
# 运行时设置（非 Swarm）
docker run \
    --cpus 2.0 \
    --memory 1g \
    --memory-swap 1g \     # 等于 --memory，禁止 swap
    --memory-swappiness 0 \ # 禁止 swap 使用
    myorg/api:latest

```

资源限制参数说明：

| 参数                    | 类型     | 说明                              |
| --------------------- | ------ | ------------------------------- |
| \--cpus               | float  | CPU 核数上限（1.5 表示 1.5 核）          |
| \--cpu-shares         | int    | CPU 权重（默认 1024，相对值）             |
| \--cpu-period         | int    | CPU CFS 周期（微秒，默认 100000）        |
| \--cpu-quota          | int    | CPU CFS 配额（微秒，--cpus 的底层实现）     |
| \--cpuset-cpus        | string | 绑定到指定 CPU 核（如 "0,1" 或 "0-3"）    |
| \--memory             | bytes  | 内存上限                            |
| \--memory-swap        | bytes  | 内存+swap 上限（等于 --memory 时禁 swap） |
| \--memory-reservation | bytes  | 软性内存预留                          |
| \--memory-swappiness  | int    | swap 使用倾向（0-100，0 最少 swap）      |
| \--oom-kill-disable   | bool   | 禁止 OOM killer（需同时设置 --memory）   |

#### JVM 容器化注意事项

```dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/app.jar app.jar

# 正确：启用容器感知，JVM 根据 cgroup limits 设置堆内存
# -XX:+UseContainerSupport 从 Java 10 开始默认启用
# -XX:MaxRAMPercentage 指定最大堆占容器内存的百分比
ENTRYPOINT ["java", \
    "-XX:+UseContainerSupport", \
    "-XX:MaxRAMPercentage=75.0", \
    "-XX:InitialRAMPercentage=50.0", \
    "-XX:+ExitOnOutOfMemoryError", \
    "-jar", "app.jar"]

```

---

## Docker 网络高级

### Macvlan 网络

Macvlan 让容器拥有独立的 MAC 地址和 IP 地址，在物理网络中作为独立主机出现，无需 NAT，适合需要容器直接接入物理网络的场景。

```bash
# 创建 macvlan 网络
docker network create \
    --driver macvlan \
    --subnet 192.168.1.0/24 \
    --gateway 192.168.1.1 \
    --ip-range 192.168.1.128/25 \
    -o parent=eth0 \
    macvlan_net

# 运行容器并指定 IP
docker run -d \
    --network macvlan_net \
    --ip 192.168.1.200 \
    --name mycontainer \
    nginx:alpine

```

```yaml
# Compose 中使用 macvlan
networks:
  macvlan_net:
    driver: macvlan
    driver_opts:
      parent: eth0
    ipam:
      config:
        - subnet: 192.168.1.0/24
          gateway: 192.168.1.1
          ip_range: 192.168.1.128/25

```

macvlan 限制：

- 宿主机无法直接与 macvlan 容器通信（需使用 macvlan 子接口绕过）
- 物理交换机需要开启混杂模式或支持多 MAC 地址

### 自定义 IPAM

```yaml
networks:
  mynet:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16
          gateway: 172.28.0.1
          ip_range: 172.28.5.0/24
          aux_addresses:
            reserved1: 172.28.5.1
            reserved2: 172.28.5.2

```

IPAM 配置说明：

| 字段             | 类型   | 说明                     |
| -------------- | ---- | ---------------------- |
| subnet         | CIDR | 网络子网                   |
| gateway        | IP   | 网关 IP                  |
| ip\_range      | CIDR | 容器 IP 分配范围（subnet 的子集） |
| aux\_addresses | map  | 保留地址，不分配给容器            |

### 服务发现与负载均衡

#### Swarm 内置 DNS

Swarm 在 overlay 网络内运行嵌入式 DNS 服务器（`127.0.0.11`），容器使用服务名作为 hostname 进行服务发现。

```bash
# 在容器内测试 DNS 解析
docker exec -it <container-id> nslookup myservice

# 结果：返回 VIP（Virtual IP）或直接返回所有 task IP（DNSRR 模式）

```

#### VIP vs DNSRR 负载均衡

```bash
# VIP 模式（默认）：DNS 返回单一虚拟 IP，负载均衡由内核 ipvs 完成
docker service create --name myservice --endpoint-mode vip nginx

# DNSRR 模式：DNS 轮询返回各 task IP，客户端负责负载均衡
docker service create --name myservice --endpoint-mode dnsrr nginx

```

| 模式      | 说明                                | 适用场景        |
| ------- | --------------------------------- | ----------- |
| VIP（默认） | 单一 VIP，内核 ipvs 负载均衡，健康检查自动剔除不健康节点 | 大多数场景       |
| DNSRR   | DNS 轮询，客户端直连 task IP，需客户端支持       | gRPC 等长连接协议 |

---

## 日常运维命令

### 清理命令完整参考

| 命令                                         | 说明                          |
| ------------------------------------------ | --------------------------- |
| docker system prune                        | 清理停止的容器、未使用的网络、悬空镜像和构建缓存    |
| docker system prune -a                     | 同上，但同时清理所有未被容器引用的镜像（不只悬空镜像） |
| docker system prune --volumes              | 同上，额外清理未使用的卷（数据会丢失，谨慎）      |
| docker system prune -f                     | 跳过确认提示                      |
| docker image prune                         | 清理悬空镜像（无 tag 或无容器引用的中间层）    |
| docker image prune -a                      | 清理所有未被运行中容器引用的镜像            |
| docker container prune                     | 清理所有已停止的容器                  |
| docker network prune                       | 清理未被任何容器使用的网络               |
| docker volume prune                        | 清理未被任何容器引用的卷                |
| docker volume prune --filter label=env=dev | 按标签过滤清理                     |
| docker builder prune                       | 清理 BuildKit 构建缓存            |
| docker builder prune --keep-storage 5GB    | 保留最新 5GB 缓存，清理其余            |
| docker system df                           | 显示 Docker 各类资源的磁盘占用         |
| docker system df -v                        | 详细列出每个镜像/容器/卷的占用            |

### 诊断与调试

#### docker inspect 高级用法

```bash
# 获取容器 IP 地址
docker inspect --format '{{.NetworkSettings.IPAddress}}' mycontainer

# 获取指定网络的 IP
docker inspect --format '{{.NetworkSettings.Networks.mynet.IPAddress}}' mycontainer

# 获取所有容器的 name 和 IP
docker inspect --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
    $(docker ps -q)

# 获取容器的环境变量
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' mycontainer

# 获取容器挂载信息
docker inspect --format '{{json .Mounts}}' mycontainer | jq .

# 获取容器的退出码和状态
docker inspect --format '{{.State.ExitCode}} {{.State.Status}}' mycontainer

# 获取容器重启次数
docker inspect --format '{{.RestartCount}}' mycontainer

# 获取 service 的 VIP
docker inspect --format '{{range .Endpoint.VirtualIPs}}{{.Addr}}{{end}}' \
    $(docker service inspect myservice -q)

```

#### docker stats 自定义格式

```bash
# 自定义 stats 输出列
docker stats --format \
    "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"

# 只查看指定容器
docker stats myapp_api myapp_db

# 单次快照（不持续刷新）
docker stats --no-stream

# 输出 JSON（机器可读）
docker stats --format '{{json .}}' --no-stream

```

docker stats 格式变量：

| 变量        | 说明              |
| --------- | --------------- |
| .Name     | 容器名             |
| .ID       | 容器 ID           |
| .CPUPerc  | CPU 使用百分比       |
| .MemUsage | 内存使用量 / 限制      |
| .MemPerc  | 内存使用百分比         |
| .NetIO    | 网络 I/O（接收 / 发送） |
| .BlockIO  | 块设备 I/O（读 / 写）  |
| .PIDs     | 容器内进程数          |

#### 其他诊断命令

```bash
# 监听 Docker 守护进程事件
docker events
docker events --filter type=container --filter event=die
docker events --since 1h --until now

# 查看容器文件系统变更（相对镜像的差异）
docker diff mycontainer
# A = 新增，C = 修改，D = 删除

# 查看容器内进程（无需进入容器）
docker top mycontainer
docker top mycontainer aux

# 查看镜像构建历史（可能泄露敏感信息）
docker history myorg/myapp:latest
docker history --no-trunc myorg/myapp:latest

# 使用 nsenter 进入容器命名空间（不依赖 docker exec，容器内无 shell 时有用）
PID=$(docker inspect --format '{{.State.Pid}}' mycontainer)
nsenter --target $PID --mount --uts --ipc --net --pid -- sh

# 导出容器文件系统为 tar
docker export mycontainer | tar -tv | head -50

# 将 image 保存为 tar（含所有层和 metadata）
docker save myorg/myapp:latest | gzip > myapp.tar.gz

# 从 tar 加载镜像
docker load < myapp.tar.gz

```

---

## 踩坑与最佳实践清单

### 镜像与构建

- 生产环境禁止使用 `latest` tag，任何时候应使用固定版本（如 `1.2.3` 或 `sha256:...`），避免因镜像更新引入不一致行为
- 镜像层数限制：Union FS 的实际限制在不同驱动下各异（overlay2 约 128 层），复杂多阶段构建应保持最终镜像层数在合理范围内（通常不超过 20-30 层）
- `docker history` 会泄露 `ENV`、`ARG`、`RUN` 中的所有值，构建时的敏感信息必须使用 `RUN --mount=type=secret`，而非 `ARG` 传递
- COPY 和 ADD 的区别：`ADD` 会自动解压 tar，并支持 URL，但行为不透明；除非需要解压，否则始终使用 `COPY`
- 多阶段构建的 `COPY --from` 应使用阶段名（`AS builder`）而非阶段序号，序号在修改 Dockerfile 后容易错位

### Swarm 与 Secrets

- Swarm secrets 不支持热更新：更新 secret 内容后，需要删除旧 secret（先从 service 移除）、创建同名新 secret、重新部署 service，应用才能读取新值
- Secret 名称全局唯一，删除后可重建同名 secret，但内容可以不同
- Docker Config 与 Secret 的区别：Config 内容可通过 `docker config inspect` 查看，适合非敏感配置；Secret 内容无法查看，适合密码等敏感数据

### 网络

- Overlay 网络 MTU 问题：在 AWS VPC（MTU 9001 大帧）以外的云环境，物理 MTU 通常为 1500。overlay 的 VXLAN 封装消耗约 50 字节，若不将 overlay MTU 设为 1450 或更低，会导致大数据包丢失或性能下降，表现为 HTTP 请求超时但 ping 正常
- 避免在生产环境使用 `--network host`：失去网络隔离，容器内的端口冲突会影响宿主机，且安全风险高
- 容器内 DNS 解析失败排查：检查容器的 `/etc/resolv.conf`，确认 nameserver 是 `127.0.0.11`（Docker 内置 DNS）；overlay 网络需要 UDP 7946 和 4789 端口畅通

### 文件系统与存储

- `read_only: true` 后应用写入 `/tmp` 会报错：必须同时配置 `tmpfs` 挂载，Compose 写法：  
```yaml  
read_only: true  
tmpfs:
  - /tmp:size=64m
  - /var/run:size=8m  
```
- 多容器写同一 NFS 卷的并发问题：NFS 的 POSIX 锁行为与本地文件系统不同，多个容器并发写入同一文件会产生竞态条件，需要应用层协调（如分布式锁）或改用支持并发写的分布式存储（如 CephFS）
- bind mount 在 Docker Desktop（macOS / Windows）上比 Linux 慢很多：原因是 macOS/Windows 通过 gRPC-FUSE 或 VirtioFS 进行文件系统代理。开发环境可以使用 devcontainer 的 Volume Mount（数据在 Linux VM 内，不过宿主机 bind），或使用 Mutagen 同步

### 应用与 JVM

- JVM 在容器内不感知 cgroup 限制的问题：Java 8u191 之前，JVM 根据宿主机物理内存（而非容器 limits）分配堆内存，导致在 2GB 内存限制的容器内 JVM 尝试分配 8GB 堆，触发 OOM kill。Java 10+ 默认启用 `-XX:+UseContainerSupport`，Java 8u191+ 需要手动添加
- Node.js 内存限制：默认堆上限约 1.5GB（取决于 V8 版本），在大内存容器内运行大型应用时需要设置 `--max-old-space-size`
- 容器内不要运行多个主要进程：每个容器应只有一个主进程，辅助进程（日志收集、metrics exporter）应作为独立 sidecar 容器

### Compose 版本兼容

- Compose v2（`docker compose`，内置于 Docker Desktop 和新版 Docker CLI）与 v1（`docker-compose`，独立 Python 包）存在行为差异：v2 默认开启 BuildKit；健康检查 `depends_on` 条件（`service_healthy`）在 v1 中需要 `docker-compose` \>= 1.29.0；部分网络配置选项在 v2 中的优先级不同
- Compose 文件的 `version` 字段在 Compose v2 中已被标记为废弃（deprecated），新文件可以不写此字段

### 安全

- 不要在 Dockerfile 中 `RUN apt-get install` 后不清理缓存：每个 `RUN` 命令会创建一层，若在同一层安装并清理缓存（用 `&&` 连接），则清理有效；若分开写则会保留缓存层  
```dockerfile  
# 正确  
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*  
# 错误（清理无效，缓存层已固化）  
RUN apt-get update && apt-get install -y curl  
RUN rm -rf /var/lib/apt/lists/*  
```
- 定期更新基础镜像：即使应用代码未变化，基础镜像中的 OS 包可能存在已知漏洞，应在 CI 中定期重建镜像

---

## 最佳实践

**多阶段构建彻底分离编译与运行环境**：在构建阶段安装所有编译依赖，最终镜像只 `COPY --from=builder` 产物，运行镜像无编译工具链，减少攻击面和体积。

**用 `--mount=type=cache` 加速 CI 层缓存**：BuildKit 的挂载缓存可跨构建保留包管理器缓存，不会污染镜像层：

```dockerfile
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

```

**生产镜像固定 digest 不用 tag**：`FROM python:3.12-slim@sha256:abc123...` 防止基础镜像在 CI 与生产环境间静默变更，tag 可变而 digest 不可变。

**用 `docker buildx bake` 统一多平台构建配置**：将 `linux/amd64,linux/arm64` 目标写入 `docker-bake.hcl`，一条命令推送多架构 manifest，替代多次手动 `buildx build --platform`。

**Seccomp 与 AppArmor 配置最小权限**：生产容器显式加载定制 Seccomp 白名单（`--security-opt seccomp=policy.json`），禁止容器调用不需要的 syscall，降低容器逃逸风险。

**镜像扫描集成 CI 门禁**：在推送步骤后加 `trivy image --exit-code 1 --severity HIGH,CRITICAL` 扫描，CVSS ≥ 7 的漏洞阻断流水线，配合基础镜像定期重建实现持续安全更新。

---

## 常见陷阱

### 陷阱：多阶段构建中 COPY 路径错误导致文件缺失

**现象：** `docker run` 时报 `No such file or directory`，容器内找不到编译产物。  
**原因：** `COPY --from=builder` 的源路径与构建阶段实际输出路径不一致，或忘记指定阶段名（默认从最后一个阶段拷贝，实际需要中间阶段）。  
**解决：** 给每个构建阶段显式命名 `AS builder`，拷贝时确认路径；可先 `docker build --target builder -t debug .` 进入中间阶段调试。

### 陷阱：BuildKit 缓存 mount 在非 BuildKit 环境失效

**现象：** 本地用 `--mount=type=cache` 正常，CI 环境构建报 `unknown flag: --mount`。  
**原因：** 旧版 Docker 或未启用 BuildKit（`DOCKER_BUILDKIT=1`）的环境不支持 `RUN --mount` 语法。  
**解决：** CI 环境设置 `DOCKER_BUILDKIT=1`（或 Docker 23+ 已默认开启）；在 `docker buildx build` 命令中使用 `--builder` 指定支持 BuildKit 的驱动。

### 陷阱：镜像层缓存因 COPY 顺序不当频繁失效

**现象：** 每次构建都重新安装依赖，即使依赖文件没有变化。  
**原因：** `COPY . .` 在 `pip install` 之前执行，任何源码变化都使安装层缓存失效。  
**解决：** 先只拷贝依赖声明文件，安装完成后再拷贝源码：

```dockerfile
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

```

---

## 参见

[Docker初级指南](https://blog.vercanti.com/docker-chu-ji-zhi-nan/)  
[Docker中级指南](https://blog.vercanti.com/docker-zhong-ji-zhi-nan/)