blog.navy82.icu / index / 05 ↑ 返回索引
#05 2026-08-17 故障分析 / Docker / runC / 版本不匹配 阅读 –

Docker 容器批量健康检查失败 · runC 版本不匹配

根因定位已降级恢复docker-runc 222→2297 容器受影响

一次系统级软件升级把 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
13:00 255
14:00 1796
15:00 1793
16:00 1676
17:00 1422
18:00 320

共 7,262 次 · 柱长为占峰值 1,796 的比例 · 自 13:51 runC 升级起持续,无自愈迹象。

CHART 02 // 日志流中断 · 按小时

closed-fifo stream errors by hour
13:00 510
14:00 3592
15:00 3599
16:00 3366
17:00 2859
18:00 608

共 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」路径,恢复正常。

  1. 停 Docker,避免状态继续恶化(本次 18:47 执行):
systemctl stop docker
  1. 版本对齐 —— 回退 runC(本次采用):把 docker-runc 降回与当前 Docker 配套的 -222:
yum downgrade docker-runc-1.0.0.rc3-222.ky10

备选方案:若仓库已无旧包、或想一劳永逸,可改为升级 Docker 到与 -229 配套的新版(yum update docker-engine)。回退与升级二选一,均需先查麒麟/厂商支持矩阵确认配套关系;本次因旧包可用、追求快速恢复,采用回退。

  1. 启动 Docker:
systemctl start docker
  1. 验证(逐项确认,缺一不可):
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 报错消失,业务恢复。

  1. 根除复发:在 yum/dnf 配置中把容器运行时组件钉死,防止被系统级升级再次顺带升级:
# /etc/dnf/dnf.conf 或 /etc/yum.conf
exclude=docker* docker-runc* containerd*

附带发现

  1. kylin-kms-activation.service 崩溃循环:重启计数达 37,976、每 30s 一次、已持续约 13 天,属长期噪声,严重淹没真实故障信号。建议 systemctl mask kylin-kms-activation(按需启用时再 unmask)。
  2. 磁盘 4 次 critical target error(13:54 / 13:58):正值升级与容器强杀的高 IO 期,判断为瞬时异常;建议 smartctl 复查 SMART 状态,若持续出现再升级为硬件告警。
  3. 昇腾 NPU devmm Free page error / Update heap error(11:09 时段):AI 推理任务释放 NPU 内存失败,一过性,与本次 Docker 故障无因果关系,建议 NPU 驱动侧跟进确认。
  4. [推理容器名] 容器:7 个受影响容器中疑似核心业务(NPU 推理),后续升级与演练需优先保障其恢复路径。

预防措施

  1. 版本配套原则:升级 runc / containerd / docker-runc 必须与 Docker 一起升级,先对照厂商支持矩阵,禁止单独升级运行时组件;
  2. 升级前停 Docker:禁止在 Docker 运行中 erase / 覆盖正在使用的 runc 二进制;变更前备份 /var/lib/docker 相关状态;
  3. 升级后冒烟:先 docker run --rm 起一个最小容器验证 exec / 网络正常,再拉起业务;
  4. 健康检查接告警:本次 7,200+ 次健康检查失败全程无告警,应接入 Docker 事件/健康检查监控,失败即告警;
  5. 清理长期噪声:处理 kylin-kms 崩溃循环,避免真实故障被淹没在刷屏日志中;
  6. 升级走变更流程:系统级 yum 升级属高风险变更,应在低峰执行并评估对运行中容器运行时的影响。