☰
内网Jenkins离线插件部署:更新中心版本锁定与依赖解析全攻略
2026/10/16 5:04:58 网站建设 项目流程

简介:针对内网部署 Jenkins 2.346.1 时无法在线拉取插件的典型难题,这份离线插件资源包提供了完整的插件文件集合与配套目录结构,适合运维工程师、DevOps人员以及需要搭建隔离CI环境的技术团队。包内共包含2000个文件,压缩后约314.4MB;除大量js、html、jar、xml等运行支撑文件外,还集中收录了90个jpi、30个hpi等不同格式的插件文件,附带css、properties、svg、png等界面与配置资源。其中js/html承载管理界面,jar/class负责核心运行,jpi/hpi即为可离线安装的插件本体,整体可支撑初始化安装并减少人工逐一下载的麻烦。资源保留了与Jenkins主目录一致的组织形式,便于理解插件安装流程与依赖关系,也能帮助使用者定位关键配置项。目前已有3171人学习下载,对正在规划内网Jenkins平台或需要批量补装插件的人员具有实用参考价值。

1. 内网 Jenkins 2.346.1 部署:卡的不是 war 包,是离线插件这一关

搭内网 Jenkins 时,很多人以为把 jenkins.war 搬进服务器就成功了,真正卡人的是 war 包后面那套插件生态。没有外网的机器上,插件管理页面白转、构建报缺 SCM、Pipeline 依赖消失,几乎每次都从一个 hpi 文件引出另一堆依赖。这套资源以 Jenkins 2.346.1 为例,讲清离线插件下载的完整链路:被锁版本的更新中心抓取、依赖递归解析、三种内网安装方式和五条踩坑记录。适合做内网 CI 交付的运维、偶尔接手离线环境的一线开发,照着先备好清单再落地,能省掉大半天的试错。

2. 更新中心与依赖链:为什么不能只拷一个 hpi 进服务器

2.1 hpi 不是普通压缩包:Manifest 里的版本与依赖声明

Jenkins 插件的分发格式是.hpi,本质上是一个可执行的 ZIP 压缩包,但和普通压缩包有个关键区别:它内部META-INF/MANIFEST.MF里存了一套插件自己的元数据,包括插件名、版本、最低 Jenkins 核心版本,以及它依赖哪些其他插件。你从镜像站下载一个 git.hpi,只把这个文件丢到服务器上,等于告诉 Jenkins 我要运行 git 插件,却没告诉它 git 插件依赖的 git-client、ssh-credentials 这些前置件在哪。

我在实际离线部署时,第一步永远是先解压看 Manifest,而不是急着拷贝:

unzip -p git.hpi META-INF/MANIFEST.MF | grep -E "Plugin-Dependencies|Jenkins-Version"

unzip -p直接把文件内容打到标准输出,不落地多余的临时文件;Plugin-Dependencies字段会列出逗号分隔的插件名和版本,格式类似git-client:4.4.5,ssh-credentials:2.6.1。这一行就是整个离线安装的依赖地图。遇到没有这个字段的插件,说明它不依赖其他插件,拷贝进去就能用,是离线包里最省心的一类。

需要留意的还有Jenkins-Version字段,它声明的是这个插件编译时基于的 Jenkins 核心版本。插件版本太新、核心版本太旧,启动时会直接报NoSuchMethodError这类异常,比依赖缺失更难排查,原因就在这个字段上。

2.2 版本锁定:update-center.json 为什么要按 2.346.1 拉取

Jenkins 官方更新中心默认给的是最新插件列表,但这个“最新”并不一定适合 2.346.1 这套旧版本核心。插件开发方通常只保证最近两到三个 LTS 版本兼容,新插件包里的类、API、内部扩展点,老核心根本不存在。直接对着通用 update-center.json 下载,等于拿一把新钥匙去开旧锁。

正确做法是拉取带版本号的更新中心,格式固定为:

https://updates.jenkins.io/<版本号>/update-center.json

把这个地址里的版本号替换成2.346.1,返回的 JSON 文件内容会以该核心版本为基准,标出兼容的插件版本和下载地址。这个文件通常有二三十兆,包含插件名、版本号、下载 URL、requiredCore 等字段。离线部署前先把这一份 JSON 保存下来,后续所有插件版本都以它为准,而不是靠脑袋记“上次装的版本号”。

对比一下两种拉取方式的差异:

拉取方式返回内容是否适合离线安装
不带版本号的 update-center.json全部插件最新版不适合,容易拉进新插件导致核心不兼容
带 2.346.1 的 update-center.json该核心兼容的插件版本集合适合,依赖解析的基准文件

实际抓包时还会看到一些镜像站把 JSON 拆成插件粒度的小文件,格式略有差异,但字段名基本一致。只要抓住version、url、requiredCore这几个字段就能解析出完整的下载清单。

2.3 核心依赖与可选依赖:一个插件背后可能挂一摞插件

依赖声明分两类:强制依赖和可选依赖。强制依赖写在Plugin-Dependencies主字段里,没装这个插件,主插件加载都会失败;可选依赖则在字段末尾标记resolution:=optional,缺少时主插件能启动,但对应功能会降级。离线环境里我建议先把强制依赖全装上,可选依赖按实际任务决定,别一股脑全拉,否则离线包体积会失控。

比如最常见的 git 插件,它的依赖往往包含 git-client、ssh-credentials、credentials、workflow-scm-step、plain-credentials、mailer 等一串。Pipeline 任务里只要用到checkout步骤,这些依赖缺一个,job 配置页就可能直接报红色错误。逐个手工点页面下载显然是玄学碰运气,所以后面要写一段递归解析脚本来统一解决。

另一个容易忽略的是依赖的版本兼容:插件 A 声明依赖git-client:4.4.5,手动装一个更新的 git-client 版本,多数情况下没问题,但碰到git-client:4.x的接口调整,也会出现运行期异常。离线包里尽量保持 update-center.json 里给出的默认版本,不要自行“顺手升级”。

3. 在外网准备离线仓库:三步把插件清单转成本地文件

3.1 先定插件清单:从实际任务反推,不要追求全量

进内网之前,先把这台 Jenkins 会跑什么任务列清楚。只做源码编译和制品归档,用 git、credentials、timestamper、pipeline-stage-view、configuration-as-code 就够;要做容器镜像构建,可能还要加 docker、docker-build-step、kubernetes 等。一次性把所有插件全拉下来,不仅同步文件慢,还会让内网 Jenkins 启动时有大量插件做兼容性校验,日志刷到看不清。

我给出一个适合多数内网交付场景的最小参考清单,按任务类型分组:

任务场景插件名
源码拉取git、git-client、ssh-credentials、credentials
流水线基础workflow-aggregator、pipeline-stage-view、timestamper
配置管理configuration-as-code、configuration-as-code-extension
凭证管理credentials-binding、plain-credentials
构建产物htmlpublisher、junit、mailer

这个清单是起点不是终点,实际使用时还要根据 job 里写的步骤继续补。补插件的原则是“最小改动原则”:每次只加一个,跑一次构建验证,不要一次性塞十个再回退排查。离线包维护就是一个不断做减法的过程,目录里每多一个插件,后续升级和排错都多一分成本。

3.2 拉取带版本号的 update-center.json 并确认文件完整

准备好插件清单后,第一步是在外网机器上把 update-center.json 拉下来:

curl --fail --compressed -L \ -o update-center.json \ https://updates.jenkins.io/2.346.1/update-center.json

--fail表示 HTTP 状态码大于等于 400 时直接退出并返回非零值,避免存下一个错误页当 JSON 用;--compressed允许服务端返回 gzip 压缩内容,这个文件有几 MB 到几十 MB,压缩后传输快很多;-L跟随重定向,因为官方更新中心有时会把请求转发到离你更近的镜像。

下载完成后先检查文件头是不是 JSON,别急着写脚本:

file update-center.json head -c 200 update-center.json

正常输出应该以{开头,文件末尾能看到"signature"相关的签名字段。如果file显示HTML document,说明前面某个环节被拦了,通常是代理或防火墙把域名拦截了,此时换镜像源或者换台能正常出网的机器,别在这个环境里硬调 curl 参数。

3.3 用 Python 解析依赖并批量下载 hpi 文件

拿到 update-center.json 后,写一个解析脚本把所有依赖闭包算出来。下面这个脚本以插件清单为入口,从 JSON 里递归读出每个插件的依赖项,最终输出插件名 版本 下载URL三列:

#!/usr/bin/env python3 import json import sys with open("update-center.json", "r", encoding="utf-8") as f: data = json.load(f) plugins = data["plugins"] base = "https://updates.jenkins.io/download/plugins/{name}/{version}/{name}.hpi" # 手工维护的入口清单,按 job 实际需要填写 wanted = [ "git", "workflow-aggregator", "pipeline-stage-view", "configuration-as-code", "credentials-binding", ] resolved = set() def resolve(name, version="*"): if name not in plugins: print(f"missing: {name}", file=sys.stderr) return item = plugins[name] ver = item.get("version") if (name, ver) in resolved: return resolved.add((name, ver)) url = item.get("url") or base.format(name=name, version=ver) print(f"{name} {ver} {url}") for dep in item.get("dependencies", []): if dep.get("optional", False): # 可选依赖默认不展开,需要哪个再单独加入 wanted continue resolve(dep["name"], dep.get("version", "*")) for plugin_name in wanted: resolve(plugin_name)

逻辑说明:脚本维护一个resolved集合来防止依赖成环,比如插件 A 依赖 B、B 依赖 A,没有这个集合脚本就会无限递归。optional字段标记为真的依赖跳过解析,只保留强制依赖,这是为了控制离线包规模,也符合多数内网场景不启用可选功能的需求。版本号优先取 update-center.json 里记录的版本,不取手工清单里的模糊版本,保证所有插件来自同一套兼容基准。

运行脚本得到plugin-list.txt后,再循环下载:

mkdir -p offline-plugins cd offline-plugins while read -r name ver url; do out="${name}-${ver}.hpi" if [ -f "$out" ]; then echo "skip $out" continue fi curl --fail -L -o "$out" "$url" || echo "download failed: $name" done < ../plugin-list.txt

参数说明:out变量同时带上插件名和版本号,避免同名不同版本的文件互相覆盖;-f前面文件已存在时跳过,适合中途断网后重新执行。

下载完成后做一次完整性检查,这是我最不建议跳过的步骤:

for f in *.hpi; do unzip -t "$f" > /dev/null || echo "corrupt: $f" done

unzip -t逐个测试压缩包完整性,> /dev/null抑制正常输出,只保留异常提示。注意 hpi 本质上就是 zip,unzip能识别;如果某台机器没有 unzip,用jar tf也一样。

4. 同步进内网后三种安装方式对比:UI 上传、CLI 批量、目录冷拷贝

4.1 图形界面上传:适合首次安装和单插件补装

把 offline-plugins 目录拷到内网机器后,最简单直接的安装路径是浏览器里的高级插件页面。入口路径是:

系统管理 -> 插件管理 -> 高级 -> 上传插件

这个上传入口每次只处理一个文件,页面会显示“已上传并安装”,但注意它默认是动态加载,也就是安装过程不触发整实例重启。上传十几个插件时,建议每传一个就刷新一下系统信息页面,确认插件出现在已安装列表里,再传下一个。如果一次传多个,加载顺序不受控,可能出现 A 已经装完 B 还没加载的中间态。

UI 上传适合的场景是:内网 Jenkins 已经跑起来,只缺一两个插件。要大规模初始化一套环境,这种方式效率太低,容易把人点到失去耐心。我自己一般只用它做临时补丁,不做批量初始化。

4.2 jenkins-cli 批量安装:把循环交给脚本

批量场景用命令行更合适。先在内网任一台能访问 Jenkins HTTP 端口的机器上拉取 jenkins-cli.jar:

curl --fail -L \ -o jenkins-cli.jar \ http://127.0.0.1:8080/jnlpJars/jenkins-cli.jar

然后写循环安装所有 hpi:

export ADMIN_USER=admin export ADMIN_TOKEN=$(cat /data/admin-token) cd /data/offline-plugins for f in *.hpi; do java -jar /data/jenkins-cli.jar \ -s http://127.0.0.1:8080/ \ -auth "${ADMIN_USER}:${ADMIN_TOKEN}" \ install-plugin "$f" -deploy done

install-plugin接受本地文件路径参数,这是离线安装的关键,它不需要联网去更新中心找版本;-deploy表示安装后立即尝试动态加载。循环里的每次调用都会建立一次独立的 CLI 连接,插件多时耗时会长一些,但好处是单插件安装失败不会中断整个循环。

这里有两个容易被坑的细节。第一,不要把密码直接写进命令行-auth admin:密码,Shell 的 history 会把明文密码记下来,内网机器也经不起这么裸奔。用环境变量传入-auth参数,至少历史记录里只有变量名。第二,-deploy动态加载偶尔会失败,尤其遇到插件间有静态字段初始化依赖时;失败后 Jenkins 日志里能看到堆栈,但 job 不一定会立刻报错。为了稳妥,批量安装完我还是会手动重启一次,不用脚本里的-restart参数去碰运气。

4.3 目录冷拷贝:最原始也最可靠的手工方式

第三种方式最直接:把.hpi文件拷到$JENKINS_HOME/plugins目录下,然后重启 Jenkins。虽然看着原始,但它是三种方式里最可控的,因为 Jenkins 启动时会扫描整个插件目录,把有效插件全部加载,加载失败会在启动日志里集中暴露。

cp -r /data/offline-plugins/*.hpi "$JENKINS_HOME/plugins/" chown -R jenkins:jenkins "$JENKINS_HOME/plugins"

注意两个细节:$JENKINS_HOME不是 jenkins.war 所在目录,而是JENKINS_HOME环境变量指向的目录,默认可能叫/var/lib/jenkins或者用户自定义的路径;chown修改属主是因为 Jenkins 进程通常以专用用户运行,插件目录属主错误时可能出现启动阶段文件不可读。

目录拷完后重启服务:

systemctl restart jenkins

重启判断标准不能只看端口起来没有,要看启动日志里出现Jenkins is fully up and running或类似输出。在插件数量较多时,这个时间点可能比端口探测晚半分钟到几分钟,别卡在端口检测上就误判成功。

三种方式总结成一句话:小规模补丁用 UI,批量初始化用 CLI,整套环境部署用目录冷拷贝。冷拷贝方式配合版本锁定的 update-center.json,是最适合批量交付的路径。

5. 常见问题排查:离线安装插件时我踩过的五个坑

5.1 上传后提示成功,但新建任务里找不到插件功能

现象:插件管理页显示“已安装”,新建任务时却没有对应的构建步骤,比如装的是 git 插件,源码管理区域看不到 Git 选项。

原因:上传接口默认动态加载,动态加载在大多数情况下会把插件激活,但遇到插件依赖了尚未加载的扩展点时,加载进程会静默失败,UI 上它仍然标记为已安装,实际功能没挂上。

解决:稳妥起见,上传完无论如何都重启一次 Jenkins,把动态加载的模糊性直接绕开。重启后去系统管理 -> 系统信息里搜插件名,确认Status不是Disabled再继续配任务。从那次插件列表标绿但构建不能用之后,我养成了“装完就重启”的习惯,再没在这个问题上耗过时间。

5.2 插件版本过新,启动报 NoSuchMethodError

现象:内网安装完成,构建到某个步骤时抛出java.lang.NoSuchMethodError,堆栈指向某个插件类方法,但插件本身明明装上了。

原因:插件从通用 update-center.json 拉到了最新版,而 2.346.1 核心没带那个方法。这个错误最容易出现在 git-client、workflow-cps 这类被大量插件依赖的底层包上,版本跳级时接口变动很频繁。

解决:不要从通用更新中心拉版本,必须用带2.346.1的版本化 update-center.json。排查现有环境时,unzip -p 对应插件.hpi META-INF/MANIFEST.MF | grep Jenkins-Version,把那个版本号和 2.346.1 对比,超出范围的插件全部替换成 JSON 里记录的版本。替换之后重启,跑同一条构建路径验证。

5.3 强制依赖缺失,SCM 配置页直接飘红

现象:git 插件装完,新建 Pipeline 任务并写checkout scm步骤时,页面提示找不到git-client插件。

原因:手工安装时只传了主插件,没处理 Manifest 里的Plugin-Dependencies字段。git 插件依赖的 git-client 没有一并进入插件目录,主插件虽然有加载,但功能分支一调用就中断。

解决:回到第 3 章的解析脚本,把依赖闭包完整展开后重新下载,再通过目录冷拷贝整体安装。不要在已经漂红的 Jenkins 上单独传依赖插件,那样依赖顺序又会变成手工人肉控制,无法保证下一轮不出问题。

5.4 直接拷整个插件目录,旧版本残留导致启动变慢或加载中断

现象:从别人那边拿到一份plugins目录,直接替换自己的环境,结果 Jenkins 启动日志刷大量Failed to load plugin,启动时间翻了数倍。

原因:插件目录里混有多个同名不同版本的 hpi、.jpi临时文件,甚至.bak备份文件。Jenkins 启动时会对目录内所有插件做依赖解析和版本仲裁,重复版本直接导致加载顺序混乱,部分插件被标记为failed。

解决:永远不要整目录替换。先清空插件目录,再把离线仓库里的 hpi 文件全量拷入,排除任何非 hpi 文件。命令层面可以用find "$JENKINS_HOME/plugins" -type f ! -name "*.hpi" -delete清掉干扰文件,再做一次重启验证。

5.5 内网插件管理页显示连接失败,误以为离线装不了

现象:进入“插件管理”页,可选插件列表是空的,页面顶部红色提示连接更新中心失败。很多人到这步就放弃,认为内网没法装插件。

原因:内网环境无法访问外网更新中心,报错来自 HTTP 握手超时,这和本地插件安装能力完全是两回事。它只影响在线搜索和在线安装,不影响你已经拷贝到服务器上的 hpi 文件。

解决:把更新中心报错当成噪音忽略,直接走第 4 章的三种安装方式。如果实在想让页面“显得正常”,可以在内网架一个静态文件服务器,把 update-center.json 和插件文件按路径结构放上去,再修改系统管理 -> 插件管理 -> 高级 -> 更新站点的 URL,但这属于锦上添花,对核心功能无实际帮助,优先保证 hpi 落盘加载才要紧。

6. 装完不等于交付:离线插件自检三件套

6.1 先跑一次批量完整性校验再接入任务

离线部署收尾前,我会强制在插件目录做一次全量自检。脚本很简单,但能兜住镜像下载解压异常的低级问题:

cd "$JENKINS_HOME/plugins" for f in *.hpi; do unzip -t "$f" > /dev/null || echo "corrupt: $f" unzip -p "$f" META-INF/MANIFEST.MF | grep -E "Plugin-Dependencies" || true done

第一段unzip -t检测压缩包完整性,第二段把依赖字段打印出来,方便和插件管理页面的状态做人工比对。这一步能过滤掉下载一半被中断、磁盘缓存导致文件截断这类问题,不然等构建跑起来才爆错误,返工成本高得多。

6.2 用 Groovy 脚本核对已加载插件版本

装完浏览器上看列表还不够,我用 jenkins-cli 直接拉运行期插件状态:

java -jar /data/jenkins-cli.jar \ -s http://127.0.0.1:8080/ \ -auth "${ADMIN_USER}:${ADMIN_TOKEN}" \ groovy = <<'EOF' def pm = Jenkins.instance.pluginManager pm.plugins.findAll { it.isActive() }.each { println "${it.shortName} ${it.version} active" } pm.plugins.findAll { !it.isActive() }.each { println "${it.shortName} ${it.version} INACTIVE" } EOF

isActive()才是真正的运行期状态,比页面显示的“已安装”更可靠。输出结果里如果出现任何INACTIVE,说明这个插件没有被加载,直接回到第 5 章排查流程。这个方法也适合作后续维护时的状态基线,备份一份输出,每次变更后重新跑一遍对比差异。

6.3 用真实 job 干跑一次,再交付给别人

插件列表验证完,最后一步是建一个最小 job 实际跑:源码管理选 Git,填一个内网仓库地址,构建步骤写简单的echo done。这一个动作能把凭证管理、SCM 插件、构建日志输出串起来跑一遍,比任何静态检查都有说服力。

从某次交付时插件清单看着正常、实际构建却卡在凭证解析的小时起,我每次离线部署都强制走完这三件套,确认无误后再把$JENKINS_HOME/plugins和启动日志一并交付。希望帮你少走这段弯路。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询