干运维这些年,“离线装docker”这个需求我前前后后碰过不下十几次。有的是机房不给外网,有的是客户要求内网裸奔部署,最后都只能靠一个U盘或者内网FTP把东西带进去。标题里这三个词——CentOS 7、Linux、离线安装docker——几乎概括了这类场景的全部矛盾:系统老、环境封闭、缺yum源,还要把容器运行时整个铺起来。这篇文章我把完整套路写清楚,从联网机器上准备离线包,到内网机器装好引擎,再到把镜像导进内网、验证跑通,一步步说透。
1. 离线安装的整体思路:为什么麻烦、方案怎么选
1.1 离线安装到底难在哪
CentOS 7默认的yum源里其实没有docker-ce,平时在线装docker要么先加官方源,要么用发行版默认的docker包。一旦断网,yum install这条路就堵死了一半,更麻烦的是docker不是单个rpm,它有一串依赖:docker-ce本身、docker-ce-cli、containerd.io,还有container-selinux这一类系统库包。这些包互相咬着版本,缺一个或者版本不对,装到一半就报错。而且CentOS 7的glibc、systemd、内核都偏老,太新的docker版本装上去可能直接启动不了,这一点离线环境下特别坑,因为没有互联网帮你确认“哪个版本能跑”,只能靠事前选型。
很多人以为离线安装就是把没事先下好的docker rpm包拷贝进去rpm -ivh一把梭,结果被依赖问题折腾一整天。所以我每次做这类实施,第一步永远是在联网机器上把依赖链理顺,而不是先碰内网机器。
1.2 两条路线:rpm批量离线包 vs 静态二进制
离线装docker主要有两条路线:
一条是收集全套rpm离线包,拷到内网后用yum localinstall安装。这个方案的好处是安装行为跟在线yum几乎一致,装完自带systemd服务、自动生成docker.socket和docker.service,管理方式最标准。缺点是要把依赖收集全,这一步的坑比较多。
另一条是下载docker官方提供的静态二进制压缩包,解压后把dockerd、docker等文件放到/usr/bin下,自己写systemd unit或者直接nohup启动。这个方案依赖极少,只要内核够用就能跑,适合rpm依赖怎么都凑不齐的场合。缺点是需要手工维护service文件,而且后续升级、卸载都比较麻烦,跟系统包管理是两套逻辑。
我的经验是:能凑齐rpm包就优先走rpm路线,因为收尾干净、行为标准。只有在内网机器实在是太老、或者依赖缺得离谱时才考虑静态二进制。下面整篇主线按rpm方案展开,静态二进制作为补充方案放在后面讲。
1.3 实施前要准备的材料
开始动手之前,先把下面这些列个清单确认到位,不然做到一半发现少东西就很被动:
- 一台跟内网机器同版本(CentOS 7.x,架构x86_64)的联网机器,用来下载离线包和导出测试镜像;
- 一套docker离线rpm目录,包括docker-ce、docker-ce-cli、containerd.io和全部依赖;
- 一个用于验证的容器镜像tar包(比如busybox或nginx),因为内网机器装完docker后,没有网络拉不了hello-world测试镜像;
- U盘或内网传输通道,把离线目录和镜像包传过去。
准备工作做好,后面基本不会卡壳。
2. 联网机器上准备离线安装包
2.1 装好yum-utils,配一个docker-ce仓库
联网机器上先确认能正常访问外网源。第一步是安装yum-utils,下面要用到它的repotrack命令来下载依赖包。
yum install -y yum-utils然后添加docker-ce仓库。我习惯在/etc/yum.repos.d/下新建一个docker-ce.repo,内容大概是这样:
[docker-ce-stable] name=Docker CE Stable baseurl=https://<docker-ce-repo>/linux/centos/7/$basearch/stable enabled=1 gpgcheck=1 gpgkey=https://<docker-ce-repo>/linux/centos/gpg尖括号里的仓库地址需要换成你实际能访问的docker-ce yum源地址,用官方源或者国内镜像源都行。加完后跑一遍makecache:
yum makecache这一步做完,联网机器就已经能看见docker-ce相关的包了。
2.2 用repotrack一把拉全所有依赖
很多教程里用的是yumdownloader --resolve,但我强烈建议用repotrack。原因是yumdownloader在解析依赖时会参考当前机器的已装情况,如果联网机器上恰好已经装了某个依赖包,它可能就不下载了,结果拷到干净的内网机器上时就缺包。repotrack不管本机装没装,会把依赖全量抓下来。
先建一个目录,然后执行:
mkdir -p /opt/docker-offline repotrack -a x86_64 -p /opt/docker-offline docker-ce docker-ce-cli containerd.io这样docker-ce、docker-ce-cli、containerd.io以及它们的所有间接依赖都会出现在/opt/docker-offline下面。执行结束后先数一数包有多少个:
ls /opt/docker-offline | wc -l正常会有二十到四十个左右的rpm文件。一定要把这个目录打包再传输,直接用scp传一堆散文件容易漏,而且U盘拷贝时小文件太多也慢:
cd /opt tar czf docker-offline.tar.gz docker-offline到这一步离线安装包就已经准备好了。
2.3 版本选型:不要盲目追新
repotrack下载之前,先想清楚要装哪个版本的docker。CentOS 7的内核和系统库决定了它不适合跑太新的docker引擎,这一点我在不同内网机器上验证了好几次。
我整理了一个自己做选型的参考:
| docker版本系列 | CentOS 7上的表现 | 适用场景 |
|---|---|---|
| 19.03系列 | 老内核也能跑,最稳 | 内核特别老的现场,比如3.10.0-514之前的系统 |
| 20.10系列 | 功能均衡,兼容性好 | 大多数内网项目,稳妥首选 |
| 24.0系列 | 新特性多,但要求内核相对较新 | 内核在3.10.0-957以上的机器 |
离线环境下装了就没法随便拉补丁,所以我的原则是保守优先。如果现场内核版本老,优先选20.10甚至19.03;如果内核比较新,选24.0问题也不大。查看仓库里有哪些版本可以这样:
yum list docker-ce --showduplicates然后指定版本下载,比如:
repotrack -a x86_64 -p /opt/docker-offline docker-ce-20.10.24 docker-ce-cli-20.10.24 containerd.io-1.6.28具体小版本号以你仓库里实际显示的为准。选完版本后,顺手在联网机器上把需要的测试镜像也导出来。
2.4 顺手导出一个验证镜像
内网机器装完docker后,不能直接docker run hello-world,因为hello-world镜像也要联网拉。所以要提前在联网机器上拉一个小体积镜像并导出tar包。busybox是最合适的,几MB大小,拿来测引擎是否正常非常够用:
docker pull busybox:1.36 docker save busybox:1.36 | gzip > /opt/busybox.tar.gz有业务镜像需要一起部署的,就挨个docker pull然后save。注意save是导镜像,不要用export,两者的区别后面专门说。
3. 内网CentOS 7上完成Docker引擎安装
3.1 环境检查:先把系统的底子摸清
内网机器上动手之前,先花两分钟把环境过一遍。很多装完启动不了的问题,其实在环境检查阶段就能避免。我习惯按这个顺序查:
cat /etc/redhat-release uname -r df -h / getenforce systemctl status firewalld --no-pager | head -5重点看几件事:系统版本是不是CentOS 7.x,内核版本是多少,根分区还够不够大,SELinux是Enforcing还是Disabled,防火墙开着还是关了。
CentOS 7默认内核是3.10系列,docker的overlay2存储驱动依赖内核里的overlay模块,顺便确认一下模块在不在:
modprobe overlay modprobe br_netfilter lsmod | grep -E "overlay|br_netfilter"如果模块不存在,先加载再写入自动加载配置,避免重启后失效:
echo "overlay" > /etc/modules-load.d/docker.conf echo "br_netfilter" >> /etc/modules-load.d/docker.conf同时把桥接流量转发和IP转发打开,这两个参数不设置,容器网络经常会出怪问题:
sysctl -w net.bridge.bridge-nf-call-iptables=1 sysctl -w net.ipv4.ip_forward=1要想重启后依然生效,写成下面这样:
cat > /etc/sysctl.d/99-docker.conf <<'EOF' net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system3.2 上传离线包并用yum localinstall安装
把之前打包的docker-offline.tar.gz传到内网机器,解压后进入目录执行安装。如果内网机器之前装过旧版docker,先清干净再装:
yum remove docker docker-client docker-common docker-engine然后解压并安装:
tar xzf docker-offline.tar.gz cd docker-offline yum localinstall -y *.rpmyum localinstall会自动解析当前目录下的所有rpm依赖关系,只要目录里包全,整个安装过程会非常顺。装完以后启动服务:
systemctl enable --now docker systemctl status docker --no-pager再看一眼版本,确认客户端和服务端都正常:
docker version docker info如果docker version里Server一项能正常显示,说明引擎已经跑起来了。万一Server没出来,多半是dockerd启动失败了,去查日志:
journalctl -u docker --no-pager | tail -50后面专门讲常见错误。
3.3 提前写daemon.json,把数据目录落在大分区
这是我最想强调的一步:不要装完就跑默认配置,先把/etc/docker/daemon.json写好再启动,或者启动后马上改。CentOS 7的默认数据目录是/var/lib/docker,而很多服务器的/分区并不大,容器镜像一多,/分区被撑满的案例我见得太多了。
先确认有没有一个大分区可以放docker数据:
df -h比如数据盘挂在/data下,那就创建目录:
mkdir -p /data/docker然后写daemon.json:
{ "data-root": "/data/docker", "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "exec-opts": ["native.cgroupdriver=systemd"], "registry-mirrors": ["https://registry.internal.example"] }其中registry-mirrors这里写的是内网镜像仓库地址,现场有就填实际地址,没有就去掉这一项。注意这里不要填外网加速地址,内网环境根本访问不到,反而会让docker启动时反复尝试连接。
如果docker已经启动过并且产生了数据,再改data-root就要先停服务再迁移数据:
systemctl stop docker mv /var/lib/docker/* /data/docker/ systemctl start docker新环境没有历史数据的话,直接配好再启动即可。另外exec-opts里的cgroupdriver,如果以后要对接k8s,保持systemd是对的;单机跑容器的话这行可以不加。
3.4 备用方案:静态二进制也能跑
如果内网机器上yum localinstall因为依赖问题怎么都装不上,还有一条后备路线:用docker官方静态二进制包。下载对应架构的docker-xxx.tgz,解压后把内容放到/usr/bin:
tar xzf docker-20.10.24.tgz cp docker/* /usr/bin/然后写一个systemd service文件:
[Unit] Description=Docker Application Container Engine After=network-online.target firewalld.service Wants=network-online.target [Service] Type=notify ExecStart=/usr/bin/dockerd ExecReload=/bin/kill -s HUP $MAINPID Restart=on-failure [Install] WantedBy=multi-user.target之后同样systemctl daemon-reload && systemctl enable --now docker。这个方案依赖极少,但收尾不如rpm方案干净,所以我只把它当备案。
4. 离线镜像的导入与部署验证
4.1 save和export千万别搞混
离线导入镜像最容易被坑的地方,就是把save和export弄混。这两个命令长得像,用途完全不一样:
- docker save保存的是一个或多个镜像,保留镜像的分层结构、标签、历史信息,是标准的镜像分发方式;
- docker export导出的是一个容器的文件系统,会把所有历史和分层拍扁,导出后没有标签,也不能再作为镜像被docker load直接恢复成原来的样子。
离线部署场景,永远用save和load组合。我在现场见过有人用export导出一堆容器,拿到内网load下来发现image名字全是none,根本没法用,最后只能重新打包。
4.2 导入镜像并做一次完整验证
把准备阶段导出的busybox.tar.gz传到内网机器,执行导入:
docker load -i busybox.tar.gz如果导出的镜像是gzip压缩过的tar包,docker load会自动解压。导入后确认镜像已经在本地列表里:
docker images然后跑一个容器验证引擎是否正常工作:
docker run --rm busybox:1.36 echo "docker offline ok"输出docker offline ok就证明整个docker引擎链路没问题。如果现场需要部署业务镜像,比如nginx,操作方式一模一样:
docker load -i nginx-1.24.tar.gz docker run -d --name nginx-test -p 80:80 nginx:1.24 curl http://127.0.0.1curl能返回nginx页面,整个部署就算彻底跑通了。
4.3 有内网仓库时,可以推上去
有些内网环境会架自己的镜像仓库,那就不需要每个机器都传tar包了。把tar包导入一次,打上仓库地址的tag,直接push:
docker tag nginx:1.24 registry.internal.example/nginx:1.24 docker push registry.internal.example/nginx:1.24其他机器配置好daemon.json里的registry-mirrors,或者直接用完整仓库地址拉镜像就行。如果内网仓库用的是自签名证书,需要在daemon.json里加一层insecure-registries配置,不然push会被TLS校验挡住。这一步细节多,但有了前面tar包离线导入打底,仓库推送只是可选的锦上添花。
5. 我踩过的坑:典型问题速查与排查技巧
5.1 依赖地狱和container-selinux版本冲突
离线安装最常见的失败场景,是yum localinstall进行到一半,突然弹出一堆依赖缺失。这种情况十有八九是离线rpm目录没收集全,或者是在联网机器上用了yumdownloader而不是repotrack,漏掉了某些依赖。
排查思路很直接:看报错信息里缺的是哪个包,然后回到联网机器,用yumdownloader单独补下那个包,再拷回内网装上。如果缺的包比较多,干脆重新repotrack一遍,别一个个补,太浪费时间。
另一个高频问题:container-selinux包版本与docker-ce要求的版本不匹配。CentOS 7基础源里的container-selinux往往比较老,docker-ce依赖的版本更高一点。解决方式有两种:一种是在联网机器的docker-ce仓库里把最新container-selinux下载回来,另一种是去epel源里找对应版本。装上高版本container-selinux后,再跑yum localinstall就不会再卡在这一步。
5.2 装完引擎却启动不了:cgroup、iptables、SELinux
docker启动失败,第一件事永远是journalctl -u docker查看日志,别瞎猜。我遇到过的几种典型情况:
SELinux处于Enforcing状态导致的权限错误,日志里会出现permission denied相关的字样,特别是存储目录权限有问题时。可以临时验证一下:
setenforce 0 systemctl restart docker如果照做后docker正常了,说明确实是SELinux拦截,需要权衡是调整策略还是把SELinux改成permissive。生产环境要慎重改SELinux,但CentOS 7配老docker时,改permissive是不少运维的实际选择。
iptables相关的问题也很多。CentOS 7默认的firewalld如果没放行容器需要的转发流量,容器网络会异常。最直接的方法是把firewalld的端口规则配清楚,而不是简单关防火墙。业务端口按需放行:
firewall-cmd --permanent --add-port=80/tcp firewall-cmd --reload另外,如果启动docker时日志里提示overlay2驱动不支持,重点检查内核模块和挂载参数。overlay模块加载好后,再看/和/data的挂载是否支持overlay。xfs文件系统如果开启了某些特殊挂载选项,也会影响overlay2驱动。
5.3 磁盘被容器日志撑爆
容器跑一段时间后磁盘满了,这个问题在离线环境更隐蔽,因为没有监控系统帮你提前发现。根因一般是默认json-file日志驱动不会限制文件大小,日志文件无限增长。
我用daemon.json里的log-opts限制日志轮转,上面已经写过max-size和max-file。但注意,这个配置只对新建容器生效,已经存在的容器改配置后不会自动收缩,需要条件重建。所以最稳妥的做法是在daemon.json刚配好、还没启动业务容器之前就加上日志限制,别等出事了再补。
磁盘不充足的机器,建议把data-root和日志目录都放到大分区上,镜像tar包删除前确认已经加载进了docker,别删早了镜像没load进去,又要回联网机器重新搞一套。
5.4 离线环境下的docker info警告
装完后顺手跑一下docker info,看看警告信息。离线环境下常见两类警告:
一类是警告说没有配置registry mirror,某些pull请求会直接请求官方仓库。这在内网环境是正常的,因为我们本来就打算离线导入,不需要理会。
另一类是存储驱动警告,提示overlay2不被当前内核支持。这个时候想想是不是之前环境检查漏了overlay模块,或者内核实在老得无法支持overlay2。老内核确实跑不动overlay2的话,可以尝试vfs驱动,但性能和镜像占用都很难看,这种机器我建议干脆别上docker,或者升级内核再考虑。
5.5 我的几条避坑心得
做多了离线部署,慢慢总结出几条习惯,这里一次性分享出来:
- 离线rpm目录永远用repotrack整依赖,不用yumdownloader,也不要手工rpm -ivh --nodeps强装。后者虽然能装上,但服务跑起来后缺依赖的隐患会让人痛不欲生;
- 内网机器的时间要校准,镜像tar包导入虽然不校验时间,但容器内应用经常依赖系统时间,时间错乱会排查很久;
- 把离线包做成一个包含版本号的标准目录,比如docker-20.10.24-centos7-x86_64/,传给别人或者过几个月自己回看都清爽;
- 每台内网机器装完docker后,拍一份docker info和docker version的输出存档,出问题好对比。
离线装docker这件事,第一次做会觉得很麻烦,主要是依赖和网络的问题在捣乱。但只要把联网准备阶段的功课做足,把版本选型、依赖收集、镜像导出都固化成一个流程,后面每套内网环境部署其实都大同小异。我个人现在做离线安装,最大的体会是前期的材料清单和版本匹配比内网机器上的安装命令更值得花时间,目录一乱、版本一错,往往要来回折腾好几趟。如果你也正要处理CentOS 7的离线环境,按这个流程走一遍,应该能少踩不少坑。