前置阅读:建议先阅读 容器内进程管理与 Entrypoint 最佳实践 与 Docker Compose 生产环境实践:从开发到部署的完整指南。本篇聚焦 健康检查语义、重启策略与容器自愈的完整编排链路。
容器的默认生命周期判据只有一个:主进程(PID 1)是否还在。这个判据太弱了。一个 JVM 进程可以在端口已经监听、但连接池尚未建立的情况下存活十分钟;一个 Nginx worker 可以在后端全部挂掉的情况下继续返回 502。健康检查要解决的,就是把这个「进程活着」的弱判据升级为「服务可被正确使用」的强判据;而自愈要解决的,是判据失效之后由谁来接管。
1. 健康检查的本质:三个递进的判据层级
很多人第一次写健康检查时,写的是一句 nc -z localhost 8080。它能用,但只回答了最浅的一层问题。
1.1 从端口探测到语义就绪
判断一个容器能不能干活,至少要区分四个层级,成本和准确度各不相同:
| 层级 | 探测方式 | 能发现的问题 | 漏报的典型故障 |
|---|---|---|---|
| 进程存活 | 检查 PID 1 是否存在 | 进程崩溃、OOM 被杀 | 死锁、事件循环阻塞 |
| 端口可达 | TCP 连接 8080 端口 | 监听套接字未就绪 | 连接池耗尽、返回 500 |
| 语义就绪 | HTTP 探针返回 200 | 依赖不可用、迁移未完成 | 业务逻辑级降级 |
| 深度就绪 | 探针内校验关键依赖 | 数据库、缓存断连 | 探针超时放大故障 |
生产环境的最低要求是第三层。第四层(在探针里连带检查下游依赖)要非常谨慎:如果探针会去连数据库,那么数据库抖动时全部容器同时变为 unhealthy,编排层会同时重启所有副本,把一次下游抖动放大成一次全站雪崩。稳妥做法是探针只检查「本进程能否正确处理一个轻量请求」,把下游依赖的健康交给下游自己的探针。
1.2 liveness 与 readiness 的分离
Kubernetes 把这个问题拆成两个独立概念,理解这个映射关系对设计探针很关键:
- liveness(存活):失败说明进程已无法自救,应当被杀死重启。判据要保守,宁可晚杀不可错杀。
- readiness(就绪):失败说明暂时不该接收流量,但不该被杀。判据可以激进,因为它只影响负载均衡摘除。
Docker 的 HEALTHCHECK 语义上更接近 readiness(unhealthy 只影响 depends_on 的编排和 Swarm 的调度,不会自动杀容器),但很多人误把它当 liveness 用,于是写出了「探针一失败容器就被杀」的激进配置,这正是后文要重点讨论的误判来源。
一句话:健康检查的目标是让编排层知道「现在能不能把请求交给它」,而不是「它有没有在跑」;前者决定流量,后者决定生死。
2. HEALTHCHECK 指令:四个时间参数的真实含义
HEALTHCHECK 有两个子命令,HEALTHCHECK NONE 用于清除基础镜像继承来的检查(当基础镜像自带的探针不适合你时非常有用)。
2.1 语法与 exec 形式的必要性
# shell 形式:会被 /bin/sh -c 包裹,探针进程是 sh 的子进程
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -fsS http://localhost/healthz || exit 1
# exec 形式(JSON 数组):直接 exec,信号与退出码更干净
HEALTHCHECK --interval=10s --timeout=3s --start-period=40s --retries=3 \
CMD ["curl", "-fsS", "--max-time", "2", "http://localhost:8080/actuator/health"]
优先用 exec 形式(JSON 数组)。shell 形式会额外拉一个 sh 进程,timeout 超时后 Docker 杀掉的是 sh,而 curl 可能变成孤儿继续挂着;exec 形式直接对探针进程发信号,退出码语义清晰。注意 JSON 数组形式不支持 || 这类 shell 语法,短路逻辑必须放进脚本。
2.2 四个参数的作用区间
| 参数 | 默认值 | 含义 | 调优要点 |
|---|---|---|---|
--interval | 30s | 两次探针之间的间隔 | 越短发现越快,但探针本身也是负载 |
--timeout | 30s | 单次探针的最长执行时间 | 必须显著小于 interval |
--start-period | 0s | 启动宽限期,期间失败不计入 retries | 慢启动服务必须显式设置 |
--retries | 3 | 连续失败多少次判定为 unhealthy | 与 interval 相乘即故障发现延迟下限 |
--timeout 的默认值与 --interval 相等(都是 30s),这是很容易踩的坑:如果探针真的卡满 30 秒,下一轮探针会在上一轮刚结束时才开始,实际探测周期被拉长到 60 秒。生产配置里 timeout 通常设成 interval 的十分之一左右,比如 interval=30s 配 timeout=3s。
2.3 退出码约定
探针进程的退出码只有三种语义,Docker 引擎据此更新状态机:
0:成功,容器进入 healthy(或从 unhealthy 恢复为 healthy)。1:失败(unhealthy),这是默认语义,也是最常被显式写出的|| exit 1。2:保留值,官方约定为「不使用」,不要依赖它的行为。
其他非零退出码一律按失败处理。因此 curl -f 返回的 22(HTTP 4xx/5xx)、7(连接被拒)都会被正确判为失败,不需要额外转换。
3. Compose 中的 healthcheck 覆盖与 start_period 实践
Compose 文件里的 healthcheck 段会整体覆盖镜像里 HEALTHCHECK 指令的配置,而不是逐字段合并。只要在 Compose 里写了 healthcheck:,镜像里的 interval、retries 全部失效,未写的字段回落到 Compose 自己的默认值(interval 30s、timeout 30s、retries 3、start_period 0s)。
3.1 覆盖写法与 disable
services:
api:
image: myapp:1.4.2
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 15s
timeout: 3s
retries: 4
start_period: 45s
# 临时禁用镜像自带的探针(调试期,或该服务本就不该有探针)
legacy-worker:
image: legacy/worker:2.1
healthcheck: {disable: true}
test 也可以写成字符串形式 test: curl -fsS http://localhost:8080/healthz,等价于 Dockerfile 的 shell 形式。三种形式要区分清楚:test: ["CMD", ...] 是 exec 形式,test: ["CMD-SHELL", "..."] 和纯字符串都是 shell 形式,test: ["NONE"] 等价于 disable。
3.2 start_period 与慢启动服务
start_period 是最容易被忽略、却最容易导致生产事故的一个参数。在宽限期内,探针失败的次数不计入 retries 计数;一旦某次探针成功,容器立刻进入 healthy 并结束宽限期。
| 服务类型 | 典型启动耗时 | 建议 start_period | 说明 |
|---|---|---|---|
| Go、Rust 编译型服务 | 100ms ~ 2s | 5s ~ 10s | 基本瞬时启动 |
| Node.js 应用 | 1s ~ 5s | 15s ~ 30s | 模块加载与连接建立 |
| Spring Boot JVM | 20s ~ 90s | 60s ~ 120s | JIT 预热、Bean 初始化 |
| PostgreSQL | 5s ~ 30s | 30s ~ 60s | WAL 恢复可能超预期 |
| Elasticsearch | 30s ~ 120s | 120s ~ 180s | 分片恢复,节点越多越慢 |
宽限期设小了,容器会在服务还没起来时被判 unhealthy,触发下游 depends_on 服务启动失败,或被 Swarm 判定为部署失败而回滚。设大了,真正的启动失败要等很久才被发现。推荐做法:把 start_period 设为实测 P99 启动耗时的 1.5 倍,同时把探针写成「启动中返回失败、就绪后返回成功」,让宽限期只覆盖真正的启动窗口。
4. 健康状态机与 docker inspect 观测
4.1 starting、healthy、unhealthy 三态
容器健康状态是一台只有三个状态的状态机,理解迁移条件才能正确排查问题:
| 状态 | 进入条件 | 退出条件 |
|---|---|---|
starting | 容器启动,探针尚未成功过 | 探针成功转 healthy;宽限期外连续失败超 retries 转 unhealthy |
healthy | 探针返回 0 | 连续失败超过 retries 转 unhealthy |
unhealthy | 探针连续失败超过 retries | 任意一次探针成功转 healthy |
关键点:unhealthy 不等于容器会被杀死。Docker 引擎本身不会因为 unhealthy 而停止容器,它只是把状态暴露出去。真正杀死容器的动作来自三处:Swarm 的调度器(会重建任务)、docker compose up --wait 的等待超时(会整体失败退出)、以及外部监控告警触发的人工或自动干预。
4.2 用 docker inspect 读取 Health 字段
# 只取健康状态字符串,最常用于脚本与告警
docker inspect --format '{{.State.Health.Status}}' myapp-api-1
# 取完整健康信息(含最近探针输出与失败计数)
docker inspect --format '{{json .State.Health}}' myapp-api-1 | jq .
# 单独取失败计数与探针日志
docker inspect --format '{{.State.Health.FailingStreak}}' myapp-api-1
docker inspect --format '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{end}}' myapp-api-1
| 字段 | 含义 | 排查用途 |
|---|---|---|
Status | starting / healthy / unhealthy | 快速判断当前状态 |
FailingStreak | 当前连续失败次数 | 观察是否逼近 retries 阈值 |
Log | 最近 5 次探针记录 | 看探针实际输出与退出码 |
Log[].ExitCode | 探针退出码 | 区分超时(124)与业务失败 |
Log 数组只保留最近 5 条记录,这是排查探针问题时唯一的原始证据。如果 Output 是空字符串而 ExitCode 是 124,说明是 timeout 触发;如果 ExitCode 是 7,说明 curl 连不上端口。
5. 重启策略四态与退出码的对应关系
5.1 四种重启策略
--restart(Compose 中为 restart:)共有四个取值,差异集中在「引擎守护进程重启后是否恢复」:
| 策略 | 触发条件 | 引擎重启后是否恢复 | 典型场景 |
|---|---|---|---|
no | 从不重启 | 否 | 一次性任务、批处理 |
on-failure[:max] | 退出码非 0,且未超 max 次 | 否 | 可能失败的短任务 |
always | 任何退出都重启 | 是 | 常驻服务、需开机自启 |
unless-stopped | 除人为 stop 外都重启 | 是(除非被手动停过) | 绝大多数生产服务 |
unless-stopped 与 always 的唯一区别:always 在守护进程重启后会把之前被 docker stop 的容器也拉起来;unless-stopped 会记住「用户主动停止」这个事实,重启引擎后保持停止。生产环境绝大多数服务应该用 unless-stopped,否则一次维护性的 docker stop 会在下次 dockerd 重启时意外复活。
on-failure 的次数上限很关键:on-failure:3 表示最多重启 3 次,之后放弃。不带数字则是无限重启,在配置错误导致的崩溃循环场景下会造成无限刷屏。
5.2 退出码语义速查
| 退出码 | 含义 | 是否触发 on-failure |
|---|---|---|
| 0 | 正常退出 | 否 |
| 1 ~ 125 | 应用自身错误 | 是 |
| 127 | 命令未找到(或 126 命令不可执行) | 是 |
| 137 | 128 + 9,SIGKILL,通常为 OOM 被杀 | 是 |
| 143 | 128 + 15,SIGTERM,正常终止请求 | 是 |
规律很简单:shell 约定的退出码是 128 加信号编号,因此 137 就是被 SIGKILL 干掉,143 就是收到 SIGTERM。在 on-failure 策略下,143 也会被判定为失败并触发重启——这一点常被误解,很多人以为「优雅退出不算失败」,但引擎只看退出码是否为 0。
5.3 OOM 与 SIGTERM 的区分
137 是排查时最常见的疑难退出码,它有两个来源:进程被内核 OOM Killer 杀死,或者被 docker kill(默认发 SIGKILL)。
# 检查是否因 OOM 被杀,以及内核侧的 OOM 记录
docker inspect --format '{{.State.OOMKilled}}' myapp-api-1
dmesg -T | grep -i 'killed process' | tail -5
docker stats --no-stream myapp-api-1 # 查看内存限制与实际峰值
如果 OOMKilled 为 true,问题在内存上限而不是应用 bug;如果为 false,则是外部 docker kill 或 Swarm 的强制停止。把 137 一律当成 OOM 处理,是最常见的误判之一。
6. 崩溃循环与指数退避
6.1 restart 的退避机制
当容器反复崩溃,Docker 引擎不会以固定频率无限重启,而是采用指数退避:首次重启延迟约 100ms,此后每次翻倍(200ms、400ms、800ms……),上限为 1 分钟。Docker 19.03 之后的行为是「一旦某次运行持续超过 10 秒,退避计数器重置」。
这意味着两件事:第一,短时间内不会出现每秒重启一次的日志刷屏;第二,如果服务启动需要 8 秒然后崩溃,退避会一直累积到 1 分钟一次,看起来像是容器卡住了。理解这一点,才能正确解读 docker ps 里频繁变化的 STATUS。
6.2 用 docker events 观测重启
# 实时观察容器的 die / start / restart 事件
docker events --filter container=myapp-api-1 \
--filter event=die --filter event=start --filter event=restart
# 带时间戳与退出码,便于统计重启频率
docker events --since 1h --filter type=container \
--format '{{.Time}} {{.Actor.Attributes.name}} {{.Action}} {{.Actor.Attributes.exitCode}}'
die 事件带 exitCode 属性,这是统计崩溃循环最直接的数据源。把它接进日志管道(例如 Fluent Bit 或 Loki),就能做「某容器 10 分钟内 die 超过 5 次」这类告警。
6.3 避免把崩溃循环掩盖掉
一个反直觉的建议:开发环境不要给频繁改动的服务配 restart: always。崩溃循环会让 docker compose logs 混入大量重复堆栈,掩盖真正的首因错误。开发期用 restart: no,让容器停在崩溃现场,用 docker logs 看第一段堆栈,效率高得多。
一句话:崩溃循环的指数退避是保护机制不是故障;真正的信号藏在第一次 die 的退出码和日志里,而不是最后那次。
7. 依赖编排:depends_on 与 service_healthy
7.1 短语法与长语法
depends_on 的短语法只保证启动顺序,不保证就绪顺序。这是 Compose 最经典的坑:api 依赖 db,短语法下 api 会在 db 进程刚拉起时就启动,此时 PostgreSQL 还在初始化数据目录,api 直接连接失败退出。
services:
api:
image: myapp:1.4.2
depends_on: [db] # 短语法:只保证 db 先启动,不保证可用
api-strict:
image: myapp:1.4.2
depends_on: # 长语法:等 db healthy、migrate 成功退出后才启动
db: {condition: service_healthy}
migrate: {condition: service_completed_successfully}
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 5s
retries: 10
start_period: 30s
三种 condition 的语义:
| condition | 等待条件 | 适用对象 |
|---|---|---|
service_started | 目标容器已启动(默认) | 无探针的辅助服务 |
service_healthy | 目标容器探针返回 healthy | 数据库、缓存、消息队列 |
service_completed_successfully | 目标容器以 0 退出 | 迁移脚本、初始化任务 |
注意 service_healthy 的等待不是无限的:如果目标容器最终进入 unhealthy,Compose 会直接报错退出,而不是一直等下去。这正是为什么 db 的 start_period 必须足够长——否则在慢速磁盘上,PostgreSQL 可能还没来得及 healthy 就被判死。
7.2 wait-for-it、dockerize 与自建探针
在不支持 condition 的场景(旧版 Compose、纯 docker run),或需要更细粒度控制时,有三类替代方案:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| wait-for-it.sh | 轮询 TCP 端口 | 零依赖、单文件 | 只看端口,不代表服务就绪 |
| dockerize | 轮询 TCP/HTTP/文件 | 支持 HTTP 探针与超时 | 需在镜像里额外安装 |
| 应用内重试 | 代码层连接重试 | 语义最准确 | 需要改代码 |
| depends_on + condition | 编排层等待 | 声明式、无需改镜像 | 仅 Compose 支持 |
wait-for-it 的典型用法是在 entrypoint 里前置:
#!/bin/sh
set -e
# 等待 db 的 5432 端口可连,最多 60 秒,超时则退出
./wait-for-it.sh db:5432 --timeout=60 --strict -- echo "db is up"
exec "$@"
注意 --strict 与超时行为:默认情况下 wait-for-it 超时后会继续执行,只有加了 --strict 才在超时后以非 0 退出,生产环境的 entrypoint 里几乎总是需要它。关于 exec "$@" 与 PID 1 信号处理的细节,可参考 容器内进程管理与 Entrypoint 最佳实践。
8. 编排层自愈、健康检查误判与生产清单
8.1 三种编排层的自愈能力
同一套健康检查配置,在不同编排层的自愈行为完全不同,这是迁移时最容易出错的地方:
| 编排层 | 健康检查来源 | unhealthy 后的动作 | 关键参数 |
|---|---|---|---|
| 单机 Docker | HEALTHCHECK 指令 | 无动作,仅暴露状态 | 无 |
| Docker Compose | healthcheck 段 | up 等待失败退出;阻塞依赖方 | wait 与 wait-timeout |
| Docker Swarm | 镜像 HEALTHCHECK | 杀死任务并重建副本 | update-order、update-failure-action |
| systemd | 无(用 OnFailure) | 重启 unit | Restart、RestartSec、OnFailure |
| Kubernetes | livenessProbe / readinessProbe | 重启容器 / 摘除 Endpoint | failureThreshold、periodSeconds |
Compose 的等待模式与 Swarm 的滚动更新参数:
# 启动全部服务并等待它们全部 healthy,超时 180 秒
docker compose up -d --wait --wait-timeout 180
# CI 典型用法:等待就绪后跑集成测试,再整体清理
docker compose up -d --wait && docker compose exec -T api pytest -q && docker compose down -v
# Swarm 侧:start-first 配合健康检查实现零中断发布
docker service update --update-order start-first --update-parallelism 2 \
--update-delay 10s --update-failure-action rollback --update-monitor 30s myapp_api
--update-order start-first 让新任务先启动并进入 healthy 再停掉旧任务,配合健康检查可实现零中断发布;--update-failure-action rollback 让健康检查失败时自动回滚,这是 Swarm 最接近自愈的能力。而 --update-monitor 决定了新任务启动后观察多久才算更新成功——这个窗口必须大于 start_period,否则健康检查还没跑出结果,更新就被判定成功了。
systemd 层的自愈用 Restart= 系列参数实现,适用于把容器当普通进程管理的场景:
[Unit]
Description=myapp container
After=docker.service
[Service]
Restart=always
RestartSec=10
ExecStartPre=-/usr/bin/docker rm -f myapp
ExecStart=/usr/bin/docker run --rm --name myapp \
--health-cmd "curl -fsS http://localhost:8080/healthz" \
--health-interval 15s --health-retries 3 myapp:1.4.2
[Install]
WantedBy=multi-user.target
ExecStartPre 前面的减号表示该命令失败不阻断启动,用于清理可能残留的同名容器。
8.2 健康检查误判:过于激进的探针
这是生产事故里排名第一的健康检查问题。典型症状是:服务本身完全正常,但容器反复被重启,日志里全是健康检查失败。误判有三个根源:
- timeout 太短。GC 停顿、锁竞争、瞬时负载高峰都会让探针响应变慢。JVM 服务把 timeout 设成 1 秒,几乎必然在 Full GC 时误判。
- retries 太少且 interval 太短。
interval=2s、retries=2意味着 4 秒内连续两次失败就判死,任何一次网络抖动都会触发。 - start_period 缺失。慢启动服务在宽限期内就被判死,尤其是 JVM。
修正方向的量化经验:timeout 至少覆盖 P99 响应时间的 3 倍,retries 乘以 interval 至少 30 秒,start_period 至少是实测启动 P99 的 1.5 倍。对于 JVM,把 start_period 设到 90 秒以上是常态而非例外。还有一个隐蔽来源:探针本身消耗资源。如果探针每次都要查数据库,而 interval 只有 5 秒,高负载下探针本身就成了压垮服务的最后一根稻草。探针应当只做进程内检查(读内存中的就绪标志位),不碰外部依赖。
8.3 健康检查脚本的写法
不同服务的就绪判据不同,以下是经过生产验证的探针写法:
# HTTP 服务:-f 让 4xx/5xx 返回非 0,-sS 静默但保留错误
HEALTHCHECK --interval=15s --timeout=3s --start-period=45s --retries=3 \
CMD curl -fsS --max-time 2 http://localhost:8080/healthz
# PostgreSQL:pg_isready 是官方探针,比 nc 准确
HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=6 \
CMD ["pg_isready", "-U", "app", "-d", "appdb", "-h", "127.0.0.1"]
curl 的 -f 与 --max-time 必须同时给:-f 负责把 HTTP 错误码转成非 0 退出码,--max-time 负责在服务假死(TCP 连接建立但永不响应)时及时断开。只给 -f 不给 --max-time,探针会在假死服务上一直挂着,直到 Docker 的 timeout 把它杀掉。
8.4 监控告警接入
健康状态需要被外部系统消费,才能真正形成闭环:
# 路径一:流式接入告警管道,延迟最低
docker events --filter event=health_status --format '{{.Time}} {{.Actor.Attributes.name}}'
# 路径二:定时轮询所有容器健康状态,输出给监控系统
docker ps -a --format '{{.Names}}' | xargs -I{} docker inspect \
--format '{{.Name}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' {}
推荐组合:用 docker events 的 health_status 事件做实时告警(延迟低),用 Prometheus 的 container_health_status 指标做趋势与 SLO 统计(可长期留存)。告警规则应区分「单容器 unhealthy」与「某服务全部副本 unhealthy」——前者通常自愈,后者需要立即人工介入。
8.5 生产清单与踩坑表
| 踩坑现象 | 根因 | 修正 |
|---|---|---|
| 容器反复重启,日志无错误 | 探针 timeout 太短,GC 期误判 | timeout 提到 P99 的 3 倍 |
| 慢启动服务刚起就被判死 | 未设 start_period | 按实测启动 P99 的 1.5 倍设置 |
| depends_on 顺序对了但连接失败 | 用了短语法 | 改用 service_healthy |
| 探针返回空但退出码 124 | 探针卡住触发 timeout | 加 max-time,缩小探测范围 |
| 下游抖动导致全站重启 | 探针内检查了外部依赖 | 探针只做进程内检查 |
| 137 被当成应用 bug | 未区分 OOM 与 SIGKILL | 查 OOMKilled 与 dmesg |
| 维护性 stop 后容器自动复活 | 用了 restart always | 改用 unless-stopped |
| Swarm 滚动更新中断服务 | 默认 stop-first 顺序 | 改 start-first 配合健康检查 |
一句话:健康检查的配置不是「设了就行」,而是「timeout、retries、start_period 三个数必须与服务实测的启动与响应分布对齐」;脱离实测数据的探针配置,是自愈系统里最大的不确定因素。
9. 总结
| 主题 | 关键结论 | 一句话记忆 |
|---|---|---|
| 健康检查层级 | 端口可达不等于服务就绪,生产至少要到语义层 | 探针答的是能不能用 |
| 四个时间参数 | timeout 必须远小于 interval,start_period 按启动 P99 定 | 三个数要对齐实测分布 |
| Compose 覆盖 | healthcheck 段整体覆盖镜像配置,不逐字段合并 | 写了就是全量替换 |
| 状态机 | unhealthy 不会自动杀容器,杀它的是编排层 | 状态只暴露,不执行 |
| 重启策略 | unless-stopped 是生产默认,on-failure 要限次数 | 记住用户主动停过 |
| 退出码 | 137 是 SIGKILL(常为 OOM),143 是 SIGTERM | 128 加信号编号 |
| 依赖编排 | 短语法只保证启动顺序,就绪要靠 service_healthy | 顺序不等于就绪 |
| 自愈能力 | 单机不自愈,Compose 阻塞依赖,Swarm 重建,K8s 重调度 | 自愈强度随编排层递增 |
| 误判防范 | timeout、retries、start_period 三处最常见 | 探针别碰外部依赖 |
健康检查与自愈是一个层层递进的体系:探针定义了什么叫可用,状态机把这个判断暴露给编排层,重启策略决定了失败之后的第一反应,而 depends_on 与更新顺序则决定了故障会不会沿着依赖链扩散。四者中任何一环配置失当,都会让整套自愈机制从保护变成放大器——最典型的就是探针过激导致健康实例被批量重启,以及探针检查外部依赖导致下游抖动扩散成全站故障。
落地时的最小可行路径是:先给每个服务写一个只做进程内检查的探针,用实测的启动与响应分布反推 timeout、retries、start_period 三个参数,把 restart 统一设为 unless-stopped,把依赖关系从短语法升级到 service_healthy,最后用 docker events 的 die 与 health_status 事件接上告警。完成这五步之后,容器就从「跑起来就行」变成了真正能在故障中自我收敛的系统组件。进一步的编排实践可参考 Docker Compose 生产环境实践。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。