1. 环境准备与安装方案选型
1.1 为什么在欧拉/麒麟上装 Docker 要单独写一篇
先交代一个大多数新手容易踩坑的认知问题:OpenEuler 和麒麟V10 虽然都是 Linux,但它们跟 CentOS、Ubuntu 在用包管理器、内核参数、软件源策略上差别不小。尤其是 OpenEuler 24.03 SP4 属于较新的 LTS 版本,默认软件源里的容器相关组件做了调整,直接照搬 CentOS 7 那套yum install docker是装不上的——你大概率会遇到“没有可用软件包”或者版本对不上的问题。
我这次是在 X86_64 架构的物理机上分别装过 OpenEuler 24.03 SP4 和麒麟V10(SP1/SP2 都测过),把踩过的坑和对的方案整理成一篇可复现的实操记录。适合这几类读者:
- 公司信创项目要求迁移到欧拉或麒麟,需要跑容器化应用的运维和研发;
- 在 VMware 或 VirtualBox 里装好了欧拉/麒麟,想在虚拟机里搭 Docker 环境做学习验证的;
- 手头有离线内网服务器,想把 Docker 以及常用镜像一次性装好的实施人员。
文章覆盖的内容包含:在线安装、离线安装(rpm 包方式)、Docker 配置调优(镜像加速、存储路径、cgroup 驱动)、以及装完后我实测过的常见报错和解决思路。
1.2 安装前必须确认的三个环境指标
安装 Docker 前,我建议你先检查三件事:操作系统版本、架构、内核版本。命令如下:
# 查看系统版本 cat /etc/os-release # 查看内核版本 uname -r # 查看架构 uname -mOpenEuler 24.03 SP4 的输出里VERSION_ID="24.03",VERSION字段会带 SP4 字样。麒麟V10 则有两种发行版,一种是基于 CentOS 兼容的“银河麒麟高级服务器操作系统 V10”,另一种是基于 Ubuntu 的“银河麒麟桌面版 V10”,两者安装 Docker 的方式完全不同——本文以服务器版(本质是 RHEL 系)为主,桌面版(apt 系)反而可以直接参考 Ubuntu 的教程装。
为什么要先确认内核?因为 Docker 依赖内核的 namespace、cgroup、iptables 等特性。欧拉默认内核 5.10 以上,麒麟V10 服务器版默认内核 4.19,这两个内核都满足 Docker 20.10+ 的运行要求。唯一需要你注意的,是某些厂商定制内核(例如部分麒麟版本使用了华为的 openEuler 内核变体),个别内核模块可能被裁剪掉,后面我会提到怎么在报错时排查。
1.3 在线安装:dnf 源配置与安装命令
OpenEuler 默认的软件源里其实已经有 docker 相关的包了,只是包名叫docker或docker-engine,不叫docker-ce。欧拉不直接提供 Docker CE 官方源,所以在线安装最稳妥的方式是用欧拉自带的源装社区版,或者自己配好 EPOL 源后安装。
我推荐的操作路径是:先确认dnf源可用,再直接安装。
# 确认 dnf 源列表 dnf repolist # 安装 docker 及相关组件 dnf install -y docker docker-cli containerd这里有个细节:欧拉源里的docker包版本比较保守,但胜在经过了系统适配,稳定性有保障。如果你一定想装 Docker CE 最新版,可以考虑在欧拉上手动引入 Docker 官方针对 RHEL 的源(欧拉与 RHEL 二进制兼容,大部分情况下能直接用),不过我个人不太建议在生产环境这么干,因为官方源没有针对欧拉做过完整测试,依赖解析偶尔会出幺蛾子。
麒麟V10 服务器版在线安装的逻辑也类似,而且麒麟自带的源里通常已经收录了 docker 包:
# 麒麟V10 服务器版 yum install -y docker docker-cli containerd.io装完以后先别急着启动,先把配置调好再启动,否则默认配置很容易让你后面收拾烂摊子。
2. Docker 核心配置详解与关键参数说明
2.1 核心配置文件:daemon.json 逐项说明
Docker 的守护进程配置集中在/etc/docker/daemon.json,这个文件默认不存在,需要手动创建。很多教程只让你填一个镜像加速地址,但在欧拉/麒麟上,我建议你至少把下面这几项一起配好:
{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "storage-driver": "overlay2", "data-root": "/data/docker", "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ], "ipv6": false }逐项解释一下:
exec-opts:设置 cgroup 驱动为 systemd。这是最容易出问题的一项。欧拉和麒麟默认的 init 系统都是 systemd,如果 Docker 的 cgroup 驱动是 cgroupfs,而系统服务用的是 systemd,两边管理 cgroup 的方式不一致,会导致容器内资源限制失效,甚至在高负载时出现“内存无法回收”的诡异问题。统一成 systemd 后,Docker 和系统对 cgroup 的控制就一致了。
log-driver 和 log-opts:限制容器日志大小。默认情况下 Docker 不限制容器日志文件大小,一个长期运行的容器可能把磁盘写满。100m × 3 的意思是单文件最大 100MB,保留 3 个轮转文件,也就是单容器最多约 300MB 日志。这个数值可以根据业务调整,但建议一定要配,别问我怎么知道的——曾见过生产环境日志把数据盘直接顶爆的案例。
storage-driver:overlay2 是当前最主流的存储驱动,也是 Docker 默认推荐的。欧拉 5.10 内核、麒麟 4.19 内核都原生支持 overlay2,不需要额外加载模块。
># 加载 br_netfilter 模块 modprobe br_netfilter # 设置开机自动加载 echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf # 开启桥接流量经过 iptables cat > /etc/sysctl.d/docker.conf <<EOF 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/docker.conf 这一段命令在 CentOS 系系统的教程里经常被一笔带过,因为 CentOS 默认镜像里这些参数往往是开着的。但欧拉和麒麟为了安全加固,默认把 实操时建议把上面的代码存成脚本一次执行,因为遗漏任何一项都会造成网络异常。另外, 关于 cgroup 驱动,我再多展开一点。所谓 cgroup 驱动,是 Docker 用来管理容器资源限制(CPU、内存、PID 数量等)的系统接口方式。Linux 内核提供两套接口:cgroup v1 和 cgroup v2。欧拉 24.03 默认启用了 cgroup v2,麒麟V10 内核 4.19 则默认还是 v1。 Docker 在 cgroup v2 环境下,如果 cgroup 驱动还是 cgroupfs 而不是 systemd,会出现容器内的系统调用被拒绝的情况,比如 Nginx 无法启动、Java 进程报 “cgroup memory limit” 相关错误。我之前在欧拉 24.03 上就遇到过 MySQL 容器启动后立刻 OOM 被杀的问题,查了半天,最后定位就是 cgroup 驱动不一致导致的。 这就是为什么 验证当前 cgroup 驱动可以用这条命令: 如果输出里出现 假设你已经在欧拉或麒麟服务器上拿到了 root 权限或 sudo 权限,按下面的顺序操作: 第 6 步的 内网环境下没有外网源,不能在线安装,这是信创项目里最常见的场景。两种离线方案,我分别说怎么做。 方案一:在有网机器上把 rpm 包全部下载下来,再拷贝到内网。 在能联网的欧拉/麒麟机器上,执行: 把整个 这个方法依赖你“下载用的机器”和“目标机器”系统版本一致。欧拉的 rpm 包放到欧拉上装没问题,但是放到麒麟V10 上装可能会因为依赖库版本差异报错。跨系统复制 rpm 包我不是很推荐,除非你能确认两边 glibc 等基础库版本一致。 方案二:利用 Docker 官方静态二进制包离线部署。 这个方案更通用,适合跨发行版。从 Docker 官方 GitHub Releases 页面下载 具体流程: 静态二进制方案在欧拉和麒麟上都验证过能跑,推荐给离线部署、需要跨系统交付的场景。 装完 Docker 之后,除了 前三步通过,说明宿主机和容器的基础网络没问题;第四步通过,说明容器内 DNS 配置正确;第五步通过,说明 bridge 网络的多容器通信正常。如果卡在第三步,优先检查上一节的内核桥接参数是否生效;卡在第四步,优先检查镜像加速配置和 这个报错的含义是 Docker 守护进程在拉镜像时请求 docker.io 官方仓库失败。在欧拉/麒麟上,绝大多数原因是网络问题——不是你的机器不能上网,而是访问 Docker Hub 在部分网络环境下会被干扰或超时。 排查路径: 如果 如果加速地址显示正常但仍拉取超时,可以换一个可用加速地址。公共加速器经常变动,保持多个备选地址是个好习惯。 这个报错出现在你用 处理办法是检查系统里有没有 dockerd 和 docker 命令: 如果命令存在但服务文件缺失,用 3.2 节里面的 systemd 服务文件内容手动补上。如果命令都不存在,说明安装过程本身就出了问题,重新走一遍安装流程,确认最后的 这个报错在内核 4.19 的麒麟V10 上比较常见。根因是 Docker 在创建 iptables 规则时,系统的 nf_conntrack 模块表满,导致规则写入失败。换句话说,这台机器的连接跟踪表太小,并发稍微一大就写不进 NAT 规则了。 临时解决办法是扩大连接跟踪表的规模: 如果调大之后仍然频繁报错,考虑减少 Docker 容器数量,或者给宿主机增加内存。因为 nf_conntrack 表是哈希表,每条连接记录都占内存,太小的内存撑不起太大的表。 这个报错就是在 2.3 节提到的 cgroup 驱动不一致的典型症状。当 解决办法: 注意,修改 daemon.json 后用 我整理一份实际项目里出现频率最高的几个问题,方便你按图索骥: 在欧拉/麒麟这类信创服务器上排障时,我养成了一个习惯:先排除系统层问题,再排查 Docker 层问题。因为系统做了很多安全加固和内核参数调整,很多“看起来是 Docker 的问题”实际根源在宿主机。 比如容器时区不对、DNS 解析慢、文件句柄不足——这些 90% 都可以在 加了 我见过太多服务器,Docker 装在系统盘上,跑着跑着磁盘被容器日志和镜像打满,最后系统盘只读,连故障排查都做不了。所以如果你在规划阶段,有条件就给 Docker 单独挂一块数据盘,没条件至少在装完后尽快把 我在欧拉上实测过:配置了 很多刚从虚拟机过渡到容器的工程师会忽略资源限制。虚拟机有 vCPU 和内存限制撑着,容器没有限制的话可以直接把宿主机 CPU 和内存吃满,影响同一台上的其他容器。 我用 Docker 跑业务容器时,都会在 结合 2.1 节的 如果是多容器编排,直接装 装完以后用 拿一个最简单的例子说明 Compose 的实际价值。假设你要部署 Nginx + MySQL 两个服务,不写 Compose 文件的话,要执行两次 然后 内网或政务网环境里拉取 Docker Hub 镜像基本不可行,即使配置了加速器也可能被安全策略拦掉。这种情况下,在服务器上搭建一个私有镜像仓库是刚需。 轻量级方案是跑一个 registry 容器: 然后用 关于私有仓库加 HTTPS 证书、配置用户认证这些进阶内容,等有需要的时候可以再展开一篇专门讲,这里先给一个能跑的交付方案。 在欧拉和麒麟上装 Docker,难度其实并不高,真正折腾人的是那些和 CentOS 默认行为不一致的小差异。比如 我个人在几次信创项目交付中养成了一个固定套路:不管目标系统是欧拉还是麒麟,装 Docker 前先把内核参数检查一遍,装完立刻把 daemon.json 完整配置写好再启动,最后跑一遍网络通关测试。这样一套流程下来,几乎不会出幺蛾子。 再说一个很多人会忽略的小技巧:装完 Docker 后,顺手把 重新登录终端后,输入 最后提醒一句:欧拉和麒麟都在快速迭代中,不同版本的源和内核参数可能有细微差异,如果跟着这篇文章操作时遇到问题,优先检查系统版本和源是否匹配,再逐项对照 daemon.json 的内核参数,大概率能在十分钟内定位到问题。ip_forward关了,而且没有加载br_netfilter模块,这导致很多第一次在这类系统上装 Docker 的人卡在“容器网络不通”上。/etc/sysctl.d/docker.conf是新建文件,不会覆盖系统已有的 sysctl 配置,安全性上没问题。2.3 cgroup 驱动的选择逻辑
daemon.json里那一行"exec-opts": ["native.cgroupdriver=systemd"]如此重要。它让 Docker 使用 systemd 来管理 cgroup,与宿主机保持一致,从根源上避免这类诡异问题。docker info | grep -i cgroupCgroup Driver: systemd,说明配置正确。如果显示cgroupfs,先检查 daemon.json 是否生效,再确认 Docker 是否完全重启(systemctl restart docker而非systemctl reload docker,后者对某些配置不生效)。3. 从安装到验证的完整实操过程
3.1 在线安装完整命令序列
# Step 1: 更新系统(可选,但建议在安装前做一次) dnf update -y # Step 2: 安装 Docker(欧拉) dnf install -y docker docker-cli containerd # Step 2 备选: 安装 Docker(麒麟V10服务器版) # yum install -y docker docker-cli containerd.io # Step 3: 创建配置目录并写入 daemon.json mkdir -p /etc/docker cat > /etc/docker/daemon.json <<EOF { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "storage-driver": "overlay2", "data-root": "/data/docker", "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ], "ipv6": false } EOF # Step 4: 开启桥接网络相关内核参数 modprobe br_netfilter echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf cat > /etc/sysctl.d/docker.conf <<EOF 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/docker.conf # Step 5: 启动 Docker 并设置开机自启 systemctl enable --now docker # Step 6: 验证安装 docker version docker run hello-worlddocker run hello-world会从镜像仓库拉取一个很小的测试镜像,成功执行说明 Docker 守护进程正常运行,能正常拉取镜像,容器也能跑起来。3.2 离线安装:rpm 包方案与依赖处理
# 创建下载目录 mkdir -p /root/docker-rpms cd /root/docker-rpms # 下载 docker 及全部依赖(只下载不安装) dnf install --downloadonly --destdir=/root/docker-rpms docker docker-cli containerddocker-rpms目录用 U 盘或内网传输工具拷到目标服务器,然后安装:cd /root/docker-rpms dnf install -y ./*.rpmdocker-<版本>-x86_64-static-bin.tar.gz,然后手动解压和注册 systemd 服务。静态编译的二进制不依赖特定发行版的库,天然解决了兼容问题。# 假设已经下载好 docker-27.x.x.tgz 并上传到服务器 tar xzf docker-27.x.x.tgz cp docker/* /usr/bin/ # 创建 Docker systemd 服务文件 cat > /etc/systemd/system/docker.service <<'EOF' [Unit] Description=Docker Application Container Engine Documentation=https://docs.docker.com After=network-online.target firewalld.service containerd.service Wants=network-online.target [Service] Type=notify ExecStart=/usr/bin/dockerd ExecReload=/bin/kill -s HUP $MAINPID LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity TimeoutStartSec=0 Delegate=yes KillMode=process Restart=on-failure StartLimitBurst=3 StartLimitInterval=60s [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now docker3.3 验证 Docker 是否可用的“通关测试”
docker version能正常输出客户端和服务端信息外,我建议你再做一次完整的“通关测试”:# 1. 拉取测试镜像 docker pull hello-world # 2. 运行测试容器 docker run --rm hello-world # 3. 测试容器网络(能 ping 通外网说明桥接配置正确) docker run --rm alpine ping -c 3 223.5.5.5 # 4. 测试 DNS 解析(能解析域名说明 DNS 配置正确) docker run --rm alpine nslookup baidu.com # 5. 测试容器间通信 docker network create test-net docker run -d --name c1 --network test-net alpine sleep 300 docker run -it --rm --network test-net alpine sh # 在交互式 shell 里执行 ping c1,能通说明容器间网络正常/etc/docker/daemon.json里的 DNS 设置(可以在daemon.json加一段"dns": ["223.5.5.5", "8.8.8.8"]强制指定 DNS)。4. 从安装到配置的高频报错排查实录
4.1 报错:Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled
# 1. 检查 daemon.json 里的镜像加速配置是否生效 docker info | grep -A 5 "Registry Mirrors" # 2. 手动测试加速地址的连通性 curl -I https://docker.m.daocloud.iodocker info里面没有显示镜像加速地址,说明 daemon.json 没有被正确读取。常见原因是 JSON 格式错误——比如多了一个逗号、引号写成了中文引号。建议用docker run hello-world前先执行docker info看一下配置项全不全。4.2 报错:Failed to start docker.service: Unit docker.service not found
systemctl start docker时,系统提示找不到 Docker 的 systemd 服务单元,也就是服务文件。这个问题的根源通常是 Docker 安装不完整,或者用的是静态二进制手动部署但没有创建 service 文件。which dockerd which dockerdnf install没有任何报错。4.3 报错:iptables failed: iptables --wait -t nat -A DOCKER ... Resource temporarily unavailable
# 查看当前表大小 sysctl net.netfilter.nf_conntrack_max # 临时扩大 sysctl -w net.netfilter.nf_conntrack_max=131072 # 持久化配置 echo "net.netfilter.nf_conntrack_max=131072" >> /etc/sysctl.d/docker.conf sysctl -p /etc/sysctl.d/docker.conf4.4 报错:docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:281: applying cgroup configuration for process caused \"Unit docker.service not found\"**
daemon.json里没有配置native.cgroupdriver=systemd,且 Docker 检测到系统用的 systemd 时,init 进程创建容器时会出现这种混乱。# 1. 确认 daemon.json 内容 cat /etc/docker/daemon.json # 2. 改完配置后必须完全重启 Docker systemctl restart docker # 3. 再次查看 cgroup 驱动 docker info | grep -i cgroupsystemctl reload docker是不够的,有部分配置项必须在重启时才会被重新读取。这是我在实际使用中踩过的小坑,reload只是重新加载部分运行时配置,不会重新解析 daemon.json 里的所有项目。4.5 常见问题速查表
报错现象 根因 解决方向 容器启动后访问外网不通 br_netfilter 未加载或 ip_forward 未开启 modprobe br_netfilter + sysctl 配置 拉取镜像超时 镜像加速地址不可用 更换/增补 registry-mirrors 地址 容器被 OOM Kill,日志无明显异常 cgroup 驱动不一致 设置 native.cgroupdriver=systemd 并重启 iptables 规则写入失败 nf_conntrack 表满 调大 nf_conntrack_max service 启动失败找不到单元 动态二进制未创建 service 补全 /etc/systemd/system/docker.service 容器 root 用户创建的文件权限错乱 宿主机和容器 UID/GID 映射 使用 --user 指定容器运行用户 执行 docker 命令提示权限不足 当前用户不在 docker 组 usermod -aG docker 用户名,重新登录 4.6 排障的通用思路:别只盯着 Docker
daemon.json里预先规避。我建议装完 Docker 后直接把时区和限制也补上:{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "storage-driver": "overlay2", "data-root": "/data/docker", "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ], "ipv6": false, "dns": ["223.5.5.5", "8.8.8.8"] }dns这一项之后,容器内默认使用阿里 DNS 和谷歌 DNS,规避了系统自带的 systemd-resolved 在某些内网环境里的解析抖动问题。不过如果你所在的内网有自建的 DNS 解析服务(比如公司内部域名),这里应该替换成内网 DNS 地址,否则容器解析不到内部服务名。5. 深度使用建议与性能调优心得
5.1 为 Docker 单独划分数据盘
>systemctl stop docker mv /var/lib/docker /data/docker # 修改 daemon.json 里的># 启动容器时加上 --restart 参数 docker run -d --name myapp --restart=always myapp:latest # 给已有容器修改重启策略 docker update --restart=always myapp--restart=always的意思是:无论容器因何退出(包括 Docker 服务重启、宿主机重启、容器内进程崩溃),Docker 都会自动把它拉起来。这在生产环境里几乎是必须的配置,否则宿主机一重启,所有容器都处于 Exited 状态,需要人工介入。--restart=always的容器在宿主机重启后会自动恢复,未配置的则全部停留在退出状态。这条经验对运维来说能省不少事。5.3 配置容器资源限制:避免一台容器拖垮宿主机
docker run时加上资源限制参数:docker run -d \ --name myapp \ --memory=1g \ --memory-swap=1g \ --cpus=1.0 \ --restart=always \ myapp:latest--memory=1g限制容器最多使用 1GB 内存,--memory-swap=1g把 swap 也禁掉(避免容器内存超限后使用 swap 导致性能骤降),--cpus=1.0限制容器最多使用 1 个 CPU 核。这几个参数加完之后,即使容器内有内存泄漏,最多也只是把容器干掉,不会牵连宿主机上的其他服务。daemon.json里配置"native.cgroupdriver=systemd",容器资源限制在欧拉/麒麟上能够正确生效,这也是我前面反复强调 cgroup 驱动一致性的原因——如果不一致,就算你加了--memory参数,内核也不一定会严格执行。5.4 docker compose 安装与用法
docker-compose-plugin比单独装 python 版 compose 要省心得多。欧拉/麒麟的源里通常有现成的包:# 欧拉/麒麟 安装 compose 插件 dnf install -y docker-compose-plugindocker compose version验证。新版 Docker Compose 已经集成到 docker CLI 里,不再需要docker-compose命令前缀加横杠。docker run,还要手动创建网络、设置端口映射、配置环境变量。用 Compose 把这些都固化在一个docker-compose.yml文件里,后续启动销毁只需两条命令:services: web: image: nginx:latest container_name: nginx-web ports: - "80:80" restart: always mysql: image: mysql:8.0 container_name: mysql-db environment: - MYSQL_ROOT_PASSWORD=yourpassword volumes: - /data/mysql:/var/lib/mysql ports: - "3306:3306" restart: alwaysdocker compose up -d启动,docker compose down批量停掉。这种方式尤其在信创项目交付时好使——可以把整个部署过程向量化,避免在客户现场对着命令行手抖。5.5 镜像仓库选择与私有化方案
docker run -d \ --name registry \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ --restart=always \ registry:2docker tag和docker push把镜像推到私有仓库,其他机器通过docker pull <服务器IP>:5000/<镜像名>拉取。唯一的条件是:所有客户端机器的 Docker 必须配置insecure-registries才能用 HTTP 协议访问私有仓库:{ "insecure-registries": ["192.168.1.100:5000"] }6. 写在最后:一些实际的体会
br_netfilter内核模块默认没加载、ip_forward默认关闭、cgroup 驱动需要显式改成 systemd,这些点不提前处理,装完 Docker 之后几乎一定会遇到网络不通的诡异现象。docker命令的自动补全也配好。欧拉/麒麟的 bash-completion 包通常已经装了,只需要把 Docker 的补全脚本放到对应目录:dnf install -y bash-completion mkdir -p /etc/bash_completion.d curl -L https://raw.githubusercontent.com/docker/docker-ce/master/components/cli/contrib/completion/bash/docker -o /etc/bash_completion.d/dockerdocker run按 Tab 就能补全参数。现场排障时少打几个字符优势不大,但少记几个参数名就不会出错,这种细节积累多了,做事效率自然就上来了。