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

资讯详情

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

鲲鹏服务器迁移实战:从零搭建DevKit持续集成部署流水线

鲲鹏服务器迁移实战:从零搭建DevKit持续集成部署流水线 这两年越来越多的业务系统往鲲鹏服务器上迁移但CI/CD这块很多团队还是老办法在x86机器上构建人工拷到鲲鹏环境部署。本地跑一两个功能样例看着没事一到生产就冒出一堆架构不兼容、依赖缺包、镜像拉错的怪问题。我先后帮几个项目搭过鲲鹏DevKit环境下的持续集成部署流水线从迁移检查到构建节点从镜像推送到远程部署都摸了一遍。这篇就把从零搭建的完整过程拆开讲透为什么鲲鹏上不能照搬x86方案、流水线每个阶段怎么改、DevKit在什么环节介入、脚本和配置长什么样、以及最容易踩的坑。适合正在做鲲鹏适配的应用开发者和负责构建发布的运维同学参考。1. 先捋清楚为什么鲲鹏流水线不能照搬x861.1 架构差异带来的三个硬约束第一个约束是二进制不通用的硬限制。x86_64编译出来的可执行文件和动态库用的是Intel/AMD那套指令集鲲鹏处理器是ARMv8-A架构指令集完全不同产物基本不能直接运行。最典型的现象是jar包能启动但里面一旦加载了native库——比如基于JNI的加解密组件、OCR识别引擎——立刻报libxxx.so: cannot open shared object file或者干脆段错误。Java字节码本身跨平台但JVM得有arm64实现系统底层的glibc、OpenSSL全是arm64版本这些都不是靠复制粘贴能解决的。第二个约束是第三方依赖的“货源”问题。同一个组件很多只有x86版本Maven中央仓库里的纯Java包没问题但带.so、带JNI的包装包很少同时发布两个架构的版本。Python这边相对好一些numpy、pandas这类大库已经有了aarch64的manylinux wheel可一些小而偏的库还是只能源码编译。所以流水线设计里必须预留一条“源码编译兜底”的路不能默认所有依赖都能直接拉到否则构建节点一装环境就会卡住。第三个约束是编译参数和性能预期。C/C代码如果不用-marcharmv8-a这类架构选项编译器会生成比较保守的指令性能差一截如果代码里写了x86的SSE内建函数在鲲鹏上要么编译报错要么静默回退成通用实现。这些东西靠人肉在流水线外盯着早晚漏。用DevKit的迁移扫描和编译优化工具把它们固化成流水线检查步骤比依赖人的自觉靠谱得多。1.2 DevKit在流水线里的角色定位先把概念说清楚DevKit不是CI/CD引擎。你依然需要GitLab CI、Jenkins这一层做任务调度DevKit更像一套围绕鲲鹏开发场景的配套工具集。常见组件包括代码迁移工具扫描x86工程输出不兼容点和迁移工作量评估、编译工具毕昇编译器能针对鲲鹏生成更优的机器码、性能分析工具采集热点函数、线程状态、资源利用率。它们的共同点是都能通过脚本调用或输出结构化报告所以很适合嵌进流水线当质量闸门。实际项目里我把DevKit放在两个位置。一是MR阶段每次代码合入前跑一次迁移扫描和兼容性检查防止有人把x86编译选项或SSE指令悄悄写回主干。二是发布前的性能回归用固定压测用例跑核心接口对比上次发布的P95延迟和CPU占用劣化超过阈值就失败。这两类检查在x86流水线里是可选项但在鲲鹏环境里我建议做强制门禁因为“能编译”和“跑得好”之间的差距比x86上大得多。1.3 两种落地场景的选择如果团队已经有一套跑在x86上的Jenkins或GitLab CI最稳妥的做法不是推倒重来而是加一个鲲鹏构建节点。在鲲鹏机器上装好arm64工具链和Runner打上标签流水线里只要有一个job指定到这个节点跑其余阶段原样不动。这种方式改动最小还方便做新旧环境的对比验证。如果是从零开始我更推荐直接用GitLab CI。Runner注册非常轻executor用shell或docker都行官方也提供arm64的安装包Jenkins的构建节点在ARM机器上也能装但SSH连接、JDK匹配这些细节更多新手容易在环境问题上耗掉半天。下面实操部分以GitLab CI为主线Jenkins的接入思路会在关键节点提示一下。2. 流水线的阶段设计与“ARM意识”改造2.1 一条完整流水线长什么样以一次发布为例我习惯把流水线拆成六个阶段代码检查、编译构建、单元测试、制品打包、镜像构建、部署验证。细一点还可以再插进迁移扫描和性能回归。整条链路用文字描述大概是开发者提交代码合并请求触发流水线在鲲鹏构建节点上跑代码检查含DevKit迁移扫描用arm64工具链编译构建在arm64节点上跑单元测试尤其是涉及native库的用例制品归档打上架构和版本标签构建ARM镜像推送仓库SSH到目标机器更新服务冒烟验证核心接口。每个阶段都是一道门上一阶段失败就停不把问题往后传。这套流程和x86的差别不在于阶段名字而在于每一道门背后都要多问一句这个环节的产物是不是arm64的。2.2 每个阶段的“ARM意识”改造代码检查这关静态分析工具本身也得是arm64版本。SonarQube Scanner、各类lint工具都要下载aarch64的包否则扫描器在x86上对arm64产物做检查结果没有参考意义如果用的是容器化扫描方式镜像同样要选对架构。编译构建这关JAVA_HOME必须指向arm64的JDK。Maven仓库建议独立出来不要和x86构建共用同一个本地仓库——同一个依赖在两种架构下内容不同混用会把x86的native库缓存进去后面运行阶段才炸。Maven解压包是纯Java的本身不挑架构但依赖的JDK必须对。单元测试这关凡是涉及native代码的用例只有真正在鲲鹏上跑才有效。JVM的单元测试在x86上跑一遍、部署到鲲鹏再回归一遍成本太高不如把测试用例直接放在arm64节点跑。纯逻辑测试可以留在通用节点别一刀切。镜像构建和部署这关基础镜像要选arm64变体。Docker Hub上带arm64v8前缀的镜像或者支持多架构manifest的官方镜像都可以用buildx构建多架构镜像是更正规的做法但需要构建机支持模拟器或者有两台真机构成构建矩阵。部署时K8s环境要写明nodeSelector裸机部署要确认systemd单元里的环境和路径。2.3 制品与版本怎么管制品仓库按架构分目录这件事很多人第一次都会忽略。同一版本号下x86和arm64的产物如果混放在同一个路径后面排查问题会非常痛苦。我建议的规则是版本号里带架构标识比如1.2.0-arm64-20250324镜像tag也写清楚比如1.2.0-linux-arm64Nexus或Harbor里按repository/release/{arch}/{version}组织。发布记录至少要有三样东西代码commit号、架构标签、镜像digest。这样回滚目标明确不用靠猜。3. 从零搭建的核心实操记录3.1 构建节点准备麒麟V10上的环境清单构建节点我用的银河麒麟高级服务器操作系统V10arm64版代号Halberd这也是鲲鹏生态里最常见的服务器系统之一。装完系统的第一件事是确认架构uname -m输出aarch64就对了。然后配置软件源——麒麟的软件仓库必须和架构匹配别沿x86的习惯直接套用旧源文件。# 确认CPU架构 uname -m # aarch64 # 查看系统版本 cat /etc/os-release # 安装基础工具链以麒麟/欧拉系yum为例 yum install -y git vim java-17-openjdk java-17-openjdk-develJDK直接用系统源里的OpenJDK最省事。Maven无所谓架构它是纯Java的但要确保JAVA_HOME指向上面安装的arm64 JDK。Node.js用官方arm64版本解压到/opt/node把bin目录加进PATH。.NET的安装略有特殊性官方提供linux-arm64的tar包下载解压后要设置DOTNET_ROOT环境变量否则dotnet命令找不到运行时。# dotnet SDK 8.0 linux-arm64 安装示例 mkdir -p /opt/dotnet tar -xzf dotnet-sdk-8.0.408-linux-arm64.tar.gz -C /opt/dotnet ln -s /opt/dotnet/dotnet /usr/local/bin/dotnet export DOTNET_ROOT/opt/dotnet echo export DOTNET_ROOT/opt/dotnet /etc/profile.d/dotnet.sh这套环境装好之后我建议先手动跑一次mvn clean package或dotnet build把环境问题在进流水线之前消化掉。环境问题混到流水线里排查日志多且乱浪费的时间远多于手动验证那几分钟。3.2 用DevKit给旧代码做“迁移体检”从x86迁移过来的项目我不建议直接扔进流水线编译。先用DevKit的代码迁移工具把源码扫一遍它会分析出几类问题编译器选项不兼容比如-mavx2这类x86专属选项、不可移植的内建函数比如SSE指令、以及对x86指令集有强依赖的汇编片段。扫描报告会给出问题位置和修改建议工作量高的文件优先处理。扫完之后记得把报告归档到流水线制品里。我见过不少项目扫过一次就再也不管结果新合入的代码又把几个SSE内建函数带回来。正确做法是把扫描做成一个流水线阶段每次MR都跑报告作为产物保存。Java层面的SSE指令依赖现在不算多重点盯C/C组件和第三方库这一条在实战中反复验证过。3.3 GitLab Runner注册与标签设计构建节点准备好之后让流水线调度到这台机器上。GitLab CI的做法是注册Runner关键是打标签比如arm64这样job里写tags: [arm64]就只会调度到对应节点。# 安装arm64版GitLab Runner以RPM包为例 wget https://packages.gitlab.com/runner/gitlab-runner/packages/el/8/gitlab-runner-aarch64.rpm rpm -ivh gitlab-runner-aarch64.rpm # 注册Runnerexecutor用shell最简单 gitlab-runner register \ --url https://gitlab.example.com \ --token 项目的注册token \ --executor shell \ --tag-list arm64 \ --run-untaggedfalse用--executor shell最简单Runner直接在构建机上执行脚本不存在镜像架构问题。如果要用docker executor注意默认拉取的镜像也是带架构的建议在Runner配置里把镜像名写成arm64变体避免每次拉错。Jenkins的话在Manage Nodes里添加节点launcher选SSH标签同样叫arm64Pipeline里把执行节点指定到该标签即可。3.4 流水线脚本示例以Java SpringBoot服务为例下面给一份GitLab CI的示例项目用Maven构建服务是SpringBoot发布到Kubernetes。实际项目里用若依这类框架做后台的非常多整个流程直接套用即可只需替换构建命令和制品路径。stages: - codecheck - build - image - deploy variables: APP_NAME: demo-app IMAGE_TAG: ${CI_COMMIT_SHORT_SHA}-arm64 migration-scan: stage: codecheck tags: [arm64] script: # 这里调用DevKit的迁移扫描能力不同版本命令不同以实际为准 - devkit migration-scan --src ./ --report scan.json artifacts: paths: - scan.json when: always build: stage: build tags: [arm64] script: - mvn clean package -DskipTestsfalse artifacts: paths: - target/*.jar expire_in: 7 days image: stage: image tags: [arm64] script: - docker build -t ${CI_REGISTRY}/app/${APP_NAME}:${IMAGE_TAG} . - docker push ${CI_REGISTRY}/app/${APP_NAME}:${IMAGE_TAG} deploy: stage: deploy tags: [arm64] script: - kubectl set image deployment/${APP_NAME} app${CI_REGISTRY}/app/${APP_NAME}:${IMAGE_TAG} only: - main这份脚本有三处特意强调了架构全局标签arm64保证所有阶段都在鲲鹏节点执行镜像tag带-arm64后缀避免和x86版本混淆部署时直接更新镜像tag回滚也简单。扫描阶段的devkit命令是占位写法不同版本的工具接入方式有差异但把它固化成流水线阶段的思路是一样的。3.5 Dockerfile与多架构镜像要点Dockerfile建议显式指定arm64基础镜像减少歧义。我常用的写法FROM arm64v8/eclipse-temurin:17-jdk WORKDIR /opt/app COPY target/app.jar /opt/app/ RUN useradd --system --create-home --uid 1001 app \ chown -R app:app /opt/app USER app EXPOSE 8080 ENTRYPOINT [java, -XX:MaxRAMPercentage75, -jar, app.jar]有人习惯在x86机器上把Dockerfile写好推到鲲鹏上再构建这没问题因为镜像构建发生在目标机上。但要小心基础镜像的变体如果写FROM openjdk:17在某些多架构的构建配置下Docker可能解析成amd64的manifest。保险办法是显式写arm64v8/前缀或者构建命令里加--platform linux/arm64/v8指定平台。生产环境如果混用多种架构节点Kubernetes的Deployment一定要加nodeSelector: kubernetes.io/arch: arm64裸机部署则确认systemd单元里的JAVA_HOME和jar包路径没问题。4. 踩坑实录与排查清单4.1 构建成功部署上去就段错误这是鲲鹏环境里遇到最多的问题没有之一。表象是流水线全绿jar包或可执行文件拷到生产机一启动就Segmentation fault。根因几乎都是依赖混入了x86的native库项目里某个组件在Maven解析依赖时因为插件版本过旧在arm64机器上依然拉到了x86_64的.so。排查方法是看启动日志最早崩在哪个库再用ldd和file挨个检查native库的架构。我建议在流水线加一个“产物体检”阶段find target/lib -name *.so | xargs file凡是输出x86-64的一律视为构建失败。这条规则看起来简单但能挡住绝大部分运行时崩溃。4.2 依赖下载慢、报404或拿到错误架构依赖问题常见有三种。第一种是yum源配置错误仓库地址还是x86的装包时拿到i686或x86_64版本检查/etc/yum.repos.d/下的baseurl即可。第二种是Maven本地仓库被x86构建污染同一个groupId和artifactIdx86和arm64缓存的.so文件内容不同混用不会立刻报错要到运行时才崩最迷惑人。解决方法是给arm64节点指定独立的本地仓库比如-Dmaven.repo.local/opt/m2-arm64。第三种是Python包没有aarch64 wheel。优先尝试pip install --only-binary:all:如果找不到匹配的wheel就走源码编译兜底。不要图省事强制下载x86轮子装上的那一刻基本就埋下了运行时崩溃的雷。4.3 Docker镜像拉下来运行报Architecture不匹配如果用的是较老的Docker版本或者私有仓库不支持manifest listdocker pull默认会解析到amd64镜像运行的时候要么报exec format error要么表现怪异。排查分三步升级Docker到20.10以上用docker pull --platform linux/arm64验证能正常拉取用docker inspect --format {{.Architecture}}确认镜像实际架构。Kubernetes集群里混用多种架构节点时这个问题更容易被忽略。调度器不理解镜像架构只会看标签不加nodeSelector就可能把arm64镜像部署到amd64节点上随后冒烟测试必然失败。4.4 常见问题速查表这里整理了一份我实际用过的排查表简明版本可以直接贴在团队文档里现象可能原因排查与解决部署启动即段错误native库是x86用file/ldd检查so架构流水线加产物体检yum安装失败或装到x86包软件源架构不匹配检查repo的baseurl改用aarch64源Maven依赖时好时坏本地仓库架构污染给arm64节点指定独立maven本地仓库pip找不到wheel镜像未同步aarch64包更新镜像源缺失时源码编译兜底Docker容器exec format error镜像架构不对显式写arm64v8基础镜像用--platform拉取静态扫描结果异常扫描器是x86版本换arm64版Scannerdotnet命令找不到DOTNET_ROOT未设置export DOTNET_ROOT并写入profile接口性能比x86差很多编译选项未针对arm优化用毕昇编译器、启用NEON做性能对比表格只是速查。真正难的是“能跑但慢”这一类不报错不崩溃只有性能数据会告诉你出了问题。4.5 性能类问题别忽略x86构建的产物迁到鲲鹏很多团队只验证功能不看性能结果上线后发现CPU跑满、接口延迟翻倍。排查方向有两条一是确认编译时是否用了针对鲲鹏的优化选项Java项目关注JIT和GC参数C/C项目建议用毕昇编译器重新构建二是用DevKit的性能分析工具对核心链路做基准采样把P95和CPU占用作为发布门禁。性能回归最好做成nightly任务固定时间跑定期出报告。不要等发版前才想起性能这件事那时候改代码、重编译、再验证的周期太长线上已经在受罪了。5. 跑顺之后的几点个人体会5.1 先小步试点别指望一次全量迁移这套流程跑顺之后最大的变化不是构建速度而是可信度。以前从x86把东西拷到鲲鹏心里没底每次上线都像开盲盒现在从提交代码到生产部署每一步都有架构检查、扫描报告和性能数据兜底。我体会最深的一点是别想着一步到位。先加一个鲲鹏构建节点跑通一条核心业务观察一周构建表现和依赖覆盖情况再逐步把其他项目迁过来。环境齐不齐、依赖坑多不多跑一个礼拜全暴露了集中的处理成本比打游击低得多。5.2 把DevKit用活别装完就扔DevKit最容易踩的坑是装完不用。迁移扫描用起来之后代码里新引入的兼容性问题能第一时间拦住性能分析工具嵌到发布前的回归里性能劣化能趋势性发现而不是等问题爆发才回头翻。还有一个值得尝试的方向把arm64和x86两套流水线并行跑同一份代码在两个平台都出制品镜像分别打-arm64和-amd64的tag。一开始会觉得成本翻倍但这也为以后做多架构发布铺好了路业务要扩展到混合架构环境时会轻松很多。最后分享一个小技巧所有依赖和产物尽量统一走公司内的制品仓库不要在构建机上裸下载。arm64的包本来就少集中缓存之后出了问题你能在一个仓库里快速确认有没有、对不对而不是翻遍各台机器。等这套基础跑起来顺手了再考虑把DevKit的编译优化做成可选的构建profile让同一套流水线既能跑“兼容模式”也能跑“优化模式”对比着看差异。这样流水线就真真正正成了鲲鹏环境的一块底座而不是一个碰运气的发布通道。
返回列表