离线部署Docker实战:x86_64内网环境从依赖到镜像的全流程

发布时间:2026/10/8 23:57:15
离线部署Docker实战:x86_64内网环境从依赖到镜像的全流程
上个月处理一台内网服务器戴尔 PowerEdge R740双路至强CentOS 7.9放在隔离网段里只能通过跳板机访问。业务方把容器化后的服务包丢过来说得很轻松“帮我把 Docker 环境整起来。”我当时也天真以为拷几个 rpm 上去rpm -ivh就完事结果一路撞见依赖缺失、iptables 版本不匹配、SELinux 拦截各种问题最后花了大半天才把环境稳定起来。所以这篇东西本质上是给准备在 x86 平台做离线 Docker 部署的朋友留一份实操记录能少踩一个坑算一个。标题里的“x86 版本环境”其实是个很宽泛的说法。现在大部分服务器不管是 Intel 还是 AMD跑的系统都是 x86_64 位离线安装 Docker 的难点反而不在 CPU 架构而在你怎么在没有外网的情况下把安装包、依赖、镜像这三样东西都凑齐。1. 为什么离线安装 Docker 是常态而不是个例1.1 真实的内网部署场景离线装 Docker 的场景比大部分人想象得多。生产环境里很多业务系统和数据库跑在隔离网段不能直接上外网金融、政务、企事业单位的自建机房网络策略一条条卡得很死还有一部分是灾备环境平时根本没有出网权限。这时候要在服务器上跑容器唯一的办法就是把安装介质准备好再手工搬到目标机器上。另外有一类场景容易被忽略目标机器能上网但源站网络不稳定yum install或apt install拉到一半就断开反复重试浪费时间。干脆在有外网的另一台机器上把包全部拉下来一次性拷贝过去反而更可控。1.2 离线安装 Docker 不是“拷个包”那么简单很多人把离线装 Docker 理解成“下载一个 rpm 或 deb拷过去装上就行”。实际动起手来你会发现一个完整的 Docker 运行环境至少包含三层内容安装包本体服务端dockerd、客户端docker、容器运行时containerd、runc等核心二进制。依赖项libltdl、iptables 相关工具、device-mapper、SELinux 策略包等。这些依赖在联网机器上会被包管理器自动拉取但在离线环境下不会凭空出现。镜像数据装好 Docker 之后你还需要业务镜像镜像得通过docker save/load或内网仓库搬进去这一步很多人会漏掉。搞清楚这三层后面的思路就顺了。2. 动手前的系统勘察架构、发行版和磁盘空间2.1 先确认到底是不是 x86_64离线安装最关键的一件事就是别拿错架构的包。虽然现在绝大多数服务器都是 x86_64但偶尔还能遇到 i686 或 arm64 的机器确认方法很简单uname -m输出x86_64那就放心去下载 amd64 架构的安装包如果输出aarch64那就是 ARM 平台后续整个部署方案完全不同下面的内容就不适用了。还可以再用lscpu看一遍 CPU 型号和指令集确认无误。注意Docker 官方二进制包的文件名里一般会带上架构标记比如linux-amd64。下载时一定要认准这个标记别因为机器能跑就随手拿了一个 arm64 的包等到exec format error报出来再回头折腾就浪费时间了。2.2 判断发行版和包管理体系同样标题里的“x86 环境”实际装上什么系统千差万别。最常见的是 CentOS / RHEL 7.9、Ubuntu 20.04 / 22.04还有一批国产 Linux 发行版逐渐在政企内网里普及。判断方法很直接cat /etc/os-release核心看两点ID字段和ID_LIKE字段。如果是centos、rhel、fedora走 rpm 包或二进制包路线如果是ubuntu、debian走 deb 包或二进制包路线。国产发行版大多数兼容其中一种比如 openEuler 系接近 rpm统信 UOS 的服务器版能兼容 deb麒麟系一般也基于 rpm 体系。拿不准的时候先在系统里执行rpm -q rpm或dpkg --version看哪个命令存在就能判断包管理体系。还需要看两个关键内核参数uname -r cat /sys/module/overlay/parameters/version 2/dev/nullDocker 默认存储驱动是overlay2内核至少要 3.18 以上才支持得比较好。CentOS 7.9 的内核是 3.10 系列官方在特定版本以后做了兼容处理但新版本 Docker 在旧内核上有时会遇到 overlay 相关的稳健性问题。如果确认内核太老或者 overlay 模块加载不了可以在/etc/docker/daemon.json里把存储驱动改成vfs但代价是镜像层会占用大量磁盘空间我一般不推荐除非实在没有别的办法。2.3 磁盘空间和目录规划Docker 的默认数据目录是/var/lib/docker镜像、容器、数据卷都落在这里。离线部署前先看磁盘df -h /var/lib/docker /opt给/var/lib/docker留出的空间按镜像总量的 1.5 到 2 倍来预估比较安全因为镜像层、容器读写层、日志文件都会继续膨胀。如果你把/和/var放在同一个分区那么直接看根分区剩余空间即可如果/var/lib/docker是独立挂载点就要单独评估。部署包建议放在单独目录mkdir -p /opt/dockeroffline/pkg mkdir -p /opt/dockeroffline/images目录建好不是为了好看离线部署过程中你会反复来回拷贝包、校验、解压固定一个路径能让你少犯“明明刚才放了这个文件怎么找不到了”的糊涂错。3. 获取安装包与依赖离线部署的“搬货”思路3.1 在有网机器上批量拉取 rpm / deb 包离线部署的本质是在另一台能联网的机器上“囤货”。以 CentOS 7 为例先在联网机器上安装yum-utils然后进入一个专门目录用yumdownloader把 Docker 相关的包和依赖一次性拉下来mkdir -p /tmp/docker-offline yum install -y yum-utils yumdownloader --resolve --destdir/tmp/docker-offline/ docker-ce docker-ce-cli containerd.io--resolve参数的意思是把所有依赖包也一并下载。这一步非常关键否则你只拿几个主包到无网机器一装就知道什么叫“依赖地狱”。在 Debian / Ubuntu 联网机器上对应操作是mkdir -p /tmp/docker-offline cd /tmp/docker-offline apt-get download docker.io不过docker.io在 Debian/Ubuntu 仓库里是系统维护的老版本。如果需要最新 Docker CE需要先联网把 Docker 仓库的.deb包下载好同样放到一个目录里。我个人的经验是如果你在离线 Ubuntu 上不想折腾 Docker 官方源依赖直接用系统自带的docker.io包最省事很多场景下基本功能已经够了。3.2 直接拿二进制包最省心的搬运方式除了 rpm / deb还有第三种路线直接下载 Docker 官方发布的二进制压缩包。这个做法在离线部署里我非常推荐因为它几乎没有包管理器的顺序问题。从 Docker 官方 Release 页面找到对应 x86_64 平台的docker-版本号.tgz文件名一般带有linux-amd64标识下载下来以后里面已经打包好了dockerd、docker、containerd、runc、docker-proxy、docker-init等全部核心二进制。整个过程不需要 yum不需要 apt也不需要解析一堆依赖关系。二进制包的缺点是没有安装记录卸载和升级都要自己维护systemd 服务文件需要手工创建。但这些缺点在离线环境下完全可以接受后面第 4 节会给出完整的服务和启动配置。3.3 下载后的完整性校验离线环境里包很容易被 U 盘、移动硬盘、网盘中转过程中弄坏。每次转移前我都建议先做一次校验sha256sum docker-*.tgz md5sum docker-*.rpm把校验值记录到自己的部署文档里传到目标机器后再算一遍。一句话校验值和版本号别嫌麻烦直接写进交接文档后面排障能省很多事情。4. 进场安装rpm、deb、二进制三种路径实操4.1 CentOS / RHEL 系列本地 rpm 包批量安装如果你走 rpm 路线把之前收集的 rpm 包全部放到目标机器的/opt/dockeroffline/pkg目录然后执行cd /opt/dockeroffline/pkg yum localinstall -y *.rpmyum localinstall会在本地目录里解析 rpm 之间的依赖顺序自动逐个安装。相比裸用rpm -ivh *.rpm --nodeps它更安全、不容易装完以后出现运行时缺库。装完以后用docker version验证能看到 client 和 server 两段信息就说明 daemon 已经在跑。提示离线部署时很容易看到“依赖被已安装的包冲突”这种报错多数是因为之前机器上装过旧版本 Docker。处理办法是把旧包先用yum remove docker docker-client docker-common清掉再重新执行 localinstall不要盲目加--nodeps硬装。我在 CentOS 7.9 上实测过顺利的话整个过程 10 分钟以内能完成慢主要慢在 rpm 包数量多、依赖解析时间较长。4.2 Ubuntu / Debian 系列deb 包的依赖顺序问题deb 路线比 rpm 更容易踩坑因为dpkg不像 yum 那样会自动解决依赖。网上很多教程让你直接dpkg -i *.deb但如果软件包之间有依赖顺序dpkg -i会在第一个缺依赖的位置报错退出。更稳妥的方式是让 apt 从本地目录安装cd /opt/dockeroffline/pkg apt-get install ./*.debapt 会优先在当前目录里寻找依赖目录里找得到就一直装下去如果报出“依赖xxx 但是尚未安装”说明你缺的包根本没在这个目录里只能回到联网机器补下载。这也是为什么我一直建议离线环境里能用二进制包就别用 dpkg少受这种折磨。4.3 二进制包部署创建 systemd 服务并启动二进制包是我在离线部署里最常用的一条路下面给出完整流程。先把压缩包放到/opt/docker目录并解压mkdir -p /opt/docker tar -xzf docker-*.tgz -C /opt/docker --strip-components1解压后把核心二进制复制到/usr/bin保证 PATH 里能直接访问cp /opt/docker/docker /opt/docker/dockerd /usr/bin/ cp /opt/docker/containerd /opt/docker/containerd-shim-runc-v2 /usr/bin/ cp /opt/docker/ctr /opt/docker/runc /usr/bin/ cp /opt/docker/docker-init /opt/docker/docker-proxy /usr/bin/ chmod x /usr/bin/docker* /usr/bin/containerd* /usr/bin/runc /usr/bin/ctr然后创建docker.socket文件路径在/etc/systemd/system/docker.socket[Unit] DescriptionDocker Socket for the API [Socket] ListenStream/var/run/docker.sock SocketMode0660 SocketUserroot SocketGroupdocker [Install] WantedBysockets.target再创建/etc/systemd/system/docker.service[Unit] DescriptionDocker Application Container Engine Afternetwork-online.target firewalld.service containerd.service Wantsnetwork-online.target Requiresdocker.socket [Service] Typenotify ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock ExecReload/bin/kill -s HUP $MAINPID TimeoutSec0 RestartSec2 Restartalways LimitNOFILEinfinity LimitNPROCinfinity LimitCOREinfinity TasksMaxinfinity [Install] WantedBymulti-user.target解释一下两个关键点。Typenotify表示 systemd 会等待 dockerd 通过 sd_notify 机制发出“已就绪”通知后才认为服务启动成功比普通Typesimple更可靠能避免 Docker 还没准备好后续脚本就急着执行容器命令。-H fd://表示 dockerd 从 systemd 传入的 socket 文件描述符接收请求这就是上面Requiresdocker.socket存在的原因。接着加载并启动groupadd docker systemctl daemon-reload systemctl enable --now docker.service docker.socket systemctl status docker看到 active (running) 就说明核心环境已经起来了。5. 启动阶段的三个常见拦路虎依赖库、iptables 与 SELinux5.1 dockerd 启动不了先查动态库离线环境里最容易犯的错是主包装了但依赖库没带全。安装完启动 Docker 时systemctl status docker显示 failed用journalctl -u docker -n 30 --no-pager看日志如果里面出现“error while loading shared libraries”说明某个动态库缺失。最直接的排查命令是ldd /usr/bin/dockerd | grep not found这个命令会把 dockerd 依赖的共享库全部列出来凡是标着not found的就是缺失的库文件。我遇到最多的两个libltdl.so.7缺失对应 rpm 包是libtool-ltdllibseccomp.so缺失对应软件包是libseccomp。回到联网机器把这些底层依赖包用yumdownloader --resolve拉下来补装完再重启 Docker基本能解决。5.2 iptables 版本与内核模块不匹配CentOS 7 环境下跑新版 Docker另一个高频问题是在启动网络时直接失败日志常出现类似“Failed to create NAT chain”或者“iptables v1.8.2 (nf_tables)”的报错。原因是 CentOS 7 默认启用了 nftables 体系但旧内核的 nf_tables 支持和 Docker 预期不一致。处理思路有两个。最常见的是把相关内核模块先加载起来确保br_netfilter模块存在调整内核参数modprobe br_netfilter echo br_netfilter /etc/modules-load.d/docker.conf sysctl -w net.bridge.bridge-nf-call-iptables1 sysctl -w net.ipv4.ip_forward1另一个思路是强制让 iptables 走 legacy 工具集。在 CentOS/SUSE 系机器上可以检查iptables --version如果输出带nf_tables字样就安装iptables-legacy相关包并把系统默认的 update-alternatives 切到 legacy 模式。这一步依赖发行版细节不同系统命令有差异但核心目标一致让 Docker 在做 NAT 和端口映射时能调用到它熟悉的 iptables 接口。5.3 SELinux 拦截容器创建在开启了 SELinux 的 CentOS/RHEL 内网机器上Docker 主进程能起来但docker run拉起的容器很可能被拦截报错内容往往是“Operation not permitted”或者“failed to create container mount”。先用getenforce确认是不是 Enforcing 状态。如果你能联网正确做法是安装container-selinux这个策略包让 SELinux 认识容器标签离线环境下部署包清单里一定要提前把container-selinux和它的依赖带上。如果内网安全基线允许也可以临时把 SELinux 切到 permissive 模式但这件事需要走内部审批流程不能盲目操作也不能简单在/etc/selinux/config里改完就完事还要确认不会影响其他安全组件。6. 镜像怎么进内网docker save/load 与本地 Registry6.1 单机场景save 与 load 的组合Docker 环境装完只是第一步真正的核心是业务镜像能不能进去。内网单机上最直接的做法是用docker save在镜像来源机器上导出再在目标机器上导入。来源机器一般是能联网的开发机或镜像构建机上执行docker save -o myimages.tar nginx:1.25 redis:7.2 mysql:8.0生成一个myimages.tar文件大小取决于镜像本身。把这个 tar 传到目标机器执行docker load -i myimages.tarload 完成以后docker images就能看到镜像了。这里有个容易误操作的点如果你想迁移的镜像非常多建议按业务域分几个 tar 分别导出不要一口气全塞进一个大 tar。原因是大 tar 在中断恢复时很难定位位置而且离线拷贝文件体积超过 U 盘或移动硬盘限制会很尴尬。还有一个来自架构的坑docker save出来的镜像本身带平台信息。如果你在 ARM 开发机上导出了一个 arm64 版本的镜像拿到 x86_64 目标机 load 之后再运行会直接报exec format error。所以导出前一定要用docker images或docker inspect确认镜像架构是amd64或者linux/amd64。6.2 多机场景搭一个内网镜像仓库如果离线环境里有三五台机器都要拉镜像一台台docker load就很低效。更聪明的做法是找一台机器当内网镜像分发点跑一个最小化的 Registry。先在有镜像的机器上导出registry:2docker save -o registry.tar registry:2把registry.tar传到内网选定的仓库机上 load 并启动docker load -i registry.tar docker run -d --name registry --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2其他机器要拉镜像先在/etc/docker/daemon.json中写入{ insecure-registries: [192.168.10.20:5000] }这里192.168.10.20:5000需要替换成你的仓库机内网地址。然后重启 Dockersystemctl restart docker接着就可以在任意一台机器上docker pull 192.168.10.20:5000/myimage:v1别忘了用curl http://192.168.10.20:5000/v2/_catalog检查仓库是否正常返回一个 JSON 列表说明 Registry 没问题。内网没有 DNS 的时候推荐直接用 IP 访问少去配置 hosts 的麻烦。6.3 大镜像分层的传输优化对于特别大的镜像我更习惯用另外两个小技巧。一个是用docker save导出后先压缩再拷贝docker save myimage:latest | gzip myimage.tar.gz目标机上gunzip -c myimage.tar.gz | docker load压缩率在 30% 到 60% 之间传输时间能明显缩短。另一个技巧是目标机器上如果已经有相同的基础镜像层可以直接在镜像来源机器上docker export容器而不是导出镜像但这种方式会丢掉镜像历史比较适合只想搬一个最终文件的场景不是常规推荐路线。7. 上线前后的维护心得与避坑清单7.1 版本固定离线环境经不起临时升级离线环境最大的特点就是“动了手难得回滚”。我在实际部署里强烈建议把 Docker 版本完全固定下来不要动不动追最新版。一旦选定一个版本就把版本号、校验值、依赖包清单、部署日期全部记录到一个文本文档里跟离线安装包一起归档。以后万一环境出问题要重装或者需要给另一批机器做同样部署直接照方抓药。镜像也一样尽量别用latest标签。离线环境里latest的含义非常模糊一旦你在开发机上重新 pull 了一次镜像内容可能就悄悄变了到了内网才发现行为对不上。直接把版本标签写死比如nginx:1.25、mysql:8.0.36内网环境不会撒谎写死什么就跑什么。7.2 数据备份与重启策略离线环境里通常没有云厂商的自动快照所以数据备份要自己规划好两件事。第一件镜像本身用docker save定期备份到独立磁盘目录备份的时候排除临时容器和数据卷第二件容器尽量设置自动重启策略尤其是一台机器重启以后希望容器跟着拉起来docker run -d --restart unless-stopped ...这个策略表示容器如果被手动停止下次开机不会自动启动机器重启引起的容器退出则会自动拉起。运维上比--restart always更可控不会出现“想停掉保安全结果开机又自己起来了”的窘境。7.3 时间同步往往是最容易被忽略的细节说到离线环境时钟问题经常被遗忘。容器日志、认证、调度的很多时间戳依赖宿主机时钟如果内网机器不能通过 NTP 对时时间漂移会越来越大排查问题时很容易被误导。离线环境再不方便也建议在部署清单里加上至少一台上游时间源或者部署一个内网 NTP 服务。我在这上面吃过亏容器里日志时间和宿主机差了好几个小时一开始完全想不到是时钟问题白白排查了很久。最后再说一个实操体会整个流程走下来离线装 Docker 最难熬的不是某个命令而是节奏。很多人习惯拿到包就装装不上就直接从网上搜报错结果越折腾越乱。我的经验是严格按“宿主系统确认架构 → 依赖包补齐 → 装核心二进制 → 启动验证 → 镜像 load 进内网仓库 → 业务容器跑起来”这个顺序来每一步验证通过再进下一步基本不会走到不可收拾的地步。祝各位在内网部署时少点折腾一次过。