拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Nexus3内网私库搭建:Maven、Yum、Apt、Nodejs统一私有源

Nexus3内网私库搭建:Maven、Yum、Apt、Nodejs统一私有源

内网交付做久了,你会发现真正消耗工时的环节往往不在业务代码本身,而卡在“把依赖搬进去”这一步。Maven 拉不到 jar、yum 装不上编译工具链、apt 卡在下载一半、nodejs 的 npm 包反复超时重试——外网环境里一条命令的事,进了隔离网就得折腾一整天。Nexus3 私库环境搭建(maven、yum、apt、nodejs)这件事,本质上就是把四种包管理器的分发中心收敛到同一台内网服务器上,让所有开发机和构建机只认一个地址,从此不再依赖公网、不再靠“谁去外面下载再拷U盘”这种原始手段。它的价值不只是省流量,更重要的是构建结果可复现:今天能编过的版本,半年后重装环境还能编过,因为依赖包一直躺在你自己的磁盘上。

这篇文章面向的是需要在企业内网、实验室隔离网、客户现场落地一套统一私有源的运维和开发同学。不管你是第一次接触 Nexus3,还是已经装过但被 yum 的 repodata、apt 的签名、npm 的认证折腾过,这里都会把“为什么这么做”讲清楚。我会按 maven、yum、apt、nodejs 四条线,从仓库类型选择、目录结构、客户端配置到踩坑排查一路走完,中间涉及参数的地方都会给出具体的计算和取舍理由,你照着抄基本能一次跑通。

1. 先想清楚:为什么是 Nexus3,以及它能覆盖到什么程度

1.1 四个包管理器的共同痛点和各自的脾气

maven、yum、apt、npm 这四样东西表面上都是“下载依赖”,但协议和元数据格式完全不同,混在一起讲最容易糊涂。Maven 走的是 HTTP 上的标准目录结构加 XML 元数据,靠 pom 文件描述依赖关系,仓库地址写死在settings.xml或pom.xml里;yum 走的是repodata/repomd.xml这个入口,通过它索引所有 rpm 及其依赖关系;apt 走的是Packages和Release两层元数据,靠 GPG 签名保证来源可信;npm 则是 JSON 描述加 tarball 下载,认证方式比前三个都灵活也更容易配错。

理解了这四套机制的差异,你就能明白为什么 Nexus3 不能对它们一视同仁。Nexus3 原生支持 maven 和 npm 的完整语义(包括 hosted、proxy、group 三种角色),对 yum 的支持在早期版本里只有 proxy 一种形态,对 apt 则完全没有原生仓库类型,必须借助 raw(原始文件)仓库自己拼目录。这不是 Nexus3 的缺陷,而是 apt 那套“由发行版维护者签名”的模型本来就不适合由第三方托管平台直接代劳。所以本文的路线是:maven 和 npm 用原生仓库类型走标准流程,yum 和 apt 用 raw 仓库加本地元数据生成工具补齐,这是目前内网落地最普遍、也最容易维护的做法。

另一个必须提前想清楚的问题是代理层。内网服务器能不能访问公网,直接决定了你是只搭 hosted(本地托管),还是同时搭 proxy(代理上游)。如果机房只有一台机器能出网,那就让 Nexus 这台机器出网,其他所有开发机构建机都指向它,由它统一去公网拉包并缓存下来。这个设计的好处是缓冲区集中、出口只有一个、审计也方便。如果整网全封闭,那就只能靠外部机器下载好再导入,这条路后面会专门讲。

提示:在动手之前先确认三件事——服务器能不能出网、要服务多少台客户端、有没有国产发行版需要兼容。这三件事决定了仓库类型的组合方式,等装完再改会返工。

1.2 部署形态的取舍:tar 包、容器还是系统包

Nexus3 官方提供三种部署形态。第一种是 tar.gz 免安装包,解压后配置bin/nexus.vmoptions和etc/nexus-default.properties,用自带脚本启动,最直观,出问题排查也最简单,我一般推荐现场首次部署用这个。第二种是 Docker 镜像,优点是环境隔离干净、升级只需换 tag,缺点是数据目录要挂载出来,而且容器的 JVM 内存限制和文件句柄数需要额外处理,不熟悉容器的话反而多一层坑。第三种是系统包管理安装,把 Nexus 注册成系统服务,升级方便,但对系统环境有侵入,而且国内镜像源里未必有最新版本。

我选 tar 包的核心理由是可控性。Nexus 是个吃内存的 Java 应用,默认nexus.vmoptions里堆内存给到了 2703MB,堆外直接内存也是 2703MB,加起来光 JVM 就要预留 5GB 以上,再加上文件缓存,实际至少要给 8GB 内存、4 核 CPU 才跑得舒服。用 tar 包我可以直接改这个文件,把参数调整到和机器规格匹配;用容器就得在启动参数里覆盖,多一次转译。如果你是在只有 4GB 内存的测试机上试水,把-Xms和-Xmx都改到 1200MB 左右、MaxDirectMemorySize改到 1200MB,也能跑起来,只是大仓库索引时会慢一点。

磁盘规划同样要在部署前定好。Nexus 的数据目录默认是安装目录下的sonatype-work/nexus3,里面包含 blob store(实际的文件存储)和数据库。这个目录会随着包数量持续增长,一个中等规模的 maven 私库加上几个 yum 源,一年下来几十 GB 很常见。所以建议单独挂一块数据盘,把sonatype-work软链或者通过配置指到数据盘上,系统盘只放程序。这样将来磁盘满了或者要迁移机器,直接搬盘或者整目录打包就行,不用碰程序。

2. 从零把 Nexus3 跑起来

2.1 环境准备与安装包获取

服务器基线建议是 4 核 8GB 起步,操作系统用你团队最熟的那个发行版就行,没有强制要求。需要提前确认的是 JDK 版本,Nexus 3.x 的不同小版本对 JDK 要求不一样,早期版本用 JDK 8,较新的版本已经要求 JDK 8 或 JDK 11,再往后的版本甚至要求 JDK 17。最稳妥的方式是直接看你要装的版本对应的官方说明,或者干脆用安装包自带的 JVM 检测逻辑——Nexus 启动脚本会去找JAVA_HOME,找不到就报错。

如果服务器本身没网,就需要在一台有网的机器上把安装包下好再传进去。这时候你其实已经需要一套临时的分发手段了,U盘、内部文件服务器、SCP 都行,先把包搬进去,后续 yum 和 apt 的私库搭好之后,这类传输就能彻底告别。

# 在能出网的机器上准备 mkdir -p /opt/nexus-install cd /opt/nexus-install # 下载完成后记录校验值,传输后核对,避免半包 sha256sum nexus-3.x.x-01-unix.tar.gz > nexus.sha256

传输到目标机之后,先核对哈希再解压,这一步不要省。我遇到过不止一次因为传输中断导致解压出来的 jar 包损坏,Nexus 启动到一半报类找不到,排查方向完全跑偏。核对完解压到/opt下,目录名带版本号,方便以后并存升级。

2.2 关键配置调整与开机自启

解压完成后先别急着启动,有两处配置值得改。第一处是bin/nexus.vmoptions,把内存参数按实际机器规格调整。第二处是etc/nexus-default.properties,这里定义了监听地址和端口,默认是0.0.0.0:8081,如果你的机器有多张网卡,建议绑定到内网那张网卡的 IP,避免意外暴露。上下文路径默认是/,如果你希望所有仓库地址都带一个统一前缀,比如/nexus,也可以在这里改,但改了之后所有客户端配置的 URL 都要跟着加前缀,中途改会很痛苦,建议一开始就定下来。

用 systemd 托管比用nohup起脚本可靠得多,尤其是服务器重启后能自动拉起。下面是我常用的 unit 文件写法,注意LimitNOFILE必须调大,Nexus 在高并发拉包时会打开大量文件句柄,默认的 1024 完全不够,会报 too many open files 然后拒绝服务。

# /etc/systemd/system/nexus.service [Unit] Description=Nexus Repository Manager After=network.target [Service] Type=forking LimitNOFILE=65536 ExecStart=/opt/nexus-3.x.x/bin/nexus start ExecStop=/opt/nexus-3.x.x/bin/nexus stop User=nexus Restart=on-abort TimeoutSec=600 [Install] WantedBy=multi-user.target

这里有个细节:建议单独创建一个nexus用户来跑服务,而不是用 root。原因不只是安全,还因为如果将来要调整数据目录权限,明确的服务账号会让权限问题好定位得多。创建用户之后记得把程序目录和数据目录的属主都改过去,否则启动时会因为写不了日志而失败。启动完成后用systemctl status nexus确认状态,再用curl -I http://内网IP:8081看看有没有返回 200,两步都通过说明服务本身没问题。

首次登录需要拿到初始密码,它不在日志里,而是写在数据目录下的一个文件里,路径大概长这样:sonatype-work/nexus3/admin.password。用cat读出来登录,登录后第一件事就是改密码并关掉匿名访问的写权限。很多人图省事不改,结果内网里任何人都能往你的私库上传包,这种污染在后期几乎无法清理,因为分不清哪个 jar 是可信的。

注意:不要在同一个数据目录上跑两个 Nexus 实例,即使端口不同也不行。数据目录里有锁文件,强行启动会导致 blob store 索引损坏,修复成本远高于重新搭一套。

2.3 仓库规划:先画好地图再开仓库

在 UI 里点“新建仓库”之前,先在纸上把命名规范定下来,这一步能省掉后面无数次返工。我的命名习惯是“包类型-用途-角色”,比如maven-releases、maven-snapshots、maven-central、npm-group、yum-raw、apt-raw。名字一旦被客户端写进配置文件,改名的成本就是全量机器改配置,所以命名要一次到位。

另一个提前要定的是“对外暴露哪个地址”。Nexus 的 group 仓库可以把多个仓库聚合成一个入口,客户端只需要配一个 URL 就能同时访问本地包和上游代理包。这种设计是内网私库的核心价值之一:开发同学不用关心包是从哪来的,配一个地址就能用。所以我建议每种包类型都建一个 group 作为统一入口,hosted 和 proxy 都挂到 group 下面,客户端永远只连 group。

仓库名类型作用是否对客户端暴露
maven-releaseshosted内部正式版本构件通过 group 间接暴露
maven-snapshotshosted内部快照版本构件通过 group 间接暴露
maven-centralproxy代理公共中央仓库通过 group 间接暴露
maven-publicgroup统一入口是
npm-hostedhosted内部私有 npm 包通过 group 间接暴露
npm-proxyproxy代理公共 npm 源通过 group 间接暴露
npm-groupgroup统一入口是
yum-rawraw存放自建 rpm 仓库文件是
apt-rawraw存放自建 deb 仓库文件是
binary-rawraw存放 nodejs 等二进制发行包是

这张表就是我实际落地时的骨架,后面所有配置都围绕它展开。你会注意到 yum 和 apt 直接暴露 raw 仓库,没有 group 层,原因是它们各自只需要一个源地址,加一层聚合反而增加理解成本。

3. Maven 私库:三种仓库角色的完整闭环

3.1 hosted、proxy、group 到底该怎么分工

很多人第一次配 Maven 私库时,会想当然地只建一个 hosted 仓库然后往里传包,结果发现团队要用的第三方依赖根本传不完。这就是没搞清楚三种角色的分工。hosted 是“我自己的东西”,用于存放公司内部开发的构件,包括正式版和快照版;proxy 是“别人的东西的镜像”,用于代理公共中央仓库,第一次请求时去上游拉取并缓存到本地,之后再请求就直接命中缓存;group 是“把上面两类聚合成一个入口”,客户端只配它即可。

Maven 的 hosted 仓库有一个容易被忽略的细节:正式版和快照版必须分仓库。原因是 Nexus 对 hosted 仓库有一个Version policy属性,取值为 Release 或 Snapshot,它决定了这个仓库接不接受带-SNAPSHOT后缀的构件。如果你把两者混在一个仓库里,要么快照传不上去,要么正式版被快照覆盖,两种情况都是灾难。所以老老实实建两个:maven-releases选 Release 策略,maven-snapshots选 Snapshot 策略。

proxy 仓库的配置重点是Remote storage这个上游地址,以及缓存策略。公共中央仓库地址填官方那个即可。如果内网访问上游不稳定,可以在HTTP配置里把连接超时和读取超时调大一点,默认值在内网环境偶尔会因为延迟抖动导致拉取失败并缓存一个失败标记。这个失败标记有个坑,就是它的默认存活时间比较长,导致你明明网络已经好了,重新拉还是报错,必须手动清缓存或者加-U参数强制更新。

3.2 创建仓库的具体动作顺序

顺序很重要,因为 group 在创建时需要选择成员仓库,如果成员还不存在就没法勾选。所以正确的顺序是先建两个 hosted,再建 proxy,最后建 group。group 里的成员顺序也有讲究,Maven 会按顺序查找,本地 hosted 排在前面意味着如果内部构件的 groupId 和公共仓库里的某个包重名,优先命中内部版本,这正是我们想要的效果。

创建 hosted 时几个关键字段这样填:Layout选maven2,Version policy分别选Release和Snapshot,Storage用默认的 blob store 即可,Deployment policy选Allow redeploy是很多人的习惯,但我建议正式版仓库把它设成Disallow redeploy,这样可以避免同一个版本号被反复覆盖导致构建结果不可复现。快照仓库则可以放开,因为快照本来就是可变的。

proxy 仓库创建时,Remote storage填公共仓库地址,Version policy选Mixed,因为它里面既有正式版也有快照。Storage里的Blob store和Strict content type validation保持默认即可。有一个选项叫Maximum component age,指的是缓存多久后重新去上游检查更新,正式版构件通常不会变,可以设长一点,比如几天;快照则要短,否则你拉到的永远是旧快照。

group 仓库创建时把上面三个都勾上,顺序是 releases、snapshots、central。这里有个小坑,group 的Version policy也要选Mixed,否则客户端拉快照时会报找不到。创建完成后,Nexus 会给出每个仓库的访问地址,形如http://内网IP:8081/repository/maven-public/,这个地址待会儿要写进客户端的配置文件。

3.3 客户端配置:settings.xml 与 pom.xml 各管什么

客户端这边有两个文件要改,职责不同,很多人会搞混。settings.xml是全局的,管镜像地址和认证信息;pom.xml是项目级的,管这个项目发布到哪个仓库。镜像配置的作用是把所有对外部仓库的请求重定向到你的私库,写法如下。

<!-- ~/.m2/settings.xml --> <settings> <mirrors> <mirror> <id>nexus-public</id> <name>internal nexus</name> <url>http://192.168.10.20:8081/repository/maven-public/</url> <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>

这里的mirrorOf取值是个容易踩的坑。用*表示拦截所有仓库请求,包括你在 pom 里自己声明的其他仓库,这在内网里是最省心的做法,因为所有依赖都必须经过 Nexus 这一道。但如果团队里有人需要访问某个 Nexus 里没有代理的特殊仓库,*就会把它也拦掉,这时候要用external:*或者显式排除。我的建议是起步阶段用*,等确实出现特殊需求再单独放行。

pom.xml这边要加的是发布地址,也就是distributionManagement节点,把正式版和快照版分别指向对应仓库。注意这里的<id>必须和settings.xml里<server>的<id>完全一致,这是 Maven 匹配认证信息的方式,不一致就会出现 401,而且报错信息往往很含糊,让人以为是密码错了。

<distributionManagement> <repository> <id>nexus-releases</id> <url>http://192.168.10.20:8081/repository/maven-releases/</url> </repository> <snapshotRepository> <id>nexus-snapshots</id> <url>http://192.168.10.20:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>

配置完成后,mvn deploy就会把构件推到私库,mvn clean install则会从私库拉依赖。判断是否真的走通了,不要看有没有报错,要看私库的浏览界面里有没有出现你刚部署的构件,以及构建日志里下载地址是不是指向你的内网 IP。两者都对,才算闭环。

实操心得:如果团队里有人用的是公司统一分发的 settings.xml,改完记得确认一下镜像是否生效。最简单的验证方式是随便删掉本地仓库里的一个 jar,然后mvn -U clean compile,看日志里的下载 URL 是不是你的 Nexus 地址。

4. yum 私库:把 rpm 和 repodata 一起管起来

4.1 yum 仓库的本质是 repodata 而不是 rpm

搭 yum 私库前必须理解一件事:客户端拉取时第一个请求的不是某个 rpm 包,而是repodata/repomd.xml。这个文件是整个仓库的索引,里面记录了所有包的文件列表、依赖关系、校验值。所以一个目录里就算堆满了 rpm 文件,只要没有 repodata,yum 也完全不认。这解释了很多人的困惑——明明包都放进去了,客户端的yum makecache还是报“无法下载仓库元数据”。

明白了这一点,搭建思路就清晰了:先在本地建一个目录放 rpm,用createrepo_c生成元数据,然后把整个目录结构通过某种方式暴露给客户端。暴露方式有两种,一种是用 Nexus 的新版 yum hosted 仓库类型(较新版本才提供,能自动处理元数据),另一种是用 raw 仓库直接托管生成好的目录。我两种都用过,raw 方案更通用,不受 Nexus 版本限制,下面以 raw 为主讲。

rpm 的来源可以是三种:自研软件打包出来的、从上游仓库下载的、以及系统安装镜像里的。第一种不用多说,第二种要注意依赖闭包问题,因为你下载某个包时它的依赖可能不在同一个仓库里,客户端装的时候会报依赖缺失。稳妥做法是先把一批包下载下来,用一个干净的最小系统去试装,把报缺的依赖逐个补进来,直到能装上为止。这个过程可以用yumdownloader配合--resolve参数自动化,把依赖一起拉下来。

# 在有网的机器上把包和依赖一起下载到本地目录 mkdir -p /data/rpm-repo/base/x86_64 cd /data/rpm-repo/base/x86_64 yumdownloader --resolve --destdir=. 你的包名 # 安装元数据生成工具 yum install -y createrepo_c # 生成元数据,-d 表示生成 sqlite 索引,老版本 yum 需要 createrepo_c -d .

生成完之后你会看到一个repodata目录,里面有repomd.xml和若干压缩的索引文件。整个base/x86_64目录连同 repodata 一起上传到 Nexus 的 raw 仓库,上传路径要和客户端配置的 baseurl 严格对应,一个字符都不能差。

4.2 raw 仓库的严格内容校验是个拦路虎

Nexus 的 raw 仓库在创建时有一个Strict Content Type Validation选项,默认是开启的。开启状态下,通过 HTTP PUT 上传文件时必须带上正确的Content-Type头,否则会被拒绝。repodata 目录下的文件扩展名五花八门,有.xml、.gz、.bz2、.sqlite,还有完全没扩展名的primary、filelists等,用脚本上传时如果没逐个设置 Content-Type,会失败得很零碎。

我一般直接把这个选项关掉,因为 raw 仓库本来存的就是各种二进制文件,严格校验只会增加麻烦。关掉之后上传用curl -T或者 UI 的“上传”按钮都能成功。这里还有个细节:通过 UI 手动上传几十个文件效率太低,建议写个循环脚本批量推,同时记录每个文件的响应码,失败的单独挑出来重传。

#!/bin/bash NEXUS_URL="http://192.168.10.20:8081/repository/yum-raw" USER="deployer" PASS="你的密码" SRC_DIR="/data/rpm-repo" cd "$SRC_DIR" find . -type f | while read -r f; do # 去掉开头的 ./ target="${f#./}" code=$(curl -s -o /dev/null -w "%{http_code}" \ -u "$USER:$PASS" --upload-file "$f" \ "$NEXUS_URL/$target") if [ "$code" != "201" ] && [ "$code" != "204" ]; then echo "FAIL $code $target" fi done

上传完成后,在 Nexus 的浏览界面里点进 raw 仓库,确认repodata/repomd.xml能正常打开,内容不是乱码也不是 404。这一步看起来简单,但它能提前发现 90% 的路径问题。

4.3 客户端 .repo 文件与缓存清理

客户端这边新建一个.repo文件放到/etc/yum.repos.d/下,内容如下。注意baseurl的结尾斜杠要和实际目录结构一致,gpgcheck的取舍后面单独说。

[inner-base] name=internal base repo baseurl=http://192.168.10.20:8081/repository/yum-raw/base/x86_64/ enabled=1 gpgcheck=0

配好之后执行yum clean all再yum makecache,这两个命令的顺序不能反。clean all会清掉旧的元数据缓存,如果先makecache再clean,等于白做。如果makecache报错,先看错误信息里提到的 URL,把它复制到浏览器或者用 curl 直接访问,看看返回的是 404 还是超时。404 基本都是路径写错,超时则多半是防火墙或者 Nexus 服务本身的问题。

关于gpgcheck,正式环境建议打开。做法是在构建 rpm 时用 GPG 密钥签名,客户端导入公钥后在 repo 文件里配置gpgkey指向 key 的地址,gpgcheck=1。内网自己维护的仓库,如果所有包都是自己打的,签名能有效防止包被替换。如果只是临时用、包里还混着从各处下载的第三方 rpm,那先关掉校验把流程跑通,之后再逐步补齐签名。

4.4 yum 元数据的更新与日常维护

往仓库里加新包之后,必须重新执行createrepo_c更新元数据,否则客户端永远看不到新包。这一点不同于 Maven,Maven 的 hosted 仓库加文件是即时的,yum 有个“生成索引”的显式步骤。如果包的数量很多,每次都全量重建会比较慢,可以用--update参数做增量,但增量更新的前提是旧元数据还在,如果你把 repodata 删了再做增量,结果会不完整。

上传新元数据有一个原子性问题:客户端在拉取元数据的过程中,如果你正好在上传新文件,可能拉到一半新一半旧,导致校验失败。稳妥做法是先把新内容上传到一个临时目录,全部传完后再一次性覆盖。Nexus 的 raw 仓库支持覆盖上传,覆盖是瞬时的,只有几个文件的切换时间,风险窗口很小。

另外,客户端偶尔会遇到“元数据校验和不匹配”的报错,这通常是因为本地缓存里存着旧版本的元数据文件。执行yum clean metadata清掉元数据缓存即可,不用clean all那么彻底。这个报错在多台机器共用一个仓库、又频繁更新仓库时比较常见,属于正常现象,不用紧张。

5. apt 私库:没有原生仓库类型时怎么自己造

5.1 apt 的元数据结构比 yum 复杂在哪

apt 的元数据分两层。第一层是Packages文件,它是一个纯文本索引,每个包一段,记录了包名、版本、架构、依赖以及最重要的Filename字段即 deb 文件的实际路径。第二层是Release文件,它记录了这个仓库包含哪些组件、哪些架构,以及各个索引文件的哈希值。客户端拿到 Release 后,会用它里面的哈希去校验 Packages 文件有没有被篡改,这就是 apt 的安全模型。

正因为有这层签名和哈希校验,apt 仓库不能像 yum 那样随便放几个文件就行。你可以选择完整实现这套结构,用 GPG 签名,客户端配置正常校验;也可以走简化路线,用[trusted=yes]让客户端跳过校验。两种方式我都用过,内网自建、包来源可控的场景下,简化路线完全够用,配置量小很多;如果是多团队共用、包来源复杂,那就老实签名。

Nexus3 没有 apt 仓库类型,所以只能建一个 raw 仓库来放这些文件。目录结构有两种选择:一种是平铺,把Packages.gz和所有 deb 包放在同一层,客户端配置用deb [trusted=yes] 地址 ./;另一种是标准的dists/发行版/组件/binary-架构/三层结构。平铺的好处是简单,缺点是一个仓库只能对应一个发行版一个架构,多发行版就得建多个仓库。标准结构可以一个仓库容纳多发行版多架构,适合规模大一点的场景。

5.2 用 dpkg-scanpackages 生成索引

生成 Packages 文件用的是dpkg-scanpackages命令,它包含在dpkg-dev包里。基本用法是指定 deb 文件所在目录和一个 override 文件(没有就写/dev/null),输出重定向到 Packages 文件。目录参数有个讲究,用.表示相对当前目录,生成的Filename字段就是相对路径;用绝对路径则会生成绝对路径,这两个都不能直接用于 HTTP 仓库,必须保证 Filename 是相对于仓库根目录的路径。

# 准备目录 mkdir -p /data/deb-repo cd /data/deb-repo # 把 deb 包拷进来 # cp /path/to/*.deb . # 生成 Packages 索引,-m 表示允许多版本共存 dpkg-scanpackages -m . /dev/null > Packages # 压缩,apt 默认优先读 .gz gzip -k -9 Packages # 查看索引内容,确认 Filename 字段是相对路径 head -30 Packages

-m参数要不要加取决于你的场景。默认情况下,dpkg-scanpackages遇到同一个包的多个版本会报错退出,原因是一个仓库里同一个包名同一架构只允许一个版本。加了-m就允许多版本共存,适合你需要保留多个版本供不同机器选择的场景。但如果你的仓库只放一个版本,不加-m更安全,因为它能帮你发现重复包。

生成完Packages和Packages.gz之后,把它们和 deb 包一起上传到 Nexus 的 raw 仓库。同样要注意 strict content type validation 的问题,Packages这个文件没有扩展名,如果校验开着会传不上去,所以要提前关掉。上传路径要和客户端 baseurl 对齐,平铺结构下所有文件都在同一层。

5.3 客户端 sources.list 的三种写法

客户端配置写在/etc/apt/sources.list或者/etc/apt/sources.list.d/下的独立文件里。最简写法是平铺加跳过校验:

# /etc/apt/sources.list.d/inner.list deb [trusted=yes] http://192.168.10.20:8081/repository/apt-raw/ ./

这里末尾的./表示 Packages 文件位于仓库根目录。如果你用的是标准三层结构,写法就变成:

deb [trusted=yes] http://192.168.10.20:8081/repository/apt-raw/ focal main

对应到服务端的目录就是dists/focal/main/binary-amd64/Packages.gz。注意客户端不需要写binary-amd64,apt 会根据本机架构自动拼接这段路径。很多人配置失败就是因为把这段写进了 sources.list 里,导致拼出来的路径多了一层。

第三种是完整签名方式,[trusted=yes]换成[signed-by=/etc/apt/keyrings/inner.gpg],服务端要在dists/focal/下提供Release、Release.gpg和InRelease三个文件。生成 Release 需要写一个描述文件,把组件、架构、哈希都列出来,然后用 GPG 签名。这套流程比较繁琐,如果非必要,内网场景我一般建议直接用[trusted=yes],把精力花在控制包来源上更划算。

注意:apt-key这个命令在新版本 apt 里已经废弃并会打印警告,网上很多老教程还在用它导入公钥。新写法是把公钥文件放到/etc/apt/keyrings/目录下,然后在 sources.list 里用signed-by指定,这样不会污染全局信任链。

5.4 多架构与国产发行版的适配思路

如果内网里既有 x86 服务器又有 ARM 服务器,仓库就要支持多架构。平铺结构做不到,必须用标准三层结构,把binary-amd64和binary-arm64分别建好,dpkg-scanpackages在生成索引时也要用不同的目录。注意不同架构的 Packages 文件里Architecture字段必须正确,否则 apt 会认为这个包不适用于本机而跳过。

部分国产发行版在 Debian 系基础上做了改动,仓库配置的位置可能不是标准的/etc/apt/sources.list,而是放在别的地方,或者提供了图形化的源管理工具。遇到这种情况,先用apt-cache policy看看当前的源配置生效情况,再决定改哪个文件。加内网源的思路和标准 Debian 完全一样,只是文件位置需要现场找一下,不要想当然地以为一定是那个路径。

6. Nodejs 与 npm 私库:二进制分发和包托管两条线

6.1 nodejs 本身的离线分发方案

nodejs 在内网落地其实是两件事,一件是 nodejs 运行时的分发,另一件才是 npm 包的托管。很多时候大家只关注后者,结果新机器上连 node 都装不上,因为官网下载走的是公网。解决方案是把 nodejs 的发行包也放进 Nexus。

官方提供的 Linux 发行包是免安装的 tar.xz 压缩包,解压后配置一下 PATH 就能用,这正是“免安装环境配置”这个说法的来源。做法是在 Nexus 里建一个 raw 仓库专门放二进制发行包,把你要用的几个版本都传上去,然后写个安装脚本给新机器用。

#!/bin/bash # install-node.sh 用法: ./install-node.sh 18.20.4 VERSION=${1:-18.20.4} NEXUS="http://192.168.10.20:8081/repository/binary-raw" PREFIX="/usr/local" cd /tmp curl -fLO "$NEXUS/nodejs/node-v${VERSION}-linux-x64.tar.xz" tar -xJf "node-v${VERSION}-linux-x64.tar.xz" -C "$PREFIX" mv "$PREFIX/node-v${VERSION}-linux-x64" "$PREFIX/nodejs" # 配置环境变量,写到 profile.d 里全局生效 cat > /etc/profile.d/nodejs.sh <<'EOF' export NODE_HOME=/usr/local/nodejs export PATH=$NODE_HOME/bin:$PATH EOF source /etc/profile.d/nodejs.sh node -v && npm -v

这个脚本的关键点是-f参数,它让 curl 在遇到 404 时直接失败退出,而不是把错误页面写进文件里。我见过因为没有-f,下载下来一个 HTML 错误页,解压时报“不是有效的压缩文件”,排查半天才发现是路径写错。

Windows 开发机同理,把 zip 包传上去,写个 PowerShell 脚本下载解压,然后手动加到系统 PATH。这里就引出了那个特别常见的报错。

6.2 npm 原生仓库的三种角色配置

npm 在 Nexus 里的配置方式和 Maven 类似,也是 hosted、proxy、group 三角色。proxy 指向公共 npm 源,用来缓存外部的公共包;hosted 存放公司内部的私有包;group 作为统一入口。创建时的关键点是 npm 仓库有自己的端口映射逻辑,你可以在仓库配置里看到一个http端口设置,早期做法是给 npm 单独开一个端口(比如 8082),然后用http://内网IP:8082/作为 registry。

现在的做法更推荐不单独开端口,直接用仓库的完整路径作为 registry 地址,也就是http://内网IP:8081/repository/npm-group/。这样所有包类型共用 8081 端口,防火墙只开一个口,管理起来简单。缺点是这个地址比短地址长,容易在配置文件里写错,建议写成文档发给团队,不要口头传达。

npm 仓库还有一个配置项叫Negative cache,指的是当一个包在 proxy 里找不到时,把这个“找不到”的结果缓存多久。如果设得太长,当你上传了内部包之后,短时间内还是会被缓存挡住报 404,让人以为上传失败。内网环境建议把这个值调小一点,半小时到一小时比较合适。

6.3 客户端 .npmrc 配置与发布认证

项目的.npmrc或者全局的~/.npmrc里要写三样东西:registry 地址、发布地址的认证信息、以及作用域包的去向。写法如下,注意认证信息这部分不同版本的 npm 有差异。

registry=http://192.168.10.20:8081/repository/npm-group/ //192.168.10.20:8081/repository/npm-hosted/:_auth=你的base64认证串 //192.168.10.20:8081/repository/npm-hosted/:always-auth=true @公司作用域名:registry=http://192.168.10.20:8081/repository/npm-hosted/

这里的_auth值是用户名:密码拼接后用 base64 编码的结果,可以用命令行生成。always-auth=true表示访问这个地址时始终带认证信息,npm 9 之后这个字段的作用有所变化,有些版本会提示废弃,但加上通常不会有副作用。如果你用的是较新的 npm,更推荐用_authToken配合 Nexus 生成的 token,不过 Nexus 的 npm 认证还是以 Basic Auth 为主,基础写法能跑通就行。

生成认证串的方式:

printf 'deployer:你的密码' | base64

把输出结果填到_auth后面即可。注意这里不能有换行,printf比echo更安全,因为echo有时会附加换行符导致编码结果多出字符,认证就失败了。

验证配置是否生效,用npm config get registry看一眼,再npm install lodash --verbose观察请求地址。如果日志里出现的是你的内网 IP,说明 proxy 配置生效了;如果出现的是公共源地址,说明registry没被正确读取,检查一下是否有别处的.npmrc覆盖了它。npm 的配置优先级是项目级高于用户级高于全局级,排查时要逐层看。

发布内部包用npm publish,它会把包推到@公司作用域名对应的那个 hosted 仓库。发布前记得先在package.json里把publishConfig或name设置好,包名带作用域前缀,否则会默认往公共源推,直接失败。

6.4 Windows 上的 npm.ps1 执行策略问题

这是 Windows 开发机上出现频率最高的一个问题,报错信息大意是“无法加载文件 npm.ps1,因为在此系统上禁止运行脚本”。原因不是 npm 坏了,而是 PowerShell 的执行策略默认限制运行脚本,而 npm 在 Windows 上是通过一个.ps1包装脚本调用的,策略一挡就全废了。

解决办法是把当前用户的执行策略改成允许本地脚本运行,命令如下。注意要在 PowerShell 里执行,改的是当前用户级别,不需要管理员权限,也不会影响系统其他用户的策略。

# 查看当前策略 Get-ExecutionPolicy -List # 改为允许本地脚本,远程签名脚本 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned # 如果上面命令提示被组策略覆盖,就需要联系域管理员

改完之后关闭并重新打开终端,npm -v应该就能正常输出了。如果提示被组策略强制覆盖,说明公司域控统一下发了策略,这种情况下就不要硬改,让 IT 在域策略里放行,或者改用 CMD 而不是 PowerShell 来执行 npm 命令。这个坑的特点是报错信息看起来像文件损坏,很多人第一反应是重装 nodejs,白折腾。

7. 权限、容量与日常运维

7.1 用角色控制谁能上传谁能下载

Nexus3 默认有匿名访问,这个设置在生产环境必须关掉,至少要把写权限关掉。正确的做法是建几个角色,把权限粒度控制在仓库级别。我一般分三个角色:只读角色,只能浏览和下载所有仓库;开发角色,能往 hosts 的 releases 和 snapshots 上传;管理角色,能改仓库配置。然后把角色分配给用户,按人分配而不是共用一个账号,这样出问题能追溯到人。

具体配置路径在安全设置里的角色管理,创建角色时可以选择具体的权限项,比如nx-repository-view-maven2-maven-releases-add这种细粒度权限。建议不要图省事直接用内置的nx-admin给所有人,权限一旦放开就收不回来了。另外认证信息不要写死在项目文件里提交到代码库,settings.xml和.npmrc都应该加进.gitignore,用环境变量或者 CI 的密钥管理来注入。

7.2 清理策略和磁盘容量规划

私库跑一段时间后磁盘会持续增长,需要设置清理策略。Nexus 提供 Cleanup Policies,可以按“多久没被下载过”来删组件。对于 Maven 的快照仓库,建议设置成 30 天未使用就清理,因为快照本来就是临时的。对于正式版仓库则要谨慎,正式版构件可能很久才被引用一次,删掉之后老构建就复现不了了,这类仓库建议不设自动清理,靠人工评估。

容量估算有个粗略方法:Maven 中央仓库的完整镜像非常大,但你实际只会缓存用到的部分,一般团队一年累积在几十 GB 量级。yum 和 apt 的 rpm/deb 包体积大,一个完整的发行版仓库可能有十几 GB,建议只放你真正需要的包,不要整镜像同步。nodejs 的二进制包每个版本大约 30-50MB,存十个版本也就几百 MB,压力不大。

定期任务也是必须的,Nexus 里可以配置两类定时任务:一类是压缩 blob store,把碎片整理掉;一类是清理未使用的组件。压缩任务建议放在业务低峰期,因为它会占用一定 IO。如果你很久不压缩,blob store 的目录会越来越碎,最终影响性能。

7.3 备份与迁移的正确姿势

Nexus 的备份最简单也最可靠的方式就是冷备:停掉服务,把整个sonatype-work/nexus3目录打包拿走。这个目录包含了所有配置、仓库定义、包文件、用户和权限,属于完整快照。恢复到新机器上只需要把这个目录放回对应位置,再启动服务即可,注意程序版本要一致,跨大版本直接替换数据目录可能不兼容。

热备的方式是导出配置加单独备份 blob store,但这种方式容易漏掉用户和权限数据,恢复时可能出现仓库在但账号没了的尴尬情况。如果不是 7x24 不能停服的场景,我建议就停服冷备,简单可靠。备份频率看更新频率,一般一周一次加上重大变更前手动备份一次就够。

迁移机器时还有一个细节:如果客户端配置里用的是 IP 地址,换机器后 IP 变了就要改所有客户端配置。所以一开始就建议用域名或者统一的内网域名解析,迁移时只改 DNS 记录,客户端完全无感。这个建议越早采纳越好,等到几百台机器都配了 IP 再改,成本会非常高。

8. 踩坑实录与排查速查表

8.1 高频报错对照表

下面这张表是我这几年在多个现场攒下来的,覆盖了八成以上的常见问题。遇到报错先从这张表里找,能省下大量搜索时间。

报错现象可能原因排查动作
maven 拉取报 401settings.xml 的 server id 与 pom 不一致核对两处 id 字符串是否完全相同
maven 报找不到快照group 的 Version policy 选了 Release改为 Mixed 并刷新缓存
yum makecache 报无法下载元数据baseurl 路径不对或 repodata 未上传用 curl 直接访问 repomd.xml 看返回码
yum 报元数据校验和不匹配客户端缓存了旧元数据执行 yum clean metadata
apt update 报 404sources.list 里多写了 binary-amd64只保留发行版代号和组件名
apt update 报签名错误未加 trusted=yes 且无有效签名加上 trusted=yes 或补齐 Release 签名
npm 发布报 403账号没有 hosted 仓库的写权限在角色里补 add 权限
npm 装包仍走公网.npmrc 被上层配置覆盖用 npm config get registry 逐层确认
Windows 下 npm 无法加载 ps1PowerShell 执行策略限制Set-ExecutionPolicy 改 CurrentUser
Nexus 启动后无法访问监听地址绑定错误或防火墙未放行检查端口监听和防火墙规则

8.2 几个卡了很久的真实问题

第一个是 raw 仓库上传无扩展名文件被拒的问题。前面提过严格内容校验,但实际遇到时的表现是上传返回 400 而且提示信息很含糊,只说内容类型不被接受。当时排查方向一度跑到了认证上,浪费了不少时间。后来才发现是Packages这个文件没有扩展名,Nexus 判断不出类型就拒绝。关掉严格校验之后立刻正常。这个坑在 apt 场景下几乎必踩,因为 apt 的核心索引文件就是无扩展名的。

第二个是 yum 仓库的路径大小写和斜杠问题。Linux 文件系统区分大小写,repodata写成repodata/或者Repodata都会导致客户端找不到。另外 baseurl 结尾如果有斜杠,会和后面拼接的部分产生双斜杠,某些场景下 Nexus 会把它当成不同路径处理。这类问题没有技术含量,但特别耗时间,建议配置完成后第一件事就是用 curl 把拼接出来的完整 URL 敲一遍,确认能拿到文件。

第三个是 npm 的负缓存。当时上传了一个内部包,客户端死活装不上,报 404。检查了权限、路径、包名全都对,最后才想到是之前请求过这个包名、当时不存在,被负缓存记住了。清掉缓存或者等缓存过期之后就正常了。所以内网环境把这个缓存时间调短很有必要,否则新人上传包之后会以为失败,反复重传。

实操心得:所有客户端配置文件的地址,都不要靠手敲,直接从 Nexus 仓库详情页复制那个 URL。手敲出错的概率远高于你的想象,而这类错误排查起来最费劲,因为你会下意识觉得地址是对的。

8.3 日常巡检建议清单

最后给一份可以贴在工位上的巡检清单,每周花十分钟过一遍,能避免绝大多数突发故障。第一项看磁盘剩余空间,低于 20% 就要开始规划清理或者扩容,Nexus 在磁盘满的情况下会拒绝写入,表现为所有上传都失败但下载正常,很容易误判。第二项看服务进程和端口监听是否正常,可以写个简单的健康检查脚本配合定时任务跑。第三项看最近有没有大量 401 或 403 的访问日志,可能是有人在暴力猜密码,也可能是某台机器配置写错了在疯狂重试。第四项看定时任务有没有正常执行,压缩和清理任务如果失败,通常是权限或者磁盘问题。

这套东西搭完之后,最大的感受是内网环境终于不用再靠“人肉搬运”了。新机器上架,跑一个初始化脚本配上源地址,几分钟就能装完开发环境,不用再翻找 U盘里那个不知道哪个版本的安装包。构建结果也变得可信,同样的代码在任何一台机器上编出来的依赖树是一模一样的,出了问题能定位到具体的包版本,而不是“在我机器上是好的”。如果你们现在还处在每台机器各自连公网的阶段,真的值得花两天时间把这件事做完,后面省下的时间远超投入。

返回列表