ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

CentOS 7 离线安装 Docker RPM 包:版本对齐与启动故障排查

CentOS 7 离线安装 Docker RPM 包:版本对齐与启动故障排查

简介:本资源是一套专为CentOS 7系统定制的Docker离线安装软件包集合,面向Linux运维工程师、容器技术初学者及需在无网络环境部署Docker的系统管理员。资源完整覆盖Docker 1.12.6核心组件及其依赖,包括docker主程序、客户端、OCI工具链(skopeo、oci-umount、oci-register-machine)、容器存储与SELinux策略支持模块等,可实现一键式本地yum源搭建与离线安装。压缩包共16个文件,含9个关键rpm安装包(如docker-1.12.6、docker-client、container-selinux)、3个gz格式元数据文件(primary.xml.gz等)、3个bz2格式仓库索引文件及1个repomd.xml,总大小19.27MB,结构符合标准RPM仓库规范,便于直接配置为本地repo使用。目前已有382人学习下载,读者可直接获取开箱即用的CentOS适配版Docker安装体系,省去手动下载依赖、解决版本冲突与构建本地源的繁琐过程。

1. Docker RPM 包离线安装:别急着yum install docker,CentOS 7 上用错版本直接卡死在dockerd启动阶段

你手头刚拿到一个叫docker.rpm.tar的压缩包,里面全是.rpm文件,没给Dockerfile,也没提docker-ce还是docker-ee——这种场景太典型了:某高校实验室的内网服务器、某公司生产环境的隔离区、某嵌入式设备的定制系统,压根连外网都没有。这时候你要是照着官网文档curl -fsSL https://get.docker.com | sh一通操作,结果只会看到终端卡在[INFO] : Starting docker service...十分钟不动,systemctl status docker显示failed to load Moby Linux kernel modules或者更玄学的Error initializing network controller: error obtaining controller instance: failed to create NAT chain: iptables failed: iptables --wait -t nat -N DOCKER。这不是 Docker 装错了,是 RPM 包版本和内核模块、container-selinux、runc三者没对齐。我踩过最深的坑是:用docker-20.10.24-3.el7.x86_64.rpm配kernel-3.10.0-1160.el7.x86_64,结果dockerd启动时疯狂报overlay2: unable to mount overlay, no such device,查日志才发现overlay模块根本没加载进内核——而这个模块在 CentOS 7.9 之后才默认启用。本文就带你从docker.rpm.tar解包开始,一层层拆解该装哪些 RPM、顺序怎么排、依赖怎么补、启动失败后看哪几行日志能秒定位。适合所有需要在无网/弱网/高安全策略环境下部署 Docker 的运维和开发同学。


2. 解包与识别:先看清docker.rpm.tar里到底藏了什么版本、哪些组件、缺不缺关键依赖

拿到docker.rpm.tar,第一反应不是tar -xf就完事。这个包极大概率是某位同事或上游构建系统打包的“自定义合集”,里面可能混着 CE(Community Edition)和旧版 EE(Enterprise Edition)的残余,也可能漏了container-selinux或libseccomp。必须先做静态识别,避免后续安装时rpm -ivh报一堆Failed dependencies然后放弃治疗。

2.1 解压并列出所有 RPM 文件及其完整包名与架构

# 创建干净工作目录,避免污染当前环境 mkdir -p /tmp/docker-rpm-install && cd /tmp/docker-rpm-install # 解压 tar 包(注意:不是 .tar.gz,是 .tar,所以不用 z 参数) tar -xf /path/to/docker.rpm.tar # 列出所有 RPM,并用 rpm -qpi 提取关键元数据:名称、版本、架构、依赖列表(前10行足矣) for rpm in *.rpm; do echo "=== $rpm ===" rpm -qpi "$rpm" 2>/dev/null | grep -E '^(Name|Version|Release|Architecture|Requires:)' | head -n 10 echo done > rpm_inventory.log

提示:rpm -qpi不会安装任何东西,纯读取包头信息。输出里重点关注Name:字段是否含docker-ce(社区版)、docker-engine(已废弃)、docker-io(极老版),以及Requires:行里有没有container-selinux、libseccomp、iptables、pigz(部分新版 RPM 依赖它解压 rootfs)。如果Requires:里出现docker-ce-cli,说明这个包是分离式结构(CLI 和 daemon 分开),不能只装 daemon。

2.2 构建 RPM 依赖图谱:识别核心四件套与可选组件

Docker 在 RHEL/CentOS 7 上稳定运行,最低必须安装以下四个 RPM,缺一不可,且版本必须严格匹配(同一小版本号,如20.10.24):

RPM 名称作用是否强制
docker-ce或docker-engineDocker daemon 主程序(dockerd)✅ 必装
docker-ce-clidocker命令行工具(docker run,docker ps)✅ 必装(即使只跑 daemon,CLI 也用于调试)
container-selinuxSELinux 策略模块,控制容器进程安全上下文(CentOS 7 默认启用 SELinux)✅ 必装(否则dockerd启动即 fail)
libseccompseccomp-bpf 系统调用过滤库,Docker 安全沙箱基石✅ 必装(< 2.3.0 版本会导致runc启动失败)

其他常见但非强制的 RPM:

  • docker-ce-rootless-extras:仅当需 rootless 模式时安装;
  • docker-compose-plugin:替代旧版docker-compose(v2+ 插件化);
  • podman相关包:与 Docker 冲突,绝对不要共存;
  • docker-ce-debuginfo:仅调试用,生产环境禁用。

2.3 版本对齐检查:为什么docker-ce-20.10.24必须配container-selinux-2.195?

这是血泪经验:container-selinux的策略文件(.cil)和docker-ce的daemon.json中selinux-enabled: true是强耦合的。例如:

  • docker-ce-20.10.24编译时依赖container-selinux >= 2.195-1;
  • 若你装的是container-selinux-2.107-1(CentOS 7.6 自带),dockerd启动时会报SELinux: Could not load policy file /etc/selinux/targeted/policy/policy.31,因为新 Docker 生成的容器进程标签(如system_u:system_r:container_t:s0:c100,c200)在旧策略里未定义;
  • 同理,libseccomp-2.5.4-1是docker-ce-20.10.24的硬性要求,低于2.3.0会导致runc: fork/exec /usr/bin/runc: no such file or directory(实际是runc因缺少 seccomp 符号而无法加载)。

验证方法(在解包目录执行):

# 找出所有 docker-ce* 包的版本号 rpm -qpi docker-ce-*.rpm 2>/dev/null | grep "Version\|Release" | head -n 2 # 找出 container-selinux 包的版本(注意:包名可能是 container-selinux、policycoreutils-python、selinux-policy-targeted) ls container-selinux-*.rpm policycoreutils-python-*.rpm 2>/dev/null # 查看 libseccomp 版本(重点看 Release 字段是否 ≥ 1) rpm -qpi libseccomp-*.rpm 2>/dev/null | grep -E "(Version|Release)"

若发现版本不匹配(如docker-ce-20.10.24但container-selinux-2.107),立刻停止安装。你需要额外下载对应版本的container-selinux(推荐从 https://vault.centos.org/7.9.2009/extras/x86_64/Packages/ 找container-selinux-2.195-1.el7_9.noarch.rpm),而不是强行--force。


3. 安装顺序与参数:rpm -ivh不是万能钥匙,--nodeps是后悔药,--replacepkgs是续命针

RPM 安装顺序错误,比版本不匹配更致命。比如先装docker-ce-cli再装docker-ce,cli会因找不到dockerdsocket 而静默失败;或者container-selinux装在docker-ce之后,SELinux 策略加载晚于 daemon 启动,导致容器进程被拒绝访问/proc。必须按「基础依赖 → 安全模块 → 核心服务 → 工具链」四级顺序安装,并用精准参数规避冲突。

3.1 第一步:安装基础依赖(libseccomp和iptables)

# 先装 libseccomp(它是 runc 的底层依赖,必须最早) rpm -ivh libseccomp-*.rpm # 检查是否安装成功且版本正确 rpm -q libseccomp # 输出应为:libseccomp-2.5.4-1.el7.x86_64(或 ≥2.3.0) # 再装 iptables(Docker 网络驱动依赖 iptables-legacy 或 iptables-nft) rpm -ivh iptables-*.rpm # 验证 iptables 命令可用且能列规则 iptables -L -n | head -n 5

参数说明:-i(install)、-v(verbose)、-h(hash 进度条)。此处不用--force,因为基础依赖冲突通常意味着系统已有更高版本,强行覆盖可能破坏系统网络管理。

3.2 第二步:安装 SELinux 安全模块(container-selinux)

# 关键:必须用 --replacefiles,因为 container-selinux 会覆盖 /usr/share/selinux/targeted/container.cil rpm -ivh --replacefiles container-selinux-*.rpm # 加载新策略(此步必须执行,否则策略不生效) sudo semodule -i /usr/share/selinux/packages/container.pp # 验证策略是否加载成功 semodule -l | grep container # 正常输出:container 2.195.0

逻辑说明:--replacefiles强制替换同名文件,避免因/usr/share/selinux/targeted/container.cil已存在而跳过更新。semodule -i是加载编译后的策略包(.pp),不是.cil源文件。如果semodule命令不存在,说明policycoreutils-python未装,需从docker.rpm.tar中找出并安装。

3.3 第三步:安装 Docker Daemon(docker-ce)

# 使用 --nodeps 仅当确认依赖已手动满足(如已装好 container-selinux 和 libseccomp) # 否则 rpm 会因 "container-selinux is needed" 报错并退出 rpm -ivh --nodeps docker-ce-*.rpm # 启动 dockerd 并观察日志(不加 --enable,先手动启) sudo systemctl start docker sudo journalctl -u docker -f --since "1 minute ago"

参数说明:--nodeps是双刃剑。它跳过 RPM 头部的 Requires 检查,但不跳过运行时依赖。如果container-selinux没装或策略未加载,dockerd仍会启动失败,只是错误从 rpm 安装阶段延后到运行时。因此--nodeps只应在你100% 确认所有依赖已就位时使用。

3.4 第四步:安装 CLI 工具与验证(docker-ce-cli)

# 安装 CLI(此时 dockerd 应已在运行,socket 可达) rpm -ivh docker-ce-cli-*.rpm # 验证 CLI 是否能通信 docker version --format '{{.Server.Version}}' # 应输出:20.10.24 # 测试拉取并运行一个最小镜像(不联网,用本地 busybox) docker run --rm busybox echo "Hello from offline Docker!"

注意:docker-ce-cli安装后无需重启docker服务,它只是客户端二进制。但如果dockerd未运行,docker version会报Cannot connect to the Docker daemon,此时要回溯第二步检查container-selinux加载状态。


4. 常见问题排查:五条真实翻车记录,每条都来自某次凌晨三点的线上救火

安装 Docker RPM 包最让人崩溃的,不是报错,而是报错信息完全不指向真实原因。下面这五条,是我从某跨平台系统部署日志里扒出来的高频问题,按「现象 → 原因 → 解决」还原,不讲虚的。

4.1 现象:systemctl start docker后立即退出,journalctl -u docker显示level=error msg="failed to load listeners: no sockets found"

原因:dockerd配置文件/etc/docker/daemon.json中指定了hosts(如"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]),但docker-ceRPM 包默认不创建该文件,空配置导致监听器初始化失败。
解决:删除或重命名/etc/docker/daemon.json,让dockerd用默认配置(只监听unix:///var/run/docker.sock);如需 TCP 监听,先确保防火墙放行端口,再手动创建配置。

4.2 现象:docker run hello-world报Error response from daemon: failed to create endpoint ... permission denied

原因:SELinux 策略未生效。semodule -l | grep container返回空,说明container-selinux包虽装了,但.pp策略未加载。
解决:执行sudo semodule -i /usr/share/selinux/packages/container.pp,然后sudo systemctl restart docker。

4.3 现象:docker info输出中Security Options不含seccomp,且runc --version报command not found

原因:docker-ceRPM 包本身不包含runc二进制,它依赖系统 PATH 中的runc。而runc通常由containerd.io包提供,但docker.rpm.tar里很可能漏了它。
解决:从 https://github.com/containerd/containerd/releases 下载对应架构的containerd.io-*.rpm(如containerd.io-1.6.32-3.1.el7.x86_64.rpm),rpm -ivh安装后再重启docker。

4.4 现象:docker images卡住超过 2 分钟,strace -p $(pgrep dockerd)显示进程在openat(AT_FDCWD, "/var/lib/docker/image/overlay2/repositories.json", ...)循环

原因:/var/lib/docker目录权限错误。docker-ceRPM 安装后,该目录属主应为root:root,但若之前手动创建过,可能属主是1001:1001(旧版 Docker 用户 ID)。
解决:sudo chown -R root:root /var/lib/docker,然后sudo systemctl restart docker。

4.5 现象:docker run -it ubuntu:20.04 /bin/bash报standard_init_linux.go:228: exec user process caused: exec format error

原因:宿主机 CPU 架构与镜像不匹配。docker.rpm.tar中的docker-ce是x86_64,但你正在aarch64(ARM64)服务器上运行,ubuntu:20.04镜像是 x86_64 架构,无法在 ARM 上执行。
解决:确认宿主机架构uname -m,下载对应架构镜像(如ubuntu:20.04-arm64v8),或改用busybox(多架构支持好)测试。


5. 启动验证与最小化测试:用三条命令确认 Docker 真正就绪,而非假性存活

装完不验证,等于白装。很多同学systemctl status docker显示active (running)就以为万事大吉,结果一跑容器就崩。真正的验证必须穿透到容器生命周期、网络栈、存储驱动三层,用最轻量的命令触发核心路径。以下三条,是我每次部署后必跑的「黄金三连」,少一条都算没通关。

5.1 验证容器运行时:docker run --rm -v /tmp:/host-tmp alpine touch /host-tmp/test-from-container

这条命令同时验证四件事:

  • OCI 运行时:runc能否成功 fork/exec;
  • 挂载传播:/tmp绑定挂载是否双向可见(touch后ls /tmp/test-from-container应存在);
  • 命名空间隔离:容器内ls /不应看到宿主机根目录全貌(alpine 镜像只有精简文件系统);
  • 存储驱动:overlay2能否正常 commit layer(--rm触发 cleanup)。
# 执行并检查宿主机 /tmp docker run --rm -v /tmp:/host-tmp alpine touch /host-tmp/test-from-container ls -l /tmp/test-from-container # 应输出:-rw-r--r--. 1 root root 0 ... /tmp/test-from-container # 清理 rm /tmp/test-from-container

为什么不用hello-world?hello-world镜像本质是exit 0,不触发挂载、网络、存储任何子系统。而alpine+touch会真实调用mount(2)、openat(2)、unlinkat(2),暴露overlay2初始化失败、seccomp策略拦截等深层问题。

5.2 验证网络栈:docker run --rm --network host alpine ip a | grep "inet " | grep -v "127.0.0.1"

--network host让容器共享宿主机网络命名空间,ip a输出应与宿主机ip a完全一致(除 lo 外)。这验证:

  • CNI 插件加载:Docker 默认用docker0bridge,但host网络模式绕过 CNI,直通内核协议栈;
  • iptables 规则就绪:dockerd启动时会写DOCKER-USER链,ip a能执行说明 netfilter 模块可用;
  • 容器内核能力:CAP_NET_ADMIN是否被正确授予(否则ip a报Operation not permitted)。
# 获取宿主机主网卡 IP(假设是 eth0) HOST_IP=$(ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') # 在容器内执行 ip a,提取非 127 的 inet 行 CONTAINER_IP=$(docker run --rm --network host alpine ip a | grep "inet " | grep -v "127.0.0.1" | head -n1 | awk '{print $2}' | cut -d/ -f1) # 对比 if [ "$HOST_IP" = "$CONTAINER_IP" ]; then echo "✅ Network stack OK: host and container share same IP" else echo "❌ Network mismatch: host=$HOST_IP, container=$CONTAINER_IP" fi

5.3 验证存储驱动:docker run --rm -v /var/lib/docker:/docker-lib alpine ls -l /docker-lib/overlay2/ | head -n 5

/var/lib/docker/overlay2/是overlay2驱动的数据目录。ls能列出子目录(如l/,diff/,merged/)证明:

  • 文件系统支持:宿主机 ext4/xfs 支持d_type=1(目录项类型),否则overlay2退化为overlay(性能差且不支持多层);
  • 磁盘空间充足:/var/lib/docker所在分区有足够 inode 和 block;
  • SELinux 上下文正确:/var/lib/docker/overlay2/目录的unconfined_u:object_r:container_file_t:s0上下文已设置(ls -Z可查)。
# 检查 overlay2 目录是否存在且可读 if [ -d "/var/lib/docker/overlay2" ]; then echo "✅ overlay2 directory exists" # 检查是否启用 d_type(关键!) xfs_info / | grep -q "ftype=1" || tune2fs -l $(df / | tail -1 | awk '{print $1}') | grep -q "Filesystem features.*extent.*flex_bg" && echo "✅ d_type enabled on filesystem" else echo "❌ overlay2 directory missing — check storage-driver config" fi

从那以后我每次部署 Docker RPM 包,都强制走一遍这三条命令:alpine touch、host network ip a、overlay2 ls。不是为了炫技,是这三条像三把探针,分别捅进运行时、网络、存储三个黑匣子,只要有一条不通,后面跑业务镜像必然翻车。线上环境没有“差不多”,只有“全通”或“重装”。希望帮到你。

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

RELATED READING

延伸阅读

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