
最近连续两周都在跟Docker离线安装打交道。客户现场是典型的x86系统架构内网服务器不能连公网又要跑MySQL、Redis、GitLab、Nginx这一堆中间件。在线环境下docker pull一把梭很轻松真到了物理隔离的机房平时最不起眼的yum源、镜像拉取、tag传递全都会变成拦路虎。这篇文章我把离线安装Docker、离线准备中间件镜像、用私有仓库统一分发、以及docker compose编排整套服务的完整流程写出来也会把文档里不写的坑单独拉出来讲。适合准备做内网部署、等保改造、离线交付的运维同学收藏参考。1. 离线安装的整体思路与架构适配1.1 为什么内网环境必须走离线安装很多项目一上来就问“能不能直接给服务器联网装Docker”现实往往是不行的。等保要求、金融隔离区、政府机房、工厂生产线这些环境要么物理隔离要么防火墙策略只允许特定端口通信yum源根本访问不到。就算能访问安全评审也过不了。这时候离线安装不是选择题而是必选题。离线安装的核心逻辑很简单在一台能联网的“准备机”上把所有需要的软件包、镜像文件、配置文件全部下载并固定好版本然后通过U盘、移动硬盘、内网FTP或者带外管理通道把这一套东西搬到目标服务器上。听起来容易实际坑很多——依赖包不全、镜像架构不对、tar包太大传输中断、私有仓库没提前规划任何一个环节都会卡住整个交付。我见过最典型的翻车现场是有人只拷了一个docker-ce.rpm到目标机执行rpm -ivh时报一堆依赖缺失当场懵了。Docker本体其实依赖一大堆底层库比如libseccomp、libcgroup、container-selinux、iptables相关组件。离线安装必须把“本体、依赖、镜像、仓库”四件事当成一个整体来看缺一不可。1.2 x86架构兼容性说明先说清楚标题里“x86系统架构”这件事。最常见的生产服务器是x86_64平台也就是amd64。目标机器上执行uname -m输出x86_64就说明是标准的64位x86架构。这个信息决定了你要下载哪个平台的Docker二进制包以及拉取哪个平台版本的镜像。这里有一个容易踩的坑Linux服务器上运行Docker本身不依赖硬件虚拟化VT-x只要内核版本满足要求CentOS 7是3.10以上CentOS 8/RHEL 8是4.18以上Ubuntu则看具体版本就能跑。但是Windows环境下的Docker Desktop不一样它依赖WSL2和硬件虚拟化之前热搜里那个“virtualization support not detected”报错就是这么来的。也就是说同样的x86架构在Linux服务器和在Windows开发机上跑Docker前置条件完全不同。另外一个坑是镜像架构。docker pull的时候如果没注意拉到了arm64的镜像在x86机器上docker run会直接报“exec format error”。所以离线准备阶段每拉一个镜像都要确认架构。可以用docker manifest inspect验证也可以直接看镜像仓库页面的标签说明。x86_64机器上统一选linux/amd64的镜像这个原则要刻在脑子里。1.3 三种离线安装方案对比我在多个项目里用过三种离线安装Docker的思路各有适用边界简单列个对比方案适用场景优点缺点rpm包直接安装单机、三五台以内部署快命令简单依赖必须提前下载全版本容易冲突本地YUM源批量十几台以上安装时自动解析依赖统一版本需要createrepo构建仓库占用磁盘空间拷贝Docker二进制目录精简系统、特殊发行版不依赖包管理器灵活性最高需要手动生成systemd服务文件步骤繁琐我自己的习惯是混合用几台机器直接rpm装简单粗暴项目数量大、节点超过十台就先在跳板机上用createrepo搭一个本地yum源所有目标机通过内网地址配置repo文件然后统一yum install。这样做的好处是依赖解析交给yum不用自己一个个去数rpm依赖树也不会出现“这台机器缺一个libseccomp”之类的低级问题。不过话说回来不管用哪种方案联网准备机上必须先想清楚目标机是什么操作系统、什么版本。CentOS 7的rpm包和CentOS 8不完全通用Ubuntu用deb包又是另一套玩法。这篇文章以最常见的CentOS 7/8 x86_64作为目标环境展开其他发行版原理一样只是包格式和源配置不同。2. 中间件镜像的离线准备与传输2.1 在联网机器上拉取镜像离线部署中间件第一步是把镜像提前准备好。建议专门找一台能联网的Linux机器作为准备机这台机器装上Docker在线装就可以然后统一配置镜像加速器。国内环境直接从Docker Hub拉镜像经常超时配置加速器之后速度会快很多这个操作属于常规基础配置不会改变镜像内容。镜像选型有个原则尽量固定版本tag不要用latest。原因很简单latest在你拉镜像和真正部署之间可能已经变了好几个版本生产环境的一致性要求必须锁定版本。比如MySQL用8.0.36Redis用7.0.15Nginx用1.24.0GitLab用16.10-ce。把版本号写进交付文档谁接手都不会蒙圈。拉镜像的命令很基础但建议养成写完整镜像名的习惯docker pull mysql:8.0.36 docker pull redis:7.0.15 docker pull nginx:1.24.0 docker pull gitlab/gitlab-ce:16.10.2-ce.0 docker pull registry:2.8.3另外所有中间件的默认配置和启动参数建议先在准备机上跑一遍确认无误后再离线搬运。我吃过这个亏现场环境无法访问外网文档想查一个参数都费劲最后只能靠事先准备的笔记。离线部署最忌讳“到现场再研究”所有验证工作要前移。2.2 docker save与docker load的原理和操作镜像准备好之后要把它们导出成文件。docker save的作用是把一个或多个镜像打包成tar文件里面包含镜像的所有层、manifest信息和配置。对应的docker load则把tar文件重新导入到目标机的Docker里。导出多个镜像可以一条命令搞定docker save \ mysql:8.0.36 \ redis:7.0.15 \ nginx:1.24.0 \ gitlab/gitlab-ce:16.10.2-ce.0 \ registry:2.8.3 \ -o middleware-images.tar这个tar文件可能很大MySQL镜像1GB多GitLab镜像快3GB几个加起来能到5GB往上。传输之前建议先压缩一下gzip middleware-images.tar压缩后体积能缩小不少。传输工具方面U盘和移动硬盘最省事内网FTP也行如果带宽允许直接用scp。不管用什么方式传完之后务必做一次md5校验确认文件没有损坏md5sum middleware-images.tar.gz别嫌校验麻烦我在现场遇到过tar包传到一半中断但系统提示“传输完成”的情况解压时才发现文件损坏当时整个人都不好了。校验一下多花一分钟能省半天排查时间。导入目标机时执行gzip -d middleware-images.tar.gz docker load -i middleware-images.tarload完成后docker images确认一下所有镜像的tag都在。这里有个细节如果之前save用的是镜像ID而不是完整的repository:tag导入后会出现一堆none的悬空镜像。所以save时一定要写全镜像名不要偷懒只填镜像ID。2.3 搭建企业私有镜像仓库registry如果只有一台目标机docker load就够用了。但真实项目里往往是十几台甚至几十台服务器都要跑同一套中间件这时候挨个load镜像会累死人版本管理也会乱套。正确做法是在内网搭建一个私有镜像仓库也就是跑一个registry容器。搭建registry本身就是离线部署的一部分所以镜像必须先准备好。在目标网络里找一台配置高一点的机器执行mkdir -p /data/registry docker run -d --name registry --restart always \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2.8.3然后需要让所有客户端信任这个私有仓库。Docker默认只允许HTTPS的镜像仓库内网没有证书的话要在每台机器的/etc/docker/daemon.json里加上insecure-registries配置把registry服务器的IP和端口加进去。这个配置后面详细讲这里先记住不配这个push和pull都会报证书相关错误。私有仓库做好之后操作流程变成联网准备机把镜像打好tagpush到内网registry目标机器直接从内网registry拉取。比如准备机上docker tag mysql:8.0.36 192.168.1.10:5000/mysql:8.0.36 docker push 192.168.1.10:5000/mysql:8.0.36目标机上就只需要docker pull 192.168.1.10:5000/mysql:8.0.36 docker tag 192.168.1.10:5000/mysql:8.0.36 mysql:8.0.36这个方式最大的价值是“一次推送处处拉取”配合容器编排工具整个中间件平台的交付效率会明显上一个台阶。3. 实操从0开始离线部署中间件3.1 步骤一准备Docker离线安装包既然目标机不能联网就在联网准备机上先把所有rpm下载下来。以CentOS 7为例先安装yum-utils工具yum install -y yum-utils配置Docker官方仓库yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo然后创建目录下载Docker相关的几个核心包mkdir -p /data/docker-offline cd /data/docker-offline yumdownloader --resolve docker-ce docker-ce-cli containerd.io docker-compose-plugin这里必须用--resolve参数否则只下载指定的几个包依赖全都不带。第一次我漏了这个参数到现场才发现缺了一堆依赖后来学聪明了。下载完检查一下目录正常会有十几个rpm包含container-selinux、libseccomp、libcgroup这些关键依赖。再用createrepo生成本地仓库索引可以使目标机直接用这个目录当yum源yum install -y createrepo createrepo /data/docker-offline最后把这个目录整个拷到目标机放在比如/data/docker-offline路径下。如果目标机数量多可以直接在这个目录上搭HTTP服务或者使用内网文件共享。3.2 步骤二安装启动Docker并配置daemon.json目标机上先把自带的老版本Docker或Podman清理干净避免冲突然后进入离线包目录安装cd /data/docker-offline yum localinstall -y *.rpmyum localinstall的好处是会尝试在当前目录的rpm包中自动解析依赖比rpm -ivh舒服很多。如果个别依赖还是提示缺失说明离线包没下载全回准备机补一下再传过来。安装完成后先别急着启动先把配置文件写好。创建/etc/docker/daemon.json{ data-root: /var/lib/docker, storage-driver: overlay2, exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, insecure-registries: [192.168.1.10:5000] }逐项解释一下data-root是Docker数据存放目录如果系统盘空间不大建议把整个/var/lib/docker挂到数据盘上storage-driver用overlay2是当前主流选择旧版本默认的devicemapper问题多exec-opts里cgroupdriver用systemd是为了和操作系统初始化系统保持一致避免集群场景下双cgroup管理打架日志限流一定要做不然一个容器能把磁盘写满insecure-registries就是刚才说的私有仓库地址。配置完成后启动并设置开机自启systemctl daemon-reload systemctl enable --now docker systemctl status docker启动后验证一下docker version docker infodocker info输出里重点看Storage Driver是不是overlay2Cgroup Driver是不是systemd。这两项不对后面跑容器会出一堆莫名其妙的问题。3.3 步骤三离线部署MySQL与Redis主从安装好Docker之后中间件的部署就顺畅多了。先导入前面准备好的镜像包docker load -i middleware-images.tar部署MySQL我习惯把宿主机的目录规划好再启动容器数据目录、配置目录、日志目录分开这样以后备份和排查都方便mkdir -p /data/mysql/conf mkdir -p /data/mysql/data mkdir -p /data/mysql/logs docker run -d --name mysql8 --restart always \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourStrongPass \ -e TZAsia/Shanghai \ -v /data/mysql/conf:/etc/mysql/conf.d \ -v /data/mysql/data:/var/lib/mysql \ -v /data/mysql/logs:/var/log/mysql \ mysql:8.0.36几个细节说明一下--restart always保证服务器重启之后容器自动拉起这在没有运维值守的机房非常关键TZ环境变量一定要设成Asia/Shanghai否则容器默认UTC时间日志时间和业务时间对不上数据目录挂载到宿主机是必须的否则容器删了数据就没了。MySQL8初始化需要一段时间通常一两分钟左右用docker logs观察docker logs -f mysql8看到日志里出现“ready for connections”就说明可以连了。测试命令mysql -h127.0.0.1 -P3306 -uroot -p顺便提一句MySQL8默认认证插件是caching_sha2_password如果业务系统用的老版本JDBC驱动连接会报认证失败。解决办法是在容器里把用户改成mysql_native_password或者升级驱动。离线环境里升级驱动不容易所以我一般在初始化SQL里顺手兼容一下老客户端。接着部署Redis主从。主节点mkdir -p /data/redis/master docker run -d --name redis-master --restart always \ -p 6379:6379 \ -v /data/redis/master:/data \ redis:7.0.15 \ redis-server --appendonly yes --requirepass RedisMasterPass从节点mkdir -p /data/redis/slave docker run -d --name redis-slave --restart always \ -p 6380:6379 \ -v /data/redis/slave:/data \ redis:7.0.15 \ redis-server --appendonly yes \ --replicaof 192.168.1.10 6379 \ --masterauth RedisMasterPass \ --requirepass RedisMasterPass注意主从节点的IP要按实际环境改--masterauth和--requirepass要一致否则复制建立不起来。验证主从状态redis-cli -p 6380 -a RedisMasterPass info replication输出里role是slavemaster_link_status是up说明主从已经正常。实际部署中Redis主从一般配合哨兵或者集群模式基础的主从验证通了之后再往上叠加高可用方案思路会更清晰。3.4 步骤四用Docker Compose编排整套中间件当中间件越来越多一条条docker run命令就不好维护了。Docker Compose是官方推荐的编排工具新版的docker-compose-plugin已经内置在docker-ce包里离线包下载的时候带上这个插件就能用。先创建项目目录写一个docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0.36 container_name: mysql8 restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: YourStrongPass TZ: Asia/Shanghai volumes: - /data/mysql/conf:/etc/mysql/conf.d - /data/mysql/data:/var/lib/mysql - /data/mysql/logs:/var/log/mysql redis: image: redis:7.0.15 container_name: redis-master restart: always ports: - 6379:6379 command: redis-server --appendonly yes --requirepass RedisMasterPass volumes: - /data/redis/master:/data nginx: image: nginx:1.24.0 container_name: nginx restart: always ports: - 80:80 - 443:443 volumes: - /data/nginx/conf:/etc/nginx/conf.d - /data/nginx/html:/usr/share/nginx/html - /data/nginx/logs:/var/log/nginx启动整套服务docker compose up -d docker compose psdocker compose的好处是版本化、可复现配置文件跟着项目走换一台新机器可以直接复用。离线环境里只要镜像版本一致compose文件在哪个节点都是同样的效果。升级某个中间件时改镜像版本号然后docker compose up -dCompose会自动重建对应的容器其他服务不受影响。4. 常见问题与排查技巧实录4.1 安装启动阶段的坑依赖缺失。这是离线安装Docker最常见的错误报错信息一般是“Requires: libcgroup、libseccomp、container-selinux”。解决方法是不要单独rpm -ivh而是用yum localinstall /data/docker-offline/*.rpm让yum从本地目录自动补依赖。如果还缺说明离线包不全回准备机重新yumdownloader --resolve再下载一次。启动失败。systemctl start docker报失败先看日志journalctl -u docker -n 50日志里常见的原因有几个iptables规则冲突解决方法是检查宿主机防火墙并放行docker0网段SELinux没关或者配置不对临时用setenforce 0验证确认是SELinux问题后通过/etc/selinux/config永久关闭还有可能是内核模块没有加载执行modprobe overlay、modprobe br_netfilter补上。磁盘空间不足。Docker默认把数据放在/var/lib/docker系统盘如果只有50GB运行几个中间件镜像就会被镜像层、容器层、日志撑爆。建议安装之前就把data-root配置到数据盘或者提前做软链接。我通常直接在daemon.json里指到/data/docker一劳永逸。docker网络不通。容器内ping不通外网或者不同宿主机容器互通异常优先排查宿主机iptables的FORWARD链是否放行了docker网段以及是否修改过docker0网卡的网段。如果宿主机所在网段和docker0默认的172.17.0.0/16冲突要在daemon.json里通过bip参数改网段比如bip: 10.10.0.1/24改完重启docker。4.2 镜像导入与容器运行的坑exec format error。这句话出现基本可以断定镜像架构不是amd64。确认方法docker image inspect老镜像名看Architecture字段。解决办法是回准备机拉取正确的linux/amd64版本镜像重新导出再导入。镜像变成 。docker load之后出现一堆 悬空镜像多半是因为save的时候只写了镜像ID没有写repository:tag。处理办法是用docker tag补上正确的名称。预防措施就是前面说的save命令里写完整的镜像名:版本号。容器启动后马上退出。先别急着怀疑镜像问题用docker logs看一下输出。比如MySQL可能是数据目录权限问题Nginx可能是宿主机挂载的目录不存在Redis可能是配置参数写错了。日志往往已经把原因说得清清楚楚。GitLab启动慢。GitLab镜像特别大第一次启动要做一系列初始化经常两三分钟还看不到端口监听这是正常的。用docker logs -f gitlab观察看到“GitLab is up”再访问。另外GitLab对内存要求高至少4GB低于这个数会频繁卡死建议独立机器部署不要和一堆中间件挤在一起。Windows x86环境跑Docker Desktop。热搜里那个“virtualization support not detected”报错本质是Windows没有开启硬件虚拟化或者WSL2没装好。需要在BIOS里打开VT-x然后在控制面板启用“适用于Linux的Windows子系统”和“虚拟机平台”再装WSL2内核更新包。真正完全离线、无法联网下载WSL离线包的Windows机器建议放弃Docker Desktop改用Windows Server上的Linux容器功能或者干脆用一台Linux虚拟机替换。4.3 中间件运行后的调优与维护离线环境装好不是终点运行维护同样重要。日志轮转在daemon.json里配置好之后是全局生效的但我还是建议给重要的中间件单独设置日志参数特别是Nginx这种访问日志增长很快的服务。可以定期用docker logs --tail查看避免日志文件无限膨胀。镜像和容器清理靠两条命令docker system prune -f docker image prune -a但执行之前一定确认镜像有没有被使用私有仓库里的原始镜像和节点上的运行镜像要区分清楚别误删。数据备份是离线环境的最后一根稻草。MySQL和Redis都有数据目录要定期冷备整个/var/lib/mysql和/var/lib/redis目录或者用mysqldump和redis-cli --rdb在容器里导出。私有仓库的数据目录/var/lib/registry也要备份我一般是直接打包传到专门的备份服务器。这样即使整个环境崩溃拿一台新机器、用一套离线包也能完整重建。这套流程我前后交付过七八套离线环境最大的体会是离线部署本质上不是技术问题而是工程管理问题。只要把依赖包清单、镜像版本清单、私有仓库这三样东西提前管好后续复制起来非常快。强烈建议每个项目都做一份《离线交付清单》把rpm文件名、镜像tag、端口规划、密码模板全部记录下来哪怕半年后换个同事接手也能按清单完整复现。最后提醒一句生产交付前一定要做一次完整恢复演练别等真正出故障时才发现备份文件是坏的或者离线包缺了某个依赖。