拓冰建站拓冰建站
首页 / 资讯中心 / 正文

离线交付工程实战:物理隔离环境下如何完整部署软件系统?

1. 项目概述离线的机房和不联网的交付做实施这行久了你会发现一个特别有意思的现象现在所有软件都往云上跑、往容器里塞、往微服务里拆但是真正到了交付现场客户机房的物理环境往往比你想象的原始得多。不联网、没外网、甚至内网都是孤岛这种情况在医院、军工、电力、某些政企内网里太常见了。这个项目就是这样一套交付工程——软件本身开发完了测试也通过了但是目标机房是个彻彻底底的物理隔离环境没有外网、没有镜像仓库、没有公共的包管理器所有东西都得靠移动介质一拷一搬运进去。标题里说“还没上过战场”意思是这套交付流程已经在预演环境里完整跑通了方案和介质都准备好了就差去现场真刀真枪装一把。这种项目的核心难点其实只有一个如何在不可上网的封闭环境里把一个依赖第三方开源组件、运行库、镜像、中间件的软件系统完整、可重复、可校验地部署起来。不只是把安装包拷贝进去那么简单里面牵扯到依赖解析、镜像离线化、版本锁定、双人复核、回滚预案等一系列工程化操作。这篇就好好聊聊这套交付工程是怎么设计的介质怎么做现场怎么装以及预演中踩过的坑。适合谁来读如果你也在做软件实施交付、系统集成、运维部署或者你只是好奇“没有网怎么装软件”这件事都能从这里面找到有用的思路。2. 离线交付的整体设计先想清楚交付物的边界2.1 离线部署的本质是“依赖搬运”很多人对离线部署有个误解以为就是把安装包复制过去、双击安装就行。实际上现代软件几乎没有单文件能跑的一套系统背后挂着操作系统补丁、运行库、数据库驱动、中间件依赖、软件包依赖树再加上容器镜像里的每一层文件系统。只要有任何一个依赖没带上现场装上就是报错给你看。所以我把离线交付这件事拆成了三层系统层操作系统基础依赖、内核参数、时区、字符集、DNS配置、Yum/Apt源如果有内网源。应用层数据库、中间件、微服务框架、应用的二进制包和配置文件。数据层初始化SQL脚本、基线数据、字典表、测试数据如果允许带。三层各自打包、各自校验、各自安装顺序不能乱。现场实施的时候也是照着这个顺序走先系统层、再应用层、最后数据层一层不通不往下走。2.2 为什么选择自建离线仓库而不是直接拷安装包最开始我们的方案很原始就是把所有RPM包用yumdownloader批量拉下来放到U盘里现场一个个装。实测下来发现一个问题RPM依赖关系太复杂手动装经常遇到循环依赖、冲突、缺依赖的情况一个包装不上就卡半天。后来换成了自建离线仓库的方案。在一台能上网的跳板机上装好Nginx把CentOS 7的Base、Extras、Updates全部同步到本地目录生成repodata元数据。到了现场直接把整个仓库目录同步到内网服务器上配置一个本地.repo文件yum就能像连了外网一样正常工作。这个做法最大的价值是解决了“依赖树”的问题安一个软件包时yum会自动把依赖从本地仓库解析出来不用人工处理依赖关系。对于容器化应用离线镜像仓库如Harbor、Docker Registry也是同样的思路。2.3 介质选型U盘、移动硬盘还是光盘交付介质的选型看着是个小事实际上翻车概率最高的就是这里。预演的时候我们用的是普通U盘结果到了现场发现服务器前面的USB口在机柜后面拔插特别费劲而且U盘容量和文件系统格式都可能掉链子。总结下来我的选型经验是这样的介质适用场景坑点U盘32GB以内小系统、纯安装包交付文件系统FAT32无法存超过4GB的单个文件移动硬盘固态镜像仓库、大数据量供电不足、NTFS格式Linux不能直接读取光盘DVD合规审计要求严格的项目速度慢、容量小、不改动数据高速固态移动硬盘exFAT推荐首选兼容性好、容量大、速度快最终我们选的是固态移动硬盘格式化成exFAT因为exFAT在Windows和Linux两边都能直接挂载不用装额外的驱动。如果你还在用NTFS现场Linux服务器上可能要ntfs-3g工具才能读会多一层麻烦。注意如果你的系统里某些安装包单文件超过4GB现在很多数据库安装包都超过这个数FAT32的U盘是装不下的一定要用exFAT或者ext4格式的移动硬盘。3. 软件准备与离线介质制作预先把每一层依赖都锁死3.1 全量依赖采集不止是安装包本身准备工作是整个项目里花费时间最长的一项原因在于要采集的东西比想象中多得多。以我们这套系统为例它依赖Nginx 1.20作为反向代理和静态资源服务PostgreSQL 13存业务数据Redis 6.2做缓存和会话管理Java 11运行环境跑Spring Boot微服务前端静态资源Nginx托管系统级依赖epel-release、gcc、openssl-devel、zlib-devel、readline-devel等Python 3.8 pip依赖跑运维脚本和数据处理任务每一项都要在跳板机上用包管理器把安装包和依赖一次性拉全。比如CentOS 7下先配置好外网源然后执行# 创建存放目录 mkdir -p /opt/offline/rpms # 下载指定软件包及其依赖 yumdownloader --resolve --destdir/opt/offline/rpms nginx postgresql-server redis java-11-openjdk # 下载系统常用基础依赖 for pkg in gcc gcc-c make openssl-devel zlib-devel readline-devel libffi-devel epel-release; do yumdownloader --resolve --destdir/opt/offline/rpms $pkg donePython依赖就用pip的离线模式拉mkdir -p /opt/offline/pip pip download -r requirements.txt -d /opt/offline/pip --platform manylinux2014_x86_64 --python-version 38 --only-binary:all:注意这里加了平台和Python版本限制避免下载到跟现场环境不兼容的包。3.2 容器镜像的离线化保存我们这套系统用了Docker Compose来做编排几个核心镜像分别是postgres:13-alpine、redis:6.2-alpine、nginx:1.20-alpine加上自己构建的应用镜像。到了现场既不可能去Docker Hub拉镜像也不可能在客户内网里构建所以必须在跳板机上提前把镜像保存成tar文件。操作并不复杂# 在跳板机上拉取镜像 docker pull postgres:13-alpine docker pull redis:6.2-alpine docker pull nginx:1.20-alpine # 标记自己的应用镜像 docker tag myapp:release-1.0.0 registry.internal/myapp:1.0.0 # 逐个保存成tar文件 docker save -o /opt/offline/images/postgres-13-alpine.tar postgres:13-alpine docker save -o /opt/offline/images/redis-62-alpine.tar redis:6.2-alpine docker save -o /opt/offline/images/nginx-120-alpine.tar nginx:1.20-alpine docker save -o /opt/offline/images/myapp-100.tar myapp:1.0.0保存完之后记得做一件事校验文件完整性。每个tar文件生成一个SHA256校验值把校验清单也存到硬盘里现场导入完镜像后跑一遍校验确保文件没损坏、没被篡改。sha256sum /opt/offline/images/*.tar /opt/offline/sha256sums.txt cat sha256sums.txt # 打印结果并记录3.3 离线仓库目录结构设计介质目录必须提前规划否则到了现场容易混乱。我们最终确定的结构是这样的/offline_delivery/ ├── 00_README.md # 安装手册入口 ├── 01_system/ # 系统级依赖 │ ├── rpms/ # 所有RPM包 │ ├── local.repo.template # 本地源配置文件模板 │ └── setup_local_repo.sh # 一键配置本地源的脚本 ├── 02_applications/ # 应用安装包 │ ├── jdk-11.tar.gz │ ├── postgresql-13.tar.gz │ ├── redis-6.2.tar.gz │ └── nginx-1.20.tar.gz ├── 03_docker_images/ # 容器镜像 │ ├── postgres-13-alpine.tar │ ├── redis-62-alpine.tar │ ├── nginx-120-alpine.tar │ └── myapp-100.tar ├── 04_database/ # 数据库脚本 │ ├── init_ddl.sql # 建表脚本 │ ├── init_dml.sql # 基础字典数据 │ └── init_admin.sql # 管理员账号初始化 └── 05_scripts/ # 自动化部署脚本 ├── install_all.sh # 一键部署主脚本 ├── check_env.sh # 环境检查脚本 └── rollback.sh # 回滚脚本这个结构背后的逻辑非常朴素谁在前谁在后脚本、配置、数据分离现场操作时按数字顺序走就行哪怕不是开发这套系统的人照着00_README也能上手。3.4 预演环境与正式介质的一致性保障“还没上过战场”不等于没有验证。我们在预演环境一台虚拟机规格和现场服务器对齐里完整跑了两遍全流程部署第一遍发现缺三个依赖包第二遍才完全通过。这里有一个经验值得分享把预演的虚拟机改成和现场完全一致的配置包括操作系统大版本小版本也要核对、CPU核数、内存大小、磁盘分区方式。因为有些软件在2核4G和4核8G上装出来的行为完全不同有的内存不够直接装不上有的安装脚本会根据内存自动调整参数现场机器和预演机器配置差异越大出问题的可能性越高。4. 现场实施流程进机房之后一步一步怎么走4.1 入场检查清单现场实施最怕的就是“以为环境是那样的”结果到了现场发现根本不是。所以进场后第一步不是安装而是检查环境我专门做了一张检查清单服务器是否上电、能ping通管理IP操作系统版本cat /etc/redhat-release 核对大版本磁盘空间df -h 确认根分区和数据分区空间足够内存大小free -m 确认符合最低要求网络内网通不通、有没有机架交换机隔离SELinux状态getenforce建议现场设置为disabled防火墙firewalld/iptables状态先确认再决定是否放行端口时钟同步date 命令看当前时间和时区是否正确时间不对会影响证书校验这些检查项看着基础但是每一项都有可能让后续安装功亏一篑。比如SELinux没关Nginx可能无法绑定80端口时钟不对HTTPS证书马上失效根分区空间不够镜像导入直接失败。4.2 系统层和应用层安装系统层用的是自建yum本地源的方式。现场操作步骤# 1. 备份原有的yum源配置 mkdir -p /etc/yum.repos.d/backup mv /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/backup/ # 2. 挂载交付介质 mount /dev/sdb1 /mnt/usb # 根据实际情况改设备名 # 3. 创建本地源配置 cat /etc/yum.repos.d/local.repo EOF [local] nameLocal Repository baseurlfile:///mnt/usb/01_system/rpms enabled1 gpgcheck0 EOF # 4. 清缓存并验证 yum clean all yum repolist这个步骤的关键在于源配好后先用repolist确认本地仓库被识别再安装任何东西。我见过有人配完源直接yum install结果走了原来的外网源如果现场还有残留外网权限装了半天装不上才发现问题。应用层的安装顺序是JDK - PostgreSQL - Redis - Nginx。数据库要先初始化数据目录和系统表Redis是编译安装Nginx是编译安装或者二进制包。每一步装完都要验证服务能启动。4.3 容器镜像导入与编排启动应用层装好后开始导入容器镜像# 导入镜像 docker load -i /mnt/usb/03_docker_images/postgres-13-alpine.tar docker load -i /mnt/usb/03_docker_images/redis-62-alpine.tar docker load -i /mnt/usb/03_docker_images/nginx-120-alpine.tar docker load -i /mnt/usb/03_docker_images/myapp-100.tar # 验证镜像 docker images导入完之后把docker-compose.yml和.env配置文件拷贝到指定目录下然后启动cd /opt/myapp docker-compose up -d docker-compose ps这里有一个在离线环境尤其容易踩的坑docker-compose.yml里如果写了image: xxx没有指定tag默认会去拉latest镜像离线环境拉不到会一直卡住。所以离线交付的compose文件里必须全部写明镜像名加tag并且确保这个tag在镜像tar包里存在。4.4 数据库初始化和数据校验数据库是整套系统最核心的一部分也是“最容易出问题但最不容易被发现”的一部分。我们采用了Docker容器化的PostgreSQL但数据库脚本是直接挂载卷在宿主机上执行的。# 进入容器执行SQL脚本 docker exec -i myapp-db psql -U postgres -d myappdb /opt/myapp/sql/init_ddl.sql docker exec -i myapp-db psql -U postgres -d myappdb /opt/myapp/sql/init_dml.sql docker exec -i myapp-db psql -U postgres -d myappdb /opt/myapp/sql/init_admin.sql执行完务必做数据校验不要只看有没有报错而是真去查几条关键表-- 检查核心表是否存在 \dt -- 检查字典表是否有数据 SELECT count(*) FROM sys_dict; -- 检查管理员账号是否创建 SELECT username, status FROM sys_user WHERE usernameadmin;我预演时遇到过SQL脚本执行完没有任何报错但实际上partitions建错了、外键引用缺失、编码不对导致中文乱码的情况。所以校验不是走过场最好把预期结果也写在交付文档里比如“admin用户应存在状态为1默认密码为xxx”。4.5 验证测试用例不能只看“服务起来了”服务起来不代表系统可用。这是这次交付里我最强调的一点“能访问”和“功能正确”是两回事。我们在交付文档里预置了一组冒烟测试用例覆盖登录、新增、查询、修改、删除、权限、日志等核心链路现场验证完以后把结果记录下来。还测了重启恢复能力——把整套docker-compose停掉再启动看服务是否自动恢复数据库连接是否会自动重连。一套软件如果经不起“停机后重启”的测试现场迟早要出问题。5. 实际遇坑问题记录与排查思路5.1 常见坑NO.1yum本地源配置后仍然无法安装现象配置完local.repoyum repolist能看到仓库但是yum install nginx时报“No package nginx available”。排查过程先用yum list | grep nginx看了下确实没有然后看repodata目录发现rpm包放进去了但repodata是空的。原因很简单直接把RPM包拷贝到了目录里但没有重新生成repodata索引。解决方案在跳板机上用createrepo重新生成索引然后重新拷贝整个目录。现场如果已经带过去了就在现场服务器上执行yum install -y createrepo # 如果现场有或者从介质里装 createrepo /mnt/usb/01_system/rpms yum clean all yum repolist经验制作离线yum源不是拷文件就行一定记得最后一步执行createrepo生成repodata并且拷过去之后用repolist验证一遍。这个坑在离线交付里出现频率极高。5.2 常见坑NO.2Docker镜像导入耗时过长甚至卡死现象现场docker load一个2GB的镜像进度条卡在99%不动了。排查过程第一反应是磁盘空间不足df -h一看确实根分区只剩2GB。docker load过程中临时文件和相关层数据占用了空间磁盘满了以后进程卡住。解决方案清理无用的中间镜像和容器或者换到空间充足的分区存储docker数据根目录。最彻底的办法是在安装规划阶段就把docker的数据目录设置为独立的大分区# 修改docker数据根目录 cat /etc/docker/daemon.json EOF { data-root: /data/docker } EOF systemctl restart docker另一个建议镜像tar文件在导入前就检查大小现场磁盘至少要留有镜像tar文件3倍以上的空闲空间一层是tar的大小一个是解压后的镜像大小一个是运行时的日志数据。5.3 常见坑NO.3内网里服务IP配置错误导致无法访问现象部署全部完成后从办公网访问不到应用页面但服务器本机curl是通的。排查过程先ping了应用服务器的IP通了再telnet应用端口不通。说明问题出在防火墙或者路由上。一查firewalld还在运行且没放行8080端口。另外nginx监听地址写的是127.0.0.1外部访问当然进不来。解决方案修改nginx配置把监听地址改为0.0.0.0放行防火墙端口。这里要提醒一点很多现场机器开机默认开着firewalld你需要根据客户的安全策略来决定禁用它还是放行特定端口。如果客户要求不能关防火墙那就在安装文档里写清楚每个服务的端口和放行规则不要一刀切。5.4 常见坑NO.4数据库中文乱码问题现象初始化完成后查询中文数据全是乱码。排查过程数据库初始化时没有指定字符集默认成了SQL_ASCII业务表没有显式定义字符集导致数据库层面就无法存储中文。解决方案在初始化PostgreSQL容器时必须显式设置字符集POSTGRES_INITDB_ARGS--encodingUTF8 --localeen_US.UTF-8或者执行完初始化后检查验证SHOW SERVER_ENCODING; SHOW LC_COLLATE;这个坑在离线交付里特别隐蔽因为安装过程不会报错直到真正写入中文数据才发现。所有交付项目里数据库字符集必须作为环境检查清单里的强制项。5.5 问题速查表问题现象可能原因排查重点yum install 提示找不到包repodata未生成检查repodata目录执行createrepodocker load 卡住磁盘空间不足df -h查看分区用量应用无法外部访问防火墙拦截/监听地址错误telnet端口测试、查看nginx监听配置中文乱码数据库字符集未指定SHOW SERVER_ENCODING确认UTF8服务启动后自动退出日志路径权限/内存不足docker logs 查看日志、free -m查看内存时区显示不一致容器和宿主机时区不同挂载/etc/localtime或者设置TZ环境变量证书校验失败服务器时间不同步date命令核对时间配置ntp或手动设置6. 交付文档与知识传承让“没上过战场”也能被信任6.1 交付文档的“新手可执行”标准做交付的不只是把软件装上更是把“怎么装”变成一套可复现的方法论。这套项目我在写文档时要求自己遵循一个标准找一个从没参与过这个项目的人只凭文档和介质能独立完成部署。所以文档里除了安装步骤还必须有每个步骤的预期输出比如执行完后应该出现什么提示每个命令的作用说明不是冷冰冰的命令堆砌常见错误提示和对应的解决手段就是上面第5章这类内容每个环节验证方法和验收标准不是装完就算完6.2 双人复核与“模拟交付”这次虽然没有正式进场但我们是按正式交付标准做了两轮预演。第一轮由项目开发人员自己装基本是“神仙操作”很多步骤靠肌肉记忆文档没暴露问题。第二轮我刻意安排了一个没参与过开发的新同事来装结果暴露出一堆问题文档里漏了创建目录的步骤、系统要求写得不明确、某个环境变量没交代来源。双人复核的意义就在这你永远不能用自己的“知道”去代替文档的“写清楚”开发人员觉得理所当然的东西对实施人员来说可能就是天书。6.3 回滚方案与现场应急包最后说一个很多交付团队容易忽略的东西——回滚方案。部署前必须想清楚如果安装到一半失败现场该怎么办我们的策略是每次变更前拍摄虚拟机的快照如果是虚机环境每个阶段的脚本都有幂等设计重复执行不会产生副作用部署日志全过程留痕script命令记录终端输出返回现场时携带一份应急U盘里面除了交付介质还有系统级别的救援工具比如rescue模式工具、磁盘工具等很多现场问题不是装不上而是装了一半卡住进退两难。有回滚预案至少能把现场恢复到某个可控状态而不是陷在故障里干等。这套交付工程虽然还没有正式上战场但整个准备过程本身就是一次高质量的系统工程演练。我个人最大的感受是离线部署不是什么高深技术真正考验人的是细致程度和预案意识。把每一层依赖锁住、把每一步验证做扎实、把每一类问题想在前面到了现场就算环境还有意外你也已经有了应对的底气和框架。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门