網(wǎng)離線安裝插件:依賴(lài)解析與版本匹配實(shí)戰(zhàn))
簡(jiǎn)介本資源面向在內(nèi)網(wǎng)、隔離網(wǎng)等無(wú)外網(wǎng)環(huán)境中部署Jenkins的運(yùn)維與DevOps工程師針對(duì)Jenkins 2.346.1無(wú)法在線拉取插件的問(wèn)題提供一套完整的離線插件安裝方案。壓縮包共約2000個(gè)文件整體314.4MB涵蓋90個(gè)jpi與30個(gè)hpi插件包、366個(gè)jar依賴(lài)、192個(gè)xml配置及大量js、html、css等前端資源并附帶properties、timestamp等元數(shù)據(jù)文件構(gòu)成可直接落地的離線插件倉(cāng)庫(kù)。資源圍繞離線下載插件、創(chuàng)建離線插件包、配置初始化腳本、啟動(dòng)驗(yàn)證、后續(xù)維護(hù)更新與安全權(quán)限控制等環(huán)節(jié)展開(kāi)幫助讀者打通內(nèi)網(wǎng)Jenkins插件安裝全流程。目前已有3171人學(xué)習(xí)下載適合需要快速搭建內(nèi)網(wǎng)CI/CD環(huán)境、排查插件加載失敗問(wèn)題的技術(shù)人員參考可有效減少重復(fù)試錯(cuò)成本。1. 內(nèi)網(wǎng) Jenkins 離線裝插件為什么 2.346.1 的插件包不能隨便下很多團(tuán)隊(duì)的內(nèi)網(wǎng)構(gòu)建機(jī)跑著 Jenkins 2.346.1版本不算新但勝在穩(wěn)定誰(shuí)也不想動(dòng)它。問(wèn)題出在裝插件上外網(wǎng)能訪問(wèn)的更新中心內(nèi)網(wǎng)機(jī)器一個(gè)字節(jié)都拉不到點(diǎn)「安裝」就是轉(zhuǎn)圈然后報(bào)錯(cuò)。有人圖省事從別的機(jī)器把.hpi拷過(guò)來(lái)直接丟進(jìn)plugins目錄重啟后插件列表里是有了但構(gòu)建任務(wù)一跑就拋NoSuchMethodError查半天才發(fā)現(xiàn)是插件版本和 Jenkins 核心對(duì)不上。這個(gè)場(chǎng)景要解決的核心問(wèn)題就一句話在沒(méi)有外網(wǎng)的內(nèi)網(wǎng)環(huán)境里給 Jenkins 2.346.1 裝上一批能正常工作的插件。適合兩類(lèi)人一是負(fù)責(zé)內(nèi)網(wǎng) CI 環(huán)境維護(hù)、被離線安裝折騰過(guò)的運(yùn)維二是需要在隔離網(wǎng)絡(luò)里搭一套完整構(gòu)建流水線的開(kāi)發(fā)。下面按「先搞懂依賴(lài)關(guān)系再動(dòng)手下載最后排錯(cuò)」的順序拆開(kāi)講每一步都能直接抄。2. 離線安裝的底層邏輯插件依賴(lài)樹(shù)與版本匹配2.1 為什么不能只下單個(gè) .hpi 文件Jenkins 插件不是孤立運(yùn)行的。一個(gè)插件會(huì)聲明它依賴(lài)哪些其他插件、依賴(lài)的最低版本是多少這些信息寫(xiě)在插件元數(shù)據(jù)里。你只下git.hpi它可能依賴(lài)git-client、credentials、structs等一串插件缺一個(gè)就裝不上或者裝上了運(yùn)行時(shí)報(bào)錯(cuò)。常見(jiàn)做法是先確定你要裝的目標(biāo)插件然后遞歸解析它的依賴(lài)樹(shù)把所有依賴(lài)插件連同目標(biāo)插件一起下載。手動(dòng)做這件事很容易漏所以一般用工具自動(dòng)解析。版本匹配是第二個(gè)關(guān)鍵點(diǎn)。Jenkins 2.346.1 對(duì)應(yīng)的插件更新中心有它自己的版本范圍同一個(gè)插件在 2.346.1 和 2.4xx 上能用的版本可能不同。判斷依據(jù)是插件頁(yè)面上的「Required Core」字段它標(biāo)明了這個(gè)插件版本要求的最低 Jenkins 核心版本。你選的插件版本Required Core 必須小于等于 2.346.1。2.2 依賴(lài)解析的兩種可行路徑路徑一用官方更新中心的數(shù)據(jù)做離線解析。Jenkins 更新中心有一個(gè)update-center.json里面記錄了每個(gè)插件的所有版本、依賴(lài)關(guān)系和 Required Core。你可以在外網(wǎng)機(jī)器上拉這個(gè)文件用腳本解析出目標(biāo)插件的完整依賴(lài)樹(shù)再按版本約束篩選出適配 2.346.1 的版本組合。路徑二用現(xiàn)成的離線下載工具。社區(qū)里有針對(duì) Jenkins 的插件下載腳本輸入插件名和 Jenkins 版本輸出一個(gè)包含所有依賴(lài)的目錄。這類(lèi)工具的原理和路徑一一樣只是把解析邏輯封裝好了。我一般會(huì)走路徑一因?yàn)榭煽?。下面給一個(gè)用 Python 解析依賴(lài)樹(shù)的骨架代碼核心是從update-center.json里遞歸找依賴(lài)。import json import requests # 從外網(wǎng)機(jī)器拉取更新中心數(shù)據(jù)內(nèi)網(wǎng)機(jī)器提前拷貝好這個(gè)文件 # 注意不同 Jenkins 版本的更新中心 URL 不同2.346.1 對(duì)應(yīng)的是穩(wěn)定版更新中心 UPDATE_CENTER_URL https://updates.jenkins.io/stable/update-center.json def load_update_center(local_pathNone): 加載更新中心數(shù)據(jù)支持本地文件或在線拉取 if local_path: with open(local_path, r, encodingutf-8) as f: raw f.read() else: raw requests.get(UPDATE_CENTER_URL).text # update-center.json 外層包了一層 JSONP 回調(diào)需要?jiǎng)兊?raw raw.strip() if raw.startswith(updateCenter.post(): raw raw[len(updateCenter.post():] if raw.endswith();): raw raw[:-2] return json.loads(raw) def resolve_deps(uc_data, plugin_name, jenkins_version, resolvedNone): 遞歸解析插件依賴(lài)返回 {插件名: 版本} 的字典 if resolved is None: resolved {} if plugin_name in resolved: return resolved plugins uc_data[plugins] if plugin_name not in plugins: raise ValueError(f插件 {plugin_name} 不在更新中心數(shù)據(jù)里) info plugins[plugin_name] # 篩選出 Required Core 當(dāng)前 Jenkins 版本的版本 candidates [] for ver, meta in info.items(): if ver name: continue required_core meta.get(requiredCore, 0) if compare_version(required_core, jenkins_version) 0: candidates.append((ver, meta)) if not candidates: raise ValueError(f{plugin_name} 沒(méi)有適配 {jenkins_version} 的版本) # 取滿(mǎn)足條件的最高版本 candidates.sort(keylambda x: x[0], reverseTrue) chosen_ver, chosen_meta candidates[0] resolved[plugin_name] chosen_ver # 遞歸處理依賴(lài) for dep_name, dep_ver in chosen_meta.get(dependencies, {}).items(): resolve_deps(uc_data, dep_name, jenkins_version, resolved) return resolved def compare_version(v1, v2): 簡(jiǎn)單的版本號(hào)比較返回 -1/0/1 parts1 [int(x) for x in v1.split(.) if x.isdigit()] parts2 [int(x) for x in v2.split(.) if x.isdigit()] for a, b in zip(parts1, parts2): if a b: return -1 if a b: return 1 return 0這段代碼的邏輯說(shuō)明load_update_center負(fù)責(zé)把更新中心數(shù)據(jù)讀進(jìn)來(lái)注意它外層有 JSONP 包裝直接json.loads會(huì)失敗必須先剝殼。resolve_deps是核心遞歸函數(shù)它先按requiredCore過(guò)濾出適配當(dāng)前 Jenkins 版本的插件版本取最高版本后再遍歷這個(gè)版本的dependencies字段繼續(xù)遞歸。compare_version是個(gè)簡(jiǎn)化版比較函數(shù)實(shí)際用的時(shí)候建議換成packaging.version庫(kù)避免遇到1.10和1.9比較出錯(cuò)。參數(shù)方面jenkins_version傳2.346.1plugin_name傳你要裝的目標(biāo)插件名比如git。跑完之后resolved字典里就是完整的插件名和版本列表。2.3 下載環(huán)節(jié)的目錄組織解析出依賴(lài)列表后下載環(huán)節(jié)要按固定結(jié)構(gòu)組織文件方便后續(xù)批量安裝。我一般用這樣的目錄offline-plugins/ ├── download_list.txt # 插件名:版本 列表 ├── hpi/ # 所有 .hpi 文件 │ ├── git-4.11.3.hpi │ ├── git-client-3.11.0.hpi │ └── ... └── install.sh # 批量安裝腳本下載 URL 的規(guī)律是https://updates.jenkins.io/download/plugins/{插件名}/{版本}/{插件名}.hpi。注意文件名里不帶版本號(hào)版本號(hào)在路徑里。下載腳本按這個(gè)規(guī)律拼 URL 即可。3. 內(nèi)網(wǎng)落地實(shí)操?gòu)耐饩W(wǎng)下載到 Jenkins 加載3.1 外網(wǎng)機(jī)器上的下載與打包在外網(wǎng)機(jī)器上先跑上面的解析腳本拿到依賴(lài)列表然后批量下載。下面是一個(gè)下載腳本讀download_list.txt逐行下載。#!/bin/bash # 在外網(wǎng)機(jī)器上執(zhí)行下載所有插件到 hpi 目錄 set -e DOWNLOAD_DIR./hpi LIST_FILE./download_list.txt BASE_URLhttps://updates.jenkins.io/download/plugins mkdir -p $DOWNLOAD_DIR while IFS: read -r name version; do # 跳過(guò)空行和注釋 [[ -z $name || $name \#* ]] continue url${BASE_URL}/${name}/${version}/${name}.hpi out${DOWNLOAD_DIR}/${name}-${version}.hpi if [[ -f $out ]]; then echo 已存在跳過(guò): $out continue fi echo 下載: $url curl -fSL -o $out $url done $LIST_FILE echo 下載完成共 $(ls -1 $DOWNLOAD_DIR | wc -l) 個(gè)文件邏輯說(shuō)明IFS:按冒號(hào)分割每行因?yàn)閐ownload_list.txt的格式是插件名:版本。curl -fSL里-f讓 HTTP 錯(cuò)誤碼直接失敗而不是寫(xiě)入錯(cuò)誤頁(yè)面-L跟隨重定向-S顯示錯(cuò)誤。下載完成后檢查文件數(shù)量是否和列表行數(shù)一致不一致說(shuō)明有下載失敗。打包時(shí)把hpi目錄和install.sh一起打成 tar 包通過(guò)內(nèi)網(wǎng)允許的方式傳到目標(biāo)機(jī)器。注意不要用zip因?yàn)?Jenkins 機(jī)器上不一定有解壓工具tar更通用。3.2 內(nèi)網(wǎng)機(jī)器上的批量安裝傳到內(nèi)網(wǎng)后有兩種安裝方式。方式一是直接拷貝到JENKINS_HOME/plugins目錄然后重啟。方式二是用 Jenkins CLI 的install-plugin命令。方式一更直接但要注意權(quán)限和文件屬主。#!/bin/bash # 在內(nèi)網(wǎng) Jenkins 機(jī)器上執(zhí)行 set -e JENKINS_HOME/var/lib/jenkins # 按實(shí)際路徑改 HPI_DIR./hpi # 備份現(xiàn)有插件目錄出問(wèn)題能回滾 BACKUP_DIR${JENKINS_HOME}/plugins.bak.$(date %Y%m%d%H%M%S) cp -a ${JENKINS_HOME}/plugins $BACKUP_DIR echo 已備份到 $BACKUP_DIR # 拷貝新插件注意屬主 cp -v ${HPI_DIR}/*.hpi ${JENKINS_HOME}/plugins/ chown -R jenkins:jenkins ${JENKINS_HOME}/plugins # 刪除插件解壓緩存強(qiáng)制重新解壓 find ${JENKINS_HOME}/plugins -maxdepth 1 -type d -name *.hpi -prune -o -type d -print | while read d; do # 只刪和 hpi 同名的解壓目錄 base$(basename $d) if [[ -f ${JENKINS_HOME}/plugins/${base}.hpi ]]; then rm -rf $d fi done echo 插件拷貝完成請(qǐng)重啟 Jenkins邏輯說(shuō)明先備份是血淚經(jīng)驗(yàn)插件裝崩了能直接回滾。chown必須做否則 Jenkins 進(jìn)程讀不到文件。刪除解壓緩存這一步很多人會(huì)漏Jenkins 加載插件時(shí)如果發(fā)現(xiàn)同名目錄已存在可能不會(huì)重新解壓新的.hpi導(dǎo)致你以為裝了新版本實(shí)際還是舊的。重啟命令按你的部署方式選systemctl restart jenkins或service jenkins restart。重啟后看日志確認(rèn)加載情況。3.3 驗(yàn)證插件是否真正生效重啟不等于裝好。驗(yàn)證分三步第一步看 Jenkins 啟動(dòng)日志里有沒(méi)有插件加載失敗的報(bào)錯(cuò)第二步進(jìn)「系統(tǒng)管理 → 插件管理 → 已安裝」看插件是否在列表里且版本正確第三步實(shí)際跑一個(gè)用到該插件的構(gòu)建任務(wù)。# 查看啟動(dòng)日志中的插件加載錯(cuò)誤 grep -i failed to load\|SEVERE.*plugin\|Plugin.*failed /var/log/jenkins/jenkins.log | tail -50 # 查看已加載插件列表通過(guò) Jenkins CLI java -jar jenkins-cli.jar -s http://localhost:8080/ -auth admin:token list-plugins | grep -i git日志里如果出現(xiàn)Failed to load: xxx (xxx)后面跟NoClassDefFoundError或NoSuchMethodError基本就是版本不匹配。list-plugins命令需要 Jenkins CLI 的 jar 包和認(rèn)證信息認(rèn)證用 API Token 比密碼安全。4. 避坑與排查離線裝插件最容易翻車(chē)的五個(gè)點(diǎn)4.1 現(xiàn)象插件列表里有了但構(gòu)建時(shí)報(bào) NoSuchMethodError原因插件版本和 Jenkins 核心版本不匹配。你下載的插件版本 Required Core 高于 2.346.1Jenkins 加載時(shí)沒(méi)攔住但運(yùn)行時(shí)調(diào)用了核心不存在的方法。解決回到更新中心數(shù)據(jù)重新按requiredCore 2.346.1篩選版本。不要只看插件的最新版最新版往往要求更高的核心版本。4.2 現(xiàn)象重啟后插件消失或者版本沒(méi)變?cè)騄enkins 的插件加載機(jī)制會(huì)優(yōu)先讀解壓后的目錄如果同名目錄已存在它不會(huì)重新解壓新的.hpi。你拷貝了新文件但舊目錄還在加載的還是舊版本。解決拷貝新.hpi后刪除對(duì)應(yīng)的解壓目錄再重啟。上面的安裝腳本里已經(jīng)包含這一步。更穩(wěn)妥的做法是停掉 Jenkins 再操作避免運(yùn)行中文件被占用。4.3 現(xiàn)象插件裝上了但功能不可用比如 Git 插件找不到 git 命令原因插件本身是 Jenkins 側(cè)的封裝它依賴(lài)系統(tǒng)里安裝的 git 可執(zhí)行文件。離線環(huán)境里 Jenkins 機(jī)器可能沒(méi)裝 git或者 git 路徑不在 Jenkins 進(jìn)程的 PATH 里。解決在 Jenkins 的「系統(tǒng)管理 → 全局工具配置」里顯式指定 git 路徑不要依賴(lài) PATH。同時(shí)確認(rèn)jenkins用戶(hù)能執(zhí)行g(shù)it --version。4.4 現(xiàn)象依賴(lài)插件缺失安裝時(shí)報(bào)「依賴(lài)未滿(mǎn)足」原因解析依賴(lài)樹(shù)時(shí)漏了傳遞依賴(lài)或者某個(gè)依賴(lài)插件的版本被其他插件約束到了不兼容的范圍。解決用resolve_deps跑完整依賴(lài)樹(shù)不要手動(dòng)挑。如果兩個(gè)插件對(duì)同一個(gè)依賴(lài)的版本要求沖突優(yōu)先滿(mǎn)足 Required Core 更高的那個(gè)插件的約束然后驗(yàn)證另一個(gè)插件是否還能工作。4.5 現(xiàn)象拷貝文件后 Jenkins 啟動(dòng)失敗日志報(bào)權(quán)限錯(cuò)誤原因cp操作是以當(dāng)前用戶(hù)執(zhí)行的文件屬主不是jenkinsJenkins 進(jìn)程讀不到。解決chown -R jenkins:jenkins整個(gè) plugins 目錄。另外注意JENKINS_HOME的父目錄權(quán)限如果 Jenkins 用戶(hù)對(duì)父目錄沒(méi)有執(zhí)行權(quán)限一樣讀不到。提示每次批量裝插件前先備份plugins目錄和config.xml?;貪L成本遠(yuǎn)低于排查成本。5. 進(jìn)階技巧用腳本做版本校驗(yàn)和增量更新裝完一批插件后后續(xù)維護(hù)的痛點(diǎn)在于怎么知道哪些插件有新版本、新版本是否適配當(dāng)前 Jenkins。我一般會(huì)寫(xiě)一個(gè)校驗(yàn)?zāi)_本把內(nèi)網(wǎng)已裝插件列表和外網(wǎng)更新中心數(shù)據(jù)做比對(duì)輸出可升級(jí)列表和對(duì)應(yīng)的 Required Core。import json def check_updates(uc_data, installed, jenkins_version): 比對(duì)已裝插件和更新中心輸出可升級(jí)且適配的插件 plugins uc_data[plugins] result [] for name, current_ver in installed.items(): if name not in plugins: continue candidates [] for ver, meta in plugins[name].items(): if ver name: continue if compare_version(meta.get(requiredCore, 0), jenkins_version) 0: candidates.append(ver) if not candidates: continue candidates.sort(keylambda v: [int(x) for x in v.split(.) if x.isdigit()], reverseTrue) latest candidates[-1] if compare_version(latest, current_ver) 0: result.append((name, current_ver, latest)) return result # installed 從 Jenkins 的 plugins 目錄或 list-plugins 輸出解析得到 # 輸出格式[(插件名, 當(dāng)前版本, 可升級(jí)版本), ...]這個(gè)腳本的價(jià)值在于它不會(huì)推薦 Required Core 超過(guò)當(dāng)前 Jenkins 版本的升級(jí)避免你升完發(fā)現(xiàn) Jenkins 起不來(lái)。installed字典可以從JENKINS_HOME/plugins目錄下的.hpi文件名解析也可以用list-plugins命令的輸出。增量更新的操作流程是跑校驗(yàn)?zāi)_本拿到可升級(jí)列表只下載這些插件的.hpi傳到內(nèi)網(wǎng)后按第 3 章的安裝腳本替換。注意替換前仍然要備份并且刪除對(duì)應(yīng)的解壓目錄。還有一個(gè)容易被忽略的點(diǎn)Jenkins 的插件更新中心數(shù)據(jù)本身會(huì)變同一個(gè)插件版本在不同時(shí)間拉到的update-center.json可能不同。所以在外網(wǎng)下載時(shí)建議把update-center.json一起打包傳到內(nèi)網(wǎng)后續(xù)排查版本問(wèn)題時(shí)能對(duì)照當(dāng)時(shí)的數(shù)據(jù)。從那以后我每次做離線插件安裝都強(qiáng)制走一遍「解析依賴(lài) → 下載 → 備份 → 拷貝 → 刪緩存 → 重啟 → 驗(yàn)證」這七步少一步都可能翻車(chē)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取