☰
内网环境Jenkins离线搭建与自动化部署实践指南
2026/10/14 17:52:12 网站建设 项目流程

1. 内网环境的“家底盘点”:先搞清你手上到底有什么

前几天某团队找我帮忙搭一套内网CI/CD环境,一上来就说“我们有一个Jenkins,但是不会用,也没法下插件,你帮整一下”。我一问细节,发现那台所谓“Jenkins服务器”上面连JDK都没装,只有个裸奔的CentOS,外加一个没人知道密码的共享依赖库。这种家底去硬扛自动化部署,纯粹是给自己挖坑。

内网搭建Jenkins的第一原则,不是在机器上装个东西就完事,而是先把你所在内网环境的“能力边界”摸清楚。如果内网和外网完全隔离,那么Jenkins本体、插件、JDK、Maven、Git、依赖包,这些在外网时代随手就能拉下来的东西,每一样都必须准备好离线包。很多团队倒在内网这一步,不是Jenkins不会配,而是前置物料根本就没备齐。

我以自己踩过的坑为例,给你梳理一份内网前置准备清单,每样都是硬通货:

  • Jenkins本体:war包有多个版本,优先选LTS(长期支持)版本,别拿最新版当小白鼠。我常备的是某LTS版本,新版本插件兼容性容易翻车,内网本来就不好排查问题,选稳定版省心很多。
  • 运行时:JRE或JDK,注意版本要和Jenkins匹配,旧版Jenkins配新JDK会直接启动失败,反过来也会报语言级别错误。
  • Maven:不要只准备一个Maven,要准备你项目所需版本。不同项目可能用不同Maven版本打包,比如某老项目还在用Maven 2,新项目已经切到Maven 3.9,这种混搭场景在内网很常见,后面细说。
  • 构建工具链:JDK版本可能不止一个,JDK8、JDK11、JDK17并存的情况很普遍。你先盘好团队正在用的版本,再往后谈其他。
  • Git或SVN:内网代码仓库对应的客户端工具必须匹配版本,老仓库的Git协议和新的Git客户端可能不兼容。
  • Maven依赖仓库:这是内网最容易被忽略的重头戏。项目能本地编译,不代表Jenkins能编译,因为Jenkins工作区是干净的,它的~/.m2/repository是空的。你必须先把整个依赖目录拷贝过去,或者准备一个内网Nexus仓库做代理/镜像。没有这一步,任何自动化构建都是空中楼阁。
  • Jenkins插件:这个放到后面单独说,因为插件的坑和内网安装方式比较特殊。

前置准备的落地动作其实不复杂:找一台和外网“若有若无”的机器,把上述内容统统拉好,打到压缩包或者走移动介质导入内网。我曾经见过一个团队用U盘拷了一整天Maven依赖,虽然土,但在物理隔离环境下反而是最可行的方案。

有一点特别提醒:别信“内网也有Yum源/APT源”这种话,即使有,那也只是操作系统层面的源,解决不了Jenkins插件和Maven依赖问题。你要把你的视角从“装系统”切换到“搬仓库”,这才是内网CI/CD能跑起来的关键假设。

2. JDK与Maven的“版本内耗”:先在公司里统一一阵线

很多人以为内网搭建Jenkins最麻烦的是网络,其实我见过更多项目死在了“版本内耗”上:开发者本机用JDK11打包没问题,拉到Jenkins用JDK8一编就报错,再一看还有个老模块必须用JDK7编译。版本问题一旦出现在内网,排查成本比外网高出好几个量级。

所以我给出的第一个实操建议是:在动手装Jenkins之前,先把公司各项目的JDK和Maven版本要求彻底盘一遍,形成一张清单。这张清单决定了你的Jenkins全局配置怎么设计,也决定了你后续的流水线脚本怎么写。别一上来就装最新JDK17,看起来很先进,结果公司主力项目还在JDK8,你那个CI环境就废了一半。

我建议在Jenkins服务器上统一装多个JDK,通过JAVA_HOME切换。具体做法如下:

  1. 将每个JDK解压到独立目录,比如/usr/local/jdk/jdk8、/usr/local/jdk/jdk11、/usr/local/jdk/jdk17。
  2. 在Jenkins的“全局工具配置”里,把每个JDK都注册一遍,填写各自的JAVA_HOME路径。
  3. 不要在系统级把JAVA_HOME写死,因为Jenkins自身的启动用哪个JDK,和项目构建用哪个JDK,可以是两回事。

这里有一个内网环境特别容易踩的细节:注册多个JDK时,Jenkins默认的“自动安装”功能是走外网下载的,内网环境必须改成“Install automatically”取消勾选,直接填本地路径。很多新手不知道这个开关,白装了半天JDK,Jenkins还是报找不到Java。

Maven的处理逻辑类似,但要多考虑一层:settings.xml里的镜像配置。内网环境有两种常见做法,一种是直接配置某个内部Nexus地址作为mirror,让所有依赖都走内部源;另一种是直接把某个员工本机的~/.m2/repository目录整个拷贝到Jenkins的~/.m2下。两种方式各有千秋:

  • 走Nexus:干净、统一、可持续,但你需要额外搭建和维护Nexus本身。
  • 拷贝repository目录:最快见效,但之后新增依赖会很麻烦,因为内网拉不到新包。

我建议有条件还是上Nexus,哪怕是单机版,因为构建这件事一旦自动化,依赖更新是必然的。但如果你只是赶一个临时项目,拷贝目录能救命。选择权在你,关键是别在两者之间来回摇摆。

再补充一个实操细节:如果你拷贝员工本机的repository目录,记得把_remote.repositories这类元数据文件删掉,否则Maven本地仓库可能会认为某些构件来源不可信,构建时报错非常隐蔽。这个问题在外网环境下几乎遇不到,内网却很容易中招,因为文件是从别人机器上原样搬过来的。

3. Jenkins本身的“离线生存术”:安装、启动与插件搬迁

Jenkins安装本身不复杂——一个war包扔到Tomcat或直接用java -jar启动都行。但内网环境的坑在别处:插件怎么装?更新中心地址怎么配?首次启动的解锁密码在哪?这些在外网三分钟搞完的事,在内网能卡你一整天。

我的做法是直接采用war包方式,不走安装包,因为war包对路径的依赖更简单,方便后续迁移。启动命令大概是这样的:

java -jar jenkins.war --httpPort=8080

如果你希望Jenkins以后台方式跑,用nohup或者配成systemd服务都行。我看到很多人喜欢用systemd,我自己也推荐,毕竟开机自启、日志管理都方便。给你一个参考的unit配置思路:

[Unit] Description=Jenkins Service After=network.target [Service] Type=simple User=jenkins ExecStart=/usr/bin/java -Djava.awt.headless=true -jar /opt/jenkins/jenkins.war --httpPort=8080 [Install] WantedBy=multi-user.target

这里注意一点:别用root用户跑Jenkins,内网环境尤其要注意权限收敛,我用的是一个名为jenkins的普通用户,目录权限给到位就行。

首次启动后,浏览器访问http://服务器IP:8080,会要求你输入一个初始密码,这个密码在/root/.jenkins/secrets/initialAdminPassword或者/home/jenkins/.jenkins/secrets/initialAdminPassword,取决于你的运行用户。内网环境没有邮件通知,这个密码容易被人忽略,我见过同事卡在第一步半小时,其实就是没找到这个文件。

插件安装就是内网环境的头号难点。Jenkins默认的插件安装功能要从外网的更新中心拉数据,内网根本连不上。正确姿势是:

  1. 在能连外网的机器上,进入https://updates.jenkins.io/download/plugins/手动下载你需要的插件.hpi文件(新版是.jpi)。
  2. 将插件文件传到Jenkins服务器的/home/jenkins/.jenkins/plugins/目录下。
  3. 重启Jenkins服务。
  4. 确保每个插件所依赖的其他插件也一并下载,否则Jenkins启动时会因为缺少依赖而报错。

插件之间还有依赖关系,这也是内网最让人头大的点。我的经验是:先确定好要用哪些核心插件,比如git、pipeline、credentials-binding、ssh-agent,然后用外网机器先安装一遍这些插件,再把plugins目录整个打包拷贝进来。这样能最大程度避免依赖缺失——外网安装插件时,Jenkins会自动处理依赖关系,你直接把结果整个搬回来,就不会漏。

有个细节要提醒:如果公司内网有搭建Nexus或Artifactory用来存第三方构件,顺手也可以在里面搭一个Jenkins更新站点的代理,但配置起来比较繁琐,如果只是小团队几十个人,整个目录搬迁移会更实在。别为了所谓“标准方案”把小项目搞复杂,内网环境的第一要务是能跑通。

4. 自动化部署的“骨架”:自由风格任务还是流水线

环境弄好之后,真正的自动化部署才刚开始。我在内网给项目配构建任务时,会先问一个问题:这个团队更熟悉“自由风格任务”还是“流水线”?这决定了后续维护成本。但就我个人的经验和偏好而言,新项目一律用流水线,也就是Pipeline,自由风格任务只用于临时验证场景。

原因很简单:Maven项目的部署流程通常包含拉代码、编译、打包、传包、执行脚本等多个步骤,自由风格任务把这些步骤散落在界面配置里,排错难,复用难。而Pipeline把整个流程写进一个Jenkinsfile,版本化存到代码库里,谁改了什么一目了然,这在内网多人协作时价值特别大。

我给出一个最精简的Pipeline骨架,它能把一个Maven项目构建出war包或jar包,这是所有自动化的起点:

pipeline { agent any tools { maven 'maven3' jdk 'jdk8' } stages { stage('拉取代码') { steps { git branch: 'master', url: 'http://内网Git地址/group/project.git', credentialsId: '内网Git账号' } } stage('构建与打包') { steps { sh 'mvn clean package -DskipTests -s settings.xml' } } stage('发布') { steps { sh './deploy.sh' } } } }

这个骨架有几个关键点需要解释一下,否则你抄过去也跑不通。

tools这一段是告诉Jenkins用哪个Maven和JDK,名称必须和“全局工具配置”里注册的名字完全一致。我在第2节强调过内网要多JDK并存,这里就是发挥价值的地方。如果项目需要JDK11,就换成jdk 'jdk11',完全不用改服务器环境变量。

git步骤里的credentialsId,是指你在Jenkins“凭据”里添加的内网Git账号密码或SSH密钥ID。内网通常没有公网那种HTTPS免密,所以凭据管理是绕不开的一环。建议用SSH密钥而不是密码,因为密钥不容易因为密码过期导致构建中断,特别是内网安全策略要求定期改密时,密码方式会很痛苦。

mvn clean package -DskipTests -s settings.xml,这里这个settings.xml你可以放到Jenkins服务器的某个固定路径,比如/home/jenkins/.m2/settings.xml,然后在命令里显式指定。为什么我不直接依赖默认配置呢?因为内网的镜像地址、私服地址等配置如果写在默认位置,一旦多人维护或迁移机器,容易悄悄失效。显式指定路径,出问题时至少能一眼看到用哪个配置。

到了这一步,你已经能“构建”了,但还没做到“部署”。发布这一步我留到了第5节展开,因为这里才是真正区分“能打包”和“能上线”的分水岭。

5. “发布”这关怎么过:先解决目标服务器信任与权限

Pipeline跑到“发布”这一步,很多人会习惯性用一些外部插件,比如Publish Over SSH或者SSH Agent,把war包扔到目标服务器再执行命令。说实话我不太推荐在内网无脑依赖这类插件,原因是目标服务器一多,插件配置里的服务器列表就变成一个没人愿意维护的黑洞。我更喜欢用最朴素的办法:写一个deploy.sh脚本,利用SSH把产物推过去再触发解压或重启。

先说基础设施:Jenkins服务器到目标服务器要建立SSH信任。这一步不复杂,但有内网特性要小心:

  1. 在Jenkins服务器上生成密钥:ssh-keygen -t rsa -b 4096,一路回车就行。
  2. 用ssh-copy-id 用户名@目标服务器IP把公钥推过去。
  3. 在目标服务器上验证:ssh 用户名@目标服务器IP 'hostname',能回显说明通了。

注意,这里我用的是Jenkins运行用户的SSH密钥,不是root的。别为了省事直接拿root密钥到处复制,权限太大会带来安全隐患,也违背内网合规要求。

接着写一个deploy.sh脚本。以一个典型的Java Web项目部署到Tomcat为例,脚本的核心逻辑应该是:

#!/bin/bash APP_NAME=myweb TOMCAT_HOME=/opt/tomcat8 WAR_SOURCE_PATH=/var/lib/jenkins/workspace/项目名/target/myweb.war echo "停止Tomcat服务" $TOMCAT_HOME/bin/shutdown.sh echo "备份当前版本" cp -r $TOMCAT_HOME/webapps/$APP_NAME $TOMCAT_HOME/backup/$APP_NAME_$(date +%Y%m%d%H%M%S) echo "清理缓存并部署新包" rm -rf $TOMCAT_HOME/webapps/$APP_NAME* cp $WAR_SOURCE_PATH $TOMCAT_HOME/webapps/$APP_NAME.war echo "启动Tomcat服务" $TOMCAT_HOME/bin/startup.sh

这个脚本里值得注意的地方:

先停服务再部署,而不是直接把war包丢进去让Tomcat自动解压。虽然Tomcat支持热部署,但在内网低配置机器上,热部署可能导致旧类没有被完全卸载,出现奇怪的内存问题。干净利落地重启一次,比事后排查来得高效。

保留备份。我见过很多人部署时不备份,上线后发现新包有问题,回滚只能靠重新构建,构建还要等好几分钟。备份目录里留着上一个版本,秒级回滚它不香吗。这个习惯在内网环境尤其重要,因为内网构建一次依赖下载可能就要很久,根本没资格频繁重来。

执行用户的问题。脚本里执行Tomcat启动脚本,要确保所在用户对/opt/tomcat8和Jenkins工作区目录有读写权限。否则很经典的一条报错是“Permission denied”,你还以为脚本写错了。解决方式是把Jenkins用户加到目标服务器某用户组里,或者调整目录属主。别用root跑所有服务,这是在给自己攒运维债。

脚本放哪。我建议deploy.sh直接存进项目代码仓库,而不是Jenkins服务器某个角落。这样每个项目自带部署逻辑,新同事接手不用满服务器找脚本,版本历史也清晰。内网维护多人协作时,治这种散乱必须从流程上解决。

6. 参数化构建与多环境隔离:一次构建,多处发布

自动化部署做到“一键把开发环境包发布到测试服务器”之后,很多人会觉得完事了。但真实项目中你很快会发现:同一个war包,要发布到测试环境、预发环境、甚至生产环境,每个环境的数据库地址、配置文件都不一样。这时候如果每次都在脚本里手改IP和配置,自动化就名存实亡了。

我在内网项目中强烈建议使用Jenkins的“参数化构建”功能,把环境差异变成一次选择,而不是一次修改。

具体做法是:在Pipeline配置页面勾选“参数化构建过程”,添加一个Choice Parameter,名字叫DEPLOY_ENV,选项写dev、test、prod。然后在deploy.sh里按参数读取不同配置,核心骨架如下:

if [ "$DEPLOY_ENV" = "dev" ]; then SERVER_IP=192.168.1.10 APP_CONFIG=/opt/config/dev.properties elif [ "$DEPLOY_ENV" = "test" ]; then SERVER_IP=192.168.1.20 APP_CONFIG=/opt/config/test.properties elif [ "$DEPLOY_ENV" = "prod" ]; then SERVER_IP=192.168.1.30 APP_CONFIG=/opt/config/prod.properties else echo "未知环境: $DEPLOY_ENV" exit 1 fi rsync -avz $WAR_SOURCE_PATH user@$SERVER_IP:/opt/deploy/ ssh user@$SERVER_IP "bash /opt/deploy/remote_deploy.sh $DEPLOY_ENV"

有人会问,为什么配置文件不打包到war里,而要放到服务器上的/opt/config/目录?我的看法是:把配置和产物分离,是内网环境持续部署的一条底线。因为war包一旦打包,里面的配置就固定了,同一个包没法往不同环境发;而把配置文件放在服务器上,由部署脚本去引用,你才能做到“一次构建、多处发布”。这在回滚场景下也特别方便:你想回滚的是业务代码,不是回滚环境配置。

多环境隔离这块还有一个坑:服务器的网络隔离。内网环境经常有防火墙规则,比如从Jenkins服务器能SSH到测试机,但SSH到生产机需要跳板机。如果你的网络拓扑是这样,deploy.sh就不能写一个简单的ssh命令,而是要提前准备好跳板配置,比如在~/.ssh/config里定义好主机别名和跳板参数。别到生产发布当天才想起来这茬。

如果项目用Docker部署,脚本逻辑会略有变化,但核心思路一致:构建出镜像/制品,传到目标机器,再按环境变量启动容器。不过Docker在内网环境还有一个镜像仓库要搭,又是一摊事,这里先把传统方式讲透,容器化以后有机会再单独讲。

7. 定时构建与触发策略:内网环境别滥用轮询

最后聊一下“什么时候自动构建”的问题。外网环境很多人都喜欢用H/5 * * * *这种轮询方式,让Jenkins每隔几分钟去Git仓库看一眼有没有新提交。但我不太建议在内网这么干,因为内网Git服务器性能一般有限,高频轮询会白白增加一台机器负担,而且触发的构建如果过于频繁,内网带宽和机器负载会很难看。

我更推荐两种触发方式:

第一种,用Git Webhook。也就是每次代码push后,由Git服务器通知Jenkins执行构建。但内网环境要注意,Git服务器能不能访问到Jenkins的接口地址,网络策略是否允许跨机器回调,很多内网默认是不通的。如果走不通,就别死磕Webhook。

第二种,用“仅当源码变化时构建”。在Pipeline任务里配置定时触发,但配合轮询忽略中间态,实际上依赖的是Jenkins记录上一次构建后的提交变化。这个方式比固定频率轮询要温和一点,但由于同样依赖轮询,也有刚刚说到的问题。

实际内网项目里,我更常做成“手动构建为主、夜间定时构建为辅”的模式。夜间让Jenkins统一跑一次全量构建,第二天早上开发直接看结果,白天需要测试包时手动点一个“构建”。模式朴素,但运维成本极低,非常适合内网小团队。

如果确实有自动化触发的硬需求,不妨在代码仓库侧搞一个提交钩子,在每次push时顺带调用一下Jenkins的构建接口。钩子脚本很短,几百字节而已,但这个方案把压力从轮询转成了事件驱动,对内网环境友好得多。

8. 那些内网环境才有的“隐藏杀手”:我把它们一次说清

写到这里,基本上搭建、构建、部署、触发的完整链路都讲到了。但内网环境的坑属于那种“你没遇到就永远不会知道”的类型,我最后再集中说几个经常造成死机的隐藏杀手,免得你走冤枉路。

第一个,时区问题。内网服务器时区往往不是中国标准时间,或者干脆是UTC。Jenkins构建记录里的时间显示和你们约定不一致,排查问题时非常痛苦。建议在Jenkins启动参数里加上-Duser.timezone=Asia/Shanghai,同时在系统管理-系统配置里再设一遍时区。

第二个,Maven仓库与Jenkins工作目录的空间。内网机器存储往往不是很大,而每次构建都会把工作区文件、Maven依赖、构建产物铺在磁盘上。我见过某台内网服务器跑了一年后,磁盘被堆到100%,构建任务全部失败,一查是/var/lib/jenkins下面累积了大量旧的构建记录和临时文件。解决办法很简单:在“系统管理-系统配置”里勾选“丢弃旧的构建记录”,并定期清理已构建的workspace。这条建议说不上技术含量,但它救过我好几次。

第三个,HTTP代理陷阱。有些内网虽然不能访问外网,但HTTP请求需要走公司统一代理,这个代理配置可能出现在settings.xml或系统环境变量里。麻烦的地方在于,它也会影响Jenkins插件中心连接和某些HTTPAPI调用,导致你看起来“不该联网的功能”也报错。排查时记得到系统变量和Maven配置里翻一翻代理设置。

第四个,字符编码。构建日志里如果出现中文乱码,首先怀疑两点:一是LC_ALL环境变量没设成en_US.UTF-8或zh_CN.UTF-8;二是MAVEN_OPTS里少了-Dfile.encoding=UTF-8。内网环境常常没有统一的字符集规范,各台机器默认值都不一样,这些细节不先统一,后面看日志会让你怀疑人生。

第五个,SVN仓库的认证缓存。如果你们用的是SVN而恰好不是Git,要注意Jenkins所在用户的认证缓存问题。命令行执行svn list能通过,但Jenkins构建里却报认证失败,这种怪现象多半是认证缓存没有落在Jenkins运行用户下。给项目配置里显式指定credentialsId是最稳妥的,别指望Jenkins自动继承某个用户的.subversion目录。

我自己的习惯是,在正式把内网CI/CD环境交付给团队使用之前,专门花一天时间写一份“环境注意事项”文档,把上面这些点连同各服务器地址、账号策略、配置位置写清楚。内网环境的特殊性决定了它很难靠别人远程帮你调试,文档做到位,以后维护和排错能少掉不少头发。

这套流程我目前带过好几个项目落地,从最简架构到多环境发布,滚动起来之后日常几乎不用再操心“谁帮我打个包”这种事。环境搭建看似繁琐,但当你看到提交代码几分钟后包已经在测试环境跑起来,那个成就感确实挺值。

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

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

立即咨询