網(wǎng)統(tǒng)一私庫(kù):Maven/YUM/APT/npm 搭建與排錯(cuò))
內(nèi)網(wǎng)做構(gòu)建這件事最容易被低估的就是依賴獲取這一環(huán)。項(xiàng)目一多、語(yǔ)言一雜Maven 拉 jar、YUM 裝 rpm、APT 裝 deb、npm 裝 node 模塊四套東西各自連各自的公網(wǎng)源誰(shuí)斷了都得停下等。我這邊的研發(fā)環(huán)境就是這么個(gè)情況十幾條流水線混著 Java、Go 打包腳本、CentOS 基礎(chǔ)鏡像和 Ubuntu 構(gòu)建機(jī)新人入職配一次環(huán)境要折騰大半天。后來(lái)用Nexus3在內(nèi)網(wǎng)做了一套統(tǒng)一私庫(kù)把maven、yum、apt、nodejs四類倉(cāng)庫(kù)全部收口到一臺(tái)機(jī)器上配好之后客戶端只需要改一個(gè)地址就能跑通全流程。這篇就把這套私庫(kù)環(huán)境從選型、安裝、建倉(cāng)、客戶端配置到排錯(cuò)按我實(shí)際落地的順序完整拆一遍參數(shù)和命令都能直接抄也把我踩過(guò)的幾個(gè)坑提前說(shuō)出來(lái)。1. 方案先想清楚Nexus3 擺什么位置四類倉(cāng)庫(kù)怎么分工動(dòng)手裝之前先把這臺(tái)機(jī)器到底承擔(dān)什么角色想明白比裝完再返工省事得多。私庫(kù)這東西本質(zhì)上是內(nèi)網(wǎng)和外網(wǎng)之間的一個(gè)緩存加中轉(zhuǎn)層它既要把公網(wǎng)的東西拉下來(lái)存住又要能收下團(tuán)隊(duì)自己產(chǎn)出的包還要在客戶端眼里只暴露一個(gè)統(tǒng)一的入口地址。Nexus3 恰好把這三種能力做成了三種倉(cāng)庫(kù)類型proxy 負(fù)責(zé)代理外部源并緩存hosted 負(fù)責(zé)托管內(nèi)部產(chǎn)物group 負(fù)責(zé)把前兩者聚合成一個(gè) URL。理解了這一層后面四類倉(cāng)庫(kù)的搭法其實(shí)是同一套邏輯換個(gè)協(xié)議而已。1.1 內(nèi)網(wǎng)直連公網(wǎng)的三個(gè)現(xiàn)實(shí)痛點(diǎn)第一個(gè)痛點(diǎn)是速度和穩(wěn)定性。公網(wǎng)源在國(guó)內(nèi)訪問(wèn)經(jīng)常抖動(dòng)Maven 中央倉(cāng)庫(kù)拉一個(gè)稍大的依賴樹(shù)就能拖到十幾分鐘npm 裝一個(gè)前端工程動(dòng)輒幾百個(gè)包一旦中途超時(shí)整個(gè)npm install從頭再來(lái)。私庫(kù)把包緩存在本地磁盤(pán)第二次開(kāi)始就是千兆內(nèi)網(wǎng)速度差別非常直觀。第二個(gè)痛點(diǎn)是安全和審計(jì)。開(kāi)發(fā)機(jī)的構(gòu)建腳本如果直接連公網(wǎng)源等于給每臺(tái)機(jī)器都開(kāi)了對(duì)外的下載權(quán)限一旦某個(gè)公網(wǎng)包被投毒內(nèi)網(wǎng)很難察覺(jué)也沒(méi)法追溯到底是誰(shuí)在什么時(shí)候引入了哪個(gè)版本。私庫(kù)把下載入口收斂成一臺(tái)機(jī)器所有包的進(jìn)出都有記錄還能在 hosted 倉(cāng)庫(kù)里做白名單式的內(nèi)部包發(fā)布。第三個(gè)痛點(diǎn)是離線可用。很多能力受限的機(jī)房、隔離網(wǎng)段、臨時(shí)搭建的測(cè)試環(huán)境根本連不上公網(wǎng)這時(shí)候如果沒(méi)有一套本地倉(cāng)庫(kù)整個(gè)編譯流程直接卡死。私庫(kù)里緩存過(guò)的依賴在斷網(wǎng)狀態(tài)下依然能完整安裝這對(duì)做交付、做演示、給客戶部署的場(chǎng)景尤其關(guān)鍵。把這三條想清楚你就知道這套東西不是錦上添花而是基礎(chǔ)設(shè)施的一部分。1.2 proxy、hosted、group 三種倉(cāng)庫(kù)的定位差異很多人第一次打開(kāi) Nexus3 新建倉(cāng)庫(kù)的界面會(huì)懵類型太多不知道選哪個(gè)。其實(shí)抓住三種角色的分工就清楚了。proxy是代理加緩存你給它一個(gè)遠(yuǎn)端 URL它第一次請(qǐng)求時(shí)去遠(yuǎn)端拉拉到之后存在本地之后同樣的請(qǐng)求直接命中緩存Maven 中央倉(cāng)庫(kù)、npm 官方源、各發(fā)行版的 yum/apt 源都屬于這一類。hosted是托管內(nèi)部產(chǎn)物團(tuán)隊(duì)自己編譯出來(lái)的 jar、打好的 rpm、deb 包、私有 npm 模塊都往這里發(fā)。它不連外網(wǎng)只接受上傳和下載是內(nèi)部交付的唯一可信來(lái)源。hosted 又分 Release 和 Snapshot 兩種版本策略這個(gè)在 Maven 里尤其重要后面會(huì)細(xì)說(shuō)。group是聚合入口它自己不存東西只是把若干個(gè) proxy 和 hosted 按順序拼在一起客戶端訪問(wèn) group 的地址時(shí)Nexus 依次去成員倉(cāng)庫(kù)里找找到就返回。對(duì)客戶端來(lái)說(shuō)永遠(yuǎn)只需要配一個(gè) group 地址既拿到了公網(wǎng)緩存又拿到了內(nèi)部包還省得在配置里寫(xiě)一堆 URL。我一般給每個(gè)協(xié)議都建一個(gè) group命名統(tǒng)一成xxx-group客戶端配置里只出現(xiàn)這一個(gè)地址。1.3 部署形態(tài)選型Docker 拉起還是裸機(jī) installNexus3 官方提供兩種主流部署方式。Docker 方式最省事拉個(gè)鏡像掛個(gè)數(shù)據(jù)卷就能跑升級(jí)就是換個(gè) tag回滾就是換回舊 tag對(duì)不熟悉 Java 服務(wù)的人特別友好。裸機(jī)安裝是把官方 tar 包解壓到某個(gè)目錄再用 systemd 托管好處是不依賴容器運(yùn)行時(shí)資源占用略低出問(wèn)題時(shí)排查鏈路更短。我實(shí)際兩套都跑過(guò)。開(kāi)發(fā)測(cè)試環(huán)境用 Docker因?yàn)樾枰l繁重建生產(chǎn)性的私庫(kù)用裸機(jī)因?yàn)檫@臺(tái)機(jī)器上還掛著別的服務(wù)不想再引入一層容器網(wǎng)絡(luò)。判斷標(biāo)準(zhǔn)很簡(jiǎn)單你的機(jī)器上已經(jīng)有成熟的容器運(yùn)維體系就選 Docker如果是一臺(tái)孤立的虛擬機(jī)、后續(xù)沒(méi)人專門(mén)維護(hù)容器就裸機(jī)。兩種方式的數(shù)據(jù)目錄都是同一個(gè)結(jié)構(gòu)遷移時(shí)把數(shù)據(jù)目錄拷過(guò)去就行所以先選哪個(gè)都不算鎖死。2. 部署前的資源估算與 Nexus3 落地安裝資源這塊不能拍腦袋給少了后面 OOM給多了浪費(fèi)。我按自己的經(jīng)驗(yàn)給一套估算方法然后分別給出 Docker 和裸機(jī)的完整落地步驟。2.1 JVM 堆、磁盤(pán)和內(nèi)存的估算過(guò)程N(yùn)exus3 是個(gè) Java 應(yīng)用跑在 JVM 里默認(rèn)的最大堆是 2703MB。這個(gè)數(shù)字不是隨便定的官方給的經(jīng)驗(yàn)是堆內(nèi)存至少要能放下你最大單次請(qǐng)求涉及的對(duì)象而私庫(kù)場(chǎng)景里最大的一塊通常是首次代理一個(gè)大依賴樹(shù)或者一個(gè)大鏡像層時(shí)的臨時(shí)緩沖。我的估算方式是基礎(chǔ)堆 2GB 打底然后看你的倉(cāng)庫(kù)規(guī)模。如果只是給幾十個(gè)項(xiàng)目做 Maven 加 npm 代理2GB 到 4GB 足夠。如果還要緩存大量 rpm/deb 并且并發(fā)構(gòu)建機(jī)器超過(guò)二十臺(tái)建議給到 6GB 到 8GB物理內(nèi)存留出堆的 1.5 到 2 倍因?yàn)?JVM 除了堆還有直接內(nèi)存、元空間、線程棧和文件緩存。磁盤(pán)是另一個(gè)大頭。Nexus3 的數(shù)據(jù)目錄里blob store 會(huì)隨著緩存不斷增長(zhǎng)。一個(gè)中等規(guī)模的 Java 團(tuán)隊(duì)一年下來(lái) Maven 緩存吃掉 50GB 到 200GB 很正常如果還代理 npm 和系統(tǒng)包翻倍也不奇怪。我的做法是數(shù)據(jù)目錄單獨(dú)掛一塊盤(pán)初始給 500GB配好清理策略并且提前把監(jiān)控做上別等到磁盤(pán)寫(xiě)滿才發(fā)現(xiàn)。提示Nexus3 官方鏡像里 JVM 參數(shù)通過(guò)環(huán)境變量INSTALL4J_ADD_VM_PARAMS傳入裸機(jī)安裝則改bin/nexus.vmoptions。改完必須重啟進(jìn)程才生效在線改文件沒(méi)用。場(chǎng)景建議堆內(nèi)存建議磁盤(pán)說(shuō)明個(gè)人或小團(tuán)隊(duì)只做 Maven 代理2GB100GB單機(jī)夠用注意清理中型團(tuán)隊(duì)Maven npm4GB500GB并發(fā)十臺(tái)以內(nèi)比較穩(wěn)多語(yǔ)言混合含 yum/apt6GB 到 8GB1TB需要配清理策略和監(jiān)控2.2 Docker 方式十幾分鐘拉起一個(gè)可用的私庫(kù)先建好數(shù)據(jù)目錄并授權(quán)。Nexus3 容器內(nèi)以 uid 200 運(yùn)行宿主機(jī)目錄權(quán)限不對(duì)會(huì)直接啟動(dòng)失敗這一步很多人會(huì)漏。sudo mkdir -p /data/nexus-data sudo chown -R 200:200 /data/nexus-data sudo chmod -R 775 /data/nexus-data然后用官方鏡像啟動(dòng)。注意把 JVM 參數(shù)和數(shù)據(jù)目錄一起傳進(jìn)去順便把 Java 的 preferences 目錄也指到數(shù)據(jù)卷里避免容器重建后配置丟失。docker run -d \ --name nexus3 \ --restart always \ -p 8081:8081 \ -v /data/nexus-data:/nexus-data \ -e INSTALL4J_ADD_VM_PARAMS-Xms4g -Xmx4g -XX:MaxDirectMemorySize4g -Djava.util.prefs.userRoot/nexus-data/javaprefs \ sonatype/nexus3:3.70.1啟動(dòng)完成后docker logs -f nexus3觀察日志出現(xiàn)Started Sonatype Nexus字樣就說(shuō)明起來(lái)了。第一次啟動(dòng)會(huì)初始化數(shù)據(jù)庫(kù)通常要一到三分鐘機(jī)器慢的話更久別急著判定失敗。注意數(shù)據(jù)目錄權(quán)限問(wèn)題是 Nexus3 啟動(dòng)失敗最常見(jiàn)的原因日志里一般會(huì)提示Permission denied或者直接卡住不輸出。遇到先查目錄屬主再查 SELinux。2.3 裸機(jī)安裝與 systemd 托管裸機(jī)安裝的步驟是先準(zhǔn)備 JDK再解壓 tar 包最后寫(xiě)成 systemd 服務(wù)。Nexus3 各版本對(duì) JDK 的要求不一樣3.6x 之后的版本普遍要求 JDK 8 或 11動(dòng)手前一定去確認(rèn)對(duì)應(yīng)版本的官方要求別隨便裝個(gè)新版 JDK 上去。# 以 JDK 11 為例路徑按你實(shí)際安裝位置調(diào)整 sudo mkdir -p /opt/nexus sudo tar -zxvf nexus-3.70.1-02-unix.tar.gz -C /opt/nexus --strip-components1 sudo useradd -r -s /sbin/nologin nexus sudo chown -R nexus:nexus /opt/nexus改bin/nexus.vmoptions把堆參數(shù)調(diào)成你估算的值-Xms4g -Xmx4g -XX:MaxDirectMemorySize4g -XX:UnlockDiagnosticVMOptions -XX:LogVMOutput -XX:LogFile../sonatype-work/nexus3/log/jvm.log接著寫(xiě) systemd 單元讓進(jìn)程跟著系統(tǒng)啟動(dòng)崩潰也能自動(dòng)拉起來(lái)[Unit] DescriptionNexus Repository Manager Afternetwork.target [Service] Typeforking LimitNOFILE65536 ExecStart/opt/nexus/bin/nexus start ExecStop/opt/nexus/bin/nexus stop Usernexus Restarton-abort [Install] WantedBymulti-user.target寫(xiě)完執(zhí)行systemctl daemon-reload再systemctl enable --now nexus。這里有個(gè)容易忽略的點(diǎn)LimitNOFILE要給足私庫(kù)并發(fā)高的時(shí)候文件句柄消耗很快默認(rèn) 1024 會(huì)在壓力上來(lái)后出現(xiàn)莫名其妙的連接失敗。我給到 65536實(shí)測(cè)很穩(wěn)。2.4 首次登錄必須做的三件事新版本 Nexus3 的初始密碼不再固定是admin/admin123而是隨機(jī)生成后寫(xiě)在一個(gè)文件里Docker 方式在/nexus-data/admin.password裸機(jī)在sonatype-work/nexus3/admin.password。cat /data/nexus-data/admin.password拿到密碼后用admin登錄http://你的IP:8081系統(tǒng)會(huì)引導(dǎo)你改密碼。改完密碼只是第一步另外三件事我建議當(dāng)天就做完第一關(guān)掉匿名訪問(wèn)或明確配置匿名可讀范圍第二刪掉默認(rèn)的 demo 倉(cāng)庫(kù)避免誤用第三建一個(gè)專用的deploy用戶只給上傳權(quán)限CI 里用這個(gè)賬號(hào)不要用 admin。這三件事看著瑣碎但直接決定了后面私庫(kù)是不是安全可控。我見(jiàn)過(guò)不少環(huán)境圖省事一直用 admin 給所有客戶端發(fā)憑據(jù)一旦泄露就是全庫(kù)可寫(xiě)可刪補(bǔ)救成本極高。3. Maven 私庫(kù)搭建hosted、proxy、group 三件套怎么拼Maven 是這套私庫(kù)里用得最多的部分也是最容易配錯(cuò)的部分。我的做法是固定建三個(gè)倉(cāng)庫(kù)一個(gè) hosted 收內(nèi)部包一個(gè) proxy 代理中央倉(cāng)庫(kù)一個(gè) group 把兩者聚合客戶端只認(rèn) group。3.1 建倉(cāng)庫(kù)的順序和命名規(guī)范順序上建議先 hosted再 proxy最后 group因?yàn)?group 的成員列表在創(chuàng)建時(shí)就要選前面的沒(méi)建好后面選不到。命名我用固定的后綴區(qū)分maven-releases、maven-snapshots、maven-central、maven-public。這樣一看名字就知道類型后期維護(hù)和交接都省心。hosted 倉(cāng)庫(kù)分兩個(gè)是刻意的。Release 版本的包一旦發(fā)布就不應(yīng)該被覆蓋Snapshot 版本可以反復(fù)覆蓋發(fā)布。把兩者分開(kāi)可以在組策略和清理策略上差異化處理比如給 Snapshot 配定期清理給 Release 配置為不可重復(fù)部署。這個(gè)區(qū)分在做正式交付的時(shí)候非常有用。3.2 proxy 代理中央倉(cāng)庫(kù)的幾個(gè)關(guān)鍵參數(shù)新建 maven2(proxy) 時(shí)遠(yuǎn)端地址填https://repo1.maven.org/maven2/其余參數(shù)大部分可以用默認(rèn)值但有兩個(gè)地方我調(diào)整過(guò)。一個(gè)是版本策略proxy 的Version policy保持Mixed即可因?yàn)橹醒雮}(cāng)庫(kù)里既有 release 也有 snapshot。另一個(gè)是緩存過(guò)期時(shí)間默認(rèn)的Not Found Cache TTL是 1440 分鐘意思是某個(gè)包如果確定不存在1440 分鐘內(nèi)不再去遠(yuǎn)端查詢。這個(gè)值對(duì)內(nèi)網(wǎng)體驗(yàn)影響很大遇到構(gòu)建突然報(bào)找不到某個(gè)剛發(fā)布的版本先想想是不是被這個(gè)緩存擋了臨時(shí)可以把值改小或者手動(dòng)清理緩存。如果你的環(huán)境有穩(wěn)定的國(guó)內(nèi)鏡像可用也可以再建一個(gè) proxy 指向它然后在 group 里排到中央倉(cāng)庫(kù)前面命中率更高。但要記住無(wú)論后面掛幾個(gè) proxy客戶端配置里永遠(yuǎn)只出現(xiàn) group 地址這是這套設(shè)計(jì)能穩(wěn)定運(yùn)行的前提。3.3 settings.xml 與 pom.xml 可直接抄的配置客戶端側(cè)主要改兩個(gè)文件。全局的settings.xml配鏡像和憑據(jù)項(xiàng)目的pom.xml配發(fā)布地址。先看settings.xml把鏡像指向 group同時(shí)在 servers 里放上傳憑據(jù)settings mirrors mirror idnexus/id namenexus private maven/name urlhttp://192.168.1.100:8081/repository/maven-public//url mirrorOf*/mirrorOf /mirror /mirrors servers server idnexus-releases/id usernamedeploy/username password你的部署密碼/password /server server idnexus-snapshots/id usernamedeploy/username password你的部署密碼/password /server /servers /settings這里mirrorOf用*表示接管所有倉(cāng)庫(kù)請(qǐng)求。有人會(huì)擔(dān)心這樣連不上其他私有源解決辦法是在 group 里把那些源也建成 proxy 成員而不是在客戶端放開(kāi)多個(gè)鏡像。保持客戶端配置單一后期換地址只需要改一處。pom.xml里加發(fā)布地址distributionManagement repository idnexus-releases/id urlhttp://192.168.1.100:8081/repository/maven-releases//url /repository snapshotRepository idnexus-snapshots/id urlhttp://192.168.1.100:8081/repository/maven-snapshots//url /snapshotRepository /distributionManagement配好之后mvn deploy就能把包推到私庫(kù)其他項(xiàng)目通過(guò)mvn clean package自動(dòng)從 group 拉取。3.4 快照版本與發(fā)布版本的策略區(qū)別這塊是我踩坑最多的地方。Release 倉(cāng)庫(kù)默認(rèn)策略是Disable redeploy也就是同一個(gè)版本號(hào)不允許重復(fù)發(fā)布這是為了保證金版本不可變。如果你調(diào)試時(shí)反復(fù)發(fā)同一個(gè)版本號(hào)會(huì)收到 400 錯(cuò)誤報(bào)錯(cuò)信息里會(huì)提到 redeploy。解決辦法是要么升版本號(hào)要么臨時(shí)把倉(cāng)庫(kù)策略改成 Allow redeploy但正式環(huán)境千萬(wàn)別改否則版本就失去可信度了。Snapshot 倉(cāng)庫(kù)則相反允許覆蓋每次發(fā)布自動(dòng)帶時(shí)間戳后綴。客戶端拉 snapshot 時(shí)會(huì)檢查遠(yuǎn)程的maven-metadata.xml所以如果你本地mvn -U還是拿到舊包很可能是 metadata 緩存沒(méi)刷新可以手動(dòng)觸發(fā)一次更新或者等緩存過(guò)期。實(shí)操心得給 Snapshot 倉(cāng)庫(kù)單獨(dú)配一條清理策略比如保留最近 30 天或者每個(gè)包最多保留 10 個(gè)快照。不做這件事快照目錄會(huì)以肉眼可見(jiàn)的速度膨脹半年就能吃掉幾百 GB。4. YUM 私庫(kù)把 RPM 統(tǒng)一收口Java 團(tuán)隊(duì)之外只要涉及 CentOS、RHEL 一類的系統(tǒng)鏡像構(gòu)建就繞不開(kāi) YUM。私庫(kù)里做 YUM 的價(jià)值在于內(nèi)網(wǎng)機(jī)器不再需要各自配置一堆外網(wǎng)源所有 rpm 從一處下發(fā)離線環(huán)境也能裝。4.1 yum hosted 與 yum proxy 的建法差異新建時(shí)選yum (proxy)或yum (hosted)。proxy 的遠(yuǎn)端地址填你要代理的發(fā)行版源比如某個(gè)穩(wěn)定鏡像站的centos/7/os/x86_64/路徑hosted 不填遠(yuǎn)端只等著你上傳 rpm。這里有個(gè)版本相關(guān)的細(xì)節(jié)必須提前確認(rèn)Nexus3 對(duì) yum hosted 的支持在較新版本才完善早期版本上傳 rpm 后不會(huì)自動(dòng)生成 repodata客戶端會(huì)直接報(bào)找不到元數(shù)據(jù)。如果你用的是老版本要么升級(jí)要么用createrepo在倉(cāng)庫(kù)目錄里手動(dòng)生成后上傳。我建議直接升到較新的 3.x 版本能省掉大量手工維護(hù)元數(shù)據(jù)的工作。4.2 客戶端 repo 文件怎么寫(xiě)客戶端只改一個(gè).repo文件指向 yum group[nexus-yum] nameNexus YUM Repository baseurlhttp://192.168.1.100:8081/repository/yum-group/ enabled1 gpgcheck0gpgcheck0只是內(nèi)網(wǎng)圖省事的寫(xiě)法正式環(huán)境應(yīng)該把公鑰下發(fā)到/etc/pki/rpm-gpg/并把gpgcheck打開(kāi)。改完文件執(zhí)行yum clean all再yum makecache緩存建起來(lái)之后再裝包就是內(nèi)網(wǎng)速度。注意改了源之后一定要先yum clean all否則老緩存還在會(huì)繼續(xù)走舊地址讓人誤以為配置沒(méi)生效。4.3 批量上傳 RPM 的三種姿勢(shì)第一種是界面手動(dòng)上傳適合零散幾個(gè)包缺點(diǎn)是包多了效率極低。第二種是curl PUT 上傳適合腳本化curl -v -u deploy:你的密碼 \ --upload-file ./nginx-1.24.0-1.el7.x86_64.rpm \ http://192.168.1.100:8081/repository/yum-hosted/Packages/n/nginx-1.24.0-1.el7.x86_64.rpm路徑里的Packages/n/只是目錄慣例方便按首字母歸類不影響功能Nexus 會(huì)自動(dòng)識(shí)別 rpm 內(nèi)容并更新元數(shù)據(jù)。第三種是從 proxy 緩存里提升到 hosted如果你已經(jīng)通過(guò) proxy 拉過(guò)某個(gè)包可以直接在界面上把它移動(dòng)到 hosted 倉(cāng)庫(kù)省一次下載。批量場(chǎng)景我一般寫(xiě)個(gè)循環(huán)腳本把本地一個(gè)目錄里的 rpm 全部推上去幾百個(gè)包幾分鐘就能跑完。4.4 元數(shù)據(jù)刷新與校驗(yàn)上傳完成后去 hosted 倉(cāng)庫(kù)的 Browse 里看是否出現(xiàn)repodata目錄里面應(yīng)該有repomd.xml和若干primary文件。有就說(shuō)明元數(shù)據(jù)生成成功??蛻舳诉@邊如果yum install報(bào)Cannot retrieve repository metadata按三個(gè)方向查網(wǎng)絡(luò)能不能通、baseurl 路徑對(duì)不對(duì)、group 的成員順序?qū)Σ粚?duì)。group 成員順序這件事容易被忽略。如果你的 hosted 里有一個(gè)包、proxy 里也有同名不同版本的包group 會(huì)按成員列表順序去找先命中誰(shuí)就用誰(shuí)。想讓內(nèi)部包優(yōu)先就把 hosted 放到成員列表最前面。5. APT 私庫(kù)給 Debian、Ubuntu 做內(nèi)網(wǎng)源APT 這套的邏輯和 YUM 類似但多了簽名密鑰這一環(huán)配置不當(dāng)會(huì)一直卡在apt update報(bào)簽名錯(cuò)誤上所以單獨(dú)拎出來(lái)講清楚。5.1 apt hosted 與 apt proxy 的建立創(chuàng)建時(shí)選apt (hosted)或apt (proxy)。hosted 需要填兩個(gè)關(guān)鍵項(xiàng)Distribution比如bionic、focal、jammy這類發(fā)行版代號(hào)和Signing Key。簽名密鑰是 APT 體系和 YUM 最大的區(qū)別客戶端默認(rèn)會(huì)校驗(yàn)包和索引的簽名沒(méi)有正確的公鑰就會(huì)直接拒絕更新。我的建議是單獨(dú)生成一對(duì)專用密鑰導(dǎo)出私鑰粘貼到 Nexus 的 Signing Key 配置里把公鑰保存好下發(fā)給客戶端。用現(xiàn)成的通用密鑰雖然省事但一旦密鑰輪換就是全量客戶端重配不如一開(kāi)始就規(guī)劃清楚。5.2 sources.list 與公鑰落地客戶端配置寫(xiě)在/etc/apt/sources.list或/etc/apt/sources.list.d/下的獨(dú)立文件里deb http://192.168.1.100:8081/repository/apt-group/ focal main然后是公鑰。把 Nexus 上導(dǎo)出的公鑰復(fù)制到客戶端后執(zhí)行導(dǎo)入sudo gpg --dearmor -o /usr/share/keyrings/nexus-apt.gpg nexus-public.key再回到 sources 文件里加上簽名引用形如[signed-by/usr/share/keyrings/nexus-apt.gpg]。這樣做的目的是把信任范圍限定到這一個(gè)倉(cāng)庫(kù)不影響系統(tǒng)其他源的校驗(yàn)邏輯比直接把 key 加進(jìn)全局 trusted 列表干凈得多。改完執(zhí)行sudo apt update看到倉(cāng)庫(kù)地址被正常讀取、沒(méi)有簽名報(bào)錯(cuò)就說(shuō)明通了。5.3 上傳 deb 包與 dists 目錄結(jié)構(gòu)deb 包上傳同樣支持 curlcurl -v -u deploy:你的密碼 \ --upload-file ./mytool_1.0.0_amd64.deb \ http://192.168.1.100:8081/repository/apt-hosted/上傳后 Nexus 會(huì)在倉(cāng)庫(kù)里維護(hù)dists/Distribution/和pool/結(jié)構(gòu)apt update讀取的就是dists下的Release和Packages索引。如果你上傳后客戶端報(bào)404八成是 Distribution 名字和 sources 里寫(xiě)的對(duì)不上比如倉(cāng)庫(kù)建的是jammy客戶端寫(xiě)的卻是focal這種錯(cuò)看起來(lái)是網(wǎng)絡(luò)問(wèn)題其實(shí)是配置不一致。5.4 apt update 報(bào)錯(cuò)的定位方法APT 的報(bào)錯(cuò)信息其實(shí)很直白按關(guān)鍵字對(duì)號(hào)入座就行。看到NO_PUBKEY或者signature verification failed說(shuō)明公鑰沒(méi)導(dǎo)入或者signed-by路徑寫(xiě)錯(cuò)看到404 Not Found先核對(duì) baseurl、Distribution、組件名三者是否和倉(cāng)庫(kù)配置一致看到Could not resolve或者超時(shí)那是網(wǎng)絡(luò)層的問(wèn)題先curl一下倉(cāng)庫(kù)地址看能不能通。我遇到過(guò)一次特別隱蔽的情況客戶端報(bào)簽名錯(cuò)誤但公鑰明明導(dǎo)入了。最后發(fā)現(xiàn)是密鑰過(guò)期時(shí)間設(shè)置得太短半年后就失效了。所以生成密鑰時(shí)有效期至少給幾年并且把輪換計(jì)劃寫(xiě)進(jìn)運(yùn)維文檔別讓它在你忘記的時(shí)候突然爆掉。6. Node.js 私庫(kù)npm 代理加私有包前端工程對(duì)私庫(kù)的依賴程度可能是四類里最高的因?yàn)閚pm install一次要拉幾百個(gè)包公網(wǎng)速度直接決定構(gòu)建時(shí)長(zhǎng)。6.1 npm 三倉(cāng)庫(kù)的建立與 group 順序和 Maven 一樣建三個(gè)npm-hosted放私有包npm-proxy代理官方源遠(yuǎn)端地址https://registry.npmjs.orgnpm-group聚合兩者。group 成員順序建議 hosted 在前這樣私有包名和公網(wǎng)包名沖突時(shí)優(yōu)先命中內(nèi)部版本避免裝錯(cuò)包。有個(gè)細(xì)節(jié)值得強(qiáng)調(diào)Nexus3 的 npm 代理在部分版本上對(duì)npm audit和某些元數(shù)據(jù)接口的支持不完整如果團(tuán)隊(duì)重度依賴npm audit可能需要額外配一個(gè)直連場(chǎng)景或者關(guān)掉相關(guān)檢查。這屬于版本特性差異遇到不要硬杠先確認(rèn)你用的 Nexus 版本對(duì) npm 的支持清單。6.2 .npmrc 的工程級(jí)配置客戶端配置分兩層。全局的~/.npmrc影響所有項(xiàng)目工程級(jí)的.npmrc只影響當(dāng)前項(xiàng)目。我一般把源地址寫(xiě)進(jìn)工程級(jí)配置并提交到代碼庫(kù)讓團(tuán)隊(duì)成員克隆下來(lái)就能用registryhttp://192.168.1.100:8081/repository/npm-group/ strict-sslfalse如果私庫(kù)上了 HTTPS 并用了自簽證書(shū)strict-ssl的處理要謹(jǐn)慎穩(wěn)妥做法是把 CA 證書(shū)裝到系統(tǒng)信任鏈里而不是一律關(guān)校驗(yàn)。上傳私有包時(shí)還需要認(rèn)證可以用npm login或者直接在.npmrc里配_auth字段。6.3 發(fā)布私有包與 scope 綁定私有包建議統(tǒng)一用 scope比如mycompany/utils并在.npmrc里把 scope 和倉(cāng)庫(kù)綁定mycompany:registryhttp://192.168.1.100:8081/repository/npm-hosted/這樣npm publish時(shí)帶 scope 的包會(huì)自動(dòng)發(fā)到 hosted不帶 scope 的走默認(rèn) registry。綁定 scope 的好處是避免了發(fā)布方向的歧義也方便在 group 里做路由控制。實(shí)操心得發(fā)布前一定在.npmrc里把認(rèn)證信息配好否則npm publish會(huì)返回 401 或 403。CI 環(huán)境里用環(huán)境變量注入憑據(jù)不要把 token 硬編碼進(jìn)倉(cāng)庫(kù)。6.4 緩存、離線與 CI 場(chǎng)景npm 私庫(kù)最直接的收益體現(xiàn)在 CI 上。同一條流水線第二次構(gòu)建時(shí)所有依賴都命中內(nèi)網(wǎng)緩存npm ci的時(shí)間能從幾分鐘壓到幾十秒。如果再配合把node_modules或者 npm 緩存目錄做持久化效果更明顯。為了控制磁盤(pán)我給 npm proxy 配了緩存清理策略把長(zhǎng)期沒(méi)人訪問(wèn)的包定期刪掉。但要注意清理策略刪的是緩存不是內(nèi)部包hosted 倉(cāng)庫(kù)的包不會(huì)受影響。配置時(shí)看清楚作用范圍別把 hosted 也掃進(jìn)去那會(huì)把團(tuán)隊(duì)發(fā)布的私有包誤刪。7. 常見(jiàn)問(wèn)題速查與排查手法這套環(huán)境跑起來(lái)之后日常遇到的問(wèn)題集中在幾個(gè)固定方向我把它們整理成速查形式遇到直接對(duì)號(hào)入座。7.1 上傳類錯(cuò)誤怎么讀上傳失敗最常見(jiàn)的三個(gè)狀態(tài)碼401是認(rèn)證沒(méi)通過(guò)檢查用戶名密碼或者 token403是認(rèn)證過(guò)了但沒(méi)有寫(xiě)權(quán)限檢查這個(gè)用戶有沒(méi)有對(duì)應(yīng)倉(cāng)庫(kù)的nx-repository-view-*-*-add權(quán)限400多見(jiàn)于 Maven 重復(fù)發(fā)布 release 版本或者路徑不符合倉(cāng)庫(kù)約定。有一個(gè)特別容易誤判的情況Maven 上傳時(shí)報(bào) 400 并提到redeploy很多人以為是權(quán)限問(wèn)題去改用戶其實(shí)是版本策略擋的??吹竭@個(gè)關(guān)鍵字直接去改策略或者升版本號(hào)。7.2 客戶端拉取超時(shí)和 404 的定位順序遇到客戶端拉不到包我固定按這個(gè)順序查第一步確認(rèn)倉(cāng)庫(kù)地址能通用 curl 訪問(wèn) group 的根路徑返回 200 說(shuō)明服務(wù)正常第二步確認(rèn)包在不在直接在界面 Browse 里搜包名看它在哪個(gè)成員倉(cāng)庫(kù)里第三步確認(rèn) group 成員配置有沒(méi)有漏加或者順序錯(cuò)了第四步查緩存過(guò)期404 被緩存住的情況在老版本里很常見(jiàn)清理一下對(duì)應(yīng)倉(cāng)庫(kù)的緩存就好。這個(gè)順序的好處是從大到小逐層收斂不用一上來(lái)就翻日志。真到第四步還解決不了再去翻nexus.log里面會(huì)有到遠(yuǎn)端請(qǐng)求的詳細(xì)記錄。7.3 磁盤(pán)告警與清理策略磁盤(pán)是私庫(kù)最現(xiàn)實(shí)的瓶頸。我的做法是每個(gè)倉(cāng)庫(kù)單獨(dú)配清理策略而不是全局一把抓。Maven snapshot 保留最近 30 天npm proxy 只保留最近 90 天被訪問(wèn)過(guò)的包系統(tǒng)包緩存保留版本數(shù)上限。同時(shí)給數(shù)據(jù)目錄做磁盤(pán)水位監(jiān)控超過(guò) 80% 就告警。需要提醒的是清理策略是定期任務(wù)不是實(shí)時(shí)執(zhí)行配置完要手動(dòng)跑一次或者等調(diào)度周期到才會(huì)生效。別配完就以為空間立刻回來(lái)了我見(jiàn)過(guò)有人等了半天以為策略沒(méi)生效其實(shí)是任務(wù)還沒(méi)跑。7.4 備份、遷移和擴(kuò)容備份的黃金法則是備份數(shù)據(jù)目錄而不是備份容器。Nexus3 的所有配置、倉(cāng)庫(kù)內(nèi)容、用戶信息都落在數(shù)據(jù)目錄下的 blob store 和數(shù)據(jù)庫(kù)里把整個(gè)目錄 rsync 出來(lái)就是完整備份。Docker 環(huán)境把/nexus-data備份走裸機(jī)把sonatype-work/nexus3備份走。遷移時(shí)新機(jī)器裝好同版本 Nexus把數(shù)據(jù)目錄拷過(guò)去權(quán)限改成對(duì)應(yīng)用戶啟動(dòng)即可。這里有個(gè)大坑目標(biāo)機(jī)器的 Nexus 版本不能低于源機(jī)器否則數(shù)據(jù)結(jié)構(gòu)不兼容會(huì)啟動(dòng)失敗。升級(jí)路徑永遠(yuǎn)是先升程序再遷數(shù)據(jù)別反過(guò)來(lái)。擴(kuò)容主要針對(duì) blob store。Nexus3 支持把 blob store 放到獨(dú)立磁盤(pán)甚至共享存儲(chǔ)上數(shù)據(jù)量大了之后可以直接把 blob store 目錄換到更大的盤(pán)改配置重啟即可不需要重建倉(cāng)庫(kù)。8. 我在實(shí)際搭建中踩過(guò)的坑和積累的小技巧前面講了標(biāo)準(zhǔn)流程這一節(jié)說(shuō)點(diǎn)文檔里不太會(huì)寫(xiě)、但實(shí)際會(huì)絆人的細(xì)節(jié)。8.1 權(quán)限別只給能上傳一開(kāi)始我給 CI 用的deploy用戶只加了上傳權(quán)限結(jié)果發(fā)布 npm 包時(shí)一直 403排查半天才發(fā)現(xiàn) npm 發(fā)布除了寫(xiě)權(quán)限還需要對(duì)倉(cāng)庫(kù)的讀取權(quán)限因?yàn)樗l(fā)布前要先檢查包是否存在。后來(lái)給這個(gè)用戶補(bǔ)上了read和browse問(wèn)題就沒(méi)了。所以配權(quán)限時(shí)別只盯著寫(xiě)讀取和瀏覽通常也得給。8.2 反向代理和上下文路徑的配合把 Nexus 放到反向代理后面時(shí)最容易出問(wèn)題的是上下文路徑。如果對(duì)外暴露的路徑前綴不是根路徑需要在 Nexus 里配置Base URL或者在代理層做路徑重寫(xiě)否則界面里的資源鏈接會(huì)指向錯(cuò)誤的地址表現(xiàn)為頁(yè)面加載一半就失效。最省事的做法是讓 Nexus 的 Base URL 和代理對(duì)外地址完全一致不要做多余的路徑變換。8.3 關(guān)于密碼和密鑰的幾條經(jīng)驗(yàn)初始密碼要第一時(shí)間改改完把文件刪掉CI 憑據(jù)走環(huán)境變量注入不要提交到代碼庫(kù)APT 的簽名密鑰有效期給足并且記錄輪換計(jì)劃Maven 的部署密碼和登錄密碼不要用同一個(gè)。這些都是小事但每一條出問(wèn)題都會(huì)變成大事。8.4 一次版本不匹配引發(fā)的連鎖反應(yīng)有一次我在測(cè)試機(jī)上升級(jí)了 Nexus把數(shù)據(jù)目錄直接拷到舊版本的機(jī)器上想回滾結(jié)果啟動(dòng)直接失敗日志里報(bào)數(shù)據(jù)結(jié)構(gòu)版本不兼容。后來(lái)才知道 Nexus 的數(shù)據(jù)庫(kù)有 schema 版本號(hào)只能向前不能向后。從那以后我給自己定了一條規(guī)矩升級(jí)前一定做完整的數(shù)據(jù)目錄快照回滾用快照而不是降級(jí)程序。這條比什么優(yōu)化技巧都重要。另一個(gè)小技巧是關(guān)于首次緩存的預(yù)熱。新搭好的私庫(kù)第一次給團(tuán)隊(duì)用大家同時(shí)拉依賴會(huì)很慢因?yàn)槎荚诘却砣ミh(yuǎn)端拉。我一般挑幾個(gè)大項(xiàng)目的構(gòu)建先跑一遍把熱門(mén)依賴提前緩存進(jìn)私庫(kù)等團(tuán)隊(duì)切過(guò)來(lái)的時(shí)候命中率已經(jīng)很高了體驗(yàn)會(huì)好很多。這個(gè)操作不復(fù)雜但對(duì)推廣私庫(kù)很有幫助畢竟大家最在意的就是切過(guò)去之后是不是真的更快了。最后再補(bǔ)一個(gè)小細(xì)節(jié)關(guān)于客戶端配置的下發(fā)。人一多最麻煩的不是搭私庫(kù)而是讓所有人把客戶端配置改對(duì)。我的做法是寫(xiě)一段幾行的初始化腳本Maven 用settings.xml模板覆蓋npm 用工程級(jí).npmrc提交到代碼庫(kù)yum 和 apt 的源文件用配置管理工具統(tǒng)一下發(fā)。這樣新人進(jìn)來(lái)跑一個(gè)腳本就配好了也不會(huì)有人漏改某一條導(dǎo)致構(gòu)建行為不一致。搭私庫(kù)這件事真正的成本從來(lái)不在服務(wù)端而在讓整個(gè)團(tuán)隊(duì)用得整齊。