
在信创或者纯内网隔离项目里你大概率会遇到这个场景刚交付一台 openEuler 22.03 LTS (aarch64) 服务器机器本身干干净净安全策略要求不能连接外网但业务方又点名要求 Docker 跑在 26.1.3 这个版本上。系统自带 yum 源配了等于没配yum install docker想都不用想。这时候很多人第一反应是“那就下载个 tar 包解压呗”真正动手才发现离线安装 Docker 的难点根本不是 Docker 本身而是依赖解析、架构匹配、镜像导入这一整套链路。这篇文章我会把我在 openEuler 22.03 LTS aarch64 上离线安装 Docker 26.1.3 的完整过程写出来从在有网机器上准备安装包、处理 RPM 依赖到配置 daemon.json、启动服务再到离线镜像的导入和管理整个过程都附上我的操作命令和踩坑记录。如果你接下来要面对的是信创环境、党政内网、生产隔离区这类场景这篇文章可以直接当操作手册来用。1. 为什么离线装 Docker 会卡在这三件事上1.1 没有可用 yum 源一切依赖都得自己背很多人以为离线安装就是把 rpm 包拷过去rpm -ivh docker-ce.rpm就完事实际上 Docker 的 RPM 包有一堆依赖container-selinux、containerd.io、docker-ce-cli、docker-compose-plugin、docker-buildx-plugin有些环境还牵扯到libcgroup、slirp4netns、fuse-overlayfs。在联网环境下这些依赖会被 yum 自动拉取并安装但你一旦离线yum 源是空的缺一个依赖就装不上而且 rpm 报错信息非常直白——Requires: container-selinux 2.74缺什么就给你列什么。所以离线安装的第一步不是下载 Docker 本身而是把所有依赖看成一个整体一揽子打包带走。1.2 aarch64 架构带来的包下载陷阱这一条最容易翻车。很多人在 x86_64 的电脑上把 Docker 的 rpm 包下载好了传到 aarch64 的 openEuler 机器上结果 rpm 安装直接报“wrong architecture。Docker 官方仓库对所有软件包都有x86_64和aarch64两套编译产物下载时认准文件名里的aarch64字样千万别想当然。另外openEuler 22.03 LTS 的 aarch64 版本和 CentOS 7/8 的 aarch64 版本在 RPM 上是兼容的Docker 官方仓库中 CentOS 目录下的aarch64包可以在 openEuler 上直接使用这是社区验证过的路线也是我们这次离线安装的基础。1.3 openEuler 22.03 与 Docker RPM 包的兼容关系openEuler 22.03 LTS 是基于 RHEL 8 的兼容性设计官方文档也说明了这一点。而 Docker 官方仓库的centos目录下同时提供el7和el8两套包。实测下来在 openEuler 22.03 LTS (aarch64) 上选择el8架构的 Docker 26.1.3 包配合 openEuler 自带的依赖环境兼容性最好。这里我明确一下版本组合这是我在生产环境反复验证过的软件包版本说明docker-ce26.1.3核心守护进程docker-ce-cli26.1.3命令行工具必须与 docker-ce 同版本containerd.io1.6.24 或 1.7.x容器运行时建议用 1.7.x 稳定版docker-compose-plugin2.24.0compose 插件如果不用 compose 可不装docker-buildx-plugin0.13.0buildx 插件如果不用自定义构建可不装docker-ce-rootless-extras26.1.3无 root 运行模式支持按需安装container-selinux 2.74关键依赖openEuler 源里一般有依赖版本不对安装过程就可能卡在container-selinux上。后面我会专门讲怎么处理这个坑。2. 在有网机器上准备好一套完整的离线安装包2.1 用一台同架构机器做“搬运工”离线包必须在有外网环境下载而且要尽量用一台同样是 openEuler 22.03 LTS aarch64 的机器来下因为这样才能用 yum 的依赖解析能力把依赖关系完整算出来。如果手头只有 x86_64 的联网机器也能下载 rpm 包但依赖解析会不准因为你本地装的依赖和 openEuler aarch64 环境可能不一样。我建议优先找一台同架构的机器云上开一台 ARM 机器也行用完就释放成本不高但能省很多事。这台机器上需要确保能和目标机同样是 openEuler 22.03 LTS 或者至少是同类 aarch64 RPM 系统。2.2 配置 docker-ce 源并锁定 26.1.3 版本在这台联网机器上执行以下命令先把 Docker 官方仓库接入# 安装 yum 工具集 yum install -y yum-utils dnf-plugins-core # 添加 docker-ce 官方仓库 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo添加完成后docker-ce.repo文件里有多个不同版本的仓库比如docker-ce-stable、docker-ce-test、docker-ce-nightly默认启用的是docker-ce-stable。为了让后续下载时版本精确锁定到 26.1.3可以临时禁用其他仓库# 查看仓库列表 yum repolist # 只保留 stable 仓库 yum-config-manager --disable docker-ce-test yum-config-manager --disable docker-ce-nightly锁定版本直接用--downloadonly是不行的还需要把版本号写全。先查一下可用的具体版本yum list docker-ce --showduplicates | grep 26.1.3输出结果里会看到类似这样的一行docker-ce.aarch64 26.1.3-1.el8 docker-ce-stable记下这个完整版本号26.1.3-1.el8下一步下载时用完整版本号指定。2.3 用 repotrack 把依赖一次拉全普通情况下很多人会用yumdownloader docker-ce --downloadonly但yumdownloader只下载指定包本身不会自动拉依赖。要一次性把 Docker 及所有依赖都拉下来最好用repotrack命令来自yum-utils包或者dnf download --resolve。我的做法是这样的# 创建存放 rpm 包的目录 mkdir -p /root/docker-offline cd /root/docker-offline # 使用 dnf --resolve 下载 docker 及所有依赖 dnf download --resolve docker-ce-26.1.3-1.el8 docker-ce-cli-26.1.3-1.el8 containerd.io docker-compose-plugin docker-buildx-plugin docker-ce-rootless-extras--resolve参数会像正常安装一样解析所有依赖并下载这样得到的 rpm 包集合是最完整的。如果这台机器的源里恰好缺某个依赖比如缺了container-selinux那么dnf download会直接报错提示缺哪个包这时候你需要先配置好 EPEL 源或者 OpenEuler 对应版本的epol源再重试。下载完成后当前目录里会有至少七八个甚至十几个 rpm 文件这些就是要带到离线环境去的全部家当。2.4 下载后必须做的包列表核对这一步很多人会跳过但恰恰是最值得做的。打包前先在联网机器上把 rpm 文件清单列出来比对一下目标机上是否已经存在这些包避免重复或者漏带。我通常会执行rpm -qpR docker-ce-26.1.3-1.el8.aarch64.rpm | grep -E container-selinux|containerd.io|docker-ce-cli-qpR可以查看这个 rpm 包安装时需要的依赖配合目标机上的rpm -qa对比就能提前知道目标机缺哪些。这里还有一个容易踩的坑dnf download --resolve会把这台联网机器上已经安装过的某些包也下载下来但这些包不一定需要在目标机重新安装。比如libcgroup如果目标机已经存在就没必要再重复安装。为了稳妥起见我建议把下载好的 rpm 全部带到目标机后先用rpm -ivh *.rpm --test做一次 dry-run 检查让 rpm 自己判断哪些需要装、哪些已经存在。3. 离线安装执行rpm 安装顺序和依赖冲突处理3.1 上传离线包到目标机的正确姿势把打包好的 rpm 文件传到目标机有几种方式内网 scp、U盘拷贝、内部文件服务器拉取都行。我个人的习惯是先打包再传因为文件一多容易丢# 在联网机器上打包 tar czf docker-offline-rpm.tar.gz /root/docker-offline/然后传到目标机的/root/docker-offline/目录下解压mkdir -p /root/docker-offline tar xzf docker-offline-rpm.tar.gz -C /root/docker-offline/注意解压后检查一下 rpm 文件的架构确认每一个都是aarch64rpm -qip /root/docker-offline/docker-ce-26.1.3-1.el8.aarch64.rpm | grep Architecture预期输出是aarch64如果这里是x86_64说明下载错了赶紧回上一步重来。3.2 依赖缺失时怎么定位和补齐进入目标机后先不要急着rpm -ivh先做一次预安装检查cd /root/docker-offline rpm -ivh *.rpm --test--test只做依赖检查不实际安装。如果输出里出现error: Failed dependencies:后面会列出一堆xxx is needed by yyy。这时候别慌按下面的顺序排查第一步先看缺失依赖是不是已经存在于目标机系统中只是版本不达标rpm -qa | grep container-selinux rpm -qa | grep containerd.io第二步如果是版本不达标看看我们下载的 rpm 包里有没有更高版本ls *.rpm | grep container-selinux第三步如果下载目录里也没有那就在联网机器上把缺的依赖专门下载下来单独拷贝过来dnf download container-selinux这里最容易卡住的就是container-selinux 2.74。openEuler 22.03 LTS 的基础源里很可能没有这个包需要在联网机器上先安装 EPEL 源或者 openEuler EPOL 源再下载container-selinux。另外如果 Docker 26.1.3 要求的container-selinux版本太高而你下载到的只有旧版本可以考虑装一个低版本的 Docker 26.1.3 不行就换 26.1.2我的建议是先尝试升级container-selinux尽量不要为了绕过依赖去加--nodeps那是给自己埋雷。3.3 旧版本 Docker 如何平滑替换如果目标机上已经存在旧版本 Docker比如 19.03.8 或者 20.10.x建议先卸载干净再装新版。直接rpm -ivh会因为版本冲突报错。卸载旧版本的顺序有讲究先停服务再卸载# 停止 docker 服务 systemctl stop docker systemctl stop containerd # 删除旧版本包 rpm -qa | grep docker | xargs rpm -e --nodeps rpm -qa | grep containerd | xargs rpm -e --nodeps # 清理残留目录 rm -rf /var/lib/docker rm -rf /var/lib/containerd这里提醒一句rm -rf /var/lib/docker会把所有本地镜像和容器数据全部清掉。如果旧环境里还有需要保留的镜像一定要先docker save备份别急着删。3.4 安装结果的快速验证预检查没问题后就可以正式安装了。安装顺序建议让 rpm 自动解析直接一次性安装cd /root/docker-offline rpm -ihv *.rpm --replacepkgs如果依赖完整这一步应该能顺利跑完。安装完可以用rpm -qa | grep docker看看装上了哪些包预期看到docker-ce-26.1.3-1.el8.aarch64、docker-ce-cli-26.1.3-1.el8.aarch64、containerd.io-1.7.x。注意此时 docker 还没有启动docker version命令能正常输出版本号但不代表 docker 服务可用。要看服务状态先往下走配置这一步。4. 启动 Docker 前的配置daemon.json、cgroup 与防火墙4.1 daemon.json 里哪些配置是离线环境真正需要的Docker 安装完成后不要急着systemctl start docker先想清楚这个离线环境的实际需求。很多网上的教程会一股脑往里塞一堆镜像加速器地址但在纯内网环境里这些地址不仅没用反而会因为无法访问导致 docker 启动时去反复探测拖慢启动速度。我针对纯离线环境推荐的/etc/docker/daemon.json是这样的{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2, data-root: /var/lib/docker, iptables: true, ip-forward: true, live-restore: true }各项配置的解释exec-opts指定 cgroup 驱动为systemd。在 systemd 作为 init 进程的系统上这能避免容器内进程和系统资源统计出现偏差尤其是后续要接 Kubernetes 的话这个必须和 kubelet 保持一致。log-driver和log-opts限制单个容器日志文件大小到 100MB保留 3 个文件。日志爆盘是生产环境最常见的故障之一离线环境没人频繁清理提前限制很重要。storage-driveroverlay2是当前内核上的最佳性能选择openEuler 22.03 的默认内核完全支持。iptables允许 Docker 自动管理 iptables 规则实现容器网络端口映射。如果内网有特殊策略不建议关闭这个选项关掉以后端口映射会大面积失效。live-restore这个选项允许在 Docker 守护进程升级或重启时容器保持运行不停机。对于生产环境非常实用。如果你在内网有一个 harbor 镜像仓库比如registry.internal:5000那么还需要加上insecure-registries配置后面讲镜像导入时会展开。4.2 cgroup 驱动选 systemd 还是 cgroupfs这一节单独拿出来说是因为systemd和cgroupfs的选错会带来非常隐蔽的问题。系统在用 systemd 作为 init 进程时也接管了 cgroup 的挂载和管理。Docker 默认使用cgroupfs驱动这样就会有两个 cgroup 管理器同时存在。平时跑几个容器没感觉但只要系统的 CPU、内存压力上来两个管理器同时修改 cgroup 树就可能出现资源统计错乱或者容器莫名其妙被 OOM Kill。用native.cgroupdriversystemd让 Docker 走 systemd 的接口来管理 cgroup就能规避这个问题。配置好后可以用下面的命令验证docker info | grep Cgroup Driver预期输出Cgroup Driver: systemd这一步不仅对单独跑 Docker 有意义也是后面接 K8s 的基础建议不管是否接 K8s都按systemd配。4.3 firewalld 默认策略对容器端口映射的影响openEuler 22.03 默认会启用 firewalld而它的默认FORWARD策略是DROP。Docker 的端口映射依赖 iptables 的FORWARD链来转发流量如果FORWARD默认是DROP就会出现一个经典问题容器起来了端口也映射了但从宿主机外部访问不到容器端口。这个问题在 x86 环境一样存在但在 openEuler 系列的有些版本里尤其明显。处理方式有两种第一种直接关掉 firewalld内网环境建议前提是安全策略允许systemctl stop firewalld systemctl disable firewalld第二种保留 firewalld但调整转发策略。可以修改/etc/firewalld/direct.xml添加 Docker 所需的转发规则但配置比较繁琐不如直接关闭省心。如果安全策略要求 firewalld 必须开着那就单独针对docker0网桥放通 FORWARD 链。我在生产环境更倾向于用第一种因为容器场景里 Docker 管理的 iptables 规则已经很多了再加一层 firewalld 反而容易互相干扰。还有一个容易被忽略的点/etc/sysctl.conf里需要确认net.ipv4.ip_forward是 1。虽然 Docker 启动时会自动打开转发但 firewalld 重启或者系统重启后这个值可能被重置导致容器无法访问外网。建议显式配置echo net.ipv4.ip_forward 1 /etc/sysctl.conf sysctl -p4.4 用 systemctl 完成启动与开机自启配置好 daemon.json 之后开始真正的启动# 重新加载 systemd 配置 systemctl daemon-reload # 设置开机自启并立即启动 systemctl enable --now docker # 查看服务状态 systemctl status dockerdocker.service正常的话状态应该是active (running)。再用这些命令做二次确认# 查看 docker 服务端和客户端版本 docker version # 查看 docker 全局信息 docker infodocker version里如果Server部分有信息说明 daemon 已经正常启动了。如果只显示Client而没有Server说明客户端能执行但 daemon 没起来需要用journalctl -u docker --no-pager -n 50查看日志。5. Docker 装好只是开始离线镜像怎么进去5.1 最直接的 docker save/load 传包法Docker 装好之后最常见的任务是我需要在离线环境里跑一个 MySQL 8.0或者跑一个 Nginx但完全没有外网可以docker pull。这时候需要在一台有外网的机器上先把镜像拉下来打包成 tar 文件带到离线环境再导入。在有网机器上# 拉取镜像并指定版本 docker pull mysql:8.0 docker pull nginx:1.25-alpine # 将多个镜像打包到一个 tar 文件 docker save mysql:8.0 nginx:1.25-alpine -o images.tar # 也可以单个镜像打一个包 docker save mysql:8.0 -o mysql-8.0.tar传给离线目标机后docker load -i images.tar加载完成后用docker images确认镜像列表。注意三点第一docker save和docker export不是一回事。save保存的是镜像分层和元数据load加载后可以基于它docker run创建容器export导出的是容器文件系统快照导入后没有镜像的历史分层。别用混了。第二镜像的架构要和宿主机一致。在 aarch64 的机器上只能运行 aarch64 的镜像如果你只有 x86_64 的镜像在 ARM 机器上docker load能成功但docker run会报exec format error。在有网机器上下载镜像时可以用docker pull --platform linux/arm64指定架构。第三一次性docker save多个镜像到一个 tar 之前先确认这些镜像的 tag 在列表里的 REPOSITORY 和 TAG 列存在。如果本地有一些none的悬空镜像打包时不会把 tag 打进去。5.2 多节点环境用 registry 私有仓库更省事如果你的离线环境有多台机器每台都docker load一遍比较低效。更专业的做法是在内网起一个私有的 Docker Registry。先在有网环境把registry:2镜像拉下来并打包带进去docker pull registry:2 docker save registry:2 -o registry2.tar在离线环境其中一台机器上导入并启动docker load -i registry2.tar docker run -d --name registry \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ --restartalways \ registry:2为了让其他机器能通过 HTTP 访问这个私有仓库需要在每台客户机的/etc/docker/daemon.json里加上{ insecure-registries: [registry.internal:5000] }然后把 daemon.json 的地址换成实际内网地址重启 dockersystemctl restart docker接下来就可以把从外网导入的镜像重新打 tag 并推送到私有仓库docker tag mysql:8.0 registry.internal:5000/mysql:8.0 docker push registry.internal:5000/mysql:8.0其他机器只需要docker pull registry.internal:5000/mysql:8.0就能拉取。这种方案的优点是镜像只需要在一个节点维护适合稍具规模的集群环境。如果公司有现成的 Harbor那就更好了直接配置到registry-mirrors或者insecure-registries都行。5.3 离线镜像的版本锁定和瘦身建议离线环境最怕的是镜像版本漂移。今天在外面下载mysql:8.0得到的镜像和三个月后再下载mysql:8.0得到的镜像很可能完全不一样因为8.0这个 tag 是活的。所以离线环境下做镜像管理必须把镜像固定到不可变版本。方法有两种第一种是用docker image inspect拿到镜像的 Digestdocker inspect --format{{index .RepoDigests 0}} mysql:8.0输出类似mysqlsha256:xxxxx记录下这个 SHA256 值后续在别的机器验证镜像时用它比对确认一致性。第二种更简单粗暴不用8.0这种短 tag而是用8.0.36这种具体版本号去拉取packages 和镜像都锁定到具体版本号。镜像瘦身方面离线环境下仓库空间有限建议拉镜像时优先选择 alpine 版本的镜像比如nginx:1.25-alpine、redis:7-alpine体积通常是完整版的一半甚至更小。另外用docker image prune定期清理悬空镜像。6. 实测中的坑从 RPM 报错到容器网络不通6.1 container-selinux 版本冲突的完整排查过程这是我安装 Docker 26.1.3 时遇到的第一道坎。在目标机上执行rpm -ivh *.rpm --test输出里赫然一行error: Failed dependencies: container-selinux 2.74 is needed by docker-ce-26.1.3-1.el8.aarch64当时我联网机器的dnf download --resolve并没有把container-selinux拉下来因为那台联网机器上已经装了高版本的container-selinux所以依赖解析认为这个条件已满足就不重复下载了。但目标机是全新的并没有这个包。这个坑的本质是dnf download --resolve的依赖结论是基于“当前这台机器”的包环境算出来的不代表目标机的环境。解决办法是回到联网机器显式下载container-selinux同时把它的依赖也拉下来dnf download --resolve container-selinux然后把下载得到的container-selinuxrpm 也一并拷到目标机重新执行rpm -ihv *.rpm --test。这次依赖检查就能通过了。这里给一个建议离线安装的完整 rpm 包集合一定要在彻底干净的全新环境里验证过一遍不要只在自己有网机器上验证。推荐的做法是用一台全新安装的 openEuler 22.03 LTS aarch64 虚拟机做一次完整的离线安装演练把所有缺的依赖都补齐形成最终版本的包列表。6.2 启动失败且 journalctl 无有效日志时怎么办有一次我在目标机执行systemctl start docker等了半天状态还是inactive (dead)。journalctl -u docker --no-pager -n 50里只有几行加载配置的日志没有报错信息。这种“无声失败”最容易让人没头绪。后来排查发现问题出在/etc/docker/daemon.json的 JSON 格式上。我在配置exec-opts时把引号写成了中文全角引号JSON 解析直接失败Docker 启动时发现配置无效就默默地退出了。检查方法很简单docker daemon --validate或者dockerd --config-file /etc/docker/daemon.json前台运行 dockerd 会打印详细的启动日志发现问题后可以直接CtrlC退出修改。从那以后我的习惯是改完 daemon.json 先用python3 -m json.tool /etc/docker/daemon.json验证一下 JSON 语法再重启 docker。6.3 容器内 DNS 解析失败的根因Docker 装好、镜像也导入了跑一个nginx容器进去以后发现容器内curl http://some-service.internal报could not resolve host。在纯内网环境这个问题非常高频。定位思路是这样的先确认容器内/etc/resolv.conf的内容。Docker 默认会把宿主机的 DNS 配置复制到容器里如果宿主机/etc/resolv.conf指向的是内网 DNS10.0.0.2容器应该也能用。但有时候系统用了 systemd-resolved/etc/resolv.conf里指向的是127.0.0.53这个地址在容器网络命名空间里并不存在。解决方法是在 daemon.json 里显式指定 DNS{ dns: [10.0.0.2, 10.0.0.3], dns-search: [internal.domain] }然后systemctl restart docker这里要提醒一下修改 daemon.json 的 DNS 后重启 docker 会重启所有容器如果没有live-restore: true或者不是用--restartalways运行的容器重启后可能不会自动拉起。生产环境操作前务必先确认容器的重启策略。6.4 端口映射不通FORWARD 链和防火墙的联合排查容器跑起来了docker ps显示端口映射正常宿主机 curl 127.0.0.1:8080也能通但其他机器访问宿主机IP:8080就是不通。这类问题的排查链路比较固定按顺序走一遍基本能定位。第一步确认 Docker 端口映射本身没问题iptables -t nat -L -n | grep 8080如果能看到DNAT规则说明 Docker 自己生成的 iptables 规则是存在的。第二步检查FORWARD链默认策略iptables -L FORWARD -n | head -20如果看到默认策略是DROP且没有针对docker0的 ACCEPT 规则那问题就出现在这里。解决办法前面已经说过了systemctl stop firewalld systemctl disable firewalld是最直接的。第三步检查端口是否被其他进程占用以及目标机的防火墙安全组是否放行了这个端口。在纯内网环境有时候是网络设备或者安全组策略挡了流量不是主机层面的问题。排查完这三个层面99% 的端口映射问题都能找到答案。剩下的 1% 往往是网络设备上的策略路由或 VLAN 隔离那已经不是 Docker 层面能解决的问题了。最后说一点我的真实体会这套流程我在好几个内网项目里完整跑过每次最大的心得就是离线安装不值钱值钱的是把依赖搞明白、把版本锁住、把镜像管理好。哪怕你这次只需要在一台机器上装 Docker也建议把下载好的 rpm 包、镜像 tar、daemon.json、安装脚本整体归档到一个目录里内部组个仓库管理起来。以后再有新机器交付直接一键装上十分钟内就能看到docker info输出完整的系统信息。如果在实际安装过程中遇到什么 RPM 依赖或者容器网络的问题欢迎在评论区留下你的报错信息我看到了都会逐一回复。别的不敢保证这类坑我踩得是真不少。