ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ARM架构aarch64服务器部署实战:从系统选型到Docker与性能优化

2026/8/31 20:22:29 拓冰建站 浏览量
ARM架构aarch64服务器部署实战:从系统选型到Docker与性能优化 简介本资源面向政府信息化项目实施人员、ARM平台系统集成工程师及国产化替代场景下的Java应用部署工程师解决在aarch64架构内网环境中无法使用yum源时Java Web项目含GIS模块所依赖的全套基础组件离线部署难题。资源包共828个文件涵盖292个Java运行与业务jar包、214个配置及样式xml/properties文件、72个地理图层png/tif等栅格资源以及MySQL、Redis、GeoServer、libstdc等关键服务的aarch64原生tar/rpm安装包和配套shell脚本整体容量697.3MB。已有1714人学习下载内容包含作者实操整理的《资源整合说明》《分组件安装教程》及一个高效可用的aarch64专用RPM搜索地址显著降低跨架构适配门槛预览可见startup.bat、shutdown.bat、ol-demo.cfg等典型部署脚本与配置体现完整服务启停、GIS样式定义与地理数据加载能力具备即取即用的工程落地价值。 接手过几台ARM架构服务器的朋友大概率都经历过这种场面本地x86环境里跑得好好的项目代码打包传到aarch64机器上一条命令下去直接甩给你一句“not this operating system platform”或者“cannot execute binary file”。哪怕装个Docker也会栽在源不对、架构不匹配这些坑上。这篇文章我想把ARM架构aarch64操作系统上做项目部署这件事从头到尾做一次资源整合和实战梳理覆盖操作系统选型、软件源配置、Docker与SpringBoot项目落地、内存与性能优化、常用工具链适配以及最后的验证清单。都是我在真实部署中踩过、填过、验证过的内容按这条链路走能帮你省下大量试错时间。1. 为什么aarch64部署总在“最后一步”翻车先看两个高频报错先说个真实场景。同事把一套基于SpringBoot的后端服务打包成jar在本地Windows的x86环境下测得好好的部署到一台aarch64架构的服务器上执行java -jar app.jar报错内容是Exception in thread main java.lang.UnsatisfiedLinkError: /path/to/libnative.so: /path/to/libnative.so: cannot open shared object file: No such file or directory跟进排查后确认这个libnative.so是x86_64版本在aarch64上根本无法加载。另一个经典报错来自Windows下尝试运行某个工具的时候程序“claude.exe”无法运行: 指定的可执行文件不是此操作系统平台的有效应用程序这句话翻译过来就是你手里拿的是x86或者别的架构编译的二进制但当前系统架构是aarch64操作系统无法识别、无法加载、无法执行。这两种报错本质上是同一个问题的两种表现——架构不匹配。1.1 报错的本质ABI层面的不兼容aarch64和x86_64之间的差异不只是一个“位数”问题而是整套ABI应用二进制接口层面的不兼容。包括指令集完全不同x86_64是CISC复杂指令集aarch64是RISC精简指令集二进制文件里的机器码互相无法解析系统调用号不同即使是最基础的read、write、mmap在x86_64和aarch64内核里的编号都不一样动态链接器路径不同aarch64 Linux下的动态链接器通常是/lib/ld-linux-aarch64.so.1x86_64下是/lib64/ld-linux-x86-64.so.2加载器都找不到后面的动态库更无从谈起。所以部署前的第一件事永远不是急着敲命令而是先确认目标机器的架构。1.2 快速确认目标环境架构的几条命令# 查看内核架构 uname -m # 输出 aarch64就是ARM 64位架构 # 查看操作系统发行版信息 cat /etc/os-release # 查看CPU型号信息 lscpu | grep Architecture # 或者直接看完整信息 lscpu部署镜像、安装包、二进制工具之前先跑这三条命令。架构确认成aarch64后面所有资源的选择都围绕这个核心展开。2. 操作系统层麒麟、openEuler与Ubuntu的选型与软件源配置清单ARM服务器上可用的操作系统其实不少。常见的有麒麟KylinV10、openEuler欧拉、Ubuntu Serveraarch64版、Debianarm64版、CentOS Streamaarch64版、Alpine Linux等。不同场景下选型逻辑不一样这里给出一张我实际部署时常用的对比表。操作系统包管理方式适用于常见问题麒麟V10基于CentOS生态yum/dnf国内服务器、政企项目、信创环境默认源速度不稳需要换源部分软件需手动编译openEuleropenEuler 22.03 LTS等dnf/yum大规模服务器集群、国产化场景NFS等系统服务需单独配置部分第三方软件源缺少aarch64包Ubuntu Server 20.04/22.04/24.04apt通用开发环境、Docker友好型环境默认源不含aarch64需切换到ports源Debian 11/12apt稳定性要求高的场景架构一般写作arm64包名需要留意Alpine Linuxapk容器镜像、轻量环境musl libc与部分glibc程序不兼容2.1 麒麟V10更换yum源最容易被忽略的架构匹配问题麒麟V10系统默认的yum源里很多包是x86_64架构的。直接在aarch64机器上执行yum install docker很可能会报错“No match for argument”或者下载后安装失败。这时要手动更换为aarch64对应的软件源。我实际使用过的一种稳定做法是配置华为云或阿里云的麒麟V10 aarch64源。以华为云镜像源为例# 备份原repo文件 mkdir -p /etc/yum.repos.d/backup mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ # 新建麒麟V10 aarch64专用repo以Kylin V10 SP1为例 cat /etc/yum.repos.d/kylin_aarch64.repo EOF [ks10-adv-os] nameKylin Linux Advanced Server 10 - Os baseurlhttps://mirrors.huaweicloud.com/kylin/KylinV10SP1/aarch64/ gpgcheck0 enabled1 [ks10-adv-updates] nameKylin Linux Advanced Server 10 - Updates baseurlhttps://mirrors.huaweicloud.com/kylin/KylinV10SP1/aarch64/updates/ gpgcheck0 enabled1 EOF # 清理并重建缓存 yum clean all yum makecache这里需要注意不同版本麒麟V10的repo路径不一样SP1、SP2、SP3各有独立目录保险做法是先打开镜像站目录页确认实际路径。另外gpgcheck0在公网环境可以接受内网或安全要求高时建议导入官方GPG密钥再开启校验。2.2 openEuler安装后必做的NFS配置一个真实的部署案例openEuler在ARM服务器上表现不错但它默认不启用NFS相关服务。一次部署中我需要让三台aarch64节点挂载同一台NFS存储服务器。操作链路如下# 在openEuler节点上安装NFS客户端工具 dnf install -y nfs-utils # 启动相关服务 systemctl enable rpcbind systemctl enable nfs-client.target systemctl start rpcbind # 挂载远程NFS目录 mount -t nfs -o vers4.0 192.168.10.20:/data/nfs /mnt/nfs最坑的地方在于openEuler默认防火墙是启用的即使NFS服务端配置正确客户端也经常挂载超时。排查命令# 查看防火墙状态 systemctl status firewalld # 临时放行NFS相关端口 firewall-cmd --add-servicenfs --permanent firewall-cmd --add-servicerpc-bind --permanent firewall-cmd --add-servicemountd --permanent firewall-cmd --reloadNFS挂载成功后建议在/etc/fstab中追加自动挂载配置否则重启后还得手动执行一次。2.3 Ubuntu aarch64换清华源注意是ports而非ubuntu目录很多同事在Ubuntu aarch64上换源时直接把x86那套清华源配置粘贴过来然后apt update报404。原因很简单x86_64的Ubuntu软件包在/ubuntu/目录下而ARM64架构的包在/ubuntu-ports/目录下二者路径体系不同。修改/etc/apt/sources.list以Ubuntu 22.04为例# 备份原配置 cp /etc/apt/sources.list /etc/apt/sources.list.bak # 写入清华ubuntu-ports源 cat /etc/apt/sources.list EOF deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse EOF # 更新索引 apt update apt upgrade -y这里有个细节如果安装的是Ubuntu Server官方aarch64版本系统里可能还带了一个/etc/apt/sources.list.d/ubuntu.sources新版本Ubuntu22.04之后默认使用deb822格式直接改sources.list可能不生效需要同步修改或者删除该文件。3. 容器化主线从Docker安装到SpringBoot镜像的多架构构建在aarch64上部署项目最省心的方案就是容器化。Docker镜像天然携带架构信息只要选对架构标签就能避免大量环境依赖问题。但Docker本身在ARM系统上的安装也是一个容易踩坑的环节。3.1 麒麟V10安装Docker优先用二进制包而非yum源麒麟V10的yum源里虽然有docker但版本通常比较旧而且依赖关系在aarch64上经常解析失败。我自己试过多次最稳定的安装方式是直接使用官方Docker二进制包。# 进入下载目录 cd /opt # 下载aarch64版docker二进制包 wget https://download.docker.com/linux/static/stable/aarch64/docker-24.0.7.tgz # 解压并安装到/usr/bin tar -zxvf docker-24.0.7.tgz cp docker/* /usr/bin/ # 创建systemd服务文件 cat /etc/systemd/system/docker.service EOF [Unit] DescriptionDocker Daemon Afternetwork-online.target [Service] Typenotify ExecStart/usr/bin/dockerd ExecReload/bin/kill -s HUP $MAINPID LimitNOFILE1048576 LimitNPROCinfinity LimitCOREinfinity [Install] WantedBymulti-user.target EOF # 启动并设置开机自启 systemctl daemon-reload systemctl enable docker systemctl start docker # 验证安装 docker infodocker info输出中需要重点检查一行Architecture: aarch64。如果显示的是x86_64说明dockerd本身运行异常或者下载错了包。3.2 Docker部署SpringBoot项目从镜像构建到容器启动SpringBoot项目在aarch64上部署核心是把项目打成镜像。以常见的openjdk:17-jdk镜像和maven构建为例这里给出一个在aarch64服务器上直接构建的Dockerfile# 第一阶段构建 FROM maven:3.8-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行 FROM openjdk:17-jdk-slim WORKDIR /app COPY --frombuilder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建命令docker build -t myapp:1.0 . # 运行容器 docker run -d --name myapp \ -p 8080:8080 \ --restartalways \ -e SPRING_PROFILES_ACTIVEprod \ myapp:1.0这里有一个很多人忽略的问题基础镜像必须是aarch64版本。openjdk:17-jdk-slim官方镜像同时提供多架构Docker在aarch64上执行docker pull时会自动拉取arm64变体但如果你在Dockerfile里强制指定了平台比如--platformlinux/amd64那构建出来的镜像在aarch64上同样无法运行。3.3 跨架构构建用buildx构建同时适配x86和ARM的镜像如果在开发机上想一次构建出多架构镜像可以使用docker buildx。它的核心原理是在x86主机上用QEMU模拟aarch64执行环境让每个架构的构建过程在模拟器中跑一遍最终生成带架构标签的多架构镜像。# 启用buildx新版Docker自带无需单独安装 docker buildx version # 创建一个多架构构建器 docker buildx create --name multiarch --use # 构建并推送多架构镜像 docker buildx build \ --platform linux/amd64,linux/arm64 \ -t your-registry.com/myapp:1.0 \ --push .执行完成后在镜像仓库里可以看到myapp:1.0同时带linux/amd64和linux/arm64两个manifest。部署到aarch64服务器上执行docker pull时Docker会自动选择arm64变体。注意事项--push是必须的buildx默认不会把多架构镜像直接保存到本地镜像列表不加--push本地只能看到构建结果缓存构建器第一次执行跨架构构建时需要下载对应的QEMU模拟组件时间较长某些原生扩展库比如JNI在模拟环境下编译经常失败这种情况建议直接在aarch64原生机器上跑构建。4. 硬核性能向内存管理链路与Neon指令集的落地优化部署只是第一步真正跑起来之后性能优化才是大头。aarch64架构下的内存管理链路和x86有不少差异理解这条链路能帮你在排查内存问题、性能瓶颈时少走很多弯路。4.1 从memblock到buddy内存管理的必经之路在Linux内核中aarch64架构下物理内存管理分为几个层级阶段机制作用早期启动内核刚解压时memblock用极简的数据结构记录可用物理内存区域供早期内存分配使用启动完成后周期性的buddy伙伴系统以页通常4KB为粒度管理物理内存负责页的分配与回收内核运行时小对象分配slab/slub为内核中频繁创建销毁的小对象如task_struct提供对象缓存内核运行时变长分配kmalloc/vmallockmalloc从slab分配物理连续且线性映射的内存vmalloc分配虚拟连续、物理不一定连续的内存我在分析一次aarch64服务器内存不足问题时用/proc/meminfo和/proc/buddyinfo对照排查定位到是某个驱动反复申请大块连续物理内存把buddy的连续块耗尽了。这时通过调整内核参数vm.min_free_kbytes预留一部分紧急内存问题得到缓解# 查看当前预留内存阈值 cat /proc/sys/vm/min_free_kbytes # 调整预留内存单位KB根据物理内存大小按比例设置 sysctl -w vm.min_free_kbytes131072 echo vm.min_free_kbytes131072 /etc/sysctl.conf4.2 kmalloc和vmalloc怎么选一张表说清楚很多人在写内核模块或者驱动时对选择kmalloc还是vmalloc纠结。我根据实际经验整理如下对比维度kmallocvmalloc物理连续性物理连续物理不一定连续靠页表映射成虚拟连续分配速度快直接查slab慢需要建立页表映射适合场景小块内存、DMA缓冲区、性能敏感路径大块内存、模块加载、不要求物理连续的场景最大分配限制受buddy连续页限制通常建议小于1MB可以分配较大内存但存在页表开销使用方式kmalloc(size, GFP_KERNEL)vmalloc(size)实际经验是内核模块优先用kmalloc迫不得已再上vmalloc。一次驱动适配中我临时改用vmalloc分配了一块2MB内存存数据性能下降了将近20%后来改成kmalloc配合内存池才恢复预期。4.3 用NEON指令优化memcpy一个可复现的案例aarch64对多媒体和SIMD有原生支持这就是NEON指令集。很多高性能场景下可以用NEON向量指令替代逐字节拷贝显著提升效率。以一个常见的memcpy优化为例#include arm_neon.h void memcpy_neon(uint8_t *dst, const uint8_t *src, size_t len) { size_t i 0; // 一次处理16字节用NEON的128位寄存器 for (; i 16 len; i 16) { uint8x16_t data vld1q_u8(src i); vst1q_u8(dst i, data); } // 剩余不足16字节的部分用普通字节拷贝 for (; i len; i) { dst[i] src[i]; } }用vld1q_u8一次性从内存加载16字节到NEON寄存器再用vst1q_u8写回目标地址相比逐字节循环减少了大量指令发射次数。做基准测试时数据量在1KB以上时NEON版本比逐字节memcpy快了约2-3倍不同CPU主频和内存带宽下表现有差异。更进一步的优化还可以同时用两个NEON寄存器做双发射把拷贝吞吐再压一档void memcpy_neon_unrolled(uint8_t *dst, const uint8_t *src, size_t len) { size_t i 0; for (; i 32 len; i 32) { uint8x16_t data0 vld1q_u8(src i); uint8x16_t data1 vld1q_u8(src i 16); vst1q_u8(dst i, data0); vst1q_u8(dst i 16, data1); } for (; i 16 len; i 16) { uint8x16_t data vld1q_u8(src i); vst1q_u8(dst i, data); } for (; i len; i) { dst[i] src[i]; } }编译时记得加-marcharmv8-asimd或者直接默认开启NEON的选项否则编译器可能不会生成NEON指令。实际项目中如果需要处理大数据块拷贝比如网络包转发、图像缓冲区复制这类优化非常实用。5. 工具链适配Kettle、宝塔、Jenkins与前后端分离项目的ARM落地方案除了项目和操作系统本身部署中最耗时间的其实是各种周边工具的适配。这里挑几个高频场景详细说。5.1 KettlePentaho Data Integration在aarch64上的报错与修复Kettledata-integration是常用的ETL工具但它对aarch64的支持比较差。直接执行./pan.sh经常会报Im sorry, this linux platform [aarch64] is not supported这个错误来自Kettle启动脚本里的平台检测逻辑它在检测到非x86架构时直接拒绝运行。解决办法有两个方向。方向一修改启动脚本强制跳过平台检测。找到pan.sh或spoon.sh中类似下面的内容if [ $MODERN true ]; then ... fi不同版本脚本结构不同但基本思路是找到包含uname -m或者platform关键字的那段判断把对应的exit 1注释掉。这种方式能启动但后续如果Kettle依赖了x86原生的JNI库仍然会崩溃。方向二换用纯Java驱动的方式。Kettle本身是Java写的核心引擎并不依赖平台特定代码报错只是启动脚本的“自以为是”。安装对应版本的JDK需要aarch64版然后直接通过Java命令启动Kettle核心类绕过启动脚本# 先确认Kettle lib目录下有所有依赖 cd># 安装Node.js 18 LTS宝塔软件商店里直接装即可 # 确认node是aarch64版本 node -p process.arch # 输出 arm64 即正常 # 全局安装pnpm npm install -g pnpm # 克隆项目代码 git clone https://github.com/your-repo/nestjs-app.git cd nestjs-app # 安装依赖 pnpm install # 构建生产包 pnpm run build # 启动建议用pm2守护 npm install -g pm2 pm2 start dist/main.js --name nestjs-app pm2 save pm2 startup注意几个细节如果node -p process.arch输出的是x64说明你装错Node版本了去宝塔软件商店换装ARM版NestJS项目里依赖的原生模块比如bcrypt、sharp在aarch64上需要重新编译。如果pnpm install时报错找不到预编译二进制先安装build-essential再执行npm rebuild部署后用pm2 logs检查启动日志重点看是否有“invalid ELF header”之类的报错这是原生模块架构不匹配的典型信号。用宝塔部署前后端分离项目时前端静态文件直接放Nginx站点目录后端API走pm2守护的Node进程Nginx里配置代理转发server { listen 80; server_name your-domain.com; # 前端静态文件 root /www/wwwroot/frontend/dist; index index.html; # API反向代理 location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }5.3 Jenkins自动部署Gitee项目aarch64服务器上的流水线配置Jenkins在aarch64上运行没问题本身是Java应用。但需要注意两个点一是Jenkins的插件生态里少部分插件附带了原生二进制可能没有aarch64版本二是构建时用到的工具链Maven、JDK、Node都必须是arm64版本。在Jenkins里配置Gitee项目的自动部署我的通用流程是Gitee仓库配置WebHook指向http://jenkins-server:8080/gitee-webhook/Jenkins安装Gitee插件在任务配置里填入仓库地址、分支、账号凭据构建触发器勾选“Gitee webhook触发构建”构建步骤里执行Shell脚本完成拉代码、编译、打镜像、推送、SSH远程部署#!/bin/bash # 构建SpringBoot项目 mvn clean package -DskipTests # 构建Docker镜像 docker build -t your-registry.com/myapp:${GIT_COMMIT} . # 推送镜像到私有仓库 docker push your-registry.com/myapp:${GIT_COMMIT} # SSH到目标服务器拉镜像并重启容器 ssh deploytarget-server docker pull your-registry.com/myapp:${GIT_COMMIT} \ docker stop myapp || true \ docker rm myapp || true \ docker run -d --name myapp -p 8080:8080 your-registry.com/myapp:${GIT_COMMIT}这里有一个比较重要的经验不要在Jenkins机器上直接保存镜像而是统一推到私有镜像仓库目标服务器再从仓库拉取。这样Jenkins服务器和目标服务器即使不是同一架构也能分别拉取到对应架构的镜像。6. 部署验证清单与常见坑位速查项目部署完成只是开始验证是否真正可用才是关键。下面是一套我在每次aarch64项目部署后都会过的验证清单。6.1 基础环境验证# 1. 确认系统架构 uname -m # 期望输出aarch64 # 2. 确认Java版本与架构 java -version # 3. 确认Docker架构 docker info | grep Architecture # 期望输出aarch64 # 4. 确认关键服务状态 systemctl status docker systemctl status nginx pm2 status6.2 网络与端口验证# 监听端口检查 ss -tlnp | grep 8080 # 从外部访问测试 curl -I http://target-server-ip:8080 # 如果访问不通优先排查防火墙 firewall-cmd --list-all6.3 性能与稳定性快速验证# CPU信息确认 lscpu # 内存信息确认 free -h # 压测如果有wrk或ab ab -n 10000 -c 100 http://target-server-ip:8080/api/health6.4 高频坑位速查表现象原因解决方案执行二进制报“cannot execute binary file: Exec format error”二进制是x86_64当前系统是aarch64下载aarch64版本重新编译Docker pull没报错但容器启动失败日志里有“exec format error”镜像架构不匹配用docker image inspect 镜像名 | grep Architecture确认架构apt/yum源更新报404使用了x86架构的软件源换成aarch64/arm64对应的源地址Node原生模块安装失败缺少编译工具链yum install -y gcc gcc-c make后重新npm rebuild程序“xxx.exe”无法运行:指定的可执行文件不是此操作系统平台的有效应用程序Windows下运行了非Windows二进制或在ARM Windows下运行了x64程序确认程序版本与当前平台一致NFS挂载卡住或超时openEuler防火墙未放行相关服务firewall-cmd放行nfs/rpc-bind/mountdKettle启动报linux platform not supported启动脚本检测到aarch64后拒绝执行修改脚本或绕过脚本直接java方式启动6.5 一个容易被忽略的细节glibc与musl libc在aarch64上部署时经常遇到静态编译的二进制在一个系统能跑换一个系统就报错。这往往不是架构问题而是C标准库实现不同。Ubuntu/Debian/麒麟/openEuler用的是glibcAlpine Linux用的是musl libc。同一个aarch64二进制在Ubuntu上编译的放到Alpine容器里经常会提示找不到/lib/ld-linux-aarch64.so.1。解决方案尽量用官方提供的多架构镜像而不是自己在Apline里编译需要移植二进制时优先选择glibc系系统作为运行环境用file 二进制名可以快速查看二进制依赖的动态链接器比如输出dynamically linked, interpreter /lib/ld-linux-aarch64.so.1就说明是glibc环境。7. 写在资源整合之后我的一点实际体会整理这份内容的过程中我又从头回想了这几年在aarch64上部署项目的经历。最深的体会是ARM架构本身并不比x86难用难的是“惯性思维”。很多人踩坑是因为默认把x86环境下的方案原样搬过来忽略了源、二进制、镜像、工具链这些资源都需要重新适配。掌握了架构确认、软件源替换、镜像构建、工具链适配这条主线之后aarch64就只是一个新的目标平台而已不再是玄学问题。最后分享一个小技巧在部署目录里维护一份DEPLOY_NOTES.md把每次遇到的架构坑和解决方案记录下来格式就按本文的坑位速查表那样。下次换一台新机器先看笔记再动手能省掉一大半排查时间。本文还有配套的精品资源点击获取