尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Rocky Linux 9离线部署Docker:从依赖打包到私有仓库搭建全流程

Rocky Linux 9离线部署Docker:从依赖打包到私有仓库搭建全流程 作为一个常年混迹在生产环境一线的运维我越来越觉得“离线部署”这四个字才是真正考验功底的地方。很多项目在联网环境下跑得飞起一到内网、保密网、专网或者机房物理隔离环境就原形毕露缺依赖、没镜像源、Docker装不上、yum install直接超时。Rocky Linux 9 作为 CentOS 停止维护后最稳的接班人选之一在离线场景下部署 Docker 更是很多人绕不开的硬仗。这篇内容我就以自己的真实操作经历为基础完整走一遍从联网机器打包依赖、到内网机器安装 Docker、再到把容器服务跑起来并开机自启的全过程。适合正在搞内网交付、私有化部署、或者给客户做离线环境实施的运维和开发同学参考。先说清楚这个方案解决的核心问题在一台完全无法访问外网的 Rocky Linux 9 服务器上不依赖任何在线仓库把 Docker 装好、镜像导进去、服务跑起来而且要保证后续重启不慌、日志能查、容器能管。整个过程我会拆成依赖打包、仓库搭建、Docker安装、镜像导入、服务编排、故障排查六个环节按顺序走一遍每一步都给出命令和关键参数的含义。1. 项目背景与整体思路拆解1.1 场景分析与需求界定离线部署这事看着简单其实第一步最容易翻车需求没搞清楚就开干。我得先问自己三个问题——目标机器是什么CPU架构目标机器的操作系统版本具体是哪个小版本宿主机上的软件和服务有哪些不能动Rocky Linux 9 生命周期内的小版本差异会导致内核和基础库不同比如从 9.0 到 9.4glibc 版本、systemd 版本都会有变化。你在一台 9.4 的机器上打包的 Docker 二进制拿到 9.0 的机器上可能因为 glibc 版本过低直接启动不了。所以我在做离线打包之前一定会让客户或者同事提供目标服务器的cat /etc/rocky-release和uname -m的输出宁可多确认一次也不要背着几十GB的物料到现场才发现全部白干。另一个容易忽视的点是“离线环境”的边界。真实项目里很多所谓离线环境并不是完全断网而是只能访问特定内网源、或者需要走审批流程开通临时访问。这直接影响我们的打包策略——如果允许短时联网我可以直接在目标机上dnf install然后配合dnf download缓存rpm包如果是物理隔离、完全不联网那必须提前准备好全部物料甚至要考虑把 YUM 仓库整个镜像同步到移动硬盘里。1.2 整体方案选型离线仓库 vs 镜像导出 vs 依赖打包我在实际操作中把离线部署 Docker 的路径分成三条按场景选第一内网YUM仓库方案。在有联网条件的机器上用dnf reposync把 Rocky Linux 9 的 BaseOS 和 AppStream 仓库同步到本地然后在目标内网搭建一个本地仓库服务器所有机器通过内网地址访问。这个方案适合机器数量多、后续还要装其他软件的场景一劳永逸但前期同步数据量很大BaseOSAppStream 全量同步通常得 30GB 往上。第二rpm依赖打包方案。只在目标机器上装 Docker 的话不需要同步整个仓库只要把 Docker 及其依赖的几十个 rpm 包全部下载下来拷贝到目标机器上dnf install *.rpm即可。这个方案是我个人最推荐的物料体积小、操作直接、还原速度快后续升级也方便。第三容器镜像 save/load 方案。这只解决 Docker 装好之后的镜像分发问题不解决 Docker 本身怎么装。用docker save把镜像打成 tar 包到内网用docker load导入。镜像文件通常很大建议在打包和传输时合理规划。本文主线的组合是“方案二 方案三”联网机器上下载 Docker rpm 依赖包和目标容器镜像离线机器上安装 Docker 再导入镜像。这个组合最灵活既能应对单机部署也能扩展成多机批量分发是离线场景下性价比最高的路径。2. 离线依赖打包在联网机器上准备全部物料2.1 确认架构与版本基线打包物料的机器不需要跟目标机器完全同型号但架构和小版本必须对齐。我一般先在打包机上跑这几个命令建立基线cat /etc/rocky-release uname -m rpm -q kernel如果打包机和目标机系统版本不一致最稳妥的做法是找一台与目标机器小版本一致的机器来打包。实在找不到至少保证大版本一致都是9.x然后尽量用较旧的小版本机器做打包机避免带出目标机器上不存在的、更高版本的依赖。架构问题更要命。x86_64 的 rpm 包不能装到 ARM64 的机器上。Docker 官方虽然提供多架构镜像但 rpm 包是分架构的。我见过有同事在 x86 机器上打包了一堆 rpm拿到国产化 ARM 服务器上折腾半天装不上后来才发现架构完全不对。所以打包第一步永远是uname -m把架构写进物料清单里。2.2 下载 Docker CE 及其依赖 rpm 包Docker 官方仓库在联网机器上配置好之后可以用dnf download把安装 Docker 所需的全部 rpm 包下载下来而不实际安装。这个命令的精髓在于--resolve参数它会自动解析依赖并把所有依赖包一起下载。先配置 Docker 官方仓库dnf install -y dnf-utils dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo然后同步 Rocky Linux 的 BaseOS 和 AppStream 仓库元数据dnf makecache接下来创建目录并下载全部依赖包mkdir -p /data/offline/rpms cd /data/offline/rpms dnf download --resolve docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin --alldeps --destdir/data/offline/rpms这里需要解释一下几个参数的作用。--resolve会自动分析依赖关系把所有需要的包全部下载--alldeps保证即使某些依赖已经下载过也会完整处理--destdir指定输出目录。我习惯把docker-buildx-plugin和docker-compose-plugin一起下载因为后面大概率会用到docker buildx和docker compose一次性备齐省的二次进场。下载完成后检查一下有没有遗漏ls /data/offline/rpms | wc -l rpm -qpR /data/offline/rpms/docker-ce-*.rpm | sort -urpm -qpR能列出 docker-ce 包的所有依赖需求逐条对照目录里的包是否都覆盖了。这步手动检查非常必要因为 dnf download 偶尔会因为源的问题漏掉某个依赖。2.3 获取容器镜像并保存为 tar 包Docker 本体搞定了接下来是镜像。在生产内网部署一个应用镜像怎么搞是另一个大坑。我常用的下载策略是在联网机器上先把镜像 pull 下来然后用docker save保存成 tar 文件docker pull nginx:1.26-alpine docker save nginx:1.26-alpine -o nginx-1.26-alpine.tar建议对多个镜像逐个 save避免把所有镜像塞进一个巨大的 tar 文件。分开保存的好处是内网导入时可以按需导入某个镜像损坏也不需要全部重新传。另外如果内网环境需要长期运行、后续还会加机器我强烈建议把 registry 镜像也打进去。registry:2是一个轻量级的镜像仓库服务大小不到 100MB到了内网先用它搭一个私有镜像仓库其他机器就能从这台机器拉镜像不需要每次都用 U 盘拷贝 tar 包。docker pull registry:2 docker save registry:2 -o registry-2.tar2.4 物料清单与校验值归档打包完成后光有文件还不够一定要生成校验值清单。我在现场吃过亏拷了十几个 rpm 包到内网安装时报包损坏原来 U 盘拷贝过程中文件出了问题。如果没有校验值你根本无法判断是拷贝坏了还是本来就这样。在打包机上生成校验清单cd /data/offline sha256sum rpms/*.rpm rpms.sha256 sha256sum images/*.tar images.sha256到了内网以后用sha256sum -c校验一遍再动手几秒钟的事能避免后续所有莫名其妙的麻烦。这一步永远不要跳过尤其是在用 U 盘、移动硬盘这些不可靠介质传输的时候。还有一个小建议镜像 tar 包里其实已经包含了镜像本身的 sha256 信息但那是文件内部校验我们生成的是针对 tar 文件的完整性校验两个维度不同不能互相替代。3. 目标服务器环境准备与离线仓库配置3.1 最小化安装检查与系统初始化到了目标离线机器上我习惯先做一轮系统体检按下面的顺序来cat /etc/rocky-release uname -a df -h free -h getenforce systemctl status firewalld重点看几个地方。磁盘空间要留够——Docker 的 overlay2 存储驱动会把镜像、容器分层数据放在/var/lib/docker再加上日志增长给 50GB 以上是比较踏实的。如果根分区空间紧张我一般把 Docker 数据目录挂载到独立的数据盘后面配置 daemon.json 时会讲到。SELinux 的状态需要特别注意。Rocky Linux 9 默认 SELinux 是 Enforcing 模式而 Docker 的 overlay2 存储驱动在较老版本上对 SELinux 支持不够完善可能导致容器内文件访问异常。虽然新版 Docker 已经解决了大部分问题但在离线环境里为了少折腾很多团队会直接把 SELinux 设为 Permissive。这个决策要看客户要求和安全合规要求如果必须保持 Enforcing那需要额外安装container-selinux和相关策略包这些依赖也要提前打进 rpm 清单里。3.2 搭建本地离线 YUM 仓库虽然我们已经把所有 rpm 包直接放到了机器上不需要再配一个仓库但实际项目中我还是建议顺手把本地 repo 建起来尤其是当机器数量不止一台时。把 rpm 包所在目录变成一个 YUM 源这样后续需要安装其他包时也能复用。先安装createrepo工具——这步需要提前把createrepo及其依赖也打进 rpm 目录里。cd /data/offline/rpms dnf install -y ./createrepo-*.rpm createrepo /data/offline/rpms然后创建本地仓库配置文件cat /etc/yum.repos.d/local-offline.repo EOF [local-offline] nameLocal Offline Repository baseurlfile:///data/offline/rpms enabled1 gpgcheck0 EOF把网上自带的 Rocky 官方 repo 全部禁用掉避免 yum 去访问外网导致超时卡死dnf config-manager --set-disabled Rocky-* dnf clean all dnf makecache注意gpgcheck0这个选项。在完全离线的环境下rpm 包是我们自己从官方源下载的完整性已经通过 sha256 校验过了所以关闭 GPG 校验问题不大。但如果客户有严格的安全要求应该把官方源的 GPG Key 也导出带进内网然后保持gpgcheck1。我通常在交付时会把两种配置都准备好现场再根据安全要求决定。3.3 准备容器镜像目录和基础工具在目标机上规划目录时我习惯按下面这种方式组织直观也方便后续维护/opt/offline/ ├── rpms/ # Docker rpm 安装包 ├── images/ # docker save 导出的镜像 tar 包 ├── compose/ # docker-compose 编排文件 └── data/ # 容器数据持久化目录创建好目录结构以后把打包机上的文件全部拷贝过来。传输方式按现场条件来千兆内网用 scp物理隔离用 U 盘注意拷贝完先跑一遍校验cd /opt/offline sha256sum -c rpms.sha256 sha256sum -c images.sha256只有校验全部通过才进入下一步安装操作。4. Docker 安装与守护进程启动实操4.1 使用 rpm 包批量安装 Docker物料都到位了安装 Docker 本身反而变成最简单的一步。直接在当前目录安装所有 rpm 包cd /opt/offline/rpms dnf install -y ./docker-ce-*.rpm ./docker-ce-cli-*.rpm ./containerd.io-*.rpm ./docker-buildx-plugin-*.rpm ./docker-compose-plugin-*.rpm如果直接指定包名安装时提示缺依赖那就干脆一把梭dnf install -y ./*.rpm让 dnf 自己解析依赖只要 rpm 包是齐全的这个命令一定能成功。千万不要用rpm -ivh *.rpmrpm 命令不会自动解析依赖一旦出现依赖顺序问题你会陷入手动逐个安装的泥潭纯属浪费时间。安装完成后验证一下docker --version docker compose version containerd --version三个命令都能正常输出版本号说明基础安装成功了。4.2 配置 daemon.json数据目录、镜像源与日志策略Docker 装好以后先别急着启动要把 daemon.json 配置好。这个文件控制着 Docker 守护进程的核心行为离线环境下有几个配置项尤其关键。我的配置文件如下mkdir -p /etc/docker cat /etc/docker/daemon.json EOF { data-root: /opt/offline/data/docker, exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2, registry-mirrors: [], insecure-registries: [registry.internal:5000] } EOF逐项解释一下我的考虑。>systemctl daemon-reload systemctl enable --now docker systemctl status dockerenable --now是 enable 和 start 的组合enable 创建开机自启动软链start 立即启动服务。执行完成后看到Active: active (running)并且能正常跑docker infoDocker 就部署成功了。这里有个小细节值得说一下Rocky Linux 9 默认防火墙 firewalld 如果开着千万别随便systemctl stop firewalld一关了之。正规做法是放行需要的端口firewall-cmd --permanent --add-port5000/tcp firewall-cmd --reload5. 服务部署镜像导入、容器编排与开机自启5.1 Docker 镜像导入与标签管理Docker 本体跑起来后开始导入镜像cd /opt/offline/images docker load -i nginx-1.26-alpine.tar docker load -i registry-2.tar导入完成以后用docker images确认镜像已经存在。但这里有一个离线环境特有的坑docker save保存的是镜像的完整 tag 信息如果打包时镜像是从 Docker Hub 直接 pull 的tag 会带docker.io/library/nginx:1.26-alpine这样的前缀。在离线环境里我们希望 tag 是内网私有仓库的完整地址因为后面所有机器的镜像都要走内网仓库拉取。所以导入以后我通常会重新打标签docker tag docker.io/library/nginx:1.26-alpine registry.internal:5000/nginx:1.26-alpine docker tag docker.io/library/registry:2 registry.internal:5000/registry:2如果内网有 DNS 能把registry.internal解析到仓库服务器直接用这个地址即可没有 DNS 就写 IP比如192.168.10.20:5000/nginx:1.26-alpine。标签规则一定要提前定好。命名规范建议仓库地址/项目名/镜像名:版本号。这样在多项目、多环境的离线集群里镜像才不会变成一团乱麻。5.2 搭建内网私有镜像仓库 registry如果后续还有其他机器需要部署同一套服务最优雅的方式是在内网先跑一个 registry 容器作为内网镜像分发中心。先把 registry 容器跑起来mkdir -p /opt/offline/data/registry docker run -d \ --name registry \ --restartalways \ -p 5000:5000 \ -v /opt/offline/data/registry:/var/lib/registry \ registry.internal:5000/registry:2然后把刚才打好的 tag 推送到本地仓库docker push registry.internal:5000/nginx:1.26-alpine push 操作走的是 HTTP所以之前 daemon.json 里必须写了 insecure-registries否则这一步会报 http: server gave HTTP response to HTTPS client 的错误。初始化只需要 push 几个基础镜像后续内网其他机器直接从 registry 拉取不再需要拿 U 盘到处拷贝。这步做完之后整个离线环境就有了一个最基础但完整可用的镜像分发基础设施。5.3 用 docker compose 编排服务并保持开机自启镜像就绪、仓库就绪接下来是把真正的应用服务跑起来。我强烈建议用 docker compose 来编排而不是裸docker run。compose 文件可读性好、版本可控、换机器迁移时只要拷贝一个 yml 文件就行跟裸命令相比完全是两个维护体验。举个例子假设我们要部署一个 nginx 加一个后端服务mkdir -p /opt/offline/compose/webapp cat /opt/offline/compose/webapp/docker-compose.yml EOF version: 3.8 services: nginx: image: registry.internal:5000/nginx:1.26-alpine container_name: webapp-nginx restart: always ports: - 80:80 volumes: - ./html:/usr/share/nginx/html:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro networks: - app-net healthcheck: test: [CMD, wget, -q, -O, -, http://localhost/] interval: 30s timeout: 5s retries: 3 backend: image: registry.internal:5000/backend:1.0.0 container_name: webapp-backend restart: always expose: - 8080 environment: - DB_HOST192.168.10.30 - DB_PORT3306 - LOG_LEVELinfo volumes: - app-data:/data networks: - app-net volumes: app-data: networks: app-net: driver: bridge EOF启动服务cd /opt/offline/compose/webapp docker compose up -d docker compose psrestart: always策略意味着只要 Docker 守护进程还活着容器异常退出就会被自动拉起。但要注意Docker 服务本身在机器重启后需要自启动——这个我们在前面已经通过systemctl enable --now docker搞定了。两个配合起来宿主机重启后容器会自动恢复不需要人为干预。5.4 compose 服务的自启依赖处理如果宿主机上跑了多个 compose 项目而且它们之间有依赖关系比如 nginx 依赖后端服务先启动光靠restart: always还不够需要在 compose 文件里显式声明依赖services: nginx: depends_on: backend: condition: service_healthy这样 docker compose 会等待 backend 的健康检查通过以后再启动 nginx避免出现 nginx 启动时后端还没就绪导致一段时间内服务不可用的问题。另外如果一台机器上跑了多个 compose 项目建议把每个项目的目录独立出来比如/opt/offline/compose/webapp和/opt/offline/compose/monitor每个项目里一份 docker-compose.yml 文件。这样每个项目可以独立执行docker compose up -d、docker compose logs -f互不干扰。6. 常见问题与排查技巧实录6.1 rpm 依赖缺失与版本冲突处理离线安装 Docker 最常见的报错就是依赖缺失比如Error: Problem: package docker-ce-3:26.1.4-1.el9.x86_64 requires containerd.io 1.6.26, but none of the providers can be installed这个问题的根因一般是打包阶段没把依赖下载全或者从不同源下载的 rpm 版本不匹配。排查方法是先看缺的依赖叫什么然后回打包机上执行dnf download --resolve 缺的包名 --alldeps --destdir/data/offline/rpms把新增的包一起拷到内网再重新执行dnf install ./*.rpm。版本冲突则是另一种情况比如目标机器上已经装了旧版本 containerd和新的 docker-ce 存在冲突。应对方法是在安装时明确替换dnf install -y ./*.rpm --allowerasing--allowerasing会把冲突的旧包自动移除然后装上新的。但这招有风险操作前务必确认旧包不是其他服务正在依赖的运行时。我自己的习惯是先看rpm -qa | grep docker和rpm -qa | grep containerd确认没有其他软件依赖它们再动手。6.2 overlay2 存储驱动与 SELinux 的兼容问题有人在内网装完 Docker 后一docker run就报错failed to mount overlay: operation not permitted原因基本是 SELinux 在捣鬼。排查方式很简单先看 SELinux 状态getenforce如果是 Enforcing最快验证方案是临时切到 Permissivesetenforce 0 docker run --rm hello-world若容器能正常跑确认是 SELinux 策略的问题。长期解决方案有两种一是永久切到 Permissive 或 Disabled修改/etc/selinux/config二是保留 Enforcing但确保安装container-selinux包并且 Docker 版本足够新20.10 以上基本没问题。如果切了 Permissive 后还是报 overlay 挂载失败那就得检查内核是否支持 overlay 文件系统用modprobe overlay手动加载试试。6.3 离线环境镜像导入后 tag 丢失或版本不匹配docker load以后docker images看不到镜像或者 tag 显示为none:none这种情况通常是因为 save 时镜像本身就没有 tag。解决办法是在打包机上对镜像重新打上 tag 后再 savedocker tag 镜像ID 你的项目名/镜像名:版本号 docker save 你的项目名/镜像名:版本号 -o 镜像名.tar另外有一种情况是镜像架构不对。比如打包机是 ARM 架构pull 的是 arm64 镜像target 是 x86_64 机器load 能成功但 run 的时候会报exec format error。这个错误极其隐蔽因为没有明显提示“架构不匹配”。判断方法是在目标机上docker image inspect 镜像名 | grep Architecture确认输出是amd64还是arm64。6.4 常见问题速查表现象可能原因排查思路解决办法dnf install 报依赖缺失rpm 包未下载完整rpm -qpR 包名.rpm对照检查回打包机补齐依赖再拷贝docker 启动失败日志显示 storage-driver 错误overlay2 模块未加载或 SELinux 冲突lsmod | grep overlay、getenforcemodprobe overlay、调整 SELinuxdocker pull 私有仓库镜像失败registry 未加入 insecure-registries查看 Docker 日志和回显修改 daemon.json 重启 docker容器启动后立即退出启动命令错误、端口被占用、卷权限不对docker logs 容器名查看报错根据日志调整配置容器时区不对基础镜像默认 UTCdocker exec 容器名 date检查添加TZAsia/Shanghai环境变量离线机器时间不同步NTP 不可达date对比手动timedatectl set-time或配置内网 NTP磁盘空间快速耗尽日志未限流、数据未挂独立盘df -h、du -sh /opt/offline/data/docker开启日志轮转、迁移数据目录6.5 离线环境下的镜像安全与最小化实践最后聊一个很多人不在意但生产环境很关键的话题离线环境也不能随便装镜像。镜像在打包机上拉下来的时候一定要留意来源是否可靠。我见过有同事图省事从某个不明来源下载了一个“精简版”业务镜像结果里面塞了挖矿程序部署到内网以后数据库被拖库。这个教训非常惨痛。在离线环境里我坚持这几个原则只从官方或可信仓库拉镜像拉下来以后用docker scan或者trivy image做一次漏洞扫描再保存扫描报告随物料一起交付归档。虽然离线机器上不能联网更新漏洞库但打包机上可以多花这一步能让内网的安全性上一个台阶。另外镜像不是越大越好。能用 alpine 做基础镜像的业务就别用 centos 或者 ubuntu 全家桶一个小镜像在离线传输和导入时的差距是非常明显的。nginx:alpine只有几十 MBnginx:latest却有两百多 MB在带宽有限的内网里这个差距就是十分钟和半小时的区别。7. 这类项目如何扩展到多机和后续维护一套离线环境交付完真正的考验才刚开始。客户总会提新需求再加一台机器、升级某个组件、换一个新服务。所以前期规划时就要给后续留好路。如果只是加机器体量不大直接把 rpm 包和镜像 tar 拷到新机器按前面的流程走一遍即可。机器多了比如超过五台就得把私有仓库派上用场所有机器配置内网 YUM 源指向仓库服务器镜像统一从 registry 拉取。这时候就不再需要人工往每台机器拷贝物料效率能提升一个量级。升级 Docker 版本时操作顺序是先在打包机上下载新版 rpm 包更新本地仓库然后到目标机器上dnf update docker-ce docker-ce-cli containerd.io升级前务必先把存量容器docker compose down升级完成后再up -d。如果直接升级不重启容器旧容器可能因为底层库变化起不来反而耽误时间。备份策略也不能少。即使离线部署容器的数据卷备份、compose 文件的版本管理都该纳入日常。我的习惯是每周把/opt/offline/compose整个目录用 tar 打包连同数据库数据卷备份传到独立存储。这一步在出事故时能救命别等出了问题再后悔。从我个人的经验来看离线环境部署 Docker 最大的难点从来不在“装不上”而在“想不全”。依赖有没有打全、镜像架构对不对、SELinux 会不会拦、日志会不会把硬盘塞满、机器重启后服务能不能自动恢复——这些才是真正决定交付是否顺利的关键。把这六个环节按照前面的流程完整走一遍Rocky Linux 9 的离线 Docker 环境就能稳定跑起来后续不管加机器还是加服务都有了清晰的路径可循。
返回列表