接到DataAI Portal在信创环境离线部署这个任务时,我的第一反应不是兴奋,而是心里发虚。信创环境意味着飞腾ARM64的CPU、银河麒麟V10的操作系统;离线部署意味着整个机房没有任何外网通道,所有安装包、依赖、补丁都得提前准备好——这两件事叠在一起,就不是“装个软件”那么简单了。整个过程从方案设计、介质准备、环境初始化、安装执行到功能与稳定性测试,前前后后花了三周时间,中间踩了不少坑。
这篇文章就把我这次完整的离线部署测试过程记录下来,包括环境概貌、依赖准备的方法、安装执行的细节、测试方案的设计思路,以及那些在文档里根本找不到的坑。无论你是要部署DataAI Portal,还是要做其他业务系统在信创平台的离线交付,这套思路都可以直接复用。
1. 接手任务时先想清楚的三件事
1.1 信创环境到底特殊在哪
先说硬件。我们这次拿到的机器是飞腾FT-2000+/64处理器,ARM64架构(aarch64),128GB内存,系统盘是2块480GB SSD做RAID1,数据盘是6块4TB SATA盘。操作系统是银河麒麟V10(国防版),内核版本4.19。
这里有个关键点:同样是“Linux”,ARM64和x86_64是两套完全不同的生态。很多软件在x86上装得好好的,换到飞腾上要么没有对应包,要么有包但内部带了x86的原生动态库(.so),跑起来就报“cannot execute binary file”。后面我会专门讲这个坑。可以说,信创环境部署的第一课就是:忘记你在x86服务器上的所有经验惯性,每一个组件都要重新确认架构兼容性。
另外,银河麒麟V10虽然是国产操作系统,但它本质上还是基于Linux内核的,所以基础操作逻辑和CentOS/RHEL非常接近。这意味着我们之前积累的systemd、yum/rpm、shell脚本等技能完全能用,只是需要额外注意ARM64的软件来源和版本匹配。
1.2 DataAI Portal的组件依赖画像
DataAI Portal是我们团队负责的一站式数据智能平台门户端,包含数据源管理、数据开发、AI模型管理和可视化报表几大模块。它的技术栈不算冷门:前端是Vue打包后的静态资源,由Nginx托管;后端是Spring Boot微服务,跑在JDK 8上;数据层用了MySQL(信创环境一般要换成达梦或人大金仓)+ Redis缓存;消息这块走RabbitMQ;文件存储用MinIO。
听起来都是常规组件,但在离线+ARM64环境下,每一个组件都可能成为拦路虎。我拿到手的第一件事,就是把DataAI Portal的部署文档翻了一遍,列了一个完整的组件依赖清单。这一步不能省,因为你后面准备离线介质时,所有依据都来自这张清单。列清单时至少要包含:组件名称、版本号、架构要求、依赖的其他包、用途、部署方式(rpm/二进制/源码编译/容器镜像)。
1.3 离线环境带来的三个连锁难题
在线部署时遇到问题,yum install、pip install、docker pull都很方便;离线环境则是另一套游戏规则。我归纳了三个核心难点:
- 依赖闭包问题:每个软件都有依赖,依赖还有传递依赖。离线环境下没有镜像源可用,所有依赖必须提前算出一个完整“闭包”,少一个都装不成。
- 架构差异问题:所有包必须是aarch64版本,不能直接从x86的机器上拷贝。
- 验证成本问题:一旦现场装不上,没法上网查方案、没法临时下载补丁,只能靠现场人员的经验和事先准备。
搞清楚了这三点,整个项目的工作重心就很明确了:离线介质准备阶段花的时间越多,现场部署阶段踩的坑就越少。这是一条非常朴素的真理,后面所有操作都围绕它展开。
2. 离线安装包的准备:像给无人区补给一样做依赖清单
2.1 影子环境:架构一致的“练兵场”
准备离线介质的第一步,得先有一台与目标机同架构(aarch64)、同操作系统版本(银河麒麟V10 ARM版)、且有网络的机器。我管它叫“影子环境”。
为什么必须同架构?因为RPM包和编译好的二进制都是架构相关的。你要是拿一台x86的机器去yumdownloader,下载下来全是x86_64的包,拿到飞腾机器上根本装不了。如果实在找不到aarch64的在线机器,可以用qemu-user模拟ARM64环境凑合用,但实测下来效率很低,而且某些包在模拟环境下会装不上,容易出现“影子环境能装、真机装不上”的假象。最可靠的做法还是找一台真机,哪怕是云上的ARM64实例都行。
影子环境的另一个用途是预演。我在影子环境里把完整的部署流程跑了一遍,成功之后再开始做介质。这一步相当于给整个项目上了一道保险:既然在影子环境能装通,到了现场只要介质和系统环境一致,理论上也能装通。
2.2 RPM依赖下载与本地仓库搭建
对于系统的基础软件(Nginx、Redis、gcc、依赖库等),我推荐用本地RPM仓库的方式。具体做法分三步。
第一步,在影子环境上用yumdownloader把目标软件的依赖包全部拉下来:
mkdir -p /opt/offline-rpms yumdownloader --resolve --destdir=/opt/offline-rpms nginx redis mariadb-server gcc如果你的系统里没有yumdownloader,可以用yum-plugin-downloadonly的方式,或者直接:
yum install --downloadonly --downloaddir=/opt/offline-rpms nginx redis第二步,在/opt/offline-rpms目录下执行createrepo生成仓库元数据:
createrepo /opt/offline-rpms第三步,把这个目录拷贝到目标机的/opt/offline-rpms,在目标机/etc/yum.repos.d/下新建一个repo文件:
[offline-rpms] name=Local Offline RPMs baseurl=file:///opt/offline-rpms gpgcheck=0 enabled=1之后目标机上的yum install就会从本地源安装,自动解决依赖。
这里有一个非常关键的教训:--resolve参数只解决当前系统能识别的依赖。如果某个RPM包在影子环境里没有被安装过,它的依赖可能不会被完整拉取。所以我在影子环境里先试装了一遍目标软件,把报错提示缺的包补齐,再重新生成repo。只有这样反复验证过,才算是一个可靠的离线源。
2.3 Java和Python依赖的离线化
DataAI Portal的后端是Spring Boot应用,JAR包本身是纯Java的,跨平台没问题,但有两个隐患需要注意。
第一个隐患是JAR内部内嵌了JNA或其他native库。很多Java中间件为了性能,会在JAR里带上C语言的动态库,比如snappy-java、rocksdb-jni、jna。这些库是分架构的,如果打包时只带了x86_64版本,到了ARM64机器上加载就会报错。这个在打包阶段就要确认,后面我也会细说。
第二个隐患是JDK本身。银河麒麟V10自带OpenJDK 8,可以直接用系统源安装,也可以单独携带一个aarch64的JDK tarball。我们是直接把影子环境里的JDK目录整个tar打包带过去的,保证版本完全一致。
如果DataAI Portal还有Python相关的服务,离线打包用pip download:
pip download -r requirements.txt -d /opt/pypkg \ --platform manylinux2014_aarch64 --only-binary=:all:到了目标机用:
pip install --no-index --find-links=/opt/pypkg -r requirements.txt注意--platform参数不能省,否则pip会在在线环境下下载当前机器的架构包,又回到老问题上。
2.4 介质校验与版本台账:别让你的U盘毁了一切
离线介质最怕什么?最怕的是拷贝过程中文件损坏,或者版本拿错,到了现场装到一半才发现。我的对策是两件事:校验和台账。
校验很简单,在影子环境里对所有安装包生成SHA256:
find /opt/offline-rpms -type f -name "*.rpm" -exec sha256sum {} \; > /opt/offline-rpms/checksum.sha256到了目标机执行sha256sum -c checksum.sha256,确认所有文件完整。
台账是一张表格,记录每个包的名称、版本、架构、来源、校验值前8位、用途。别小看这个动作,离线环境里没有外网搜索能力,现场工程师拿到你的介质包,全靠这个台账判断该装什么、不装什么、装错了怎么排查。
| 组件 | 版本 | 架构 | 用途 | 部署方式 |
|---|---|---|---|---|
| OpenJDK | 8u312 | aarch64 | Java运行时 | tar包 |
| Nginx | 1.20.1 | aarch64 | 前端静态资源与反向代理 | rpm |
| Redis | 6.2.7 | aarch64 | 缓存 | rpm |
| DM8 | 8.1.2 | aarch64 | 关系型数据库 | bin安装包 |
| RabbitMQ | 3.9.15 | aarch64 | 消息队列 | rpm |
| MinIO | 2023-08-01 | aarch64 | 对象存储 | 二进制 |
这份台账我建议统一命名为manifest.txt,放在介质包的根目录。现场部署时,第一件事就是对着台账清点文件,缺什么立刻反馈,不要等到安装时才意识到介质不全。
3. 麒麟V10飞腾环境的初始化与预检
3.1 系统基线:内核参数、字符集与时区
到了现场,第一步不是急着装DataAI Portal,而是把操作系统基础环境调好。我按顺序做这几件事。
先看系统基本信息,确认没拿错机器:
cat /etc/os-release uname -m lscpu | grep "Model name"再看内核参数。信创环境下跑Spring Boot微服务,有几个参数必须调:
cat >> /etc/sysctl.conf <<EOF vm.max_map_count = 655360 vm.swappiness = 10 fs.file-max = 6553560 net.core.somaxconn = 4096 EOF sysctl -pvm.max_map_count不调的话,Elasticsearch这类组件或者JVM的mmap文件操作会报错;vm.swappiness调低是为了减少swap抖动,毕竟数据库和应用都在同一批机器上,频繁换页会很影响性能。
字符集必须确认是UTF-8:
echo $LANG localectl set-locale LANG=zh_CN.UTF-8这个坑很隐蔽。如果系统字符集是POSIX或C,应用写入中文数据可能乱码,报表和日志中的中文全都变成问号。我们这次直接一步到位改成了zh_CN.UTF-8。
然后是时区和时间同步。离线环境没有外网,不能走公网NTP,但通常企业内部会有NTP服务器。配置chrony指到内网NTP地址:
yum install -y chrony cat > /etc/chrony.conf <<EOF server 192.168.10.5 iburst local stratum 10 EOF systemctl restart chronyd chronyc sources -v时间不同步会导致两个严重后果:一是分布式组件之间的消息乱序;二是SSL证书校验失败。特别是在离线环境里,系统时间如果跑偏,应用启动时的证书相关报错会让人误判成介质问题,排查半天才发现是时间差了几分钟。
3.2 数据库与中间件的离线安装
数据库我们最终选了达梦DM8,这也是信创环境里最常见的组合。DM8的离线安装包是一个bin文件,直接执行./DMInstall.bin,可以选择命令行或图形界面安装。命令行方式适合远程操作,推荐用dmdba用户来安装,不要用root:
groupadd dmdba useradd -g dmdba -m -d /home/dmdba dmdba mkdir -p /opt/dmdbms chown -R dmdba:dmdba /opt/dmdbms su - dmdba -c "/data/install/DMInstall.bin -q"创建实例的时候有两点提醒:字符集选UTF-8;大小写敏感配置要跟应用需求一致。我们第一次创建实例时大小写敏感选项没估算好,后面应用连上后表名大小写对不上,重建了一次实例才解决。达梦对Oracle和MySQL的兼容模式有选项,DataAI Portal如果原本是基于MySQL开发的,建议用兼容MySQL的模式初始化,能省很多SQL方言的适配工作。
Redis和Nginx的离线安装就简单多了,直接用本地RPM源:
yum install -y redis nginx装完后把Redis的maxmemory、requirepass按生产要求配好。Nginx要注意的是编译模块,如果只用反向代理和静态文件,麒麟源里的默认版本完全够用,没必要非得自己编译。自己做编译的话要带gcc、make、pcre-devel、openssl-devel等一堆工具链,离线环境下亏得很。
3.3 运行用户、目录与systemd服务规划
生产环境部署最忌讳用root跑业务应用。我统一建了一个dataai用户:
useradd -m -d /home/dataai dataai mkdir -p /opt/dataai/{bin,conf,lib,logs} mkdir -p /var/log/dataai chown -R dataai:dataai /opt/dataai /var/log/dataai每个服务写一个systemd unit文件。这一步很多人会偷懒用nohup启动,但在离线交付场景,我强烈建议用systemd。原因很简单:现场的运维习惯是systemctl管理服务,而且systemd能实现宕机自动拉起,这在没人值守的服务器上太重要了。
以网关服务为例,unit文件长这样:
cat > /etc/systemd/system/dataai-gateway.service <<EOF [Unit] Description=DataAI Portal Gateway After=network.target mysqld.service redis.service [Service] User=dataai WorkingDirectory=/opt/dataai ExecStart=/usr/bin/java -Xmx4g -jar /opt/dataai/lib/dataai-gateway.jar ExecStop=/bin/kill -s TERM \$MAINPID Restart=on-failure RestartSec=10 LimitNOFILE=65535 [Install] WantedBy=multi-user.target EOF写完记得systemctl daemon-reload,然后逐一启动。服务规划的原则是:基础组件先起,业务服务后起;所有服务都有明确的启动顺序说明,写进交付文档里。现场工程师不一定懂你的业务,但只要能照文档按顺序执行,就不会有大的偏差。
4. DataAI Portal安装执行与逐项验证
4.1 配置文件的“信创改动点”
DataAI Portal的配置主要分为几类:数据库连接、缓存连接、文件存储地址、各微服务的注册中心地址。在信创环境里,最需要留意的是数据库驱动的替换。
假设应用原本连的是MySQL,连接串长这样:
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/dataai spring.datasource.username=dataai spring.datasource.password=******换成达梦DM8后:
spring.datasource.url=jdbc:dm://192.168.10.10:5236/dataai spring.datasource.driver-class-name=dm.jdbc.driver.DmDriver spring.datasource.username=SYSDBA spring.datasource.password=******问题来了:Spring Boot的默认配置里并没有达梦的驱动依赖。如果打包时没把DmJdbcDriver18.jar放进去,运行时就会报ClassNotFoundException: dm.jdbc.driver.DmDriver。这个驱动JAR需要提前从达梦的安装包里找到,手动放进应用的lib目录,或者用-Dloader.path=/opt/dataai/lib指定外部依赖加载路径。
另一个容易忽略的连接配置是连接池校验SQL。不同数据库的校验语句不一样,MySQL是SELECT 1,达梦同样可以用SELECT 1,但有些版本需要写成SELECT 1 FROM DUAL。如果配置不对,应用启动后连接池会持续报错,接口全部超时。建议在部署前就用数据库客户端验证一遍JDBC连接串和驱动兼容性,别等应用起来再排查。
4.2 启动顺序与服务健康检查
服务的启动顺序很重要。我们的顺序是:达梦数据库→Redis→RabbitMQ→MinIO→注册中心→各业务微服务→Nginx前端。
为什么要按这个顺序?因为业务服务启动时会去连接依赖的基础组件。如果数据库还没起来,业务服务启动时连接失败,可能直接退出,或者进入无限重试。虽然大多数框架支持重试,但重试期间服务状态是“半死不活”的,现场人员不好判断到底有没有问题。所以按照依赖关系逐层启动,每层确认健康后再启动下一层,是最稳妥的方式。
健康检查我建议用两条腿走路。
第一是进程层面:
systemctl status dataai-gateway.service第二是应用层面。DataAI Portal的微服务如果暴露了Actuator端点,就通过HTTP检查:
curl -s http://127.0.0.1:8081/actuator/health | jq .返回"status":"UP"才算真正就绪。如果没暴露Actuator,就检查应用的启动日志,找到“Started XXXApplication in x seconds”这样的标志性日志来确认。
我还写了一个简单的巡检脚本,可以循环检查所有服务的端口和健康接口:
#!/bin/bash services=("8080:web" "8081:gateway" "8082:data-service" "8083:model-service") for item in "${services[@]}"; do port="${item%%:*}" name="${item##*:}" if ss -tlnp | grep -q ":$port "; then echo "[OK] $name listening on $port" else echo "[FAIL] $name not listening on $port" fi done这个脚本在每次部署或重启后跑一遍,30秒就能给出结论,现场排查问题时非常好用。
4.3 首次登录与端到端链路联调
服务全部启动后,打开浏览器访问门户地址,用管理员账号登录。这里我强调一个原则:一定要做一遍完整的核心链路,而不是只看登录页能打开就宣布部署成功。
我的核心链路是:登录→添加数据源→测试数据源连通→创建数据开发任务→提交离线调度任务→任务执行成功→在报表模块创建可视化图表→导出PDF。每一步都实际执行一遍,并把结果记录下来。只有核心链路全部走通,才敢说是真正部署完成。
为了加速验证,我通常会写一个Postman集合或者一个简单的shell脚本,按顺序调用这些核心接口,每步输出响应码:
curl -s -X POST http://127.0.0.1:8080/api/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"xxx"}' -o /tmp/login.json cat /tmp/login.json如果每个步骤耗时都在合理范围、返回码都是200,那基本可以确认部署没问题了。这里要提醒一句:首次登录通常要重置密码,这个环节虽然小,但最容易漏掉。之前有次交付,客户反馈“部署成功但登录不了”,结果是因为我们用的默认密码过期策略,管理员账号首次登录强制改密,而文档里没写清楚。
5. 功能测试与稳定性验证:不能只证明“能跑起来”
5.1 功能测试用例的设计思路
部署完成只是开始,测试才是重头戏。我的测试策略是:以核心业务链路为主线,覆盖周边功能模块,兼顾异常和边界场景。
用例设计按模块拆分,每个模块一张表。以登录认证为例:
| 用例编号 | 前置条件 | 测试步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|
| LOGIN-001 | 系统已部署 | 输入正确用户名密码登录 | 登录成功,跳转首页 | 通过 |
| LOGIN-002 | 系统已部署 | 输入错误密码 | 提示“用户名或密码错误” | 通过 |
| LOGIN-003 | 用户已登录 | 等待会话超时后操作 | 跳转登录页并提示重新登录 | 通过 |
| LOGIN-004 | 系统已部署 | 连续5次输错密码 | 账号锁定,提示联系管理员 | 通过 |
数据源管理模块要覆盖:新增MySQL数据源、新增达梦数据源、测试连通性成功/失败、编辑数据源、删除数据源。其中“测试连通性失败”这个负向用例特别重要,因为很多现场环境的内网网段做了隔离,数据库端口可能不通,一定要在功能测试阶段就暴露出来。
数据开发模块重点测:新建任务、配置调度周期、手工触发执行、查看执行日志、任务失败重跑。AI模型管理模块重点测:模型上传、模型部署上线、发起在线推理、查看推理结果。报表模块重点测:创建报表、选择图表类型、渲染展示、导出PDF/Excel。
关于前端自动化,我多说两句。信创环境常用的浏览器是奇安信浏览器、铨兴浏览器这类基于Chromium内核的产品,它们的WebDriver版本和架构(ARM64)是否能适配,必须在影子环境里提前验证。如果driver不兼容,Selenium根本驱动不了浏览器。如果时间紧,手工回归加接口自动化测试的组合更实际,别在前端自动化上死磕。
5.2 性能基准与容量估算
功能测试通过后,还要回答“这台机器能扛多大的并发”这个问题。我们用的是JMeter,因为JMeter是纯Java应用,在ARM64上跑没问题。
压测分两个阶段。
第一阶段是单接口基准。选登录接口、报表查询接口、模型推理接口各跑一轮,每轮5分钟,并发从10到50逐级增加,记录TPS和响应时间。以我们的环境为参考:20并发下登录接口TPS约320,P95响应80ms;报表查询接口TPS约45,P95响应1.2s。这个数据本身没有绝对意义,重要的是建立基准线,后续如果系统升级或数据量增长,可以对比这个基准判断是否劣化。
第二阶段是混合场景。模拟真实用户行为:30%用户在看报表、20%用户在跑数据开发任务、30%用户在做模型推理、20%用户在做数据源管理。混合压测能暴露组件间的资源竞争问题,比如数据库连接池不够、内存和CPU的争抢导致部分接口P95飙升。
压测过程中要同步监控系统资源:
top -b -d 5 | grep -E "Cpu|Mem" free -h iostat -x 5还有一个容易被忽略的指标是GC日志。Spring Boot应用启动时加上GC打印参数:
-Xloggc:/var/log/dataai/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps压测结束后检查GC日志,看Full GC的频率和耗时。如果压测过程中Full GC次数暴增,说明JVM堆配置不对,或者有内存泄漏迹象,需要回查代码和配置。
5.3 72小时稳定性与故障切换测试
性能达标之后,我建议做一轮72小时的稳定性测试。做法很简单:用JMeter跑一个低并发的混合场景脚本(比如10并发),持续72小时,期间观察四个指标:
- 内存曲线是否持续上升(持续上升意味着内存泄漏)
- GC频率是否稳定
- 应用日志有没有ERROR堆积
- 定时任务是否按时触发并且执行成功
72小时结束后,把每天的TPS和响应时间画成趋势图,如果曲线平稳,没有明显劣化,就可以认为稳定性达标了。
故障切换测试也是交付前必须做的。我们做了三个场景:
第一个场景是kill掉一个网关实例,观察流量是否自动切换到另一个实例。如果网关做了负载均衡(Nginx upstream配置了多个后端),kill掉一个后,Nginx应该自动将请求转发到健康实例,客户端无感知或只有一次重试。
第二个场景是停掉Redis,观察应用的降级表现。缓存挂了之后,查询接口应该降级为查询数据库,而不是直接报500。即使有些接口会变慢,也不能让核心业务完全不可用。
第三个场景是数据库主备切换。如果达梦配置了主备集群,把主库停掉,备库接管后,应用应该能自动重连并继续提供服务。Spring Boot的数据源如果配置了HikariCP,重连机制相对成熟,但要确认初始连接失败后不会永久性失败。
这些故障测试看起来麻烦,但非常值得做。信创环境里,硬件和系统软件都比较新,稳定性问题比成熟的x86生态更容易出现。提前暴露问题,总比交付后客户那边出事再救火强。
6. 踩坑记录:离线部署中最容易翻车的几个环节
6.1 glibc和OpenSSL版本引发的“装好即失败”
最典型的一个坑:某个组件在影子环境里装得好好的,rpm也没报依赖缺失,但到目标机上一启动就报:
./xxx: /lib/aarch64-linux-gnu/libc.so.6: version `GLIBC_2.29' not found (required by ./xxx)原因很直接:影子环境里的系统版本比目标机新,二进制文件编译时链接的glibc版本,在目标机上不存在。这种情况在离线环境特别尴尬,因为没法现下兼容包。
排查方法是用objdump看二进制依赖了哪些GLIBC版本:
objdump -T /opt/xxx/xxx | grep GLIBC看到GLIBC_2.29就说明目标机的glibc(麒麟V10一般是2.28)不满足要求。解决办法是换用更老的构建版本,或者干脆用源码在目标机上重新编译。如果必须用新版本,就只能评估升级系统glibc的风险——这个动作在信创现场一般不建议做,风险太大。
所以我在影子环境准备介质时,现在都会给每件工具记录一个“glibc需求版本”,对照目标机的glibc版本核对。这个检查项我已经写进了交付前的前置检查清单。
OpenSSL也有类似问题。有些安全工具和Python扩展在编译时链接了OpenSSL 3.x,但麒麟V10默认是OpenSSL 1.1.1。即使能装上,运行时报的也是version OPENSSL_3.0.0 not found这种诡异错误。遇到这种问题,优先找目标系统软件源里已有的版本,不要随便从网上拉新版本。
6.2 ARM64架构下原生库缺失的典型症状
我们第一次启动某个微服务时,日志里直接报了一个原生库加载错误:
java.lang.UnsatisfiedLinkError: no snappy-jni-aarch64 in java.library.path这个报错信息非常直白:应用在加载s snappy压缩库时,没有找到aarch64版本的JNI库。如果你用的是x86环境,这里会加载snappy-jni-x86_64,但ARM64平台上需要单独带aarch64版本的依赖。
排查方法:把JAR包解压开,看里面的.so文件:
unzip -l dataai-model-service.jar | grep -E "\.so|\.dll|\.dylib" jar tf dataai-model-service.jar | grep -iE "native|jni|natives"如果发现只有x86_64的.so,就需要去Maven仓库找对应的aarch64版本,替换后重新打包。JNA库的兼容性也要检查:如果应用用了JNA,需要把平台相关的jnidispatch.so换成aarch64版本,或者在启动参数里加-Djna.nosys=true,让JNA走纯Java实现,但性能会有一定损耗。
这个坑的难点在于:它不会在安装阶段暴露,而是在应用运行初期才报错,而且报错信息五花八门。有的报UnsatisfiedLinkError,有的报ExceptionInInitializerError,还有的干脆是NoClassDefFoundError。所以我的经验是:在影子环境做预演时,要把每一个微服务的启动日志都过一遍,专门搜索UnsatisfiedLinkError、ClassNotFoundException、ExceptionInInitializerError这三个关键词,全部确认没有才做介质。
6.3 离线环境的DNS超时与字体问题
两个看起来很不起眼但对体验影响极大的问题。
第一个是DNS超时。离线环境没有外网的DNS服务器,但应用配置里还残留了一些公网域名(比如某个开源组件默认连接NTP服务器,或者应用配置了外网回调地址)。每次请求这些域名时,系统先尝试DNS解析,等超时之后才返回失败,这一等就是5到30秒。现象就是接口偶尔特别慢,过一会儿又好了,极难排查。
我的排查思路是抓应用日志,看是不是卡在InetAddress解析上。确认是DNS问题后,处理方式有两个:一是把涉及的外网域名在/etc/hosts里直接写死指向本地回环地址;二是确认应用代码里有没有外网地址配置,统统改成内网地址。最简单的排查命令:
strace -f -e trace=network -p <pid> 2>&1 | grep -E "connect|DNS"第二个问题是中文字体缺失。离线环境为了精简通常会砍掉中文字体包,而应用生成报表或导出PDF时如果调用了系统字体渲染,中文就会显示成方块。这个在功能测试阶段很容易漏掉,因为界面上大多数是数字和英文。直到我们导出第一张中文报表,才发现所有中文都变成了“□□□”。
解决办法是提前把中文字体包放进介质。在影子环境执行:
yum install -y fonts-noto-cjk wqy-microhei然后把/usr/share/fonts目录一起打包,到目标机解压覆盖,最后:
fc-cache -fv提醒一点:字体包在很多人眼里不是“软件依赖”,所以离线介质准备时特别容易被漏掉。我建议把字体、时区数据、CA证书这些“隐形依赖”也纳入台账管理。
6.4 部署脚本的幂等性:别让现场跑两遍就翻车
最后一个坑来自我们自己:部署脚本不幂等。某个初始化脚本在全新环境跑一遍没问题,但现场工程师不小心执行了两次,就报错——用户已存在、目录已存在、SQL插入了重复数据。
离线交付场景下,脚本能不能重复执行,决定了现场运维的体验。我在这次项目里把脚本全部改成了幂等写法:
# 幂等创建用户 id dataai &>/dev/null || useradd -m -d /home/dataai dataai # 幂等创建目录 mkdir -p /opt/dataai/{bin,conf,lib,logs} # 幂等初始化数据库(SQL里先判断表是否存在) CREATE TABLE IF NOT EXISTS t_user (...);还有一点:初始化SQL里的数据插入,能加唯一约束的就加唯一约束,或者用INSERT ... ON DUPLICATE KEY UPDATE。否则重复执行脚本会导致测试数据翻倍,正式数据重复,后面排查起来很痛苦。
写完脚本后,我会在影子环境里把脚本连续执行三遍,确认第三遍和第一遍的结果一致才交付。这个习惯我保持到现在,因为它真的能拯救现场工程师的头发。
最后再说一点个人体会。信创环境离线部署,技术栈本身并不神秘,难的是“闭环”两个字。在线环境下遇到问题可以随时安装、更新、搜索、绕路;离线环境下每一样东西都要提前想到、提前准备,想不周全就得现场加班。做完这次项目之后,我养成了一个习惯:每次交付都附一张“环境预检清单”,让客户在部署前先跑一遍,确认系统版本、架构、磁盘空间、glibc版本、中文字体、时区这些前置条件全都正常,再动安装。算下来,这张清单能省掉现场至少一半的排查时间。这大概是这次踩坑换来的最大收获。