ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

内网离线安装Docker与docker-compose全流程及避坑指南

内网离线安装Docker与docker-compose全流程及避坑指南

简介:这份资源面向需要在无外网环境下部署容器服务的内网运维与开发人员,提供一套完整的离线安装方案,解决服务器无法访问公网、无法通过 yum 在线拉取 Docker 组件的痛点。压缩包共 22 个文件,约 120.91MB,以 20 个 rpm 依赖包为主,涵盖 docker-ce、docker-ce-cli、containerd.io、container-selinux、fuse-overlayfs、slirp4netns 等核心组件及 python、policycoreutils 等前置依赖,另附 1 个 docker-compose-linux-x86_64 二进制文件与 1 个 install.sh 一键安装脚本,覆盖从依赖补齐到服务启动的完整链路。已有 4706 人学习下载,适合 CentOS 7 环境下快速完成离线部署。读者可直接执行脚本完成安装,省去逐条排查依赖版本与顺序的繁琐过程,同时借助脚本中的命令逻辑理解各 rpm 包的作用与安装次序,为后续容器编排与镜像管理打好基础。

1. 内网离线装 Docker:为什么你每次都得重装一遍

很多做交付的工程师都有过这种经历:客户现场是纯内网,没有外网出口,连个 yum 源都连不上,但项目又必须跑在容器里。你带着笔记本进去,发现目标机器上 Docker 没装,或者版本太老,而客户的安全策略不允许你临时开外网。这时候唯一能依赖的就是你提前准备好的离线安装包。

这个场景在金融、能源、制造行业的私有化部署里非常普遍。Docker 和 docker-compose 的离线安装本身不复杂,但坑特别多:依赖包版本对不上、systemd 单元文件路径写错、compose 插件和独立二进制的区别搞混、SELinux 和防火墙在装完之后才出来捣乱。我见过太多人第一次装完以为成功了,结果docker run hello-world直接报错,又得从头排查。

这篇内容面向的是需要在内网环境完成容器运行时部署的工程师,不管你是第一次做离线交付,还是已经装过几次但总在某个环节翻车,下面的步骤和参数说明都可以直接抄作业。整篇会从包的选择开始,到二进制安装、systemd 配置、compose 部署,再到实际验证和排错,把每个环节的边界条件讲清楚。

2. 离线包怎么选:二进制、rpm 和 deb 的取舍

2.1 三种分发形式的适用边界

Docker 官方提供的离线安装方式主要有三种:静态二进制包、rpm 包、deb 包。选哪种不取决于你习惯用什么,而取决于目标机器的发行版和包管理策略。

静态二进制包(docker-<version>.tgz)是最通用的方式。它不依赖包管理器,解压后把二进制文件放到/usr/bin就行。适合目标机器发行版不确定、或者客户不允许你用包管理器改动系统的情况。缺点是 systemd 单元文件要自己写,升级也得手动替换二进制。

rpm 包适合 CentOS、RHEL、Fedora、Rocky Linux 这类系统。你需要下载containerd.io、docker-ce、docker-ce-cli、docker-compose-plugin这几个包,用yum localinstall或rpm -ivh安装。好处是依赖关系由包管理器处理,systemd 单元文件自动就位。坑在于版本必须和系统自带的依赖匹配,比如containerd.io的版本和docker-ce有对应关系,下错了会报依赖冲突。

deb 包适合 Ubuntu、Debian 系列,逻辑和 rpm 类似,用dpkg -i安装。需要注意的是 Ubuntu 不同大版本对 Docker 版本的支持范围不同,比如 Ubuntu 18.04 上装太新的 Docker 可能会缺 libc 版本。

我一般会优先确认目标机器的发行版和版本号,然后去 Docker 官方下载页找对应版本的离线包。如果客户机器是 CentOS 7,我会直接下 rpm 包;如果是那种定制过的国产化系统,静态二进制更稳妥。

2.2 下载哪些文件、怎么带进去

以静态二进制方式为例,你需要准备的文件清单如下:

文件用途备注
docker- .tgzDocker 引擎和 CLI 二进制从官方下载页获取
docker-compose-linux-x86_64Compose 独立二进制如果不用插件方式
containerd.servicecontainerd 的 systemd 单元二进制包内已含
docker.serviceDocker 的 systemd 单元二进制包内已含

如果用 rpm 方式,需要下载的文件更多:

# 以 CentOS 7 + Docker 24.0.7 为例,需要下载以下 rpm 包 containerd.io-1.6.25-3.1.el7.x86_64.rpm docker-ce-24.0.7-1.el7.x86_64.rpm docker-ce-cli-24.0.7-1.el7.x86_64.rpm docker-buildx-plugin-0.11.2-1.el7.x86_64.rpm docker-compose-plugin-2.23.0-1.el7.x86_64.rpm

这些包之间有版本对应关系,不能随便混搭。docker-ce24.0.7 要求containerd.io不低于 1.6.4,docker-compose-plugin的版本也要和 Docker 引擎兼容。我一般会去下载页看同一个发布批次里的文件,避免跨版本组合。

注意:如果目标机器是 aarch64 架构,所有包都要换成对应的 aarch64 版本,x86_64 的包装不上。

把文件带进内网的方式取决于客户的安全策略,常见做法是用刻录光盘、加密 U 盘,或者通过客户提供的文件摆渡系统。这一步没有技术含量,但一定要提前确认文件完整性,我习惯在带进去之前先算一遍 sha256,进去之后再校验一次。

3. 二进制安装全流程:从解压到 systemd 拉起

3.1 解压、放置二进制和配置 containerd

假设你已经把docker-24.0.7.tgz带到了目标机器上,接下来的操作按顺序执行。

# 解压二进制包 tar -xzvf docker-24.0.7.tgz # 把二进制文件复制到系统路径 sudo cp docker/* /usr/bin/ # 确认关键二进制已就位 which dockerd which docker which containerd which runc

解压后docker/目录里包含dockerd、docker、containerd、containerd-shim、runc、ctr、docker-init等文件。全部复制到/usr/bin/之后,系统就能找到这些命令了。

接下来配置 containerd。containerd 是 Docker 的底层运行时,Docker 24 之后默认使用 containerd 来管理容器生命周期。需要生成默认配置文件:

# 生成 containerd 默认配置 sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml # 修改 cgroup 驱动为 systemd,与 Docker 保持一致 sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

SystemdCgroup这个参数非常关键。如果 containerd 用 cgroupfs 而 Docker 用 systemd,或者反过来,容器启动时会出现资源限制不生效、容器无法正常退出的问题。我一般会强制两边都用 systemd,避免玄学问题。

3.2 编写 systemd 单元文件并启动服务

二进制安装方式不会自动创建 systemd 单元文件,需要手动写。先创建 containerd 的:

sudo tee /etc/systemd/system/containerd.service <<'EOF' [Unit] Description=containerd container runtime Documentation=https://containerd.io After=network.target local-fs.target [Service] ExecStartPre=-/sbin/modprobe overlay ExecStart=/usr/bin/containerd Type=notify Delegate=yes KillMode=process Restart=always RestartSec=5 LimitNPROC=infinity LimitCORE=infinity LimitNOFILE=infinity TasksMax=infinity OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target EOF

ExecStartPre加载 overlay 内核模块,这是 Docker 存储驱动 overlay2 的前提。Delegate=yes允许 containerd 管理自己的 cgroup 子树。LimitNOFILE=infinity避免容器多的时候文件描述符不够用。

然后创建 Docker 的 systemd 单元:

sudo tee /etc/systemd/system/docker.service <<'EOF' [Unit] Description=Docker Application Container Engine Documentation=https://docs.docker.com After=network-online.target docker.socket firewalld.service containerd.service Wants=network-online.target Requires=docker.socket containerd.service [Service] Type=notify ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock ExecReload=/bin/kill -s HUP $MAINPID TimeoutSec=0 RestartSec=2 Restart=always StartLimitBurst=3 StartLimitInterval=60s LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity TasksMax=infinity Delegate=yes KillMode=process OOMScoreAdjust=-500 [Install] WantedBy=multi-user.target EOF

ExecStart里的-H fd://表示通过 systemd socket 激活方式监听,--containerd指定 containerd 的 socket 路径。After和Requires确保 containerd 先于 Docker 启动。

还需要一个 socket 单元:

sudo tee /etc/systemd/system/docker.socket <<'EOF' [Unit] Description=Docker Socket for the API [Socket] ListenStream=/var/run/docker.sock SocketMode=0660 SocketUser=root SocketGroup=docker [Install] WantedBy=sockets.target EOF

创建 docker 用户组,然后把当前用户加进去,这样不用 sudo 也能执行 docker 命令:

sudo groupadd docker 2>/dev/null || true sudo usermod -aG docker $USER

最后重新加载 systemd 并启动服务:

sudo systemctl daemon-reload sudo systemctl enable containerd sudo systemctl start containerd sudo systemctl enable docker sudo systemctl start docker

启动后检查状态:

sudo systemctl status containerd sudo systemctl status docker docker version docker info

docker version会分别显示 Client 和 Server 的版本信息,如果 Server 部分报错,说明 dockerd 没起来,需要看journalctl -u docker的日志。

4. docker-compose 离线部署:独立二进制还是插件

4.1 两种形态的区别和选择

docker-compose 在 v2 之后有两种使用方式:一种是作为 Docker CLI 的插件,命令是docker compose(中间有空格);另一种是独立的二进制文件,命令是docker-compose(中间有横杠)。两者功能基本一致,但安装方式和路径不同。

插件方式需要把二进制放到~/.docker/cli-plugins/或/usr/local/lib/docker/cli-plugins/目录下,Docker CLI 启动时会自动加载。独立二进制方式就是把文件放到/usr/local/bin/docker-compose并赋予执行权限。

我一般优先用插件方式,因为docker compose是官方推荐的未来形态,而且和 Docker CLI 的集成更好。但如果目标机器上的 Docker 版本较老(低于 20.10),可能不支持插件机制,那就只能用独立二进制。

4.2 安装步骤和版本对齐

插件方式的安装:

# 创建插件目录 sudo mkdir -p /usr/local/lib/docker/cli-plugins # 复制二进制并赋予执行权限 sudo cp docker-compose-linux-x86_64 /usr/local/lib/docker/cli-plugins/docker-compose sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose # 验证 docker compose version

独立二进制方式的安装:

sudo cp docker-compose-linux-x86_64 /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose docker-compose version

版本对齐方面,docker-compose v2.23 对应 Docker 24.0 及以上,v2.20 对应 Docker 23.0。如果版本差距太大,docker compose命令可能无法识别某些 compose 文件语法。我一般会让 compose 的版本和 Docker 引擎的版本保持在同一个发布周期内。

注意:如果之前装过 python 版的 docker-compose(v1),要先卸载掉,否则/usr/local/bin/docker-compose可能被旧版本占用,导致命令行为异常。

4.3 用 compose 跑一个验证服务

装完之后不要只看版本号就结束,要实际跑一个多容器服务验证网络和存储是否正常。写一个最简单的 compose 文件:

# docker-compose-test.yml services: web: image: nginx:alpine ports: - "8080:80" depends_on: - redis redis: image: redis:alpine command: redis-server --appendonly yes volumes: - redis-data:/data volumes: redis-data:

这个文件定义了两个服务:nginx 和 redis,它们在同一个默认网络里,web 可以通过服务名redis访问 redis。redis 挂了一个命名卷做持久化。

启动命令:

docker compose -f docker-compose-test.yml up -d

启动后验证:

# 查看服务状态 docker compose -f docker-compose-test.yml ps # 测试 web 是否能访问 redis docker compose -f docker-compose-test.yml exec web ping -c 2 redis # 测试端口映射 curl -s http://localhost:8080 | head -5 # 清理 docker compose -f docker-compose-test.yml down -v

如果ping redis能通,说明 compose 创建的自定义网络和 DNS 解析正常。如果 curl 返回 nginx 欢迎页,说明端口映射正常。down -v会同时删除容器和命名卷,避免残留。

5. 避坑排查:离线安装最常见的五个翻车点

5.1 坑一:dockerd 启动报错 “failed to start daemon: error initializing graphdriver”

现象是systemctl start docker之后状态变成 failed,journalctl -u docker里看到 graphdriver 初始化失败。

原因通常是 overlay2 存储驱动不可用。可能是内核模块没加载,也可能是文件系统不支持 overlay2,比如某些老版本的 ext4 或者 xfs 在 ftype=0 的情况下不支持。

解决方式是先确认内核模块:

lsmod | grep overlay modprobe overlay

如果模块加载了还是报错,检查/var/lib/docker所在文件系统的类型:

df -T /var/lib/docker xfs_info /var/lib/docker | grep ftype

如果是 xfs 且 ftype=0,需要重新格式化文件系统或者换一个支持 overlay2 的分区。临时方案是在/etc/docker/daemon.json里把存储驱动改成vfs,但性能会差很多,只适合测试环境。

5.2 坑二:容器内无法解析 DNS

现象是容器能启动,但ping外部域名失败,或者apt update卡住。

原因是宿主机的/etc/resolv.conf里配置的 DNS 服务器在容器网络里不可达,或者 Docker 默认的 DNS 配置被覆盖了。

解决方式是在/etc/docker/daemon.json里显式指定 DNS:

{ "dns": ["223.5.5.5", "114.114.114.114"] }

改完之后systemctl restart docker。如果内网有自建的 DNS 服务器,就填内网地址。注意这个配置会影响所有容器,如果某个容器需要不同的 DNS,可以在 compose 文件里单独指定。

5.3 坑三:docker compose 命令找不到插件

现象是docker compose version报错 “docker: 'compose' is not a docker command”。

原因是插件二进制没有放到正确的目录,或者文件名不对。Docker CLI 在/usr/local/lib/docker/cli-plugins/和~/.docker/cli-plugins/里查找插件,文件名必须是docker-compose,不能是docker-compose-linux-x86_64。

解决方式是确认路径和文件名:

ls -l /usr/local/lib/docker/cli-plugins/ # 应该看到 docker-compose 这个文件,且有执行权限

如果路径对但还不行,检查 Docker CLI 的版本是否支持插件机制。docker version里 Client 版本低于 20.10 的话,插件机制可能不生效,需要改用独立二进制方式。

5.4 坑四:SELinux 导致挂载卷权限拒绝

现象是容器启动时报 “Permission denied”,尤其是挂载了宿主机目录的时候。

原因是 SELinux 处于 enforcing 模式,容器进程没有权限访问宿主机上的文件。

解决方式有两种:临时把 SELinux 设为 permissive 模式,或者在挂载时加:z或:Z标签:

volumes: - ./data:/app/data:z

:z表示多个容器可以共享这个卷,:Z表示私有。如果不想改 compose 文件,也可以直接关掉 SELinux:

setenforce 0 # 永久生效需要改 /etc/selinux/config

生产环境不建议直接关,优先用:z标签解决。

5.5 坑五:防火墙规则导致容器间通信失败

现象是同一台机器上的两个容器无法通过服务名互相访问,或者容器无法访问宿主机上的服务。

原因是 firewalld 或 iptables 规则拦截了 Docker 创建的网桥流量。Docker 在启动时会自动添加 iptables 规则,但如果 firewalld 在 Docker 之后启动,可能会覆盖这些规则。

解决方式是重启 Docker 让规则重新生效:

systemctl restart docker

或者把 Docker 的网桥接口加入 firewalld 的信任区域:

firewall-cmd --permanent --zone=trusted --add-interface=docker0 firewall-cmd --reload

如果目标机器用的是 iptables 而不是 firewalld,检查iptables -L FORWARD的默认策略是不是 DROP,如果是,需要改成 ACCEPT 或者添加 Docker 相关的放行规则。

6. 验证与固化:把离线安装变成可重复的交付流程

装完一次不算完,交付场景里你需要在多台机器上重复同样的操作。我一般会把整个流程固化成一个脚本包,包含二进制文件、systemd 单元、daemon.json 模板和一个安装脚本。安装脚本里做三件事:检查架构和发行版、按顺序执行安装步骤、跑一个最小验证。

验证环节我习惯用docker run --rm hello-world加上一个 compose 的多容器测试。hello-world 验证引擎基本功能,compose 测试验证网络、存储和编排能力。两个都过了,才算这次交付的运行时环境是可靠的。

还有一个容易忽略的点是 daemon.json 的配置。离线环境里我一般会加上这些参数:

{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" }, "storage-driver": "overlay2", "live-restore": true, "default-address-pools": [ { "base": "172.30.0.0/16", "size": 24 } ] }

log-opts限制日志大小,避免容器日志把磁盘写满。live-restore让 Docker 守护进程重启时容器继续运行,减少升级时的停机时间。default-address-pools自定义容器网络的地址段,避免和客户内网已有网段冲突。这个参数在客户内网环境里特别重要,我遇到过好几次 Docker 默认的 172.17.0.0/16 和客户内网网段撞了,导致容器网络不通。

从那以后我每次进内网交付之前,都会先问清楚客户的网段规划,然后把default-address-pools改成不冲突的地址段再装。这个习惯帮我省了很多事后排查的时间。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进