Jenkins Agent 以 systemd 方式启动时 conda 找不到命令的排查与解决
背景
在将 Jenkins Agent 从手动命令行启动迁移到 systemd 服务管理后,执行测试任务时出现了如下报错:
1 | 16:21:40 + echo '=== Conda Environment Setup ===' |
奇怪的是,在节点机器上,不管是 root 用户还是 autotest 用户,conda 都是存在的。使用命令行直接启动 agent 时任务执行完全正常,一旦改用 systemctl start jenkins-agent.service 启动,就必然报错。
根本原因
这是一个经典的 systemd 服务环境变量缺失问题。
通过命令行手动执行 java -jar agent.jar ... 时,shell 会加载用户的 .bash_profile 或 .bashrc,其中包含 conda 的初始化脚本(即 conda init 写入的那段代码),PATH 和各类 CONDA_* 环境变量都会被正确设置。
但 systemd 启动服务时,不会加载任何用户 shell 的初始化文件,进程的环境变量只有系统默认的最小集合。conda 的路径不在 PATH 中,CONDA_EXE、CONDA_PYTHON_EXE 等变量也全部缺失,导致 conda shell.bash hook 虽然能被找到并执行,但返回了空字符串,eval '' 自然什么都没初始化,后续的 conda activate 也就彻底失败了。
顺带出现的另一个问题
任务失败后,Jenkins 并没有直接将构建标记为失败,而是一直打印:
1 | 17:18:13 Still waiting to schedule task |
这是因为 agent 因环境异常导致进程崩溃,Jenkins Master 收到的信号是”节点离线”而非”任务正常失败”,因此触发了重新调度逻辑,去寻找同一 label 下其他可用的节点。其他节点已经下线,就会无限等待。
解决这个问题最直接的方式就是修复 conda 环境,让 agent 不再异常退出,任务就会正常返回失败或成功状态,不会再触发重调度。此外,建议在 Pipeline 中加入 timeout 作为兜底:
1 | pipeline { |
两种可行的解决方案
方案一:使用 EnvironmentFile 注入完整环境变量
先以 autotest 用户身份导出 conda 相关的环境变量到一个文件:
1 | su - autotest -c "env | grep -E '^(CONDA|PATH|_CE|JAVA)'" > /home/autotest/vlt/jenkins_home/agent.env |
确认文件内容包含类似如下的条目:
1 | PATH=/home/autotest/tools/miniforge3/bin:/home/autotest/tools/miniforge3/condabin:... |
然后修改 service 文件,用 EnvironmentFile 替换原来手动写的 Environment= 行:
1 | [Unit] |
这种方式适合需要精确控制环境变量、对生产环境有规范管理要求的场景。
方案二:通过 bash login shell 启动(改动最小)
修改 ExecStart,让服务通过 bash 登录模式启动,从而自动加载 .bash_profile 中的完整环境:
1 | ExecStart=/bin/bash -l -c 'exec /usr/java/jdk-17.0.5/bin/java \ |
-l 参数让 bash 以 login shell 模式运行,会自动 source 用户的 .bash_profile,conda init 自然随之生效。这是改动最小、最直接的方式。
操作验证
两种方案修改完成后,执行以下命令使配置生效:
1 | systemctl daemon-reload |
通过日志确认服务状态正常:
1 | journalctl -u jenkins-agent.service -f |
小结
systemd 管理的服务不继承用户 shell 环境,是这类问题最常见的根源。遇到”命令行执行正常,systemd 服务执行报错”的情况,第一反应应该是检查环境变量是否完整,尤其是 PATH 和工具链自身依赖的初始化变量。
对于 conda 这类依赖 shell hook 初始化的工具,仅补充 PATH 往往不够,还需要补全 CONDA_EXE、CONDA_PYTHON_EXE 等变量,或者直接使用 login shell 方式启动,让工具链自己完成初始化。