ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Nexus 3.24内网制品库搭建:Maven/NPM私服与运维避坑

Nexus 3.24内网制品库搭建:Maven/NPM私服与运维避坑

简介:这份资源是 Sonatype 官方发布的 Nexus Repository Manager 3.24.0-02 Unix/Linux 安装包,面向 Java 与前端开发团队中负责构建、依赖治理和私服运维的工程师。它解决的是 Maven、Gradle 依赖与 NPM 包分散下载、外网访问慢、内部组件难以统一托管的问题,可用于搭建企业级制品仓库,代理远程仓库并缓存常用依赖。压缩包约 149.69MB,解压后分为服务程序目录与 sonatype-work 运行时数据目录,前者含可执行文件、配置与依赖库,后者承载制品存储、索引、日志及数据库内容,升级时保持数据独立。通过修改 conf 下的配置文件并用 Web 界面管理,可完成端口、代理仓库、存储库组与权限设置;NPM 用户还可将 Nexus 设为默认注册表,实现私有包托管与加速。目前已有 212 人学习下载,适合需要落地私服或补齐依赖治理方案的中级开发者参考。

1. 从 3.24.0-02 这个 tar.gz 说起:内网为什么还要自己维护一个制品库

nexus-3.24.0-02-unix.tar.gz 解出来就是一个能直接跑的 Nexus Repository Manager 3 实例,Maven 的 jar、NPM 的 tarball 都能往里塞、往外发,配置全在界面上点。它的价值不在于「又一个私服」,而在于把构建链里最脆弱的一段——外网仓库——挪进内网:中央仓库抖动、依赖被撤版、NPM 包作者删库,这类事故在内网镜像面前就变成可控的缓存问题,而不是集体构建失败。适合两个人以上、有 CI、有不能公开的包的后端和前端团队。下面按解包、开机自启、Maven 三层仓库、NPM 发布链路、排错、备份的顺序拆一遍,都是我在内网环境里实际走通的路径。

2. 解包到 systemd:把 tar.gz 变成一个正经服务

拿到 tarball 之后最容易犯的错,是就地解压、用 root 跑、把数据目录和安装目录混在一起。跑到第三个月要做版本升级时,你会发现安装目录里既有程序又有几百 G 的 blob,回退只能整份拷回来。所以第一步不是启动,而是把「可丢弃的程序」和「不能丢的数据」在磁盘上分开,然后交给 systemd 托管,让启停顺序、文件句柄上限、停止超时都可控。

2.1 落地前的三件事:账号、磁盘、JDK

账号必须是不可登录的系统账号,因为 Nexus 会以运行用户身份写 blob 和 OrientDB 的本地存储,用 root 跑出来的文件,后面换成普通用户启动时会一片 Permission denied。磁盘要单独规划:blob 是只增不减的,删掉组件只是删元数据,磁盘空间要等压缩任务跑完才回来。文件系统用 xfs 或 ext4,别放 NFS——3.24 的元数据还在内嵌 OrientDB 的 plocal 存储里,它对文件锁的假设很强,网络盘上跑起来可能几天没事、某次重启后数据库直接打不开。

JDK 这条线的主跑环境是 JDK 8,装完先确认JAVA_HOME指向的是 JDK 不是 JRE,bin/nexus启动脚本靠它找 java。

# 1) 系统账号:不可登录、无家目录依赖 sudo groupadd -r nexus sudo useradd -r -g nexus -M -d /opt/nexus -s /sbin/nologin nexus # 2) 解包到 /opt,安装目录改名成不带版本号的路径,方便以后软链切换 sudo tar -xzf nexus-3.24.0-02-unix.tar.gz -C /opt sudo mv /opt/nexus-3.24.0-02 /opt/nexus # 3) 数据目录独立挂盘,先建空目录,首次启动时 Nexus 自己初始化 sudo mkdir -p /data/nexus-data sudo chown -R nexus:nexus /opt/nexus /data/nexus-data sudo chmod 750 /data/nexus-data # 4) 确认 JDK 版本与路径 java -version # 期望 1.8.x readlink -f $(which java)

--strip-components这里没用,是因为官方包根目录下同时有nexus-3.24.0-02/和sonatype-work/两个目录,剥掉一层会把两者混在一起。数据目录改成/data/nexus-data之后,安装目录就变成了「随时可以删掉重来」的东西,这一点在升级和回滚时会救命。

2.2 bin/nexus 与 vmoptions:内存参数别照抄

bin/nexus是启动器,子命令有start / stop / run / status / restart。调试阶段用run前台跑,日志直接刷屏,起不来时能立刻看到堆栈;生产用start,它自己会后台化。内存不写在启动脚本里,而是写在同目录的nexus.vmoptions,格式是一行一个 JVM 参数,没有-Dkey=value以外的语法糖。

# /opt/nexus/bin/nexus.vmoptions(节选,只列需要改的) -Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m # 数据目录改成绝对路径:不依赖安装目录的相对位置 -Dkaraf.data=/data/nexus-data -Dkaraf.log=/data/nexus-data/log -Djava.io.tmpdir=/data/nexus-data/tmp

三个内存值取同一个数是有原因的:堆内存和直接内存是两块独立的账,Nexus 上传下载走的是直接内存(Netty / Jetty 的缓冲区),只把-Xmx调大、MaxDirectMemorySize留着小值,表现就是大 jar 传到 99% 报 500,日志里是OutOfMemoryError: Direct buffer memory。经验值是三者之和不超过机器内存的 60%,8G 的机器给 2703m 刚好,16G 可以给到 4G 一档。

改完内存重启如果发现没生效,先看/data/nexus-data/log/jvm.log开头那行完整启动参数——INSTALL4J_ADD_VM_PARAMS这个环境变量会和nexus.vmoptions叠加,两处都设了内存时,最终生效的是哪一组,只有 jvm.log 说了算,别靠猜。

2.3 systemd 托管与三个启动检查点

# /etc/systemd/system/nexus.service [Unit] Description=Nexus Repository Manager 3.24.0-02 After=network.target [Service] Type=forking User=nexus Group=nexus Environment="JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64" LimitNOFILE=65536 ExecStart=/opt/nexus/bin/nexus start ExecStop=/opt/nexus/bin/nexus stop Restart=on-failure TimeoutStopSec=180 [Install] WantedBy=multi-user.target

Type=forking是因为nexus start会自己 fork 到后台,用 simple 会让 systemd 误判主进程退出。LimitNOFILE不给够,并发上传时日志里会出现Too many open files,而且报的位置往往在很深的 IO 栈里,不容易第一眼认出来。TimeoutStopSec=180是给 OrientDB 关库留的时间,systemd 默认 90 秒就 SIGKILL,被强杀之后下次启动要做恢复,慢的话能卡上好几分钟。

sudo systemctl daemon-reload sudo systemctl enable --now nexus sudo systemctl status nexus --no-pager # 检查点 1:日志里出现启动完成标记 tail -f /data/nexus-data/log/nexus.log | grep -i "Started Sonatype Nexus" # 检查点 2:端口在听 ss -lntp | grep 8081 # 检查点 3:HTTP 有响应(302 跳登录页也算正常) curl -sI http://127.0.0.1:8081/ | head -n 1

首次登录的初始密码不在配置文件里,而是首次启动时随机生成在/data/nexus-data/admin.password,登录后立刻改掉,改完这个文件会被删除——找不到它说明已经有人初始化过了。端口和上下文路径在etc/nexus.properties里改,application-port换端口,nexus-context-path换路径前缀。改路径前缀是个连锁操作,所有客户端的仓库地址都要跟着改,非必要不动。

3. Maven 私服:proxy、hosted、group 三层怎么摆

Maven 侧的设计核心只有一句话:客户端只认一个 group 地址,其余全部由服务端决定。这样settings.xml在团队里发一次就够,后面加仓库、换镜像、下线某个代理,都不用通知任何人改配置。反过来,如果让每个人的settings.xml里写三四个仓库地址,那你以后每次调整都会变成一次全员协同。

3.1 三种仓库类型的职责边界

仓库名类型Version policyDeployment policy作用
maven-centralproxyRelease—代理 repo1.maven.org,负责缓存公共依赖
maven-releaseshostedReleaseDisable redeploy团队正式版本,禁止覆盖
maven-snapshotshostedSnapshotAllow redeploy联调快照,允许反复覆盖
maven-publicgroup——对外唯一入口,成员按顺序聚合上面三个

Disable redeploy是刻意的:正式包一旦允许覆盖,同一个版本号在两个人机器上可能是两份不同的字节,线上问题就无法复现。快照仓库反过来必须允许覆盖,否则每次联调都得升版本号,-SNAPSHOT的意义就没了。

group 的成员顺序有实际影响:查找是从上往下命中的,把自己维护的maven-releases、maven-snapshots放在maven-central前面,同名坐标会优先命中内部包——这在打补丁替换某个第三方包时非常有用,也意味着如果有人误传了一个和公共包同坐标的包,全团队都会拿到它。我一般会把顺序固定成 snapshots → releases → central,并在群里说明这条规则,避免有人图省事往 releases 里塞别人的包。

3.2 建 maven-public 组与 settings.xml 写法

界面上按齿轮 → Repository → Repositories → Create repository 依次建:先maven2 (proxy)填远程地址https://repo1.maven.org/maven2/,再两个maven2 (hosted),最后maven2 (group)把前三个加进 Members。建完在 group 页面右上能看到完整的仓库 URL,复制下来。

<!-- ~/.m2/settings.xml --> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"> <mirrors> <mirror> <id>nexus-public</id> <name>internal maven-public</name> <url>http://nexus.lab.local:8081/repository/maven-public/</url> <!-- 全部走内网;个别第三方仓库要直连时用 *,!repoId 排除 --> <mirrorOf>*</mirrorOf> </mirror> </mirrors> <servers> <server> <id>nexus-releases</id> <username>deployer</username> <password>{加密串}</password> </server> <server> <id>nexus-snapshots</id> <username>deployer</username> <password>{加密串}</password> </server> </servers> </settings>

<id>是这套配置里唯一不能随便写的字段:mirror的 id 决定本地缓存归属,server的 id 必须和pom.xml里distributionManagement的仓库 id 逐字一致,不一致就是 401,而且报错信息只说Unauthorized,不会告诉你是哪个 id 对不上。密码用mvn --encrypt-password生成密文,别在 settings.xml 里留明文——这个文件经常被顺手提交进 dotfiles 仓库。

mirrorOf=*有个副作用:它会拦下所有仓库请求,包括某些 pom 里自定义的第三方仓库,表现为那个依赖永远拉不下来、只报 404。真遇到这种情况,用*,!thirdparty-repo把那个仓库 id 排除掉,而不是把 mirror 关了。

3.3 发布到 hosted:distributionManagement 与 redeploy 策略

<distributionManagement> <repository> <id>nexus-releases</id> <url>http://nexus.lab.local:8081/repository/maven-releases/</url> </repository> <snapshotRepository> <id>nexus-snapshots</id> <url>http://nexus.lab.local:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>
# 正式包发布 mvn -B clean deploy -DskipTests # 第三方 jar 没有源码、无法自己构建时,直接推上去 mvn deploy:deploy-file \ -DgroupId=com.demo -DartifactId=legacy-sdk -Dversion=1.2.3 \ -Dpackaging=jar -Dfile=legacy-sdk-1.2.3.jar \ -DrepositoryId=nexus-releases \ -Durl=http://nexus.lab.local:8081/repository/maven-releases/

-B是批处理模式,CI 里不加会有满屏的下载进度;-DskipTests只跳过测试执行、测试代码照样编译,想连编译一起跳过得用-Dmaven.test.skip=true,两者混用是常见的「本地能过 CI 过不了」来源之一。deploy:deploy-file的-DrepositoryId同样要和 settings.xml 的 server id 对上,这条命令是我用得最多的一个——老系统里那些没有源码的 jar,靠它才能进内网仓库。

版本号已经是1.2.3却又发了一次,会得到400 Repository does not allow updating assets。这不是权限问题,是Disable redeploy在起作用,正确做法是升版本号;临时把策略改成Allow redeploy能过,但那等于把可复现性丢了。改完 mirror 之后本地还可能有旧缓存残留,~/.m2/repository下的_remote.repositories记录了来源仓库 id,对不上时会重新下载,实在诡异就把对应目录连同*.lastUpdated一起删掉重来。

4. NPM 私服:从 proxy 到 npm publish 的完整链路

前端这边比 Maven 更容易踩坑,因为 NPM 的认证走的是 Bearer Token,和 Maven 的 Basic Auth 不是一套机制,Nexus 里对应的 Realm 默认没开。很多人装完 Maven 一切正常,转去配 NPM 就卡在 401,来回改.npmrc改到怀疑人生,其实问题在服务端的开关上。

4.1 三个 npm 仓库怎么组合

仓库名类型关键配置作用
npm-proxyproxyRemote storage: https://registry.npmjs.org缓存公共包
npm-hostedhostedDeployment policy: Allow redeploy放内部包、私有 scope
npm-groupgroupMembers 顺序:hosted 在前,proxy 在后对外唯一入口

顺序是刻意的:内部包优先命中npm-hosted,公共包落到npm-proxy。如果反过来,某个内部包名恰好和公共包重名(比如内部改过的 fork),客户端会拿到公共版本,而且不会有任何提示——这种问题排查起来极其难受,因为package.json里看不出任何异常。

npm-hosted的部署策略选Allow redeploy:NPM 生态里同一个版本号重发是常态(尤其是内网工具包),禁了之后每次都要手动改版本号,反而没人愿意发布。

4.2 npm Bearer Token Realm 与 .npmrc 写法

第一步不在命令行,在界面:齿轮 → Security → Realms,把npm Bearer Token Realm从 Available 挪到 Active,保存。这一步不做,npm login一定失败。

# 登录:registry 指向 hosted,不要指向 group npm login --registry=http://nexus.lab.local:8081/repository/npm-hosted/ --always-auth # 发布(在包目录下) npm publish --registry=http://nexus.lab.local:8081/repository/npm-hosted/ # 安装:走 group npm config set registry http://nexus.lab.local:8081/repository/npm-group/
# .npmrc(项目级,只提交 registry 那两行,token 走 CI 变量) registry=http://nexus.lab.local:8081/repository/npm-group/ @demo:registry=http://nexus.lab.local:8081/repository/npm-group/ # 注意 key 是完整路径,少写一段就是 401 //nexus.lab.local:8081/repository/npm-hosted/:_authToken=NpmToken.xxxxxxxx always-auth=true

.npmrc里最容易错的是_authToken那一行的 key:它不是一个域名,而是协议://主机:端口/仓库路径/整串,Nexus 按这个完整前缀匹配凭证。写成//nexus.lab.local:8081/:_authToken=...在别的 registry 上能work,在这里就会 401,因为路径没对上。发布指向 hosted、安装指向 group,也是同一个道理——group 是只读聚合入口,往里 publish 时到底写进哪个成员,取决于成员顺序,与其事后去猜包落在哪,不如发布时直接写 hosted 地址。

4.3 用 curl 验证 group 顺序和 tarball 落点

浏览器点得开不代表命令行能用,中间还隔着代理、路径编码和缓存。三步验证,从元数据到实际文件:

BASE=http://nexus.lab.local:8081/repository/npm-group # 1) 公共包的元数据能不能取到 curl -s $BASE/lodash | head -c 200 # 2) 私有 scope 包:斜杠必须编码成 %2f,否则中间层可能直接 404 curl -s $BASE/@demo%2futils | head -c 200 # 3) tarball 是否真的能下载(200 说明 blob 落盘正常) curl -sI $BASE/lodash/-/lodash-4.17.21.tgz | head -n 1

第 1 步通、第 3 步 404,说明元数据缓存住了但 tarball 没取回来,通常是 proxy 仓库的 Remote storage 地址写错,或者出网策略只放行了元数据接口。第 2 步 404 而第 1 步正常,八成是 scope 没在 hosted 仓库的配置里对上,或者.npmrc里@demo:registry写成了 group 但 group 成员顺序不对。用npm view <pkg> version --registry=$BASE也能验证,但 curl 更快,而且能直接看到 HTTP 状态,不用猜 npm 把错误包装成了什么。

5. 避坑与排查:五个真实翻车现场

5.1 启动与资源类

现象一:第二次启动卡住,日志里 OrientDB 报本地存储被锁。原因通常是上一次stop被 SIGKILL 掉了,锁文件没释放;或者更隐蔽的——复制了一份安装目录做「备份环境」,却忘了改nexus.vmoptions里的-Dkaraf.data,两个进程指向同一个数据目录。解决:ps -ef | grep karaf.data确认只有一个 java 进程在跑,bin/nexus status看状态,把 systemd 的TimeoutStopSec调到 180 秒给足关库时间;复制环境时务必把数据目录也复制一份并改绝对路径。

现象二:上传大 jar 到 99% 报 500,界面上什么都没留下。先看/data/nexus-data/log/nexus.log,如果是Direct buffer memory,按 2.2 调MaxDirectMemorySize;如果不是 OOM 而是写入失败,df -h和df -i都跑一遍——inode 耗尽在小文件极多的 NPM 场景里很常见,表现和磁盘满几乎一样,但df -h显示还有空间,很容易被忽略。

5.2 依赖解析类

现象三:某个依赖中央仓库明明有,内网就是 404,而且过很久都不恢复。原因是 proxy 仓库把「没找到」也缓存了,HTTP request settings里的Not found cache TTL默认是 1440 分钟,也就是一次失败要背一天的锅。解决:把这个值改成 0 或几分钟,然后在仓库页面点Invalidate cache清掉负缓存再重试。这个坑最坑的地方在于,你在这期间无论怎么验证「上游确实有这个包」都没用,因为请求根本没出去。

现象四:mirrorOf=*之后,某个第三方仓库的依赖全挂了。原因是镜像规则把 pom 里声明的额外仓库一并拦下,请求全打到 group,而 group 里没有那个仓库。解决:改成*,!thirdparty-repo精确排除,或者在 group 里补一个对应 proxy。判断方法很简单,mvn -X看依赖是从哪个 URL 解析的,一眼就能看出是不是被拦了。

5.3 权限与发布类

现象五:npm publish一直 401,账号密码确认没错,Maven 那边却能正常发布。原因就是 4.2 里说的npm Bearer Token Realm没启用,Maven 走 Basic Auth 不受影响,NPM 走 Token 直接被拒。解决:Realms 里加上去,重启不需要,保存即生效,然后重新npm login。如果 Realm 开了还是 403,那就是权限问题——给发布账号的角色里补上nx-repository-view-npm-*-add和nx-repository-view-npm-*-edit,而不是直接给 admin。

6. 备份、清理与升级:一套能复用的维护动作

备份这件事,在 3.24 上只有一种可靠做法:停服务、整份拷、再启动。原因是元数据还在内嵌 OrientDB 的db/目录里,热拷贝出来的文件大概率是坏的,恢复时才发现就晚了。Admin - Export databases for backup这个任务可以在不停服的情况下导出一份数据库快照,但它和 blob 目录不在同一时间点,真要恢复还得靠冷备兜底。

#!/usr/bin/env bash set -euo pipefail TS=$(date +%F) /opt/nexus/bin/nexus stop # 等进程真的退出,OrientDB 关库不是瞬间完成的 while pgrep -f 'karaf.data=/data/nexus-data' >/dev/null; do sleep 2; done tar -czf /backup/nexus-$TS.tar.gz \ -C /data nexus-data \ --exclude=nexus-data/tmp \ --exclude=nexus-data/log /opt/nexus/bin/nexus start find /backup -name 'nexus-*.tar.gz' -mtime +7 -delete

db/和blobs/必须在同一个 tar 里、同一个时间点,分开备份等于没备。tmp和log排除掉能省不少体积,恢复时 Nexus 会自己重建。

日常清理靠两个计划任务:Admin - Compact blob store回收已删除组件的磁盘空间(删除组件时磁盘不会立刻释放,就是等它),Maven - Delete SNAPSHOT按「保留最近 N 个、超过 N 天」清快照。这两个任务建议错开时间跑,同时跑会争 IO。

升级的后悔药只有一份:升级前必须做一次完整冷备。做法是把安装目录做成软链/opt/nexus -> /opt/nexus-3.24.0-02,新版本解到旁边、nexus.vmoptions里的karaf.data仍指向老数据目录,启动时它会自动跑数据库迁移——而迁移是单向的,一旦新版本启过库,旧版本就再也读不了了。我因为跳过一次备份,在一次小版本升级里把测试环境的元数据搞成了半迁移状态,最后是重建仓库配置、重传所有私有包才恢复的。从那以后我每次动版本号,都强制先跑一遍那个 tar 脚本,并当场解压一份到临时目录验证能起来,再动车生产。这份 tar.gz 拿回去先在一台测试机上按第 2 章跑通,比直接上生产稳得多。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表