尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Jenkins节点创建与连接:SSH直连、反向注册、标签调度与排障维护

Jenkins节点创建与连接:SSH直连、反向注册、标签调度与排障维护 上周有个朋友来找我说他的 Jenkins 又堵死了——四十多个 job 全排在一台机器的一个执行器上早上九点提交的构建中午十二点还在队列里躺着。他问我要不要加台机器我说你这不是要不要的问题是你早就该做了。Jenkins 创建节点并连接这件事说起来只有两个动作但真到动手的时候卡人的从来不是界面上那七个输入框而是密钥怎么给、Java 版本对不对得上、50000 端口通不通、标签怎么设计才不会半年后变成一团乱麻。这篇内容我打算把这几年在不同环境里给 Jenkins 挂节点的过程完整写一遍什么情况下该挂、SSH 直连和反向注册两条路各自适合什么场景、每一步为什么这么填、连不上时按什么顺序排查、节点上线之后怎么维护。适合已经装好 Jenkins、能跑通单机流水线、但还没系统接触过多节点调度的同学也适合那些节点已经挂上、但隔三差五掉线想找根因的人。全程按我自己的实操顺序讲不绕弯子。1. 什么时候该给 Jenkins 加节点先判断再动手1.1 单机 Jenkins 最先撑不住的三个信号很多人加节点是被逼的其实早就有征兆。第一个信号是队列长度。你在 Jenkins 首页看到构建队列里堆着五六个任务每个任务前面都写着等待下一个可用的执行器这就说明执行器数量已经是瓶颈。判断阈值不用太精确如果一天里有超过三分之一的构建时间花在排队上加节点带来的收益就非常明显了。第二个信号是资源互相抢占。同一台机器上前端项目跑npm install吃满内存后端项目跑 Maven 编译吃满 CPU两个任务互相拖着结果都比在各自独立的机器上慢一截。更烦的是磁盘Docker 构建产生的镜像层、Node 的node_modules、Maven 的本地仓库三样东西一起堆磁盘水位很快见底接着就是no space left on device这种一看就头疼的报错。第三个信号是环境差异。你的构建需要同时产出 Linux 上的可执行文件、Windows 上的安装包、macOS 上的应用这件事在单机上根本没法优雅解决。这时候不是要不要加节点而是必须按平台拆节点因为每个平台的编译工具链和签名机制都不可能在同一个文件系统里和平共处。1.2 主节点不应该承担构建任务Jenkins 从 2.x 后期开始官方文档里的术语已经从 master/slave 改成了 controller/agent界面上的内置节点Built-In Node其实就是主节点本身。它有两个职责调度任务以及给所有节点提供统一的配置、凭证和插件。这两个职责已经够重了再让它跑构建风险很不划算。原因很直接。主节点的磁盘上放着$JENKINS_HOME里面是全部的任务配置、构建历史、插件、凭证。任何一个构建脚本里写错一行rm -rf或者某个构建把磁盘写满导致 Jenkins 无法写入配置整个 Jenkins 就瘫了所有节点一起失联。我见过一次事故某人在主节点上跑了一个往工作目录大量写日志的任务两天后磁盘满Jenkins 重启后直接起不来恢复配置花了半天。所以比较稳妥的做法是把内置节点的执行器数量改成 0所有构建全部下沉到 agent 节点。具体操作是进入管理 Jenkins → 节点点进 Built-In Node把并发构建数设为 0保存。已经绑定到主节点的任务用后面讲的标签机制重新指向即可。改完之后你会发现主节点的负载明显下来界面响应也顺畅不少。2. SSH 直连与反向注册两种连接方式各自的适用场景2.1 SSH 直连的工作模型SSH 方式下Jenkins 主节点是主动方。它自己发起一条 SSH 连接登录到目标机器把一小段 Java 程序agent.jar通过 SFTP 推过去然后用指定的 Java 执行它。整个过程对目标机器的要求是有 sshd 在跑、有一个能登录的账号、装了 Java、工作目录可写。这条路的优点是配置干净、链路清晰、日志集中。主节点知道每一台 agent 的地址和账号增删改都在一个界面上完成不需要登录每台机器去敲命令。缺点是它对网络方向有要求——主节点必须能直连 agent 的 SSH 端口。如果你的 agent 在内网深处只有单向出口这条路就走不通。另一个隐藏门槛是凭证管理。SSH 方式依赖一对密钥公钥放在 agent 的~/.ssh/authorized_keys私钥存进 Jenkins 凭证库。密钥一旦泄露等于那台机器的登录权限送人所以给 Jenkins 用的密钥必须是专用密钥不能和运维日常登录的密钥混用。2.2 反向注册Inbound Agent解决的现实问题反向注册在旧文档里叫 JNLP agent现在的界面叫通过连接到控制器来启动 agent。这条路的主动权在 agent 手上agent 机器上手动执行一条命令主动去连主节点的 50000 端口或者走 WebSocket 挂在 80/443 上连上之后由主节点下发任务。它解决的典型场景有三个。一是 agent 在内网或者跨网段主节点够不着它但它能访问主节点。二是 Windows 机器上不想额外装 OpenSSH 服务端少一层配置少一堆麻烦。三是容器化的临时节点容器启动时执行一条注册命令就能上线用完销毁非常适合弹性扩容。代价是启动动作得手动做一次节点重启之后默认不会自己回来必须自己包一层服务或者脚本让它开机自启。另外 secret 这种凭证需要保管好泄露后人拿到它就能伪装成合法节点连上主节点所以别把它随手贴进公共文档或者聊天记录里。2.3 怎么选一张对照表对比维度SSH 直连反向注册连接方向主节点连 agentagent 连主节点agent 端必需组件sshd、Java、可写目录Java、可写目录端口要求主节点能访问 agent 的 22 端口agent 能访问主节点的 50000 或 80/443节点重启后是否自恢复是主节点会重连否需要自己做自启适合平台Linux、macOSLinux、Windows、临时容器密钥/凭证形式SSH 私钥节点 secret 或 agent.jar 参数跨网段场景不友好友好选的时候我的习惯是只要能直连优先 SSH因为它省事、日志清楚、重启自恢复只有直连不了、或者节点是临时拉起用完就删的才用反向注册。这两条路在同一个 Jenkins 里可以混用没有任何冲突。3. 用 SSH 方式创建一个节点的完整过程3.1 agent 机器上必须先具备的东西动手之前在 agent 机器上确认四件事这四件事任意一件不满足后面都会变成莫名其妙的报错。第一件是 Java。Jenkins 主节点是什么版本agent 上的 Java 版本就不能太旧。Jenkins 2.4xx 之后主推 Java 17 和 Java 21用 Java 11 去连新版本主节点经常报Unsupported major.minor version 61.0这种字节码版本不匹配的错误。查版本用java -version找不到 Java 就按发行版装一个 OpenJDK装完确认which java有真实路径。第二件是 SSH 服务端。用systemctl status sshd或者systemctl status ssh看状态端口不是 22 的话记下来后面填节点配置要用。第三件是工作目录。提前建好比如/home/jenkins/agent把属主改成 Jenkins 用来登录的那个账号。目录权限不对是最容易被忽略的问题表现是节点显示在线但一跑构建就报无法创建工作区。第四件是时间同步。主节点和 agent 的时间差太大会影响日志排序和部分证书校验装个时间同步服务是最省心的做法。3.2 生成一对专用密钥并分发公钥在任意一台你信得过的机器上生成密钥不要在主节点的~/.ssh里随手生成避免和运维密钥混淆ssh-keygen -t ed25519 -C jenkins-agent-key -f ./jenkins_agent_key这条命令会产出两个文件jenkins_agent_key是私钥jenkins_agent_key.pub是公钥。Ed25519 比 RSA 短很多兼容性在现代发行版上没问题如果你的 agent 系统很老改用-t rsa -b 4096。公钥分发就是把.pub文件的内容追加到 agent 上那个账号的~/.ssh/authorized_keys里cat jenkins_agent_key.pub | ssh jenkins10.0.0.21 mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys分发完先手动验一次用私钥直接登录 agent能进去说明密钥没问题ssh -i ./jenkins_agent_key jenkins10.0.0.21 hostname java -version这一步千万别跳过。我见过太多人在 Jenkins 界面上折腾半天最后发现是公钥根本没追加进去或者authorized_keys权限过宽被 sshd 拒绝。3.3 在 Jenkins 界面里把七个字段填对进入管理 Jenkins → 凭证 → 系统 → 全局凭证新增一条SSH Username with private keyID 起个好认的名字比如agent-10.0.0.21-key用户名填jenkins私钥选直接输入把刚才那个私钥文件的内容整段粘进去。然后回到管理 Jenkins → 节点 → 新建节点填这几个关键字段名称用能表达用途的名字比如linux-build-01别用node1这种以后自己也看不懂的名字。并发构建数按机器核数定4 核的机器给 2 到 4给太多会导致构建之间抢内存。远程工作目录填/home/jenkins/agent这个目录下的 workspace 子目录会按任务名分文件夹。标签这是后面调度的关键比如linux amd64 jdk17。用法选只允许绑定到此节点的任务避免团队里其他人的任务被莫名其妙调度过来。启动方式选通过 SSH 启动 agent。主机填 IP 或能解析的主机名端口非 22 时单独填。下面还有一项容易忽略的主机密钥验证策略。生产环境建议用Manually trusted key Verification Strategy把 agent 的主机公钥提前录进去测试环境图省事可以用Non verifying但公网环境下不要这么干中间人风险是真实存在的。3.4 连上之后必须做的三项验证节点保存后Jenkins 会自动尝试连接日志区会打印全过程。看到Agent successfully connected and online只是第一步我习惯再做三个验证。一是在 agent 机器上执行ps -ef | grep remoting确认 agent.jar 进程真的活着而不是 Jenkins 界面上显示在线、实际进程已经挂了。二是在节点的系统信息页面点进去看 Java 版本、系统属性、环境变量是否和预期一致。这里能直接暴露 Java 版本不对、路径不对之类的问题。三是随手建一个最小流水线任务用标签绑定到新节点只跑一句sh hostname pwd whoami确认输出里的主机名是新节点、工作目录在主节点配置的那个路径下、执行用户是预期账号。这三项一过节点基本就算真正可用了。4. 反向注册节点的落地secret、agent.jar 与开机自启4.1 创建节点并抓取连接命令在新建节点里把启动方式改成通过连接到控制器来启动 agent保存后节点会处于离线状态页面上会给出三种连接方式命令行、通过 Java Web Start、通过浏览器。实际用得最多的是命令行那种它长这样java -jar agent.jar \ -jnlpUrl http://jenkins.example.com:8080/computer/win-build-01/jenkins-agent.jnlp \ -secret 8f3a1c9e2b7d4f6a0c5e8b1d3f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f2a \ -workDir /home/jenkins/agentagent.jar 需要先从主节点的/jnlpJars/agent.jar下载下来离线环境里可以用 U 盘或者内部文件服务传。把命令在 agent 机器上执行一次节点就会变成在线。4.2 一行命令背后的参数含义-jnlpUrl是节点在 Jenkins 里的唯一标识地址里面带了节点名所以节点改名之后这条命令必须重新生成。-secret是这个节点的身份凭证本质上是节点级的认证令牌泄露了别人就能拿它连上你的主节点所以别把它写进公开的仓库或者截图发群里。-workDir是工作目录不指定的话默认落在当前目录很容易和别的东西混在一起。如果 agent 只能访问主节点的 80 或 443加上-webSocket参数让它走 WebSocket这样就不需要单独开放 50000 端口java -jar agent.jar -jnlpUrl https://jenkins.example.com/computer/win-build-01/jenkins-agent.jnlp -secret secret -workDir /home/jenkins/agent -webSocket用 WebSocket 的时候主节点的反向代理要把 WebSocket 升级请求转发出去否则连接会在握手阶段断掉日志里看到的是 400 或 502。4.3 做成服务让它掉线后自己回来命令行方式最大的问题是节点重启后不会自己回来。解决办法是把它包成一个系统服务。Linux 上用 systemd[Unit] DescriptionJenkins Agent Afternetwork-online.target Wantsnetwork-online.target [Service] Userjenkins WorkingDirectory/home/jenkins/agent ExecStart/usr/bin/java -jar /home/jenkins/agent/agent.jar -jnlpUrl http://jenkins.example.com:8080/computer/linux-build-02/jenkins-agent.jnlp -secret secret -workDir /home/jenkins/agent Restartalways RestartSec10 [Install] WantedBymulti-user.targetRestartalways加上RestartSec10这两个配置是关键网络抖动或者主节点重启导致的断连agent 会在十秒后自己重连不需要人工介入。Windows 上可以用 WinSW 把同样的命令包成服务原理一样。有一个细节要注意secret 写进服务文件等于以明文存在磁盘上文件权限至少要设成 600并且只有该账号可读。同机器上如果还有别的用户这个风险要评估。5. 标签与调度策略让构建落到正确的机器上5.1 标签怎么起名才不会失控标签是 Jenkins 多节点调度里最重要的设计也是最容易被随便对待的部分。常见错误是所有人都按自己的理解加标签半年后你会在下拉框里看到linux、Linux、linux64、ubuntu、ubuntu20、u20六种写法指代同一批机器。我的习惯是按三个维度组合操作系统、架构、用途或工具链。比如linux amd64 docker表示一台能跑 Docker 的 Linux AMD64 机器windows amd64 vs2019表示装了 VS2019 的 Windows 构建机。这样写的好处是可以在流水线里做组合筛选比如agent { label linux docker }需要的机器不在线时任务会排队等待而不是随便挑一台跑挂。还有一个技巧是给节点打上能力标签而不是身份标签。能跑 Android 构建是能力android-03是身份。流水线里永远只用能力标签节点换机器、改名、扩容都不影响流水线这是长期可维护的关键。5.2 两种用法策略的差别节点的用法有两个选项。尽可能使用此节点意味着只要这个节点有空闲执行器Jenkins 就可能把任意未被绑定的任务派过来。只允许绑定到此节点的任务则相反只有明确指定了这个节点或其标签的任务才会被调度过去。生产环境我一般这样分通用的 Linux 构建机选尽可能使用因为它们本来就是用来分担负载的装了特殊工具链、或者有独占资源的机器比如接了签名设备、跑数据库压测选只允许绑定到此节点的任务防止别人误用把机器搞脏。5.3 Jenkinsfile 里写 label 的几种写法脚本式流水线里最直接的写法是node(linux docker) { stage(Build) { sh mvn -B clean package -DskipTests } }声明式流水线里可以放在 agent 段pipeline { agent { label linux jdk17 amd64 } stages { stage(Compile) { steps { sh mvn -B compile } } stage(Test) { steps { sh mvn -B test } } } }更灵活的做法是把标签抽成参数用parameters或者环境变量控制同一套 Jenkinsfile 在测试环境和生产环境指向不同的节点池pipeline { agent { label ${params.BUILD_LABEL} } parameters { string(name: BUILD_LABEL, defaultValue: linux jdk17, description: 构建节点标签) } // stages 略 }这种写法的价值在于扩容时只需要给新机器打上同样的标签流水线一行都不用改。6. 连不上和连上又掉线一条可以照抄的排查链路6.1 先看连接日志再动手改配置节点连不上时绝大多数人的第一反应是去改配置这是最低效的做法。正确的顺序是先看日志。位置有两个节点页面上会显示最近一次连接尝试的日志主节点的$JENKINS_HOME/logs里也有更完整的记录。日志里通常会直接告诉你失败在哪一层——Connection refused是网络或端口问题Permission denied (publickey)是密钥或账号问题Unsupported major.minor version是 Java 版本问题Host key verification failed是主机密钥没录。有一次我遇到节点显示在线但一跑就断日志里翻到一行java.io.IOException: No space left on device才想起来那台机器的/tmp分区只有 2Gagent 启动时的临时文件把它填满了。如果不看日志我能在这上面耗一整个下午。6.2 按层排查网络、认证、运行时、权限、资源我整理过一张排查表按顺序走基本不会漏层次检查动作典型报错网络nc -vz agent-ip 22、telnet controller-ip 50000Connection refused、Connection timed outDNSgetent hosts 主机名UnknownHostException认证手动用私钥 ssh 登录 agentPermission denied (publickey)主机密钥对比 agent 的 host key 指纹Host key verification failed运行时java -version、对比主节点版本Unsupported major.minor version权限检查工作目录属主和权限Permission denied 写入工作区失败资源df -h、free -mNo space left on device、OOM端口占用ss -lntp看 50000、看是否有冲突Address already in use顺序很重要从下层往上层走。网络不通的时候去查 Java 版本是浪费时间认证没过的时候去查磁盘也一样。6.3 我实际踩过的五个坑第一个坑是密钥格式。从某些工具生成的私钥带密码短语直接粘进 Jenkins 凭证时如果不填对应的 passphrase连接会一直失败日志里的报错还不明显。要么生成无密码短语的专用密钥要么把 passphrase 一并填进去。第二个坑是 agent 上装了多个 Javajava命令指向的是老版本。Jenkins 节点配置里可以指定JavaPath填绝对路径比如/usr/lib/jvm/java-17-openjdk/bin/java比依赖 PATH 可靠得多。第三个坑是authorized_keys权限。sshd 对它的权限很敏感如果这个文件或.ssh目录对同组或其他用户可写登录会被静默拒绝日志里只有一句含糊的认证失败。第四个坑是工作目录被前一次构建留下的文件占满或者权限被改乱。解决方案是定期清理旧的 workspace或者在工作目录之外单独划一个分区。第五个坑是主节点重启后反向注册的节点没回来。前面说的 systemd 自启能解决但还要注意 agent.jar 的版本。主节点升级之后旧版本的 agent.jar 有时会连不上需要重新从新版主节点下载jnlpJars/agent.jar覆盖过去。这个坑不太常见但一旦遇上会让人很困惑因为节点配置一个字都没改突然就连不上了。7. 环境变量、工具路径和工作区节点上的三件细活7.1 Jenkins 会自动注入哪些变量节点上跑构建时Jenkins 会注入一批环境变量用得好能省很多硬编码。最常用的几个是WORKSPACE当前工作目录、NODE_NAME当前节点名、NODE_LABELS当前节点的全部标签、BUILD_NUMBER构建号、BUILD_URL本次构建的地址、JOB_NAME任务名、EXECUTOR_NUMBER当前执行器编号。EXECUTOR_NUMBER这个变量在并发构建时特别有用。同一台机器上多个执行器同时跑同一个任务时如果构建过程需要占用固定端口或者固定目录用EXECUTOR_NUMBER做后缀就能天然隔离比如把测试数据库端口写成5432 EXECUTOR_NUMBER。除了这些内置变量还可以在节点的节点属性里添加自定义环境变量比如MAVEN_OPTS、GRADLE_USER_HOME让这台机器上的所有构建都生效不用每个任务重复配一遍。7.2 全局工具配置与节点级路径覆盖Jenkins 的全局工具配置里可以定义多个 JDK、Maven、Gradle、NodeJS 版本主节点统一管理这些定义。节点上的工具位置可以针对每个工具做路径覆盖意思是全局定义的是JDK 17这个逻辑名称节点上说明这个名称在这台机器上具体对应哪个路径。这套机制的价值在于流水线里只写逻辑名称tools { jdk jdk17 maven maven-3.9 }节点换了、路径变了只改节点配置流水线不动。如果每台机器上都硬编码绝对路径扩容到第五台机器的时候你就会开始骂人。7.3 工作区放在哪、怎么清工作区默认落在远程工作目录/workspace/任务名下面。这个位置如果放在系统盘很容易被构建产物撑满我的建议是单独挂一块盘挂载点比如/data/jenkins然后把远程工作目录指过去。清理策略有几种做法。手工方式是任务配置里勾选构建前删除工作区简单但每次都全量拉取速度慢。更合理的是用cleanWs()步骤做条件清理比如只在参数为真时清理post { always { script { if (params.CLEAN_AFTER_BUILD) { cleanWs() } } } }再配合一个定期跑的清理任务把超过 N 天没有更新的 workspace 目录删掉磁盘水位就能控制住。8. 节点上线之后的日常维护8.1 临时下线、排空任务、重新上线节点需要重启、打补丁、换硬件的时候直接关机是最粗暴的做法会导致正在跑的任务失败。正确做法是在节点页面上点暂时断开连接这样 Jenkins 不会再往这个节点派新任务但已经在跑的任务会继续跑完节点状态变成一条波浪线的离线标记。等任务跑完再重启重启后点上线即可。这个动作在批量维护的时候很关键。如果有一批节点要一起打补丁按顺序逐个断开、等待空闲、再断开下一台整个过程中流水线不会出现红叉。8.2 磁盘和内存要盯的两个指标节点跑久了出问题八成是资源。我一般盯两个数字工作目录所在分区的剩余空间以及 agent 进程的常驻内存。磁盘可以用监控插件接进来也可以简单地在每个节点的定时任务里跑一条清理脚本空间低于阈值时发通知。内存方面Maven 和 Gradle 这类构建工具默认的堆上限可能吃掉几个 G如果机器上并发构建数开得多很容易触发系统层面的 OOM把 agent 进程一起干掉。把MAVEN_OPTS的-Xmx按机器内存和并发数合理分配比事后追查 OOM 日志省事得多。8.3 批量扩容时怎么少做重复劳动节点从三台扩到十台的时候如果还在界面上一个个点那说明前面的设计有问题。能自动化的部分尽量自动化agent 机器的基础环境用配置管理脚本初始化Java 版本、目录结构、SSH 配置一次写好节点的创建工作可以走 Jenkins 的远程 API把节点名、标签、主机做成参数批量提交如果是容器化节点直接用云插件在需要时拉起临时 agent用完销毁连维护都省了。标签体系在这一步的价值会体现得特别明显。只要新节点打上和老节点一样的标签所有历史流水线立刻就能把任务派过来一行配置都不用改。反过来如果前面标签起得随心所欲扩容就是一场灾难。我自己的习惯是每加一台节点就顺手在内部文档里记一行节点名、标签、用途、负责人、上线日期。等到半年后某台机器报硬盘故障你能立刻知道它上面跑着哪些任务、能不能直接下线这个记录的价值远比当时多花的那两分钟大。
返回列表