ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

离线环境下的软件交付工程实战:从容器打包到冷启动部署

离线环境下的软件交付工程实战:从容器打包到冷启动部署 去年年底接到一个活儿要把一套业务软件部署到客户的离线机房。去过现场才知道这间机房不仅不能上外网连内网的其他网段都做了严格隔离U盘要申请、光盘要审批服务器操作系统是之前没人碰过的版本。整个交付工程从采购算起折腾了三个多月软件装上了、页面能打开了、数据库也起来了但严格意义上它还是一次“建好了、还没上过战场”的交付。今天这篇是把这段时间踩过的坑、想明白的事和没验证完的隐患一起写出来给自己留个备忘也给打算做离线部署的朋友当个参考。1. 为什么离线部署必须当成“交付工程”来做1.1 客户机房“连不上外网”可能比预想的更彻底很多人一开始对离线部署的理解就是“把安装包拷贝进去点击安装”。真到现场会发现客户对网络隔离的要求往往比想象中严格得多。我遇到的情况是机房内所有服务器只有业务网段没有互联网出口DNS解析只保留内网域名连系统自带的软件源都被切掉了。更麻烦的是现场不允许随意带入私人笔记本电脑所有拷贝动作必须通过指定的摆渡机器完成U盘的读写要登记。这种环境下传统安装流程里“缺什么就联网下什么”的路径是彻底走不通的。你不仅要准备软件本身的安装包还得把它的运行时、依赖库、基础组件甚至操作系统层面的补丁、字符集、时钟同步工具全部打包好。不然装到一半系统告诉你缺一个动态链接库而这个库在客户的机器上根本没有你就只能干瞪眼。所以我的第一原则是离线部署不是“拷贝安装包”而是“拷贝一台机器的运行环境”。这个思路直接决定了后续所有步骤的做法。1.2 交付物不应该是“一堆文件”而应该是一个“可验证的结果”把软件装进不能上网的机房本质上是一种“盲投”。你没机会在客户现场临时查资料、下载依赖、看在线文档所以交付物必须自带闭环拿过去就能装装完就能验证验证完就能交接。我理解里一个完整的离线交付工程至少包含四样东西离线安装介质包括软件本体、中间件、依赖库、操作系统补丁一套清晰的安装顺序和部署脚本知道先装什么后装什么一套校验手段能确认文件完整、服务正常、业务可用一份故障手册覆盖已知坑位和快速回滚方案。如果你的交付物做不到这四点那就不叫交付工程只能叫“碰运气”。1.3 我选的方案容器打包 自制离线源 按层解耦结合现场环境我最后定下的技术栈是用 Docker 容器封住业务应用和中间件用仓库服务器承载离线镜像用 Nginx 做一个内网文件分发点业务数据用 PostgreSQL 持久化对外访问通过宿主机端口映射完成。这么选的原因很简单容器能把“环境差异”这个大坑直接抹平。业务代码、依赖库、基础镜像、配置文件全部打进镜像后到了现场只要 Docker 能跑起来应用就能跑起来。离线源的目的是解决基础软件安装的问题因为很多服务器默认没有装 Docker或者 Docker 装了但仓库里没有企业版镜像。把这两层分开既能让交付逻辑更清晰也能在各环节出问题时快速定位。2. 离线交付包的四个核心模块2.1 镜像仓库不是把 Docker 镜像导出一个文件就完了最开始的方案确实是“docker save 再 docker load”简单直接。后来发现一套业务系统往往有十几个服务每个服务又依赖不同的基础镜像版本。如果只是导出导入现场的镜像会有大量冗余而且版本管理非常混乱。比如 A 服务用的是 PostgreSQL 14 的镜像B 服务用的是 15这两份镜像必须同时保留在离线环境里根本没有“在线拉取指定 tag”的机会。所以我建了一个 Harbor 仓库先把所有需要的镜像按 tag 推上去再做一次全量导出。到客户现场后先部署一个临时仓库把导出的镜像全部 load 上去再由每台服务器从仓库拉取。这样做的好处是现场任何一台机器缺镜像都能从仓库补而不需要重新拷贝整个 U 盘。操作上要注意Harbor 本身也是容器应用它也有依赖部署 Harbor 之前得先把 Docker Compose 准备好。这部分依赖我在离线包里单列了一个目录叫“基础工具”包括 Docker RPM 包、docker-compose、jq、vim、curl 等常用命令行工具。这个习惯帮了大忙因为现场真遇到过一台机器连 tar 解压某些大文件都要单独装 lrzsz 的情况。2.2 离线软件源让每台服务器都能“自己装基础软件”Docker 解决了应用运行的问题但操作系统层面的软件安装仍然绕不开。客户服务器是麒麟 V10 SP3默认源指向的是公网外网一断yum 基本是废的。要装 Docker、openssl-devel、postgresql 的客户端库全都装不上。我的做法是准备一台和现场系统同版本的虚拟机把需要的软件包用 yumdownloader 下载下来包括必要的依赖然后创建一个本地 yum 仓库把仓库目录整个拷贝到离线包里。现场部署时把仓库目录放到内网服务器配置一个 repo 文件指向它yum 就能干活了。这里有个关键细节下载软件包时符号链接和软链接很容易丢。Windows 下解压的时候如果直接把目录拖进压缩包很多以“新文件”方式传入的内容会把符号链接变成普通文件结果到现场一执行系统根本找不到库文件。后来我的解决方法是统一在 Linux 下用 tar 打包并且加上-h参数保留符号链接到现场用 tar 解包这样问题就没了。2.3 依赖清单与版本锁定宁可多带不可少带离线安装最怕的就是“现场告诉你少一个依赖”。在线的环境里缺什么敲一条 yum install 就完了。离线环境里少一个依赖就意味着要么重新摆渡一次数据要么现场解决编译问题后者的工作量往往比想象中大得多。我的做法是拆成三张清单系统依赖清单操作系统基础软件如 docker、python3、perl、openssl、libaio 等应用依赖清单业务软件运行所需的 Python 库、Java 包、Node 模块等统一放在一个离线目录数据依赖清单数据库初始化脚本、初始数据包、字典表数据等。三张清单分别归档并在归档完成后逐一执行安装验证。先在一台干净的测试机上装一遍确认缺了什么补进去再重装一遍。这个过程非常费时间但是值得反复做因为现场你永远不会比测试机上更顺利。2.4 部署脚本固定安装顺序减少人为误差人的记忆在压力下是不可靠的尤其到了客户现场面对一台从未见过的服务器操作者很容易漏步骤。所以我把整个安装流程写成了一个带编号的部署脚本从环境检查、系统参数配置、基础软件安装、仓库部署、镜像导入、应用启动到数据初始化一共分了八个阶段。每个阶段都有独立的脚本文件执行成功后打一个标记文件下次执行前先检查标记避免重复执行。这样做的好处是即使中途断电、断网或者操作失误也能从上一次成功的阶段继续跑而不是从头再来。脚本里所有路径都使用绝对路径变量避免因为当前工作目录不同导致找不到文件这一点在自动化部署中非常重要。3. 实操过程从离线包制作到现场冷启动3.1 前置准备先造一台“模拟现场”的干净机器正式制作离线包之前我先在自己的环境里搭了一台与客户环境尽可能一致的虚拟机。系统版本完全一样内核版本一样磁盘分区方式也按照客户给的规划来。为什么要这么做因为在离线部署里很多问题都是因为“开发环境和现场环境不一致”才暴露的只有在能模拟的范围内尽量还原才能提前发现坑。比如我一开始在 CentOS 上验证的部署脚本到了麒麟环境就发现yum的仓库配置路径不一样导致安装 Docker 时拉不到依赖。后来我用麒麟系统的 ISO 手动安装了一台同样配置的虚拟机才把这个问题提前暴露并解决。如果不做这一步到了现场就会浪费整整一天。这台模拟机同时也是离线包的“裁剪台”。我先在它上面正常安装一遍所有软件记录下了安装过程中的所有输出和报错然后把安装日志里提到的文件路径、仓库配置和依赖项整理成清单再交给打包脚本去生成离线包。这样产出的包就像“照着答案写卷子”可信度高很多。3.2 介质制作离线包的目录结构与打包细节离线包的最终目录结构大概是这样的offline-package/ ├── 00-system/ │ ├── docker-ce/ │ ├── docker-compose/ │ ├── local-repo/ │ └── install-docker.sh ├── 10-mirror/ │ ├── harbor-images.tar.gz │ └── load-images.sh ├── 20-app/ │ ├── app-images.tar.gz │ ├── config/ │ └── deploy-stack.yml ├── 30-data/ │ ├── init.sql │ ├── seed-data/ │ └── init-db.sh ├── 40-tools/ │ ├── jq、mysql-client、redis-cli、lrzsz 等 ├── docs/ │ ├── 部署手册.md │ ├── 验收清单.md │ └── 故障排查速查表.md └── VERSION目录结构定好后我再把整体打包成一个 tar 文件并在包内生成一个SHA256SUMS文件记录所有文件的哈希值。到现场后先用sha256sum -c校验一遍确保文件没有在摆渡过程中损坏。这个校验流程不能省我遇到过一次 U 盘拷贝大文件后文件大小变了的情况如果不校验现场装到一半才发现文件损坏会非常被动。3.3 现场冷启动的八个阶段现场冷启动是整个过程中最紧张的部分。我把过程拆成八个阶段每个阶段对应一个可验证的产出物环境检查确认硬件、磁盘空间、内存、操作系统版本、SELinux 状态系统参数设置关闭不必要的防火墙干扰、调整vm.max_map_count、net.core.somaxconn等内核参数安装 Docker使用离线包里的 RPM 包和本地源安装 Docker 及 docker-compose部署本地仓库启动 Harbor 或者简单用 Nginx 映射镜像目录导入所有离线镜像导入业务镜像从仓库拉取业务相关镜像确保所有 tag 都存在编写应用编排文件把 docker-compose.yml 里的镜像地址改成仓库地址映射持久化目录初始化数据库执行建库脚本和数据导入脚本启动应用并验证启动所有服务检查日志、端口、健康检查接口。这八个阶段里最容易出问题的是第 6 步。因为离线包里镜像文件打包时用的镜像名带仓库地址到了现场如果仓库地址变了docker-compose.yml 里的镜像引用也必须同步改。我用了一个变量文件专门管理这些地址脚本里统一替换减少手工改动。3.4 验证不只是“看页面能打开”应用启动成功后很多人就觉得交付完成了。我的经验是页面上能看到登录框离真正可用还差得远。至少要做三层验证功能层走一遍核心业务流比如创建一条数据、上传一个文件、触发一个定时任务确认闭环数据层确认数据库里的初始化数据完整时间字段正确序列自增正常稳定性持续观察半个小时以上看 CPU、内存、磁盘 IO、日志有没有异常抖动。这三层验证我都会写入交付记录作为最终版本的验收依据。验收清单里每一项都有“是否已确认”的勾选框交接时客户签字确认才算完。4. 明明“建好了”为什么我总说它“还没上过战场”4.1 只有一次验证路径不等于覆盖了很多使用方式我的这套离线交付包虽然完整跑通了一条安装链路但只验证了一条路径标准的“全新服务器 默认配置 我的脚本”。真实的战场可能会出现各种分支情况客户已有的系统上装了数据库、占用了端口或者他们要求用已有的 NFS 存储做数据持久化或者应用需要对接客户的统一认证系统。这些分支我几乎都没有在离线包里验证过。所以“建好了”的含义要打个折扣它证明了一个标准路径可行但无法证明其他路径都可行。这份不确定感只能靠更大规模的演练来消解而我目前只做了一次小范围演练。4.2 依赖“静态打包”不等于运行期“动态兼容”离线包里所有东西都是安装时决定好的一旦到了客户现场软件版本就固定了。可客户的业务不是固定的数据量会增长文件会积压数据库性能会变化。我的离线交付包有没有考虑“半年后磁盘满了怎么办”“日志轮转没配置会不会把磁盘占满”这类运行期问题坦白说第一版里我确实没有全面覆盖。比如 PostgreSQL 的 autovacuum 参数、Docker 的日志切割、NFS 挂载的 noatime 选项这些运行期稳定性细节我是在测试时才发现问题的。现在包里已经补了配置模板和优化脚本但还没有经过长时间运行验证。4.3 文档写得再细到了现场依然会出幺蛾子我花了不少时间写部署手册每一步都配了截图和命令说明。但文档写得多好都不能代替“亲手演练”。因为文档是流程的线性描述而现场的问题是发散的。客户机房可能临时改变网段规划带来 IP 冲突可能出现某个盘符被其他系统占用了导致数据盘没挂载上可能客户方要求的安装时间窗口远小于实际需要。这些不确定性靠文档是挡不住的只能靠“一版一版地演练、把人训练成不用看文档也能装完”的能力来破解。5. 离线交付的故障排查与回滚预案5.1 常见问题速查表下面这张表是我在构建和测试过程中遇到问题的集中梳理后面做离线交付的朋友可以直接参考。现象大概率原因排查手段解决方式服务启动后容器反复重启应用配置文件里的数据库地址不对查看容器日志确认数据库连接信息修正配置文件重新启动服务Docker 拉取镜像超时仓库地址未生效镜像名带外部仓库检查/etc/hosts和仓库配置改镜像引用为本地仓库地址yum 安装软件失败本地 repo 没有配置或密钥过期查看 yum 报错信息重新生成 repo 文件关闭 gpgcheck数据库初始化报编码错误字符集未配置成 UTF-8检查locale和数据库模板先export LANG再重建数据库模板开机后服务未自启docker、compose 服务未设为 enabled执行systemctl list-dependencies确认设置systemctl enable docker配置 restart 策略文件校验失败拷贝过程中文件损坏sha256sum 比对从原始包重新拷贝避免使用压缩软件二次转存5.2 回滚策略不是“删掉重装”在离线环境里回滚没有“重新拉代码”这么简单。现场没有外网如果新版本装完发现有问题旧的安装介质又没有留好就会卡在那里。所以我在每一次变更前都要求先做快照至少在数据库层面做一个pg_dump备份。上线前如果客户允许优先做整机快照如果虚拟化环境不允许那至少在数据盘和服务配置层面做完整备份。另外我的部署脚本支持一键回滚到上一个版本新版本的数据目录会放到带时间戳的目录里启动脚本去读软链接指向当前版本。这样回滚只需要改一个软链接然后重启服务而不是重装整个应用。5.3 我最担心的“隐形清单”问题离线交付里有一种特别隐蔽的坑你以为清单是完整的其实根本不完整。我遇到过的情况是某台服务器上跑了一个很小的工具它在初始化的时候需要libaio而这个库在最初环境检查时没有被发现因为测试机上已经装过了。到了现场这个库的缺失会导致数据库初始化直接失败。为了尽量避免这种问题我现在的做法是每次在干净机器上安装时打开一个“审计模式”记录系统里所有被导入的文件路径然后反向检查它们是否都来自于离线包。只要有一个文件路径不是来自离线包就说明清单有遗漏。这个方法不敢说 100% 完备但比肉眼检查可靠得多。6. 这套交付工程后续还能怎么打磨写到这里我脑子里对这套离线交付工程的“版本号”已经清晰了目前算 1.0硬件环境能跑通常规流程能走通但距离真正的“无脑可交付”还有距离。后续我想做几件事一是把异常分支的验证覆盖度提上去比如在已有数据库的机器上做升级验证、在不改 hosts 的情况下只通过 IP 配置仓库、在不同操作版本上交叉验证脚本二是把部署脚本的幂等性做到极致让运维现场重复执行也不慌三是把监控和告警顺手带进去虽然机房不能上网但内网的监控还是可以做的不能因为离线就把“运行期可观测性”丢掉了。另外我也在考虑把这份离线包的结构做成“模板化”以后接新的业务系统时只需要替换镜像和配置模板其他骨架可以直接复用。如果这一步能做到离线交付的成本能降一大截质量也会稳很多。说实话做离线部署的活儿很多时候不是技术多复杂而是心要细、手要稳、坑要提前踩。整套流程跑完之后最有价值的反而不是最后装上去的软件而是那一沓覆盖了异常场景的故障手册和一份能扛住压力的验收清单。这份工程现在还没真正上过战场我既期待它经受住现场考验又希望自己永远不需要在半夜收到项目群里的求救消息。真到那一天我希望包里那份故障速查表能派上用场。
返回列表