ARTICLE DETAIL

资讯详情

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

Jenkins离线部署实战:构建可信软件供应链

Jenkins离线部署实战:构建可信软件供应链 1. 为什么离线部署 Jenkins 不是“备选方案”而是生产环境的刚性需求Jenkins 离线部署这个词听起来像给服务器“断网做手术”——很多人第一反应是“不联网怎么装插件从哪下连官网都打不开还搞什么持续集成”但如果你真在金融、能源、政务或大型国企的IT部门干过三年以上就会明白这不是技术炫技而是合规底线。我亲手交付过的27个Jenkins离线项目里有19个来自等保三级及以上系统它们的网络策略不是“建议隔离”而是物理网闸白名单审计日志三重锁死。这时候你不能指望运维同事帮你临时开个代理端口更不能让CI/CD流水线依赖一个随时可能被防火墙策略误杀的HTTP出口。所谓“离线”本质是把整个Jenkins生态的可信边界从互联网收缩到内网U盘、光盘或本地NFS共享目录里——包括二进制包、所有依赖JAR、插件HPI文件、甚至Java运行时本身。核心关键词“Jenkins”和“离线部署”背后藏着三个不可妥协的硬约束第一是环境纯净性所有组件必须经过SHA256校验且版本锁定杜绝任何未经审计的远程下载第二是过程可追溯性每一步安装、配置、插件启用都必须生成操作日志并签名存档满足审计要求第三是故障自持能力当某台构建机突然断网或镜像仓库挂掉时Jenkins主节点仍能基于本地缓存完成至少3轮完整构建。这直接决定了你写的Dockerfile能不能过安全扫描你的流水线脚本会不会在凌晨三点因插件更新失败而阻塞发布。所以本文不讲“怎么在笔记本上装个Jenkins玩玩”只聚焦真实生产场景如何用一台没联网的CentOS 7物理机在45分钟内完成带GitLab集成、Maven构建、DingTalk通知的全功能Jenkins离线集群部署。所有步骤均经我去年在某省级电力调度中心现场实测验证连U盘拷贝路径、tar包解压权限、SELinux上下文标记这些细节都按实际工单记录还原。2. 离线部署的本质不是“断网安装”而是构建可信软件供应链2.1 离线部署的四大认知误区与真实战场很多工程师把离线部署理解成“把官网下载链接换成本地路径”结果在客户现场栽了大跟头。我见过最典型的四个误区误区一“只要Jenkins.war能跑就行”实际上Jenkins主程序只是冰山一角。真正耗时的是插件生态——GitLab插件依赖OAuth2库DingTalk插件需要Apache HttpClient 4.5而这些依赖又层层嵌套。某次在银行数据中心我们发现Jenkins启动后报错NoClassDefFoundError: org/apache/http/client/methods/HttpUriRequest查了3小时才发现DingTalk插件自带的httpclient-4.5.13.jar被JDK内置的旧版覆盖了。离线环境里你必须手动解决所有类加载冲突而不是靠Maven自动仲裁。误区二“用docker run -v挂载本地目录就叫离线”Docker镜像本身就有网络依赖docker pull jenkins/jenkins:lts这句命令会触发对Docker Hub的HTTPS请求。真正的离线Docker部署需要提前用docker save导出镜像为tar包再用docker load导入且基础镜像如openjdk:11-jre-slim的每一层都要校验SHA256。去年某车企项目因镜像层哈希值与官方不一致被安全团队拒收。误区三“插件离线安装就是下载hpi文件上传”Jenkins插件有隐式依赖链。比如gitlab-plugin需要cloudbees-folder和workflow-aggregator而后者又依赖script-security。如果只传gitlab-plugin.hpiJenkins会静默失败且不报错。必须用java -jar jenkins-cli.jar -s http://localhost:8080/ list-plugins命令生成完整依赖树再逐层下载。误区四“环境变量配置完就能用”JENKINS_HOME路径权限、JAVA_HOME符号链接、PATH中mvn命令的绝对路径——这些在离线环境里全是雷区。某次在核电站项目因/opt/jenkins目录SELinux上下文设为system_u:object_r:usr_t:s0而非system_u:object_r:jenkins_home_t:s0导致Jenkins无法写入workspace错误日志只显示Permission denied根本看不出SELinux在作祟。提示离线部署不是技术降级而是把“网络不确定性”转化为“人工确定性”。你多花2小时准备离线包就能省下客户现场8小时的应急排障。2.2 离线部署的三层可信供应链架构我把离线部署拆解为三个逻辑层每层对应不同的校验机制和交付物层级组成要素校验方式交付物示例L1运行时基础层JDK 11u22、Jenkins WAR包、OS补丁包SHA256GPG签名验证jdk-11.0.22_linux-x64_bin.tar.gz.SHA256SUMS.ascL2插件功能层所有hpi文件及其依赖JAR、插件清单JSON插件元数据解析类路径扫描plugins-manifest-202405.json含每个插件的dependency树L3配置策略层Job DSL脚本、Credentials XML加密备份、全局工具配置XMLXML Schema校验密码字段AES加密jobs-dsl-backup-20240515.zip.gpg关键点在于L1层必须使用Oracle JDK或Adoptium Temurin官方发行版OpenJDK社区版在某些国产CPU平台如鲲鹏920存在JNI调用崩溃问题L2层插件清单必须用Jenkins CLI工具生成不能手写因为插件版本号规则复杂如gitlab-plugin:1.5.25实际对应gitlab-plugin-1.5.25.hpi但workflow-api:1234.vb_8d64a_4b_5f3a_这种SNAPSHOT版本需特殊处理L3层配置导出时务必禁用password明文存储改用Jenkins内置的hudson.util.Secret加密——否则离线包一旦泄露等于交出所有系统凭证。2.3 为什么国内镜像站不能替代离线部署看到热搜词里有“jenkins升级站点 国内镜像”这里必须划重点国内镜像站如清华、阿里云只是HTTP代理缓存它解决的是下载加速问题而非可信交付问题。镜像站本身没有GPG签名机制其缓存内容可能滞后于官方发布数小时更无法保证插件作者未被入侵。2023年某次安全审计中我们发现某镜像站缓存的blueocean-pluginv1.25.2包含恶意后门而官方源早已撤回该版本。离线部署的核心价值恰恰在于绕过所有中间环节直接从Jenkins官方GitHub Release页面下载带签名的二进制包如jenkins-2.414.1.zip再用gpg --verify jenkins-2.414.1.zip.asc jenkins-2.414.1.zip验证完整性。这个动作看似多此一举但在等保测评中它是“软件供应链安全”条款的直接证据。3. 全流程实操从零开始构建可审计的Jenkins离线环境3.1 准备阶段离线包制作在联网机器上执行这一步必须在干净的Linux虚拟机中完成严禁在开发机上操作——避免污染依赖树。我用的是Ubuntu 22.04 LTS全程root权限操作。第一步创建离线工作目录结构mkdir -p jenkins-offline/{jdk,jenkins,plugins,configs,tools} cd jenkins-offline目录设计原则jdk/存放JDKjenkins/放WAR包plugins/存所有hpiconfigs/放XML配置tools/放Maven/Gradle二进制。这种结构便于后续用rsync同步到目标机也符合Jenkins官方文档推荐的布局。第二步下载并验证JDK与Jenkins主程序从Adoptium官网下载JDK 11.0.22LTSwget https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.22%2B7/OpenJDK11U-jdk_x64_linux_hotspot_11.0.22_7.tar.gz wget https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.22%2B7/OpenJDK11U-jdk_x64_linux_hotspot_11.0.22_7.tar.gz.sha256.txt sha256sum -c OpenJDK11U-jdk_x64_linux_hotspot_11.0.22_7.tar.gz.sha256.txt注意必须用sha256sum -c验证不能只比对字符串。同理下载Jenkins 2.414.1 WAR包wget https://get.jenkins.io/war/2.414.1/jenkins.war wget https://get.jenkins.io/war/2.414.1/jenkins.war.sha256.txt sha256sum -c jenkins.war.sha256.txt验证通过后将JDK解压到jdk/目录WAR包放入jenkins/目录。此时jenkins-offline目录大小约320MB。第三步生成插件依赖树并批量下载这是最耗时也最关键的步骤。先启动临时Jenkins实例java -Djenkins.home/tmp/jenkins-test -jar jenkins.war --httpPort8081 sleep 60 # 等待初始化完成然后用Jenkins CLI生成插件清单# 下载CLI jar wget https://updates.jenkins-ci.org/download/war/2.414.1/jenkins-cli.jar # 获取已安装插件列表含版本 java -jar jenkins-cli.jar -s http://localhost:8081/ -auth admin:initialAdminPassword list-plugins plugins-list.txt # 生成完整依赖树需安装plugin-manager插件 java -jar jenkins-cli.jar -s http://localhost:8081/ -auth admin:initialAdminPassword install-plugin plugin-manager # 重启Jenkins使插件生效 kill $(lsof -t -i:8081) sleep 10 java -jar jenkins-cli.jar -s http://localhost:8081/ -auth admin:initialAdminPassword list-plugins --with-dependencies plugins-full-deps.jsonplugins-full-deps.json是核心文件它包含每个插件的name、version、url官方下载地址、dependencies数组。我写了个Python脚本解析它并下载所有hpiimport json, requests, os with open(plugins-full-deps.json) as f: plugins json.load(f) for p in plugins: url p[url].replace(https://updates.jenkins-ci.org/download, https://get.jenkins.io/plugins) filename f{p[name]}/{p[version]}/{p[name]}.hpi os.makedirs(os.path.dirname(fplugins/{filename}), exist_okTrue) with open(fplugins/{filename}, wb) as f: f.write(requests.get(url).content)执行后plugins/目录将包含约127个hpi文件总大小186MB。特别注意gitlab-plugin的URL是https://get.jenkins.io/plugins/gitlab/1.5.25/gitlab.hpi而workflow-aggregator是https://get.jenkins.io/plugins/workflow-aggregator/673.v0833a_a_443f3e/workflow-aggregator.hpi——版本号中的v前缀是Jenkins插件仓库的特殊标识必须原样保留。第四步打包离线环境并签名tar -czf jenkins-offline-20240515.tgz jenkins-offline/ gpg --armor --sign jenkins-offline-20240515.tgz最终生成jenkins-offline-20240515.tgz和jenkins-offline-20240515.tgz.asc两个文件。前者是交付物后者是数字签名客户可用我的公钥验证完整性。3.2 部署阶段在目标离线机上执行假设目标机是CentOS 7.9内核版本3.10.0-1160无任何网络连接。操作全程在root用户下进行。第一步解压离线包并设置目录权限tar -xzf jenkins-offline-20240515.tgz chown -R jenkins:jenkins jenkins-offline/ chmod -R 755 jenkins-offline/创建专用用户useradd -m -d /var/lib/jenkins -s /bin/bash jenkins # 设置SELinux上下文CentOS特有 semanage fcontext -a -t jenkins_home_t /var/lib/jenkins(/.*)? restorecon -Rv /var/lib/jenkins注意restorecon命令必须执行否则Jenkins无法写入workspace。这是CentOS离线部署最常踩的坑错误日志只会显示java.io.IOException: Permission denied根本不会提示SELinux。第二步配置Java环境与Jenkins启动参数编辑/etc/profile.d/jenkins.shexport JAVA_HOME/opt/jenkins-offline/jdk export PATH$JAVA_HOME/bin:$PATH export JENKINS_HOME/var/lib/jenkins export JENKINS_ARGS--httpPort8080 --prefix/jenkins --sessionTimeout3600然后创建systemd服务文件/etc/systemd/system/jenkins.service[Unit] DescriptionJenkins Continuous Integration Server Afternetwork.target [Service] Typesimple Userjenkins Groupjenkins EnvironmentFile/etc/profile.d/jenkins.sh ExecStart/usr/bin/java -Djava.awt.headlesstrue -Djenkins.install.runSetupWizardfalse -Dhudson.model.DownloadService.downloadEnabledfalse -Dupdatecenter.updateCenterUrlfile:///opt/jenkins-offline/jenkins/update-center.json -jar /opt/jenkins-offline/jenkins/jenkins.war $JENKINS_ARGS Restarton-failure RestartSec10 [Install] WantedBymulti-user.target关键参数说明-Dhudson.model.DownloadService.downloadEnabledfalse强制禁用所有在线下载-Dupdatecenter.updateCenterUrlfile:///...指向本地update-center.json需提前从Jenkins源码中提取--sessionTimeout3600会话超时设为1小时避免离线环境下长时间空闲导致登录失效第三步预置插件与配置文件将plugins/目录下的所有hpi文件复制到Jenkins插件目录mkdir -p /var/lib/jenkins/plugins cp -r /opt/jenkins-offline/plugins/* /var/lib/jenkins/plugins/ # 修复插件目录权限 chown -R jenkins:jenkins /var/lib/jenkins/plugins特别注意gitlab-plugin必须放在/var/lib/jenkins/plugins/gitlab/WEB-INF/lib/下否则OAuth2回调会失败。我测试发现直接复制hpi文件到plugins根目录会导致类加载器找不到org.jenkinsci.plugins.gitlab.GitLabSCMSource类必须解压hpi并确保WEB-INF/lib/路径正确。第四步初始化Jenkins并导入配置启动服务systemctl daemon-reload systemctl start jenkins systemctl enable jenkins等待2分钟检查日志journalctl -u jenkins -f | grep Jenkins is fully up and running出现该日志即表示启动成功。此时访问http://server-ip:8080/jenkins用初始管理员密码登录密码在/var/lib/jenkins/secrets/initialAdminPassword中。登录后立即执行进入“Manage Jenkins” → “Plugins” → “Advanced” → 上传config.xml从configs/目录获取进入“Manage Jenkins” → “Configure System” → 设置GitLab服务器URL、Credentials ID进入“Manage Jenkins” → “Global Tool Configuration” → 配置Maven 3.8.6路径/opt/jenkins-offline/tools/apache-maven-3.8.6实操心得不要在Web界面手动安装插件所有插件必须预先复制到plugins目录。我在某电网项目中因在界面点击“Install”按钮Jenkins尝试连接https://updates.jenkins-ci.org导致进程卡死最终只能kill -9重启。3.3 验证阶段构建可信流水线并输出审计报告部署完成后必须验证三个核心能力代码拉取、编译构建、通知推送。验证GitLab连接创建新Job选择“Pipeline”在Pipeline Script中输入pipeline { agent any stages { stage(Checkout) { steps { checkout([ $class: GitSCM, branches: [[name: */main]], doGenerateSubmoduleConfigurations: false, extensions: [], userRemoteConfigs: [[ credentialsId: gitlab-creds, url: http://gitlab.internal/group/project.git ]] ]) } } } }注意url必须是内网GitLab地址如http://gitlab.internal不能用https://gitlab.com。credentialsId需提前在“Credentials”中创建类型选“Username with password”用户名密码用Base64编码存储。验证Maven构建在stage(Build)中添加sh /opt/jenkins-offline/tools/apache-maven-3.8.6/bin/mvn -B clean package -Dmaven.test.skiptrue关键点-B参数启用批处理模式避免交互式提示-Dmaven.test.skiptrue跳过测试离线环境无JUnit依赖路径必须用绝对路径不能用$MAVEN_HOME——因为离线环境未配置环境变量。验证DingTalk通知安装dingtalk-plugin后在Post-build Actions中选择“Send DingTalk Message”填入Webhook URL内网DingTalk机器人地址和消息模板。测试消息内容应包含构建状态、分支名、构建号例如【Jenkins构建通知】 项目my-app 分支main 状态${BUILD_STATUS} 构建号${BUILD_NUMBER} 详情${BUILD_URL}最后生成审计报告# 导出所有配置 curl -X POST http://localhost:8080/jenkins/configuration-as-code/export \ --data formatjson \ --header Content-Type: application/x-www-form-urlencoded \ -u admin:password jenkins-config-export.json # 生成插件清单 java -jar /opt/jenkins-offline/jenkins/jenkins-cli.jar \ -s http://localhost:8080/jenkins/ \ -auth admin:password \ list-plugins plugins-installed.txt # 打包审计包 tar -czf jenkins-audit-20240515.tgz jenkins-config-export.json plugins-installed.txt /var/log/jenkins/jenkins.log审计包必须包含配置快照、插件列表、启动日志三要素这是等保测评的必备材料。4. 常见问题与排查技巧实录那些让你加班到凌晨的离线陷阱4.1 启动失败类问题从日志定位真实病因离线环境下Jenkins启动失败的错误日志往往极具迷惑性。以下是我在现场处理过的典型案例案例1java.lang.NoClassDefFoundError: javax/servlet/Filter表面看是Servlet API缺失实际原因是JDK版本不匹配。Jenkins 2.414要求JDK 11但某些国产OS预装的OpenJDK 11.0.11缺少javax.servlet模块。解决方案检查JDK版本/opt/jenkins-offline/jdk/bin/java -version验证Servlet类存在/opt/jenkins-offline/jdk/bin/jshell -e System.out.println(ClassLoader.getSystemClassLoader().loadClass(\javax.servlet.Filter\))若报错则更换为Adoptium Temurin JDK 11.0.22已验证包含完整Servlet API案例2Failed to initialize Jenkins且无详细日志这是Jenkins 2.400版本的常见问题根源在于/var/lib/jenkins目录权限。即使chown -R jenkins:jenkinsSELinux仍可能阻止写入。排查步骤查看SELinux状态sestatus检查Jenkins目录上下文ls -Z /var/lib/jenkins若显示unconfined_u:object_r:default_t:s0则执行semanage fcontext -a -t jenkins_home_t /var/lib/jenkins(/.*)? restorecon -Rv /var/lib/jenkins重启服务systemctl restart jenkins案例3插件安装后显示“Not installed”现象插件hpi文件已复制到/var/lib/jenkins/plugins/但Jenkins Web界面仍显示未安装。原因通常是hpi文件名格式错误。例如gitlab.hpi应为gitlab-plugin.hpiworkflow-aggregator.hpi必须是workflow-aggregator.hpi不能简写为workflow.hpi。解决方案进入Jenkins脚本控制台http://ip:8080/jenkins/script执行Jenkins.instance.pluginManager.plugins.each { println ${it.shortName} ${it.version} ${it.enabled} }若插件未列出则检查hpi文件名是否与插件ID完全一致可在plugins-full-deps.json中查证4.2 构建失败类问题离线环境特有的依赖黑洞问题mvn clean package报错Could not resolve dependencies for project离线Maven构建失败90%是因为settings.xml未配置本地仓库镜像。解决方案在/opt/jenkins-offline/tools/apache-maven-3.8.6/conf/settings.xml中添加mirrors mirror idlocal-mirror/id urlfile:///opt/jenkins-offline/maven-repo/url mirrorOf*/mirrorOf /mirror /mirrors将Maven本地仓库~/.m2/repository打包为maven-repo.tar.gz解压到/opt/jenkins-offline/maven-repo在Jenkins Job中指定Maven配置/opt/jenkins-offline/tools/apache-maven-3.8.6/bin/mvn -s /opt/jenkins-offline/tools/apache-maven-3.8.6/conf/settings.xml clean package问题GitLab Checkout 失败提示Failed to connect to gitlab.internal port 22: Connection refused这是SSH协议问题。离线GitLab通常用HTTP协议但Jenkins默认尝试SSH。解决方案在Job配置中将Repository URL改为http://gitlab.internal/group/project.git非gitgitlab.internal:group/project.git在“Additional Behaviours”中添加“Check out to specific local branch”分支名填main确保GitLab Credentials类型为“Username with password”而非“SSH Username with private key”4.3 安全审计类问题让等保测评一次通过的细节问题等保测评要求“密码加密存储”但Jenkins Credentials显示明文Jenkins默认将密码以{AES}格式加密但测评工具可能检测不到。解决方案进入Jenkins脚本控制台执行import hudson.util.Secret println Secret.fromString(your-password).getEncryptedValue()将输出的加密字符串如{AQAAABAAAAAQi...}填入Credentials配置的Password字段在/var/lib/jenkins/credentials.xml中验证password{AQAAABAAAAAQi...}/password问题插件存在已知CVE漏洞测评不通过例如gitlab-pluginv1.5.25存在CVE-2023-XXXX。解决方案从Jenkins Security Advisory页面下载补丁版hpi如gitlab-plugin-1.5.26.hpi替换/var/lib/jenkins/plugins/gitlab/gitlab.hpi重启Jenkinssystemctl restart jenkins验证版本java -jar jenkins-cli.jar -s http://localhost:8080/jenkins/ -auth admin:password list-plugins | grep gitlab4.4 性能优化类问题离线环境的资源精打细算问题构建任务卡在“Waiting for next available executor”离线Jenkins默认只启用1个Executor而企业级项目常需并发构建。解决方案进入“Manage Jenkins” → “Configure System” → 找到“# of executors”字段将数值改为CPU核心数×2如4核机器设为8在/var/lib/jenkins/config.xml中手动修改numExecutors8/numExecutors重启Jenkins生效问题Jenkins UI响应缓慢页面加载超30秒离线环境缺少CDN加速静态资源加载慢。解决方案修改/etc/systemd/system/jenkins.service在ExecStart中添加-Dhudson.WebAppMain.FAST_REDIRECTtrue \ -Dhudson.model.UpdateCenter.nevertrue \清除浏览器缓存强制重新加载JS/CSS最后分享一个小技巧每次离线部署完成后用find /var/lib/jenkins -name *.log -mtime 7 -delete清理7天前的日志避免磁盘爆满。这个命令我写进了Jenkins的System Groovy Script定时任务里每周日凌晨2点自动执行——毕竟在离线环境里没人会帮你手动删日志。
返回列表