一次系统级软件升级把 docker-runc 从 222 单独升到 229,而 Docker 引擎未同步升级,新旧 runC 之间进程状态格式不兼容(init_process_start 字段 string↔number 对不上),导致 7 个容器健康检查批量失败 7,262 次、日志流中断、容器反复被强杀,形成无法自愈的 restart 循环。处置:停 Docker 后降级 docker-runc 回 222、重启 docker.service,业务恢复正常。
本案例目录
CHART 01 // 健康检查失败 · 按小时
health-check failures by hour共 7,262 次 · 柱长为占峰值 1,796 的比例 · 自 13:51 runC 升级起持续,无自愈迹象。
CHART 02 // 日志流中断 · 按小时
closed-fifo stream errors by hour共 14,534 次 · 容器 stdout/stderr 通道反复关闭 · 15:29 大规模重启尝试时达到峰值。
服务器:
[生产服务器](麒麟 V10 aarch64,昇腾 NPU + Docker,主机名已隐去)· 故障日期:2026-08-17 日志来源:journalctl导出(当日 03:38 ~ 18:57,共 4.3 万行) 脱敏说明:容器 ID、真实 IP、主机名、实例名均已隐去。
环境与系统信息
| 项目 | 信息 |
|---|---|
| 操作系统 | 麒麟 V10(Kylin Linux Advanced Server V10)· aarch64 |
| 内核 | 4.19.90-89.45.v2401.ky10.aarch64 |
| CPU 架构 | aarch64(ARM64) |
| 硬件加速 | 华为昇腾 NPU(Ascend),昇腾驱动在库 |
| Docker | docker-engine(API v1.43,对应 Docker 24.x 时代),未随本次事务升级 |
| 运行时 runC | docker-runc 1.0.0.rc3-222(升级前)→ 1.0.0.rc3-229(升级后) |
| 业务形态 | Docker 容器化;7 个业务容器,含 NPU 推理容器 [推理容器名] |
| 故障时间 | 2026-08-17 13:51 ~ 18:57 |
故障现象
当日下午一次系统级软件升级后,Docker 进入异常状态,持续数小时未能自愈:
- 7 个业务容器的健康检查批量失败,共 7,262 次,报错高度一致:
Health check for container [容器ID] error: OCI runtime exec failed: exec failed:
json: cannot unmarshal number into Go struct field CompatState.init_process_start
of type string: unknown
- 容器日志流大面积中断:
stream copy error: reading from a closed fifo共 14,534 次; - 容器反复被停止/强杀:
Container failed to exit within 2s of signal 15 - using the force、超时后再SIGKILL共 93 次,overlay2 挂载随之反复卸载/重挂; - 期间运维多次尝试重启容器 / Docker,每次拉起后
docker exec依旧失败,形成 restart 循环,业务持续受影响; - 18:47 运维手动
systemctl stop docker暂停服务以处置,日志在 18:47:50 显示docker.service: Succeeded.; - 处置结果:停 Docker 后将
docker-runc降级回-222并重启docker.service,容器docker exec、健康检查全部恢复正常,业务恢复。
关键点:容器本身在 docker ps 里可能显示为 running,但 docker exec(健康检查走的就是 exec)全部失败 —— 这是「能看不能进」的典型形态。版本对齐后该报错彻底消失。
关键日志摘录
以下日志为当日原样摘录(时间戳保留,容器 ID 等敏感信息以 [占位符] 代替),可对照上文现象与时间线阅读。
① 升级动作:runC 被单独升级(根因起点)
Aug 17 13:49:39 localhost [RPM][3047762]: erase docker-runc-1.0.0.rc3-222.ky10.aarch64: success
Aug 17 13:51:27 localhost [RPM][3047762]: install docker-runc-1.0.0.rc3-229.p02.ky10.aarch64: success
② 核心报错:健康检查 / exec 批量失败
Aug 17 13:51:30 localhost dockerd[3112418]: time="2026-08-17T13:51:30.916506666+08:00" level=warning msg="Health check for container [容器ID] error: OCI runtime exec failed: exec failed: json: cannot unmarshal number into Go struct field CompatState.init_process_start of type string: unknown"
Aug 17 13:51:30 localhost docker-runc[3053105]: {"level":"error","msg":"exec failed: json: cannot unmarshal number into Go struct field CompatState.init_process_start of type string\n","time":"2026-08-17T13:51:30+08:00"}
同一秒,
dockerd与docker-runc两个进程同时打出同一报错 —— 问题发生在 runC 层,与业务容器本身无关。
③ 日志流中断 / 容器被强杀
Aug 17 13:51:30 localhost dockerd[3112418]: time="2026-08-17T13:51:30.915198381+08:00" level=error msg="stream copy error: reading from a closed fifo"
Aug 17 15:29:22 localhost dockerd[3112418]: time="2026-08-17T15:29:22.026939745+08:00" level=info msg="Container failed to exit within 2s of signal 15 - using the force" container=[容器ID]
Aug 17 15:29:32 localhost dockerd[3112418]: time="2026-08-17T15:29:32.040091676+08:00" level=error msg="Container failed to exit within 10s of kill - trying direct SIGKILL" container=[容器ID] error="context deadline exceeded"
容器无法被正常停止(daemon 与 shim 协商失败):2s 后强杀、10s 后直接
SIGKILL,与 exec 全挂互为因果。
④ 运维处置
Aug 17 18:47:32 localhost systemctl[3324926]: [systemctl stop docker] called by PID 3324198 (-bash)
Aug 17 18:47:50 localhost systemd[1]: docker.service: Succeeded.
处置动作由 bash 会话手动发起(
-bash),符合「人工介入 → 版本对齐 → 重启恢复」的处置路径。
故障时间线
| 时间 | 事件 |
|---|---|
| 03:38 ~ 18:57 | kylin-kms-activation.service 崩溃循环,30s 一次、重启计数达 37,976(长期噪声,与本次无关,见「附带发现」) |
| 11:02 ~ 11:10 | 昇腾 NPU 推理任务运行(多进程),出现一过性 devmm Free page error(见「附带发现」) |
| 13:43 | 用户会话 systemd 实例启动,开始执行升级操作 |
| 13:49:39 | RPM 事务开始:erase docker-runc-1.0.0.rc3-222.ky10(Docker 运行中直接卸载旧 runc) |
| 13:51:27 | RPM 事务:install docker-runc-1.0.0.rc3-229.p02.ky10,Docker 引擎本体未升级 |
| 13:51:30 | 首个健康检查失败出现,此后 7 个容器陆续全部沦陷 |
| 13:54 / 13:58 | 磁盘上报 4 次 block_rq_complete ... [critical target error](升级 + 强杀 IO 高峰期的瞬时事件) |
| 14:00 ~ 18:45 | 健康检查失败风暴:14h 1,796 / 15h 1,793 / 16h 1,676 / 17h 1,422 / 18h 320 |
| 15:29:12 ~ 15:29:26 | 一次大规模容器重启尝试:docker-runc 进程自身连续报 exec failed: json: cannot unmarshal number ...,overlay 挂载批量卸载、容器被强杀 |
| 18:47:32 | 运维手动 systemctl stop docker(由 bash 会话发起) |
| 18:47:50 | docker.service 停止成功,进入处置 |
| 处置后 | 降级 docker-runc 回 -222 → 重启 docker.service → 健康检查转 green,业务恢复正常 |
根因定位
直接原因:runC 被单独升级,与 Docker 版本不配套
对比升级前后 RPM 状态,锁定唯一关键变更:
| 软件包 | 升级前 | 升级后 | 是否配套升级 |
|---|---|---|---|
docker-runc |
1.0.0.rc3-222 |
1.0.0.rc3-229 |
否 |
docker-engine(Docker 本体) |
旧版 | 旧版(未变) | — |
docker-runc 是麒麟发行版中 runc(OCI 容器运行时)的包名,被 Docker 作为依赖使用。本次它被一个系统级大规模升级事务顺带从 222 升到 229,而 Docker 引擎因仓库中无配套新版本而留在旧版,运行时组件与守护进程版本脱节。
机制:新旧 runc 之间进程状态格式不兼容
容器在升级之前已由旧 runc(222)的 shim 拉起并持有。升级后,任何 docker exec(健康检查、手动进入容器)都要与这些仍由旧 shim 管理的容器交互,此时由新 runc(229)读取进程状态文件,两者对 init_process_start(进程启动时间戳)字段的类型约定不一致:
- 状态文件中该字段为数字,而解析方(
CompatState结构体)期望字符串 →json: cannot unmarshal number ... of type string: unknown; - 15:29 日志中该错误直接由
docker-runc进程自身打出,进一步坐实这是 runc 层(而非应用层)的失败。
后果链:exec 全挂 → 健康检查全红 → 容器无法被正常 stop(daemon 与 shim 协商失败,只能 2s 后强杀、10s 后 SIGKILL)→ 每次重启后旧病复发,循环无法自愈。
为什么会「升级一半」
这是一次 yum/dnf 系统级升级事务,顺带安装了整套 GUI/内核/glibc/firefox/openjdk 等包。docker-runc 作为依赖被升级,但 Docker 引擎不在事务内、仓库也没有对应新版本,于是运行时组件先走一步,依赖链断裂。另外该事务在 Docker 运行中直接 erase 了正在被使用的旧 runc 二进制,属于高危操作。
处置流程
核心思路:停 Docker → 版本对齐 → 启动验证 → 根除复发。本次实际处置走「回退 runC」路径,恢复正常。
- 停 Docker,避免状态继续恶化(本次 18:47 执行):
systemctl stop docker
- 版本对齐 —— 回退 runC(本次采用):把
docker-runc降回与当前 Docker 配套的-222:
yum downgrade docker-runc-1.0.0.rc3-222.ky10
备选方案:若仓库已无旧包、或想一劳永逸,可改为升级 Docker 到与
-229配套的新版(yum update docker-engine)。回退与升级二选一,均需先查麒麟/厂商支持矩阵确认配套关系;本次因旧包可用、追求快速恢复,采用回退。
- 启动 Docker:
systemctl start docker
- 验证(逐项确认,缺一不可):
docker ps # 容器状态正常
docker exec -it [容器名] sh -c 'echo ok' # 对每个业务容器 exec 冒烟
docker inspect [容器名] | head # 状态可解析
# 观察 journalctl -u docker 持续数分钟,确认不再出现 CompatState / closed fifo 报错,
# 容器健康检查由 unhealthy 转 healthy
本次实际结果:降级 + 重启后,容器
exec正常、健康检查全部转 green、CompatState/closed fifo报错消失,业务恢复。
- 根除复发:在
yum/dnf配置中把容器运行时组件钉死,防止被系统级升级再次顺带升级:
# /etc/dnf/dnf.conf 或 /etc/yum.conf
exclude=docker* docker-runc* containerd*
附带发现
kylin-kms-activation.service崩溃循环:重启计数达 37,976、每 30s 一次、已持续约 13 天,属长期噪声,严重淹没真实故障信号。建议systemctl mask kylin-kms-activation(按需启用时再 unmask)。- 磁盘 4 次
critical target error(13:54 / 13:58):正值升级与容器强杀的高 IO 期,判断为瞬时异常;建议smartctl复查 SMART 状态,若持续出现再升级为硬件告警。 - 昇腾 NPU
devmm Free page error / Update heap error(11:09 时段):AI 推理任务释放 NPU 内存失败,一过性,与本次 Docker 故障无因果关系,建议 NPU 驱动侧跟进确认。 [推理容器名]容器:7 个受影响容器中疑似核心业务(NPU 推理),后续升级与演练需优先保障其恢复路径。
预防措施
- 版本配套原则:升级
runc / containerd / docker-runc必须与 Docker 一起升级,先对照厂商支持矩阵,禁止单独升级运行时组件; - 升级前停 Docker:禁止在 Docker 运行中 erase / 覆盖正在使用的 runc 二进制;变更前备份
/var/lib/docker相关状态; - 升级后冒烟:先
docker run --rm起一个最小容器验证 exec / 网络正常,再拉起业务; - 健康检查接告警:本次 7,200+ 次健康检查失败全程无告警,应接入 Docker 事件/健康检查监控,失败即告警;
- 清理长期噪声:处理 kylin-kms 崩溃循环,避免真实故障被淹没在刷屏日志中;
- 升级走变更流程:系统级 yum 升级属高风险变更,应在低峰执行并评估对运行中容器运行时的影响。