
简介本资源面向在内网、隔离网等无外网环境中部署Jenkins的运维与DevOps工程师针对Jenkins 2.346.1无法在线拉取插件的问题提供一套完整的离线插件安装方案。压缩包共约2000个文件整体314.4MB涵盖90个jpi与30个hpi插件包、366个jar依赖、192个xml配置及大量js、html、css等前端资源并附带properties、timestamp等元数据文件构成可直接落地的离线插件仓库。资源围绕离线下载插件、创建离线插件包、配置初始化脚本、启动验证、后续维护更新与安全权限控制等环节展开帮助读者打通内网Jenkins插件安装全流程。目前已有3171人学习下载适合需要快速搭建内网CI/CD环境、排查插件加载失败问题的技术人员参考可有效减少重复试错成本。1. 内网 Jenkins 离线装插件为什么 2.346.1 的插件包不能随便下很多团队的内网构建机跑着 Jenkins 2.346.1版本不算新但胜在稳定谁也不想动它。问题出在装插件上外网能访问的更新中心内网机器一个字节都拉不到点「安装」就是转圈然后报错。有人图省事从别的机器把.hpi拷过来直接丢进plugins目录重启后插件列表里是有了但构建任务一跑就抛NoSuchMethodError查半天才发现是插件版本和 Jenkins 核心对不上。这个场景要解决的核心问题就一句话在没有外网的内网环境里给 Jenkins 2.346.1 装上一批能正常工作的插件。适合两类人一是负责内网 CI 环境维护、被离线安装折腾过的运维二是需要在隔离网络里搭一套完整构建流水线的开发。下面按「先搞懂依赖关系再动手下载最后排错」的顺序拆开讲每一步都能直接抄。2. 离线安装的底层逻辑插件依赖树与版本匹配2.1 为什么不能只下单个 .hpi 文件Jenkins 插件不是孤立运行的。一个插件会声明它依赖哪些其他插件、依赖的最低版本是多少这些信息写在插件元数据里。你只下git.hpi它可能依赖git-client、credentials、structs等一串插件缺一个就装不上或者装上了运行时报错。常见做法是先确定你要装的目标插件然后递归解析它的依赖树把所有依赖插件连同目标插件一起下载。手动做这件事很容易漏所以一般用工具自动解析。版本匹配是第二个关键点。Jenkins 2.346.1 对应的插件更新中心有它自己的版本范围同一个插件在 2.346.1 和 2.4xx 上能用的版本可能不同。判断依据是插件页面上的「Required Core」字段它标明了这个插件版本要求的最低 Jenkins 核心版本。你选的插件版本Required Core 必须小于等于 2.346.1。2.2 依赖解析的两种可行路径路径一用官方更新中心的数据做离线解析。Jenkins 更新中心有一个update-center.json里面记录了每个插件的所有版本、依赖关系和 Required Core。你可以在外网机器上拉这个文件用脚本解析出目标插件的完整依赖树再按版本约束筛选出适配 2.346.1 的版本组合。路径二用现成的离线下载工具。社区里有针对 Jenkins 的插件下载脚本输入插件名和 Jenkins 版本输出一个包含所有依赖的目录。这类工具的原理和路径一一样只是把解析逻辑封装好了。我一般会走路径一因为可控。下面给一个用 Python 解析依赖树的骨架代码核心是从update-center.json里递归找依赖。import json import requests # 从外网机器拉取更新中心数据内网机器提前拷贝好这个文件 # 注意不同 Jenkins 版本的更新中心 URL 不同2.346.1 对应的是稳定版更新中心 UPDATE_CENTER_URL https://updates.jenkins.io/stable/update-center.json def load_update_center(local_pathNone): 加载更新中心数据支持本地文件或在线拉取 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 回调需要剥掉 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): 递归解析插件依赖返回 {插件名: 版本} 的字典 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} 不在更新中心数据里) info plugins[plugin_name] # 筛选出 Required Core 当前 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} 没有适配 {jenkins_version} 的版本) # 取满足条件的最高版本 candidates.sort(keylambda x: x[0], reverseTrue) chosen_ver, chosen_meta candidates[0] resolved[plugin_name] chosen_ver # 递归处理依赖 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): 简单的版本号比较返回 -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这段代码的逻辑说明load_update_center负责把更新中心数据读进来注意它外层有 JSONP 包装直接json.loads会失败必须先剥壳。resolve_deps是核心递归函数它先按requiredCore过滤出适配当前 Jenkins 版本的插件版本取最高版本后再遍历这个版本的dependencies字段继续递归。compare_version是个简化版比较函数实际用的时候建议换成packaging.version库避免遇到1.10和1.9比较出错。参数方面jenkins_version传2.346.1plugin_name传你要装的目标插件名比如git。跑完之后resolved字典里就是完整的插件名和版本列表。2.3 下载环节的目录组织解析出依赖列表后下载环节要按固定结构组织文件方便后续批量安装。我一般用这样的目录offline-plugins/ ├── download_list.txt # 插件名:版本 列表 ├── hpi/ # 所有 .hpi 文件 │ ├── git-4.11.3.hpi │ ├── git-client-3.11.0.hpi │ └── ... └── install.sh # 批量安装脚本下载 URL 的规律是https://updates.jenkins.io/download/plugins/{插件名}/{版本}/{插件名}.hpi。注意文件名里不带版本号版本号在路径里。下载脚本按这个规律拼 URL 即可。3. 内网落地实操从外网下载到 Jenkins 加载3.1 外网机器上的下载与打包在外网机器上先跑上面的解析脚本拿到依赖列表然后批量下载。下面是一个下载脚本读download_list.txt逐行下载。#!/bin/bash # 在外网机器上执行下载所有插件到 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 # 跳过空行和注释 [[ -z $name || $name \#* ]] continue url${BASE_URL}/${name}/${version}/${name}.hpi out${DOWNLOAD_DIR}/${name}-${version}.hpi if [[ -f $out ]]; then echo 已存在跳过: $out continue fi echo 下载: $url curl -fSL -o $out $url done $LIST_FILE echo 下载完成共 $(ls -1 $DOWNLOAD_DIR | wc -l) 个文件逻辑说明IFS:按冒号分割每行因为download_list.txt的格式是插件名:版本。curl -fSL里-f让 HTTP 错误码直接失败而不是写入错误页面-L跟随重定向-S显示错误。下载完成后检查文件数量是否和列表行数一致不一致说明有下载失败。打包时把hpi目录和install.sh一起打成 tar 包通过内网允许的方式传到目标机器。注意不要用zip因为 Jenkins 机器上不一定有解压工具tar更通用。3.2 内网机器上的批量安装传到内网后有两种安装方式。方式一是直接拷贝到JENKINS_HOME/plugins目录然后重启。方式二是用 Jenkins CLI 的install-plugin命令。方式一更直接但要注意权限和文件属主。#!/bin/bash # 在内网 Jenkins 机器上执行 set -e JENKINS_HOME/var/lib/jenkins # 按实际路径改 HPI_DIR./hpi # 备份现有插件目录出问题能回滚 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 # 删除插件解压缓存强制重新解压 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 插件拷贝完成请重启 Jenkins逻辑说明先备份是血泪经验插件装崩了能直接回滚。chown必须做否则 Jenkins 进程读不到文件。删除解压缓存这一步很多人会漏Jenkins 加载插件时如果发现同名目录已存在可能不会重新解压新的.hpi导致你以为装了新版本实际还是旧的。重启命令按你的部署方式选systemctl restart jenkins或service jenkins restart。重启后看日志确认加载情况。3.3 验证插件是否真正生效重启不等于装好。验证分三步第一步看 Jenkins 启动日志里有没有插件加载失败的报错第二步进「系统管理 → 插件管理 → 已安装」看插件是否在列表里且版本正确第三步实际跑一个用到该插件的构建任务。# 查看启动日志中的插件加载错误 grep -i failed to load\|SEVERE.*plugin\|Plugin.*failed /var/log/jenkins/jenkins.log | tail -50 # 查看已加载插件列表通过 Jenkins CLI java -jar jenkins-cli.jar -s http://localhost:8080/ -auth admin:token list-plugins | grep -i git日志里如果出现Failed to load: xxx (xxx)后面跟NoClassDefFoundError或NoSuchMethodError基本就是版本不匹配。list-plugins命令需要 Jenkins CLI 的 jar 包和认证信息认证用 API Token 比密码安全。4. 避坑与排查离线装插件最容易翻车的五个点4.1 现象插件列表里有了但构建时报 NoSuchMethodError原因插件版本和 Jenkins 核心版本不匹配。你下载的插件版本 Required Core 高于 2.346.1Jenkins 加载时没拦住但运行时调用了核心不存在的方法。解决回到更新中心数据重新按requiredCore 2.346.1筛选版本。不要只看插件的最新版最新版往往要求更高的核心版本。4.2 现象重启后插件消失或者版本没变原因Jenkins 的插件加载机制会优先读解压后的目录如果同名目录已存在它不会重新解压新的.hpi。你拷贝了新文件但旧目录还在加载的还是旧版本。解决拷贝新.hpi后删除对应的解压目录再重启。上面的安装脚本里已经包含这一步。更稳妥的做法是停掉 Jenkins 再操作避免运行中文件被占用。4.3 现象插件装上了但功能不可用比如 Git 插件找不到 git 命令原因插件本身是 Jenkins 侧的封装它依赖系统里安装的 git 可执行文件。离线环境里 Jenkins 机器可能没装 git或者 git 路径不在 Jenkins 进程的 PATH 里。解决在 Jenkins 的「系统管理 → 全局工具配置」里显式指定 git 路径不要依赖 PATH。同时确认jenkins用户能执行git --version。4.4 现象依赖插件缺失安装时报「依赖未满足」原因解析依赖树时漏了传递依赖或者某个依赖插件的版本被其他插件约束到了不兼容的范围。解决用resolve_deps跑完整依赖树不要手动挑。如果两个插件对同一个依赖的版本要求冲突优先满足 Required Core 更高的那个插件的约束然后验证另一个插件是否还能工作。4.5 现象拷贝文件后 Jenkins 启动失败日志报权限错误原因cp操作是以当前用户执行的文件属主不是jenkinsJenkins 进程读不到。解决chown -R jenkins:jenkins整个 plugins 目录。另外注意JENKINS_HOME的父目录权限如果 Jenkins 用户对父目录没有执行权限一样读不到。提示每次批量装插件前先备份plugins目录和config.xml。回滚成本远低于排查成本。5. 进阶技巧用脚本做版本校验和增量更新装完一批插件后后续维护的痛点在于怎么知道哪些插件有新版本、新版本是否适配当前 Jenkins。我一般会写一个校验脚本把内网已装插件列表和外网更新中心数据做比对输出可升级列表和对应的 Required Core。import json def check_updates(uc_data, installed, jenkins_version): 比对已装插件和更新中心输出可升级且适配的插件 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 输出解析得到 # 输出格式[(插件名, 当前版本, 可升级版本), ...]这个脚本的价值在于它不会推荐 Required Core 超过当前 Jenkins 版本的升级避免你升完发现 Jenkins 起不来。installed字典可以从JENKINS_HOME/plugins目录下的.hpi文件名解析也可以用list-plugins命令的输出。增量更新的操作流程是跑校验脚本拿到可升级列表只下载这些插件的.hpi传到内网后按第 3 章的安装脚本替换。注意替换前仍然要备份并且删除对应的解压目录。还有一个容易被忽略的点Jenkins 的插件更新中心数据本身会变同一个插件版本在不同时间拉到的update-center.json可能不同。所以在外网下载时建议把update-center.json一起打包传到内网后续排查版本问题时能对照当时的数据。从那以后我每次做离线插件安装都强制走一遍「解析依赖 → 下载 → 备份 → 拷贝 → 删缓存 → 重启 → 验证」这七步少一步都可能翻车。希望帮到你。本文还有配套的精品资源点击获取