Docker 基础体系 · 第 21/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker 生产交付体系:CI、灰度、回滚、容量和运行手册
生产交付不是“把镜像构建出来并执行 docker run”。它至少包含一条可追踪的数据链:
源代码
↓
可复现构建与测试
↓
镜像、SBOM、签名、漏洞结果
↓
不可变版本发布
↓
灰度部署与健康验证
↓
流量切换
↓
指标、日志、事件和容量观察
↓
回滚或继续扩大范围
本文以现代 Docker Engine、BuildKit 和 Compose 规范为基础,重点讨论 Linux 主机上的容器交付。Docker Compose 适合单机、多容器应用编排;它不是跨主机调度器,也不会自动提供 Kubernetes 一类的滚动发布、服务发现或故障迁移能力。跨主机生产系统需要额外的编排器、负载均衡器、镜像仓库和监控系统。
一、先定义交付对象:不可变镜像和可验证版本
1.1 镜像标签不是版本身份
镜像引用通常有两种形式:
registry.example.com/payments/api:2025.03.08-abc123
registry.example.com/payments/api@sha256:...
标签是人可读的别名,可以被重新推送;摘要是镜像内容的哈希身份。生产部署若只记录:
image: registry.example.com/payments/api:latest
就无法回答“当前运行的到底是哪一份内容”,也无法保证回滚时取得的仍然是原版本。
更可靠的流程是:
- CI 根据提交构建镜像;
- 推送带有提交标识的标签;
- 读取推送后的 digest;
- 将 digest 写入发布清单;
- 生产只按 digest 拉取和启动;
- 记录提交 ID、构建器版本、基础镜像 digest 和部署时间。
例如:
services:
api:
image: registry.example.com/payments/api@sha256:0123456789abcdef...
这里的摘要必须是真实仓库返回的完整 digest,不能使用示例值。使用 digest 不代表镜像永久存在:仓库仍可能执行删除或保留策略,所以生产环境还应保证已发布 digest 的保留周期。
1.2 Linux 容器的边界
Linux 容器通过 namespace 隔离进程、网络、挂载点等视图,通过 cgroup 限制或统计资源,并与宿主机共享 Linux 内核。因此:
- 容器不是虚拟机,不能隔离不同内核;
- 宿主机内核漏洞可能影响所有容器;
--privileged、挂载/var/run/docker.sock、挂载宿主机根目录都会显著扩大权限边界;- 文件权限、信号、cgroup 和网络行为以 Linux 为主要语义;
- Windows 容器和 Linux 容器的镜像格式、内核接口、隔离机制不同,不能直接套用本文全部命令。
生产主机还需要明确 Docker Engine 的 rootful 或 rootless 模式。rootless 可以降低 Docker daemon 对宿主机的权限,但会影响端口绑定、存储驱动和 cgroup 能力;不能把“使用 rootless”误认为自动解决了容器内的所有权限问题。
二、CI:从源代码到可部署制品
2.1 一个可审计的流水线
典型流水线可以分为以下状态:
flowchart LR
A[提交代码] --> B[静态检查与单元测试]
B --> C[BuildKit 构建]
C --> D[镜像集成测试]
D --> E[生成 SBOM 与 provenance]
E --> F[漏洞与策略门禁]
F --> G[推送镜像]
G --> H[按 digest 生成发布清单]
H --> I[部署灰度环境]
I --> J[健康检查与业务验证]
J --> K[扩大流量或停止发布]
每个状态都应有明确的输入、输出和失败处理。例如,漏洞扫描失败时不能继续部署;部署后健康检查通过,也不能替代业务指标验证,因为进程存活并不等于请求正确。
CI 至少应执行:
- 代码格式和静态检查;
- 单元测试;
- 使用生产近似依赖的集成测试;
- Dockerfile 构建;
- 容器启动测试;
- HTTP 或 RPC 健康测试;
- SBOM 生成;
- 镜像漏洞扫描;
- 签名或可信来源校验;
- 发布清单生成。
2.2 使用 BuildKit 构建,而不是把密钥写进镜像
下面是一个基础 Dockerfile:
# syntax=docker/dockerfile:1
FROM golang:1.23-bookworm AS build
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 /out/api ./cmd/api
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/api /api
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/api"]
关键点如下:
- 多阶段构建把编译器和源码留在构建阶段;
ENTRYPOINT使用 JSON 数组形式,避免额外 shell 成为 PID 1;- 使用非 root 用户运行;
GOOS=linux明确目标平台;-trimpath减少构建路径泄露,但不等同于完整可复现构建;- 基础镜像仍然应按 digest 固定,而不是仅使用
debian:bookworm这类可变标签。
构建命令:
docker buildx build \
--platform linux/amd64 \
--tag registry.example.com/payments/api:2025.03.08-abc123 \
--provenance=true \
--sbom=true \
--push \
.
这些 --provenance 和 --sbom 选项依赖较新的 Buildx、BuildKit 和仓库支持;CI 中应先用 docker buildx version、docker buildx inspect 确认能力。它们生成的证明和 SBOM 是供应链元数据,不能替代漏洞扫描和签名。
不要这样处理构建凭据:
ARG NPM_TOKEN
RUN npm config set //registry.example.com/:_authToken=$NPM_TOKEN
因为构建参数可能进入历史或元数据。应使用 BuildKit secret mount,且让凭据只在需要的 RUN 步骤存在:
# syntax=docker/dockerfile:1
FROM node:22-bookworm AS build
WORKDIR /src
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci
COPY . .
RUN npm run build
构建时:
docker buildx build \
--secret id=npmrc,src="$HOME/.npmrc" \
--tag registry.example.com/web:2025.03.08-abc123 \
--push .
前置条件是本机或 CI 工作空间存在 .npmrc。命令成功时,secret 不应被复制到最终镜像;仍需检查 Dockerfile 和构建产物,防止应用主动把凭据写入输出目录。
2.3 SBOM、签名和策略门禁各自解决什么问题
SBOM 是软件物料清单,描述镜像内包含哪些包及其版本。它回答“里面有什么”,但不直接回答“是否安全”。
漏洞扫描 将 SBOM 或镜像文件系统与漏洞数据库匹配。扫描结果受数据库更新时间、发行版修复回移和误报影响。例如,发行版可能将安全修复回移到旧版本号,因此不能仅按上游软件版本字符串机械判断。
签名 证明某个身份对某个镜像摘要执行过签署。签名不保证代码没有漏洞,也不保证构建过程本身可信。
provenance 记录构建来源、构建器和输入等信息,可帮助回答“这个 digest 是由什么源代码和流水线构建的”。
因此,一个门禁的逻辑通常是:
digest 存在
∧ SBOM 可生成
∧ provenance 满足要求
∧ 漏洞结果满足阈值
∧ 签名身份属于允许发布者
∧ 基础镜像属于允许来源
→ 允许进入部署仓库
签名工具可以使用 Cosign 等 OCI 生态工具;Docker Engine 本身不会替你设计完整的组织身份和策略体系。策略必须明确:
- 阻断哪些严重等级;
- 是否允许有修复版本的漏洞;
- 是否允许开发依赖存在于最终镜像;
- 签名身份与环境的对应关系;
- 例外由谁批准、何时过期;
- 基础镜像和仓库是否在允许列表中。
“扫描通过”也不是永久事实。镜像 digest 不变,但漏洞数据库会更新,所以应定期重新扫描已部署 digest。
三、健康检查、PID 1 和优雅关闭
3.1 PID 1 的实际作用
容器内由 Docker 启动的第一个进程通常是 PID 1。它负责接收停止信号,并承担孤儿进程回收等职责。常见错误是:
ENTRYPOINT ["sh", "-c", "python app.py"]
此时 PID 1 是 sh。如果 shell 没有正确转发 SIGTERM,应用可能直到超时后才收到 SIGKILL,造成连接中断或事务未完成。
更稳妥的是:
ENTRYPOINT ["python", "app.py"]
如果必须使用 shell,应显式使用 exec:
ENTRYPOINT ["sh", "-c", "exec python app.py"]
也可以在镜像中使用专门的 init 进程处理信号和僵尸进程。docker run --init 使用 Docker 提供的 init 机制,但是否采用应结合应用本身是否已经正确处理子进程。
3.2 Stop signal 和超时的因果链
容器停止的大致过程是:
docker stop
↓
向容器 PID 1 发送停止信号,默认通常为 SIGTERM
↓
等待 stop grace period
↓
仍未退出则发送 SIGKILL
Compose 中可以明确声明:
services:
api:
image: registry.example.com/payments/api@sha256:...
stop_signal: SIGTERM
stop_grace_period: 30s
stop_signal 指定应用应处理的信号,stop_grace_period 指定等待时间。它们不是“保证优雅关闭”的开关:应用必须实现收到信号后的停止逻辑,例如:
- 标记实例不再接收新请求;
- 从服务发现或负载均衡中摘除;
- 等待进行中的请求;
- 停止消费新消息;
- 提交或回滚当前事务;
- 关闭连接池;
- 在期限内退出。
如果应用需要 90 秒才能完成最长请求,而 grace period 只有 10 秒,那么第 10 秒会被强制杀死。反过来,盲目设置很长的超时也会拖慢发布和故障恢复。
3.3 Probe 的状态和限制
Compose 的 healthcheck 是容器内部探针,不是完整的业务监控:
services:
api:
image: registry.example.com/payments/api@sha256:...
healthcheck:
test: ["CMD", "/bin/sh", "-c", "wget -q -O- http://127.0.0.1:8080/readyz"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
如果基础镜像没有 wget,该检查会永远失败;精简镜像通常需要把探针程序编译进镜像,或者使用 CMD-SHELL 调用已有工具。检查命令退出码为 0 才表示成功。
容器健康状态通常经历:
starting → healthy
starting → unhealthy
healthy → unhealthy
unhealthy → healthy
start_period 允许启动初始化阶段的失败不立即计入重试;interval 是检查间隔;timeout 是单次检查超时;retries 是连续失败次数阈值。具体状态可查看:
docker inspect --format '{{json .State.Health}}' api
健康检查失败不会自动重启普通 Docker Compose 服务。它会改变健康状态;是否重启取决于外部编排、监控自动化或人工操作。Compose 的:
depends_on:
db:
condition: service_healthy
主要用于启动顺序:启动 api 前等待 db 健康。它不等于运行期间持续监督,也不解决数据库迁移锁、连接重试和网络瞬断。
应区分两个端点:
livez:进程是否仍然能工作,通常不检查所有依赖;readyz:是否应该接收流量,可能检查数据库连接、配置加载和关键依赖。
把数据库短暂故障直接映射为 liveness 失败,可能造成重启风暴;把所有故障都报告为 ready,则会让流量进入无法正确处理请求的实例。
四、Compose 生产部署的边界和基本模型
一个最小 Compose 文件可以是:
services:
api:
image: ${API_IMAGE}
restart: unless-stopped
init: true
read_only: true
tmpfs:
- /tmp
user: "10001:10001"
ports:
- "127.0.0.1:18080:8080"
environment:
PORT: "8080"
healthcheck:
test: ["CMD", "/bin/sh", "-c", "wget -q -O- http://127.0.0.1:8080/readyz"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
read_only 会把根文件系统设为只读;应用确实需要写入时应显式挂载卷或 tmpfs,否则启动可能失败。restart: unless-stopped 是本机 Docker 的重启策略,不是高可用机制:宿主机损坏、Docker daemon 故障或整机断电仍可能导致服务不可用。
部署前先展开并校验配置:
export API_IMAGE='registry.example.com/payments/api@sha256:...'
docker compose config
docker compose pull
docker compose up -d --wait
docker compose ps
docker compose config 可以发现变量未替换、缩进和合并结果问题;pull 拉取清单中的镜像;up -d 创建或重建容器;较新的 Compose 支持 --wait,它会等待服务达到运行或健康状态,具体行为和可用选项应以 docker compose up --help 为准。不能把命令返回成功理解为业务发布成功,还要验证 HTTP、关键业务操作和指标。
五、灰度发布:先验证新版本,再改变流量
5.1 灰度的定义
灰度发布是让新版本只接收一部分可控流量或一部分实例,然后依据观测结果逐步扩大范围。它与“启动第二个容器”不同:
启动新版本 ≠ 新版本收到流量
收到少量流量 ≠ 已验证全部场景
健康检查通过 ≠ 业务指标正常
单机 Compose 不负责按百分比分配外部请求。通常需要反向代理、服务网格或云负载均衡器执行流量切换。Compose 只负责运行蓝、绿两个版本。
一种结构如下:
flowchart LR
U[客户端] --> P[反向代理/负载均衡器]
P --> B[blue: 旧版本]
P --> G[green: 新版本]
B --> DB[(数据库)]
G --> DB
M[指标与日志系统] --> P
M --> B
M --> G
部署顺序是:
- 保持 blue 接收 100% 流量;
- 启动 green;
- 等待 green 的 readiness;
- 对 green 执行版本、配置和关键业务验证;
- 将少量流量切到 green;
- 比较错误率、延迟、业务成功率、资源使用和日志异常;
- 逐步增加比例;
- 观察完整窗口后完成发布;
- 保留 blue,直到回滚窗口结束。
若代理配置按服务名路由,必须验证 DNS 缓存、连接复用和长连接行为。已经建立的 WebSocket、HTTP/2 或长轮询连接不一定会因配置切换立即迁移。
5.2 数据库迁移决定了回滚能力
应用镜像通常可以快速回滚,数据库 schema 却可能已经向前演进。安全的迁移一般采用扩展—迁移—收缩模式:
- 先增加向后兼容的字段或表;
- 部署同时兼容旧 schema 和新 schema 的应用;
- 回填数据;
- 切换读写路径;
- 确认旧版本不再运行后,再删除旧字段或约束。
反例是新版本先删除旧字段,然后发现应用错误并回滚镜像。旧版本恢复后仍访问已删除字段,回滚反而扩大故障。
六、回滚:恢复旧制品,不是重新构建“差不多的版本”
6.1 何时触发回滚
回滚条件应在发布前定义,例如:
- 5 分钟错误率超过基线和绝对阈值;
- p95/p99 延迟持续超过服务目标;
- 关键业务成功率下降;
- 数据库连接池耗尽或队列持续增长;
- 新版本出现不可接受的日志错误;
- 内存或 CPU 进入持续饱和状态。
不能只看平均延迟。少量慢请求可能被平均值隐藏,而 p99 可能已经破坏用户体验。
6.2 Compose 中的回滚过程
如果部署文件使用环境变量:
services:
api:
image: ${API_IMAGE}
回滚时设置上一个已批准的 digest:
export API_IMAGE='registry.example.com/payments/api@sha256:previous...'
docker compose config
docker compose pull
docker compose up -d --no-deps --wait api
docker compose ps
docker inspect --format '{{.Config.Image}} {{.Image}}' "$(docker compose ps -q api)"
这里:
.Config.Image显示容器配置使用的引用;.Image显示实际镜像 ID;- digest 引用可以避免标签漂移;
--no-deps防止回滚应用时无意重启依赖服务;--wait只验证启动/健康状态,仍需要执行业务验证。
回滚后应检查:
docker compose logs --since=10m api
docker events --since=10m --filter container="$(docker compose ps -q api)"
若新版本已经写入不可逆数据,先停止继续扩大损害,再判断是应用回滚、前向修复还是数据恢复。数据库回滚不能仅凭“旧镜像还能启动”决定。
6.3 回滚的常见失败表现
- 容器启动成功但请求 500:健康检查只检查端口,未覆盖依赖或业务逻辑;
- 镜像拉取失败:旧 digest 未被仓库保留,或生产主机无访问凭据;
- 回滚后仍有新版本流量:代理缓存、连接复用或另一台主机仍运行新版本;
- 旧版本启动后数据库报错:迁移不向后兼容;
- 容器反复重启:入口进程退出、配置不完整、权限错误或 OOM;
- 回滚无法降低延迟:根因可能是数据库、外部依赖或容量,而非应用代码。
因此发布记录必须保存镜像 digest、Compose 配置、环境变量版本、迁移版本、代理配置和执行人,而不是只保存一个 Git 分支名。
七、容量:从请求量推导实例数,而不是凭感觉扩容
7.1 Little 定律和并发容量
对稳定系统,Little 定律为:
其中:
- 是系统内平均并发请求数;
- 是吞吐率,单位为请求/秒;
- 是请求在系统内的平均停留时间,单位为秒。
例如,流量为 120 请求/秒,平均响应时间为 80 毫秒:
这意味着系统平均约有 10 个并发请求,但不意味着只需要 10 个线程或 1 个容器,因为 p99 延迟、CPU、内存、连接池和突发流量还会增加需求。
如果每个实例安全承载 40 个并发请求,按目标利用率 70% 计算:
数学上得到 1,但生产还要考虑实例故障、部署期间双版本并存和流量突发。若要求任意丢失一个实例后仍能承载目标流量,至少需要 2 个;若灰度期间 blue 和 green 各保留实例,还要计算总主机资源。
7.2 CPU 容量算例
假设压测得到:
- 峰值流量:120 请求/秒;
- 每个请求平均消耗 12 毫秒 CPU;
- 目标 CPU 利用率:不超过 60%;
- 每个实例提供 1 个 CPU 核的有效配额。
所需 CPU 核数为:
因此至少需要 3 个实例的 CPU 容量。若要求 N-1 容错,则 3 个实例中损失 1 个后只剩 2 个,不足 2.4 个核,应至少配置 4 个实例。若灰度期间新旧版本同时运行,主机必须能容纳旧版本和新版本的总 CPU、内存以及代理开销。
CPU 配额可以限制容器:
services:
api:
deploy:
resources:
limits:
cpus: "1.0"
memory: 768M
在 Compose 的不同运行模式和版本中,deploy 资源字段的实际执行能力需要验证;不要仅凭文件写入就假定主机已经施加限制。可在运行后检查:
docker inspect api --format '{{json .HostConfig.NanoCpus}} {{json .HostConfig.Memory}}'
docker stats --no-stream api
docker stats 显示的是观测值,不是压测替代品。内存限制触发后可能发生 OOM kill;查看:
docker inspect api --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
dmesg -T | grep -i -E 'oom|killed process'
7.3 内存、连接和磁盘是不同的容量问题
内存容量不能只看应用堆:
M_container-peak 应使用压测和生产观测中的峰值,而非启动后的瞬时值。还要考虑 page cache、线程栈、运行时元数据和突发分配。
连接容量也可能先于 CPU 饱和。例如:
实例数 × 每实例数据库连接池上限
> 数据库允许的最大连接数
扩容应用可能因此压垮数据库。队列消费者则要同时观察队列深度、消费速率和消息处理时间;单纯增加容器数量不一定提高吞吐,可能只是增加下游争用。
磁盘容量需要包括:
- 可写层;
- volume 数据;
- 镜像层;
- 容器日志;
- 构建缓存;
- 临时文件;
- 删除但仍被进程打开的文件。
查看 Docker 空间:
docker system df
df -h
docker ps --size
不要在故障中直接执行:
docker system prune -a --volumes
它可能删除未使用镜像、网络、容器和卷,卷删除尤其可能造成数据丢失。生产清理必须先确认对象归属、备份状态和保留策略。
八、日志、指标和事件:为发布与容量提供证据
8.1 Logging Driver 和日志流转
Docker logging driver 决定容器标准输出和标准错误如何被 Docker 接收、存储或转发。常见的 json-file 便于本机查看,但若不设置轮转,日志可能填满磁盘:
services:
api:
logging:
driver: local
options:
max-size: "20m"
max-file: "5"
local 驱动使用 Docker 的本地存储格式并支持轮转;实际集中采集方案也可以使用 syslog、journald、Fluentd 等驱动,取决于主机和日志平台。不要同时让应用写大量文件日志又依赖 Docker 标准输出,否则会造成重复采集和磁盘不可见增长。
检查实际配置:
docker inspect api --format '{{json .HostConfig.LogConfig}}'
docker logs --since=15m --timestamps api
docker logs 只能查看 Docker 能捕获的标准输出/错误,不会自动显示应用写入任意文件的内容。生产日志至少要包含时间、级别、服务、版本 digest、请求 ID 和错误上下文,但密码、令牌和个人敏感信息不得写入日志。
8.2 指标与事件的不同职责
指标适合回答“系统现在是否恶化”:
- 请求速率;
- 错误率;
- p50、p95、p99 延迟;
- CPU、内存、网络和文件系统;
- 重启次数;
- OOM 次数;
- 队列深度;
- 数据库连接池使用率。
事件适合回答“Docker 刚刚做了什么”:
docker events --since=30m \
--filter type=container \
--filter event=die \
--filter event=restart \
--filter event=health_status
事件是实时流,不是长期审计数据库;应转发到持久化系统。docker inspect 适合检查单个对象当前状态,但不是历史变化记录。
Docker daemon 的指标接口、容器指标采集器和应用指标端点在不同部署中有所不同,不能把 docker stats 当作完整监控系统。生产应将应用指标、主机指标、Docker/容器指标和日志关联到同一个版本与实例标识。
九、运行手册:把诊断和恢复写成可执行路径
运行手册不是“重启服务”四个字,而是包含触发条件、证据、操作、验证和退出条件的故障流程。
9.1 发布前检查
set -eu
docker version
docker compose version
docker compose config
docker image inspect "$API_IMAGE"
docker compose pull
应确认:
- Docker Engine 和 Compose 版本满足流水线要求;
- 目标平台与镜像平台匹配;
- digest 可从生产仓库拉取;
- 所需 secret、网络、卷和环境变量存在;
- 数据库迁移版本与应用版本兼容;
- 旧版本镜像仍在保留期内;
- 监控和回滚权限可用。
如果 docker image inspect 失败,通常表示本地没有该镜像或引用格式错误;如果 pull 失败,应先区分 DNS、仓库认证、网络策略和 digest 不存在,不要立即改回 latest。
9.2 容器启动失败
按以下顺序收集证据:
docker compose ps
docker compose logs --tail=200 api
docker inspect "$(docker compose ps -q api)" \
--format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
常见判断路径:
Exited且退出码为 0:入口程序可能正常结束,但服务本应常驻;- 退出码为 126:通常涉及不可执行权限;
- 退出码为 127:命令不存在;
OOMKilled=true:先调查内存峰值、限制和泄漏;unhealthy但进程运行:检查探针命令是否存在、端口是否正确、依赖是否可用;- 重启次数持续增加:检查
restart、入口异常和外部依赖。
不要为了“让容器保持运行”把入口改成 sleep infinity。这会掩盖真实启动失败,并让健康检查和监控失去意义。
9.3 延迟和错误率升高
先区分新版本特有问题和全局基础设施问题:
docker stats --no-stream
docker compose logs --since=10m api
docker events --since=10m
ss -s
df -h
然后按版本、实例和依赖拆分指标:
新版本错误率高、旧版本正常
→ 暂停扩大灰度,切回旧版本
所有版本同时变慢
→ 检查数据库、外部依赖、主机 CPU/IO 和网络
应用无明显错误但 p99 上升
→ 检查连接池、线程池、队列、下游超时和资源争用
磁盘接近满
→ 先确认日志、容器层、volume 和删除打开文件,再按保留策略清理
回滚前后都要保存证据。没有证据的回滚可能恢复可用性,却丢失定位根因所需的信息。
9.4 优雅停止验证
可以使用一个临时容器验证信号路径:
docker run --name signal-test --rm \
--stop-signal SIGTERM \
alpine:3.20 \
sh -c 'trap "echo got-term; exit 0" TERM; while :; do sleep 1; done'
另一个终端执行:
docker stop --time 5 signal-test
预期容器输出 got-term 并在 5 秒内退出。这个例子只验证 shell 进程的信号处理,不代表真实应用的请求排空逻辑已经正确。真实服务还应在压测下验证:停止实例后,新请求不再进入该实例,进行中的请求在期限内完成,超时请求不会无限阻塞。
十、一次完整交付示例
假设 CI 已得到镜像 digest:
registry.example.com/payments/api@sha256:aaa...
Compose 使用环境变量:
services:
api:
image: ${API_IMAGE}
init: true
restart: unless-stopped
stop_signal: SIGTERM
stop_grace_period: 30s
healthcheck:
test: ["CMD", "/bin/sh", "-c", "wget -q -O- http://127.0.0.1:8080/readyz"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
部署过程为:
export API_IMAGE='registry.example.com/payments/api@sha256:aaa...'
docker compose config > rendered-compose.yaml
docker compose pull
docker compose up -d --wait
docker compose ps
curl --fail http://127.0.0.1:18080/readyz
其中 rendered-compose.yaml 应进入发布审计记录,但要先确认其中没有明文 secret。健康检查和 curl 都成功后,再让代理把少量流量转到该实例。若业务错误率、p99 延迟或下游连接数异常,恢复代理流量到旧版本,并将 API_IMAGE 改回旧 digest:
export API_IMAGE='registry.example.com/payments/api@sha256:old...'
docker compose pull
docker compose up -d --wait --no-deps api
curl --fail http://127.0.0.1:18080/readyz
最后检查运行中的镜像身份、日志、事件和业务指标,确认回滚不是只恢复了容器进程,而是恢复了对用户可用的服务。
生产交付的核心不是某个 Compose 参数,而是让“构建的内容、部署的版本、实际接收的流量、观测到的行为和执行的恢复动作”能够互相对应。只有这样,灰度才有验证意义,回滚才有确定对象,容量计算才有观测依据,运行手册才不会退化为未经验证的命令集合。
系列导航与关联阅读
- 系列入口:Docker 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker Engine API 与自动化:SDK、事件、权限和幂等操作
- 下一篇:Docker CLI 与对象模型:Container、Image、Network、Volume 和 Filter
- 延伸:Docker 健康检查与优雅关闭:PID 1、Probe、Stop Signal 和超时
- 延伸:Docker 软件供应链:SBOM、签名、扫描、可信基础镜像和策略门禁
- 延伸:Docker 日志与监控:Logging Driver、Metrics、事件和容量告警
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论