Docker OCI Runtime 启动失败问题排查与解决

一、问题背景

在某次容器部署过程中,执行 docker run 命令后遇到如下错误:

typescript 复制代码
docker: Error response from daemon: OCI runtime start failed: listen unixgram /var/run/docker/runtime-runc/moby/1e9d6aa4461f4e8352307b75f0580e7edacaa60797c6add24bbb6a9582e0e21f/notify/notify.sock: bind: invalid argument: unknown.

该错误导致容器无法启动,且无论创建多少个新容器,均以相同原因失败。本文将详细分析该问题的根本原因,并提供完整的解决方案。

二、环境信息

2.1 软件版本

组件 版本
操作系统 Kylin V10 SP3
内核版本 4.19.90-89.26.v2401.ky10.x86_64
Docker 18.09.0(EulerVersion: 18.09.0.261)
containerd 1.2.0.213.p05.ky10(升级后:1.2.0-213.p09.ky10)
runc 1.1.12(commit: v1.1.12-0-g51d5e946)
Go 版本 go1.15.7
libseccomp 2.5.4
SELinux Disabled

2.2 系统环境检查命令

bash 复制代码
# 查看操作系统版本
cat /etc/os-release

# 查看内核版本
uname -r
# 输出:4.19.90-89.26.v2401.ky10.x86_64

# 查看 Docker 版本
docker version

# 查看 containerd 版本
containerd --version

# 查看 runc 版本
runc --version

# 查看 SELinux 状态
getenforce
# 输出:Disabled

# 查看 Docker 服务配置
systemctl cat docker

# 查看 Docker 日志(最后 100 行)
sudo journalctl -u docker -n 100 --no-pager

2.3 容器信息

  • 镜像地址ouma-registry.cn-hangzhou.cr.aliyuncs.com/jiangxi/ygj:ygj-gateway-jx-prod-2.0
  • 容器名称ygj-gateway
  • 端口映射-p 8080:8080
  • 环境变量PROFILE=prod

三、错误分析

3.1 错误信息解读

错误信息中的关键部分是 listen unixgram .../notify/notify.sock: bind: invalid argument。这表明 runc(OCI 容器运行时)在尝试创建一个 Unix 域套接字(Unix Domain Socket)时,bind() 系统调用返回了 EINVAL(无效参数)错误。

notify.sock 是 runc 用于与容器内 systemd 通信的套接字。Docker 在启动容器时,runc 会尝试在 /var/run/docker/runtime-runc/moby/<容器ID>/notify/ 目录下创建该套接字文件。

3.2 根本原因:Unix 域套接字路径长度限制

在 Unix/Linux 系统中,sockaddr_un 结构体中的 sun_path 字段最大长度为 108 字节 (包括结尾的空字符)。这是 POSIX 标准定义的限制,超过此限制会导致 bind()connect() 系统调用失败。

计算一下上述错误中的路径长度:

javascript 复制代码
/var/run/docker/runtime-runc/moby/ (31 字符)
+ 64 位容器 ID (64 字符)
+ /notify/notify.sock (17 字符)
= 112 字符

112 > 108 ,路径长度超限,导致 bind 失败。

3.3 验证:用 containerd 原生工具测试

为了确认问题是否出在 Docker 层,使用 containerd 的原生 CLI 工具 ctr 直接启动容器:

bash 复制代码
ctr run -d --net-host --env PROFILE=prod \
  ouma-registry.cn-hangzhou.cr.aliyuncs.com/jiangxi/ygj:ygj-gateway-jx-prod-2.0 \
  ygj-gateway

结果:容器成功启动,日志中显示应用正常运行。

这说明底层 runc 和内核均正常工作,问题被锁定在 Docker 18.09 的 notify.sock 创建逻辑 上。ctr 命令不创建 notify.sock,因此绕过了该问题。

四、解决方案

4.1 方案对比

方案 优点 缺点 推荐度
升级 Docker 到 20.10+ 根本解决,保留原生 Docker EulerOS 可能无官方包,升级有风险 ⭐⭐⭐⭐
安装 nerdctl 替代 Docker 语法 100% 兼容,绕开 Docker bug,安装简单 需额外安装工具 ⭐⭐⭐⭐⭐
使用 ctr 命令 无需装新工具 不支持 -p 端口映射,操作不便 ⭐⭐
缩短 exec-root 路径 不引入新组件 仅缓解,不保证彻底解决 ⭐⭐⭐

4.2 推荐方案:安装 nerdctl

nerdctl 是 containerd 官方出品的 Docker 兼容 CLI 工具,直接与 containerd 通信,绕过了 Docker daemon 的 notify.sock 创建逻辑。绝大多数 docker 命令都可以无缝替换为 nerdctl

安装步骤

步骤一:下载 nerdctl-full

推荐使用 full 版本,它包含了 containerd、runc、CNI 网络插件等所有必需组件。

bash 复制代码
cd /tmp
wget https://github.com/containerd/nerdctl/releases/download/v2.3.5/nerdctl-full-2.3.5-linux-amd64.tar.gz

注:nerdctl v2.3.5 兼容 containerd v1.7、v2.0、v2.1、v2.2 和 v2.3。

步骤二:解压安装

sql 复制代码
sudo tar Cxzvvf /usr/local nerdctl-full-2.3.5-linux-amd64.tar.gz

解压到 /usr/local 后,所有二进制文件(nerdctl、containerd、runc、buildctl 等)会自动安装到 /usr/local/bin

步骤三:验证安装

bash 复制代码
nerdctl --version
# 输出示例:nerdctl version 2.3.5

步骤四:配置 CNI 网络(如需要)

full 版本已包含 CNI 插件,但需确认配置文件存在:

bash 复制代码
sudo mkdir -p /etc/cni/net.d
sudo tee /etc/cni/net.d/nerdctl-bridge.conflist << 'EOF'
{
  "cniVersion": "1.0.0",
  "name": "bridge",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "nerdctl0",
      "isGateway": true,
      "ipMasq": true,
      "ipam": {
        "type": "host-local",
        "ranges": [[{"subnet": "10.89.0.0/24"}]],
        "routes": [{"dst": "0.0.0.0/0"}]
      }
    },
    {
      "type": "portmap",
      "capabilities": {"portMappings": true}
    }
  ]
}
EOF

4.3 启动容器

安装完成后,即可用 nerdctl 替代 docker 启动容器,命令语法完全一致

bash 复制代码
# 清理之前的残留
ctr container rm -f ygj-gateway 2>/dev/null
docker rm -f ygj-gateway 2>/dev/null

# 用 nerdctl 启动
nerdctl run -d \
  -e PROFILE=prod \
  --name ygj-gateway \
  -p 8080:8080 \
  ouma-registry.cn-hangzhou.cr.aliyuncs.com/jiangxi/ygj:ygj-gateway-jx-prod-2.0

4.4 验证容器运行

bash 复制代码
# 查看容器状态
nerdctl ps

# 查看日志,确认环境变量生效
nerdctl logs ygj-gateway

# 测试服务
curl http://localhost:8080/actuator/health

4.5 常用命令对照

功能 Docker 命令 nerdctl 命令
运行容器 docker run -d --name xxx -p 8080:8080 image nerdctl run -d --name xxx -p 8080:8080 image
查看容器 docker ps nerdctl ps
查看日志 docker logs xxx nerdctl logs xxx
停止容器 docker stop xxx nerdctl stop xxx
删除容器 docker rm xxx nerdctl rm xxx
查看镜像 docker images nerdctl images
拉取镜像 docker pull xxx nerdctl pull xxx
进入容器 docker exec -it xxx bash nerdctl exec -it xxx bash

五、技术原理深度解析

5.1 为什么 nerdctl 能解决问题?

Docker 18.09 在启动容器时,runc 会额外创建一个 notify.sock 套接字用于与容器内 systemd 通信。该套接字的路径由 Docker 硬编码生成,格式为:

bash 复制代码
/var/run/docker/runtime-runc/moby/<64位容器ID>/notify/notify.sock

当容器 ID 为 64 位十六进制字符串时,完整路径长度恰好超过 108 字符限制。

nerdctl 作为 containerd 的客户端,直接与 containerd 通信,不经过 Docker daemon,因此绕过了 Docker 创建 notify.sock 的逻辑。containerd 本身不创建这个套接字,所以不会触发路径长度限制。

5.2 为什么 ctr 可以但 docker 不行?

ctr 是 containerd 的原生 CLI,与 nerdctl 一样直接与 containerd 通信。它不创建 notify.sock,因此可以成功启动容器。

ctr 的局限在于不支持 -p 端口映射 ,只能使用 --net-host 让容器共享宿主机网络。而 nerdctl 完整支持 -p 参数,与 Docker 体验一致。

5.3 路径长度计算验证

bash 复制代码
# 获取容器 ID
CID="1e9d6aa4461f4e8352307b75f0580e7edacaa60797c6add24bbb6a9582e0e21f"

# 计算路径长度
echo -n "/var/run/docker/runtime-runc/moby/${CID}/notify/notify.sock" | wc -c
# 输出:112

六、总结

6.1 问题根因

Docker 18.09 在启动容器时创建的 notify.sock 套接字路径(112 字符)超过了 Linux 系统对 Unix 域套接字路径的 108 字符限制,导致 bind() 系统调用失败。

6.2 解决方案总结

安装 nerdctl-full 作为 Docker 的替代 CLI,直接与 containerd 通信,绕开 Docker 的缺陷。nerdctl 与 Docker 命令 100% 兼容,迁移成本极低。

6.3 经验教训

  1. Unix 域套接字路径限制:在容器化环境中,路径往往多层嵌套,容易超过 108 字符限制,需要特别注意。
  2. 老版本 Docker 的已知 Bug:Docker 18.09 及更早版本存在此问题,建议升级到 20.10+ 或使用 nerdctl 替代。
  3. containerd 生态日趋成熟:nerdctl 作为 containerd 的官方 CLI,已能很好地替代 Docker CLI,值得关注和采用。
  4. 问题定位方法论 :当 Docker 启动失败时,可用 ctr 命令绕过 Docker 层测试,快速判断问题在 Docker 侧还是底层运行时侧。

七、参考文献

相关推荐
XuCoder1 小时前
Redis 哨兵模式:它到底是怎么保证高可用的
后端
醇氧1 小时前
MVC、MVP、MVVM 架构详解、分析与对比
架构·mvc
lizhongxuan1 小时前
AI Software Architecture OS:给 AI 的“软件世界地图”
架构
IT小白杨2 小时前
2026年短视频平台风控技术解析:设备指纹体系、行为建模与环境隔离的边界在哪里
chrome·经验分享·架构·音视频·安全架构·指纹浏览器
深圳尚鼎2 小时前
芯片新架构大幅降低空间望远镜计算功耗及其与工业防潮柜的关系
架构
敲个大西瓜2 小时前
Springboot核心面试题
java·spring boot·后端
weixin199701080162 小时前
☁️《抖店API基础¥0.018/百次·增值¥0.05/百次:云内云外价差架构实战》(附Python源码)
开发语言·python·架构
运维行者_3 小时前
网络监控与ITSM集成:从告警到工单,实现运维自动化闭环
开发语言·网络·分布式·后端·架构·flask·php
子兮曰3 小时前
解剖 Claude Code:从入口架构逆向工程看 AI 编程工具的信任边界设计
前端·后端·claude