Windows 上 Jenkins 节点开机自启:sc 命令的 1053 错误与 NSSM 方案

问题:节点必须登录才能拉起

自动化测试环境里有 5 台 Windows 10 机器作为 Jenkins 节点,Master 是一台 Ubuntu。公司安全策略要求服务器必须设密码,所以我之前试过的开机自启方案都用过:计划任务、shell:startup 启动文件夹。这俩有同一个毛病,必须有用户登录到桌面才会执行。

结果是每次服务器重启之后,不管定期维护还是意外断电,我都得手动 RDP 上去敲一次密码登录,Jenkins 节点才会起来。凌晨三点重启一次,第二天上班发现整夜的自动化测试全没跑。

这种依赖登录的方案本质就不对。要彻底解决,得让节点以系统服务的方式开机即起,跟用户会话脱钩。

第一次尝试:sc 命令注册服务

Windows 服务正好满足需求:开机自动启动,不需要用户登录,跑在后台会话里,还能配失败重启策略。

用管理员身份开 cmd,sc 命令注册服务:

1
sc create "JenkinsNodeService" binPath= "\"[Java安装路径]\bin\java.exe\" -jar \"[Jenkins工作目录]\agent.jar\" -url http://[Jenkins-Master-IP]:[端口]/ -secret [节点密钥] -name \"[节点名称]\" -webSocket -workDir \"[Jenkins工作目录]\"" start= auto DisplayName= "Jenkins 节点服务"

sc 的语法有个坑:binPath=start= 这些等号后面必须留一个空格,这是它的解析规则,少了空格会直接报错。

服务创建成功,服务管理器里也能看到。

但启动的时候直接报错。

1053 错误的根因

启动服务,系统提示:

1
[SC] StartService 失败 1053:服务没有及时响应启动或控制请求

奇怪的是去 Jenkins Master 上看,节点其实连上过,但十几秒后又掉线。进程确实起来了,服务却判定失败。

根因在 Windows 服务的生命周期机制:

  1. SCM(服务控制管理器)启动服务程序后,期望程序在 30 秒内调用 SetServiceStatus API 上报”已启动”状态。
  2. 30 秒内没收到这个信号,SCM 认为服务启动失败。
  3. SCM 强制终止进程,把服务标记为失败。

java.exe 跑的 Jenkins agent 只是个普通后台进程,它根本不知道要去调 SetServiceStatus,也没有实现服务控制接口。所以 SCM 等满 30 秒,把进程杀掉,节点也就掉线了。

这就是 1053 的根本原因:不是命令写错了,而是被注册成服务的程序本身不是服务。sc 只是把任意可执行文件登记成一个服务条目,它不会替这个程序去实现服务协议。Java 程序、Python 脚本、控制台 exe,只要它自己没写服务控制那套逻辑,直接用 sc 注册都会撞上 1053。

调大超时也救不了,因为程序压根不会去调那个 API,等多久都是一样。

NSSM:服务包装器

要解决的就是”程序不懂服务协议”这件事。NSSM(Non-Sucking Service Manager)就是干这个的。

NSSM 本身是一个符合 Windows 服务规范的程序,能被 SCM 正常启动、能正确上报状态。它启动之后,再去拉起真正想跑的程序,这里是 Java 进程,并监控它。对 SCM 它扮演标准服务,对你的程序它扮演启动器。只要告诉它跑哪个 exe、带什么参数,剩下的服务协议握手它全包了。

名字里的 “Non-Sucking” 是有来历的:早期的 srvany 之类工具在子进程崩溃时不会正确清理,NSSM 就是冲着这个毛病做的,崩溃能干净退出,还能配自动重启策略。

安装 NSSM

下载地址:

下的是 nssm-2.24.zip,解压后按系统位数选 nssm.exe,Win10 一般用 win64 那个。

先把之前 sc 建的失败服务删掉,再装新的:

1
2
3
sc stop JenkinsNodeService
sc delete JenkinsNodeService
nssm.exe install JenkinsNodeService

最后一条命令会弹出图形化的 NSSM Service Installer。

配置服务参数

在 NSSM 安装界面填以下内容。

Application 选项卡:

  • Path:Java 可执行文件完整路径,例如 C:\Program Files\Java\jdk-11\bin\java.exe

  • Startup directory:Jenkins 工作目录,例如 C:\jenkins

  • Arguments:节点启动参数

    1
    -jar "C:\jenkins\agent.jar" -url http://[Jenkins-Master-IP]:[端口]/ -secret [节点密钥] -name "[节点名称]" -webSocket -workDir "C:\jenkins"

参数含义:

  • -jar 指定 agent.jar 路径
  • -url Jenkins Master 地址
  • -secret 节点连接密钥,在 Jenkins 节点配置页拿
  • -name 节点名,必须和 Jenkins 里配的一致
  • -webSocket 走 WebSocket 连接,相比轮询更省资源
  • -workDir 节点工作目录

Details 选项卡(可选):

  • Display nameJenkins 节点服务
  • DescriptionJenkins 自动化测试节点服务

点 “Install service”,提示 Service 'JenkinsNodeService' installed successfully! 就成了。

启动与验证

启动服务:

1
nssm.exe start JenkinsNodeService

查状态:

1
sc query JenkinsNodeService

显示 STATE: RUNNING,去 Jenkins Master 看节点状态变成”已连接”并且稳定不掉线,就说明对了。

真正要验证的是开机自启,光手动启动成功不算数。重启其中一台 Windows 服务器,不做任何 RDP 登录,等机器完全启动后直接看 Jenkins Master。节点能自动连上,才算真正解决。我这边的几台机器都验证通过。

踩过的坑

Secret 不要外泄。节点密钥会出现在命令行和配置里,截图、文档、日志里别用真实值,用占位符代替。

服务账户读不到用户级环境变量。这是最容易忽略的一个坑。Windows 服务默认跑在 Local System 账户下,它读不到用户级的环境变量。表现是节点能连上,但执行构建时找不到 pnpmnode 之类装在用户目录下的命令。

解决方法是把需要的用户级 Path 搬到系统级:

  1. 右键”此电脑” -> 属性 -> 高级系统设置 -> 环境变量
  2. 在”系统变量”的 Path 里补上,比如 C:\Users\[用户名]\AppData\Roaming\npm
  3. 重启服务让变量生效

工作目录权限。Jenkins 工作目录要保证可读写,Local System 一般权限够,但如果改过服务账户就要留意对应账户的访问权限。

取舍

sc 命令是系统自带、零依赖,但它只登记不包装,只适合本身就是服务程序的 exe,也就是那些自己实现了服务控制接口的。对于普通控制台程序、JAR 包、脚本,必须用 NSSM 这类包装器,否则就是 1053。

NSSM 的代价是多一个第三方二进制要维护和分发,换机器得重新装。如果团队就几台服务器,这个成本可以忽略;机器数量多了,可以用 Chocolatey 统一装,或者把 nssm.exe 跟启动脚本一起纳入配置管理。

NSSM 不止能跑 Jenkins 节点。任何控制台程序想做成 Windows 服务都能套这个思路:Node 服务、Python 脚本、Go 编译的 exe,统一用 NSSM 包装就行,不用再给每个程序单独写服务适配层。