
拿到一台飞腾FT-2000/4处理器、麒麟V10 ARM版的机器我第一件事是装Docker。装的过程倒很顺利系统源里直接就有docker.io装完之后习惯性执行systemctl start docker结果屏幕上跳出一行经典红字Job for docker.service failed because the control process exited with error code.我一开始以为是系统源里的Docker版本太老换源、重装、卸载重来折腾了大半天问题纹丝不动。后来静下心一层一层查才发现根子根本不在安装包而在这套“国产系统ARM处理器”组合环境里Docker启动时要连过三关存储驱动、网络控制器、cgroup。这篇文章就围绕麒麟V10 ARM架构下Docker启动报错这个主题把完整的排查链路、根因分析和修复配置全部写出来已经踩过坑的朋友可以直接照着操作准备踩坑的可以先收藏。1. 麒麟V10 ARM环境里Docker启动翻车的典型背景1.1 麒麟V10的两种“血统”决定你该用哪套排查姿势很多人拿到麒麟V10就开始装软件却忽略了它其实有两条技术路线。简单说麒麟V10既有基于Debian/Ubuntu系的方向也有基于CentOS/RHEL系的方向两种系统的包管理方式、默认内核配置、iptables后端都有差异。所以排查Docker启动问题之前第一步不是去查各种报错而是先搞清楚你手上这台机器是哪条路线cat /etc/os-release uname -m uname -ros-release里能看到系统版本和IDuname -m确认处理器架构。ARM平台的输出通常是aarch64如果是x86_64那就不在本文讨论范围内。后面的uname -r是内核版本麒麟V10 ARM版常见的内核版本在4.19到5.10之间不同的内核版本会影响cgroup和overlay行为的默认表现。这一步看起来简单但非常重要。我见过不少人用Ubuntu的思维去处理基于CentOS的麒麟V10比如用apt装包发现没有或者反过来明明是基于Debian的版本却去添加CentOS的Docker源最后装出来的架构都对不上。先认清“血统”后面所有排查手段才能对上号。1.2 Docker启动时到底要过哪些“关卡”很多人把Docker启动理解为执行一个二进制其实dockerd启动时要做的事情远比想象中多。你可以把启动一个Docker守护进程类比成开一家餐厅要先检查厨房设备能不能用存储驱动要开通点餐系统网络控制器还要确保消防通道畅通cgroup挂载。任何一个环节不过关整个服务就起不来。具体来说dockerd启动时会依次做这些事情加载存储驱动并挂载/var/lib/docker下的overlay文件系统初始化网络栈创建docker0网桥并写入iptables规则挂载cgroup文件系统并注册容器运行时。在这个过程里存储驱动对内核的overlay模块有依赖网络初始化对iptables命令和内核netfilter模块有依赖cgroup初始化则对内核的cgroup挂载方式有依赖。在通用的x86 Linux发行版上这些依赖大多已经提前安装好、配置好所以很少出问题。但麒麟V10的ARM版通常跑在飞腾、鲲鹏这类处理器上厂商会根据硬件平台对外围内核模块做定制和裁剪部分模块不会默认加载。这就导致Docker在启动期特别容易“暴雷”而且报错往往不是一句“安装失败”而是在服务启动阶段直接红字退出。1.3 从热搜词看大家普遍卡在哪一步我搜了一下和这个话题相关的热搜词发现大家遇到的问题高度集中麒麟V10 ARM架构 Docker 启动报错、docker安装mysql启动服务报错、docker镜像下载慢、麒麟v10添加永久默认路由。这说明同一个环境里Docker启动失败之后还会跟着衍生出镜像下载慢、容器跑不起来、网络配置死活不生效等一系列连锁问题。比较讽刺的是不管热搜词怎么变核心原因就那么几个。你如果把dockerd启动要做的三件事逐一验证绝大多数问题都能定位到具体环节。接下来我就按照实际排查的顺序把我整理的最稳妥的链路写出来。2. Docker启动报错的完整排查链路从systemd状态到内核日志2.1 先别急着重装systemctl status告诉你的第一层信息面对Job for docker.service failed第一反应最好是打开systemd给我们的信息入口而不是直接卸载重装。执行systemctl status docker --no-pager journalctl -u docker --no-pager -n 100第一条命令只能看到一个“服务启动失败”的通用结论真正的关键信息在第二条命令里。journalctl输出的末尾几行往往就是dockerd启动时真正卡住的位置。常见的有三类Error initializing network controller... iptables failed说明问题在网络初始化环节。error initializing storage driver... failed to mount overlay说明问题在存储驱动环节。Devices cgroup isnt mounted说明问题在cgroup挂载环节。看到这些关键字心里就有数了。不要急着改配置先把日志完整截下来因为后面每一步修复后都要拿日志做对比验证。这里有个我自己的小习惯每次排查都会把journalctl -u docker --no-pager -n 200的输出重定向到一个文件里方便回头翻看和对比不会越改越乱。2.2 用 dockerd --debug 把真正报错逼出来systemd日志给的是“浓缩版”错误有时候信息量不够尤其是涉及网络规则写入失败或存储驱动细节时。更直接的办法是停掉服务让dockerd在前台跑加--debug参数看到完整日志systemctl stop docker dockerd --debug这个命令会把启动过程中的每一步都打出来包括加载存储驱动、创建网桥、写iptables规则、挂载cgroup等。如果启动失败你会看到一条非常详细的错误栈通常会在栈顶的Error行明确指出失败点。这里要提醒一句dockerd --debug会占用当前终端如果它正常启动不退出说明当前环境其实能跑起来问题出在systemd的启动参数或环境变量上。如果它依然报错退出那问题一定在Docker本身依赖的某个底层组件上。这两种情况后续处理方式完全不同所以这条命令判断起来非常高效。2.3 内核模块与转发参数启动失败的“隐形元凶”排查到这一步如果错误指向存储或网络那大概率是内核模块没加载。Docker正常工作依赖几个关键内核模块overlay存储驱动、br_netfilter网桥过滤、iptable_natNAT规则、nf_nat网络地址转换。在ARM架构的定制内核上这些模块经常不随系统启动自动加载。检查方法lsmod | grep -E overlay|br_netfilter|iptable_nat|nf_nat如果输出为空手动加载模块modprobe overlay modprobe br_netfilter modprobe iptable_nat加载成功后再检查lsmod确认。之后还需要确认内核转发参数Docker跨容器通信和端口映射依赖IP转发sysctl net.ipv4.ip_forward sysctl net.bridge.bridge-nf-call-iptables如果ip_forward返回0或bridge-nf-call-iptables报“No such file or directory”说明模块没加载或参数没设置。通常做法是写进sysctl配置cat EOF /etc/sysctl.d/99-docker.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl -p /etc/sysctl.d/99-docker.conf这里有个顺序问题必须先加载br_netfilter模块再执行sysctl设置bridge-nf-call-iptables否则文件读不到对应参数。这个坑我踩过不止一次顺序反了之后内核参数怎么都写不进去。2.4 顺便检查目录权限与daemon.json语法排除了内核模块和转发参数后还有一个很容易被忽略的环节Docker的数据目录权限和配置语法。默认数据目录是/var/lib/docker如果这个目录的权限不对启动时也会直接失败。检查一下ls -ld /var/lib/docker正常情况下这个目录应该是root:root并且权限是drwx--x--x。如果你之前用非root用户操作过这个目录或者resize过系统盘导致目录归属乱了可能就会出现权限问题。最直接的办法是chown -R root:root /var/lib/docker修复归属。同时检查/etc/docker/daemon.json。这个文件只要JSON语法出错Docker启动时会直接拒绝加载配置。用python3 -m json.tool /etc/docker/daemon.json或者jq . /etc/docker/daemon.json验证语法比人眼盯靠谱得多。我在实际项目里还遇到过一种情况daemon.json里同时指定了storage-driver: overlay2和data-root: /mnt/docker但/mnt/docker所在分区是xfs且没开ftype导致启动失败。所以“配置语法没问题”不代表“配置没问题”后面我会详细展开。3. 三类高发启动报错的根因拆解与修复iptables、存储驱动、cgroup3.1 iptables failed麒麟里最容易被误判的网络问题先看journalctl里最常见的报错形态failed to start daemon: Error initializing network controller: error creating default bridge network: iptables failed: iptables --wait -t nat -A DOCKER -j RETURN: iptables: Unknown error 2看到iptables failed很多人的第一反应是“系统iptables被禁了”然后上网搜出各种关闭iptables的教程。先别急着关这个问题的本质不是iptables不能用而是Docker调用的iptables命令和后端不兼容。麒麟V10如果是基于CentOS系的方向默认的iptables往往走的是nftables后端也就是iptables-nft。这种模式通过nf_tables内核接口实现iptables兼容层。但Docker对iptables的调用逻辑比较老派期望直接操作legacy netfilter接口两边一碰撞就容易出“Unknown error”。尤其是在新内核和老版本Docker搭配时这种问题非常常见。排查方法iptables -L -n iptables -t nat -L -n update-alternatives --display iptables ls -l /usr/sbin/iptables看到/usr/sbin/iptables指向xtables-nft-multi说明系统在走nftables后端。解决办法是切回legacyupdate-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy如果系统里没有iptables-legacy用包管理安装即可。Ubuntu/Debian系执行apt install iptables-legacyCentOS/RHEL系则安装iptables-services。切换完成后重新跑iptables -L -n确认能正常列出规则再启动Docker。有一种情况要说明如果你启用着firewalld它的nftables规则可能会和Docker写入的iptables规则冲突。安全起见在测试环境可以先systemctl stop firewalld systemctl disable firewalld再试Docker。如果问题消失就在firewalld里放行Docker需要的网段而不是一直关着防火墙。这里我不推荐通过daemon.json里设置iptables: false来绕过问题。这种配置虽然能让Docker启动但容器的端口映射和跨容器通信都会被影响等于把Docker的对外网络能力废了治标不治本。3.2 overlay/overlay2挂载失败xfs与数据目录的恩怨存储驱动报错在ARM架构上也很常见典型形态是error initializing storage driver: failed to mount overlay: invalid argument或者更简短的failed to start daemon: error initializing graphdriver: driver not supported这个问题的根因通常有两个一个是真的没有overlay内核模块另一个是底层文件系统不支持overlay2。模块问题前面讲过用modprobe overlay就能解决。文件系统问题需要你重点检查数据分区。Docker的overlay2驱动挂在底层文件系统上需要底层文件系统支持d_type特性。对xfs而言这要求格式化时开启ftype1如果格式化时没开overlay2就会挂载失败并提示invalid argument。ext4通常默认支持所以问题更多出现在xfs上。检查底层文件系统类型和ftypedf -T /var/lib/docker xfs_info /path/to/partition比如你的/var/lib/docker在根分区/下就执行xfs_info /。输出中如果看到ftype0基本就是overlay2挂载失败的原因。解决办法很简单把Docker数据目录放到一个支持的ext4分区上。先准备一块数据盘并格式化为ext4挂载到/data然后在daemon.json里修改{ data-root: /data/docker }最后systemctl restart docker用docker info确认Storage Driver: overlay2。如果你的机器没有独立数据盘也可以把现有分区改成ext4但这通常需要重新挂载或格式化数据和系统都会受影响工程上不太推荐。更稳妥的方案是加一块盘。网上有些办法说把内核参数改成overlay而不是overlay2在某些老内核上有用但新内核里两者差异不大性能也不一样不建议在这个方向上绕太久。3.3 cgroup版本不匹配老Docker碰到新内核的经典翻车第三种高发问题集中在cgroup上。ARM平台的麒麟V10如果内核是5.x版本系统很可能默认挂载cgroup v2。而老版本Docker18.x、19.x对cgroup v2的支持并不好启动时会出现failed to start daemon: Devices cgroup isnt mounted或者类似“cgroup manager”相关的报错。判断系统当前用的cgroup版本stat -fc %T /sys/fs/cgroup/输出cgroup2fs就是v2如果是tmpfs那就继续看有没有/sys/fs/cgroup/cgroup.controllers文件存在也说明是v2。另外docker info里会显示Cgroup Driver: systemd或cgroupfs。这个问题有两个解决方向我更推荐第一个升级Docker到20.10以上版本。新版Docker对cgroup v2的支持已经很完善。升级方式取决于系统“血统”Debian系可以添加Docker官方apt源安装docker-ceCentOS系用dnf config-manager --add-repo添加官方yum源再dnf install docker-ce。ARM aarch64架构的包官方源里都有放心用。第二个方向是强制系统回到cgroup v1。编辑/etc/default/grub在GRUB_CMDLINE_LINUX里追加systemd.unified_cgroup_hierarchy0然后重新生成grub配置并重启grub2-mkconfig -o /boot/grub2/grub.cfg # 或者 update-grub reboot重启后确认stat -fc %T /sys/fs/cgroup/变成tmpfs且没有cgroup.controllers文件再启动Docker。这个方法对老Docker有效但相当于把系统的控制器降级了新内核的很多特性用不上从长远看不如升级Docker。还有一个容易被忽略的细节daemon.json里可以指定exec-opts: [native.cgroupdriversystemd]让Docker和systemd使用的cgroup驱动保持一致。如果你在用Kubernetes之类的编排系统cgroup driver不一致会直接导致节点异常这个设置要提前想清楚。4. ARM架构特有的坑镜像架构、数据目录与安全策略4.1 exec format errorARM机器拉错镜像Docker服务起来之后紧接着就会碰上一个ARM架构特有的问题容器启动失败日志里出现standard_init_linux.go:228: exec user process caused: exec format error这个报错的含义非常直白你拉下来运行的镜像是x86_64架构但当前系统是aarch64CPU执行不了x86指令。很多人会疑惑“我不是已经从Docker Hub拉的最新镜像吗怎么会拉错架构”其实Docker Hub上的很多官方镜像都有多架构manifestDocker会自动匹配当前平台的架构。但如果你指定了某个特定tag而那个tag对应的manifest里没有arm64版本或者你手动执行了docker pull --platform linux/amd64 xxx就会拿到错误架构的镜像。排查方法uname -m docker manifest inspect mysql:8.0 | grep -E architecture|os正常情况下uname -m输出aarch64而docker manifest inspect应该能看到architecture: arm64的条目。如果拉错架构重新拉取时显式指定平台docker pull --platform linux/arm64 mysql:8.0这里顺便提一下热搜词里还有“tomcat arm架构”说明很多人想在麒麟V10上跑Tomcat容器。Tomcat官方镜像同样有arm64版本直接用docker pull tomcat:9.0-jdk8-temurin这类多架构tag即可不用自己折腾交叉编译。只有一些小众镜像或者企业内部老镜像只发布了x86版这种才需要用源码重新构建arm64镜像。4.2 ARM架构下的性能与日志配置建议ARM平台跑Docker有一种感受特别明显镜像拉取和容器启动比x86机器更容易有延迟。一方面是ARM生态的镜像构建质量参差不齐另一方面是部分镜像没有针对ARM做优化容器内应用的运行效率不如x86。因此在配置daemon.json时建议统一加上日志大小限制避免容器日志无限膨胀把系统盘挤爆。一个比较实用的daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://docker.1panel.live ], data-root: /data/docker, exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }其中registry-mirrors里的加速地址用于缓解“docker镜像下载慢”的问题。需要注意公开加速地址经常变化可能今天能用明天就失效了所以配置后一定要验证docker info | grep -A5 Registry Mirrors如果加速器不生效就换当前可用的公开地址或者使用你所在云厂商提供的容器镜像服务加速域名。ARM平台的镜像虽然体积通常比x86略小但网络下载环节一样吃带宽加速器配置好能省很多时间。4.3 麒麟安全模块对Docker的次生影响启动Docker成功后有时容器能创建但宿主机上通过端口访问容器服务时不生效或者容器内写文件时提示权限不足。这种问题在麒麟V10上还多了一个变量系统安全模块。麒麟V10的部分版本默认开了SELinux有的还带自研安全模块。SELinux状态下Docker容器进程需要被赋予正确的安全上下文否则对文件系统的操作会被拒绝。最直接的验证方法是临时关闭SELinuxsetenforce 0然后在DAEMON未启动或已启动状态下重启Docker再试容器端口映射。如果关闭后一切正常说明问题确实和安全策略相关。这时不要想着长期关闭SELinux生产环境不现实。正确做法是把Docker需要的端口和目录添加到SELinux策略或者给容器挂载卷时加上正确的标签。Docker的-v挂载本地目录到容器时如果宿主机目录的SELinux类型不对容器内可能读不了可以临时用:z后缀自动设置标签docker run -v /data/mysql:/var/lib/mysql:z ...这条命令会为挂载卷设置SELinux上下文适合非生产环境快速验证。生产环境还是建议按你所在企业的安全基线配置策略。4.4 镜像下载慢别只怪网络先看架构和并发“docker镜像下载慢”几乎人手一个热搜但在ARM平台上有时候不只是加速器的问题。同样是拉一个镜像如果本地没缓存Docker会拉取整个多架构manifest下的指定平台层。有些镜像的ARM层体积虽然和x86差不多但部分层比较大比如MySQL、Java环境的镜像动不动几百MB甚至上GB。在这种机器上网络带宽一旦被占满其他容器和服务的网络延迟就会明显升高。建议下载大镜像时错峰操作或者用docker pull后台执行。另外如果同一时间要拉多个镜像可以设置Docker的并发下载数比如daemon.json里加{ max-concurrent-downloads: 3 }需要注意这里不是把数字改得越大越好并发太高容易把带宽占满反而不稳定。实际使用下来3到5之间的值比较稳妥。5. 启动成功后的固化操作模块、自启、镜像加速与MySQL实测5.1 把模块和系统参数固化避免重启后再次翻车Docker服务启动成功千万不能以为就完事了。如果你前面的排查过程中手动modprobe加载了模块、手工写了sysctl参数这些在系统重启后会全部失效。我之前就吃过这个亏现场调试好了重启机器后又翻车白白多跑一趟。把模块固化成系统启动加载cat EOF /etc/modules-load.d/docker.conf overlay br_netfilter iptable_nat EOF把sysctl参数也固化cat EOF /etc/sysctl.d/99-docker.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF这两步做完重启机器后模块和参数会自动加载。然后设置Docker开机自启systemctl enable docker systemctl daemon-reload还有一个不值得踩的坑很多人启动成功之后发现systemctl restart docker后之前创建的容器全部变成Exited状态。这很正常Docker服务重启不会自动拉起已有容器。除非在创建容器时加了--restart策略。如果你希望容器在Docker重启后自动恢复创建容器时带上--restartalways这个参数在ARM平台、x86平台都一样有效。5.2 用MySQL容器做一次完整验收热搜词里有一条“docker安装mysql启动服务报错”说明很多人把Docker跑起来后的第一件事就是装MySQL。这个操作在ARM平台上能不能成功很大程度上取决于镜像架构和目录权限。我给你一套可以直接抄的命令docker pull mysql:8.0 mkdir -p /data/mysql chown -R 999:999 /data/m