Debian 13 部署 Jenkins WebSocket Agent:systemd 自启、私有 CA 与 1Panel 排错实录

yaoyue 最后更新于 1 天前 16 次阅读 2818 字


记录时间:2026-10-26 环境:Debian 13 / Jenkins Inbound Agent / systemd / 1Panel + OpenResty / 私有 CA。

目标:在 Debian 13 构建节点上以 root 身份运行 Jenkins Inbound Agent,通过 HTTPS WebSocket 连接 Jenkins Controller,由 systemd 管理开机自启、断线重启和日志,并记录私有根证书、文件目录及反向代理的排错过程。

脱敏声明:文中 jenkins.example.com、192.168.50.10、192.168.50.20、Lab Root CA 均为示例值。Linux-Agent-D13 是演示节点名;实际使用时应替换为自己 Jenkins 上创建的节点名称。不会披露 Agent Secret 或内部 CA 真实私钥。

验证边界:这次现场已确认:原 CA 安装文件为 0 字节,修复后 curl 不再出现原来的证书验证错误;后续出现的 curl (23) 是下载目录不存在,systemd 的 200/CHDIR 是工作目录不存在。对话尚未提供最终 Connected 日志,因此本文把 Agent 上线和 Pipeline 构建列为“最终需验收”,不冒充实测完成。

一、部署架构与端口

Debian 13 构建节点 Linux-Agent-D13
    └── HTTPS / WSS :443(主动出站)
          └── 1Panel / OpenResty 反向代理
                 192.168.50.10:443
                   └── Jenkins Controller
                          192.168.50.20:8080

私有 PKI(示例):
Lab Root CA → Lab Issuing CA → jenkins.example.com
服务 / 端口 网络方向 是否需要
HTTPS / TCP 443 Agent → HTTPS 反向代理 必须
Jenkins 后端 TCP 8080 反向代理 → Controller 仅内网代理侧需要
传统 TCP Inbound Agent 端口(常见 50000) Agent → Controller WebSocket 模式不需要
SSH / TCP 22 运维终端 → Agent 按需限制来源

WebSocket 的主要优势是不需要为 Inbound Agent 再开放单独的 TCP 端口;代理必须支持 HTTP/1.1 协议升级。不要仅凭浏览器能打开 Jenkins 首页就认定 WebSocket 已正常。

二、先在 Jenkins 后台创建节点

  • 在 Manage Jenkins → Nodes 中新建节点,例如 Linux-Agent-D13,类型选择 Permanent Agent。
  • Remote root directory 设为 /data/jenkins,标签可设为 Linux-Agent-D13。
  • Launch method 选择 Launch agent by connecting it to the controller(入站方式)。
  • 在节点页面获取 Jenkins 生成的 Agent Secret。不要把它放进 Shell 历史、公开仓库或博客。

三、Debian 13 安装 Java 与 Agent JAR

1. 安装 Java 运行时

apt update
apt install -y openjdk-21-jre-headless \
  curl ca-certificates ca-certificates-java
java -version
command -v java

示例使用 Java 21。Jenkins Controller 与 Remoting Agent 都受 Jenkins Java 支持策略约束,部署前应核对当前 Controller 版本的最低 Java 要求;构建项目所需 JDK 可以另行配置,不必与 Agent 进程使用的 Java 版本相同。

2. 确认数据盘并创建目录

# 如果 /data 应该是独立磁盘或 NFS,先确认挂载
findmnt /data || true
ls -ld /data 2>/dev/null || true

# 确认 /data 确实位于预期文件系统后创建
install -d -m 0755 /opt/jenkins-agent
install -d -m 0755 /data/jenkins
install -d -m 0700 /etc/jenkins-agent

注意:如果 /data 原本应挂载数据盘或 NFS,切勿在其未挂载的情况下直接开始构建,否则工作空间可能落到系统盘。

3. 从 Controller 下载匹配版本的 agent.jar

curl -fSL \
  https://jenkins.example.com/jnlpJars/agent.jar \
  -o /opt/jenkins-agent/agent.jar

test -s /opt/jenkins-agent/agent.jar
chmod 644 /opt/jenkins-agent/agent.jar
ls -lh /opt/jenkins-agent/agent.jar

curl -o 不会自动创建父目录;若 /opt/jenkins-agent 不存在,就会报 curl: (23) client returned ERROR on write。

四、私有根证书信任与 curl (60) 排查

本例 Jenkins 的 TLS 证书由私有 CA 颁发。Debian 必须信任正确的根证书,并由服务端正确提供叶证书及中间 CA 证书。遇到 curl: (60) SSL certificate problem: self-signed certificate in certificate chain,不要直接通过 -k 长期跳过校验。

1. 检查根 CA 文件是否真的有效

file /usr/local/share/ca-certificates/lab-root-ca.crt
ls -lh /usr/local/share/ca-certificates/lab-root-ca.crt

openssl x509 \
  -in /usr/local/share/ca-certificates/lab-root-ca.crt \
  -noout -subject -issuer -fingerprint -sha256 \
  -ext basicConstraints

此次现场根证书文件虽然存在,系统也有对应的 /etc/ssl/certs/ 软链接,但 file 实际输出 empty、大小 0 字节,所以 OpenSSL 报 Could not find certificate。文件名和软链接存在不代表证书内容有效。

2. 用可信来源的正确 PEM 根证书替换空文件

先从可信任的 CA 发布渠道取得并核对根证书指纹,再安装。以下示例假设已经在 /root/lab-root-ca.pem 保存并验证了 PEM 编码根证书;不要复制未经验证的证书到系统信任库。

openssl x509 -in /root/lab-root-ca.pem \
  -noout -subject -issuer -fingerprint -sha256

# 如果原目标文件确认为 0 字节,先备份
test ! -e /usr/local/share/ca-certificates/lab-root-ca.crt \
  || cp -a /usr/local/share/ca-certificates/lab-root-ca.crt \
    /root/lab-root-ca.crt.backup

install -m 0644 /root/lab-root-ca.pem \
  /usr/local/share/ca-certificates/lab-root-ca.crt

update-ca-certificates --fresh

如果源证书是 DER 编码,需要先使用 openssl x509 -inform DER -in source.cer -out /root/lab-root-ca.pem 转换为 PEM。重新安装后再执行 curl -vI https://jenkins.example.com/。此次问题是空文件,不是已确认的 DER 格式问题。

3. 验证服务器证书链

openssl s_client \
  -connect jenkins.example.com:443 \
  -servername jenkins.example.com \
  -verify_hostname jenkins.example.com \
  -CAfile /etc/ssl/certs/ca-certificates.crt \
  -showcerts </dev/null 2>&1 | \
  grep -E 's:|i:|verify error:|Verify return code:'

curl -vI https://jenkins.example.com/

预期看到 Verify return code: 0 (ok) 且域名匹配。如果缺失中间 CA,应修复 Jenkins HTTPS 前端提供的 fullchain,不应把私钥或不必要的 CA 凭据分发到 Agent。若 curl 已通过而 Java 仍报 PKIX path building failed,再核对 ca-certificates-java 和实际 JVM 的 truststore。

五、保存 Secret 并用 root 运行 systemd

1. 将 Secret 保存到 root 专用文件

nano /etc/jenkins-agent/secret
chown root:root /etc/jenkins-agent/secret
chmod 600 /etc/jenkins-agent/secret

文件内容只保留 Agent Secret 本身,避免将 Secret 拼接进 ExecStart=。Jenkins Remoting 支持 -secret @/path/to/file 从文件读取。

2. 创建 systemd Unit

写入 /etc/systemd/system/jenkins-agent.service,以下是本例选择 root 身份运行的完整配置:

[Unit]
Description=Jenkins Inbound Agent - Linux-Agent-D13
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/data/jenkins

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/data/jenkins
ExecStart=/usr/bin/java \
    -jar /opt/jenkins-agent/agent.jar \
    -url https://jenkins.example.com/ \
    -secret @/etc/jenkins-agent/secret \
    -name Linux-Agent-D13 \
    -webSocket \
    -workDir /data/jenkins
Restart=always
RestartSec=10
TimeoutStopSec=30
SuccessExitStatus=143
UMask=0077
StandardOutput=journal
StandardError=journal
SyslogIdentifier=jenkins-agent

[Install]
WantedBy=multi-user.target

RequiresMountsFor=/data/jenkins 用于声明挂载依赖,不会自动创建目录。启动之前仍必须确保路径已经存在,并且数据盘已按预期挂载。

3. 启动与查看日志

systemd-analyze verify /etc/systemd/system/jenkins-agent.service
systemctl daemon-reload
systemctl enable --now jenkins-agent

systemctl status jenkins-agent --no-pager -l
journalctl -u jenkins-agent -n 100 --no-pager
journalctl -u jenkins-agent -f

确认 Agent 真正连接成功,应以日志中的连接状态和 Jenkins Nodes 页面显示 Online 为准,Active: active (running) 只能证明进程已启动,不能代替服务端连接验收。

六、1Panel / OpenResty WebSocket 反向代理

现场原配置使用 proxy_set_header Connection $http_connection;,它只是透传客户端的 Connection 请求头,不是最稳妥的 WebSocket 处理方式。推荐在 Nginx http 作用域使用 map:

map $http_upgrade $jenkins_connection_upgrade {
    default upgrade;
    ''      '';
}

然后在 Jenkins 对应的 server 内使用下面的 location(注意上面 map 不能写在 location 里面):

location ^~ / {
    proxy_pass http://192.168.50.20:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $jenkins_connection_upgrade;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Port $server_port;

    proxy_connect_timeout 30s;
    proxy_send_timeout 3600s;
    proxy_read_timeout 3600s;
    proxy_request_buffering off;
}

如果 1Panel 只允许编辑局部 location 而无法配置 http 作用域,可临时使用 proxy_set_header Connection "upgrade"; 替代变量方案;这会对普通 HTTP 请求也设置 upgrade,不如 map 精细。应用后应做 Nginx 语法检查并重新加载配置。不要在 proxy_pass 的上游 URL 末尾随意追加斜杠。

Jenkins 本身的 Jenkins URL 也应与浏览器的 HTTPS 域名一致。若全部 Inbound Agent 均使用 WebSocket,可以在 Jenkins 安全设置中禁用独立 TCP Agent 监听器;如果旧节点仍依赖传统 TCP,则不能直接关闭。

七、故障速查:本次实际遇到的三个错误

现象 定位结论 处理
curl (60) + self-signed certificate in certificate chain 本次发现已安装 CA 文件是 0 字节,系统没有有效的可信根证书 检查 file、openssl x509,从可信来源重新安装 PEM 并刷新 CA
curl (23) + Failed to open the file ... No such file or directory Agent JAR 的目标父目录不存在 先创建 /opt/jenkins-agent,再重新下载
status=200/CHDIR systemd 在启动 Java 前无法进入 WorkingDirectory=/data/jenkins 确认 /data 挂载并创建目录,重启服务

额外排错方向

  • WebSocket 400/403:核查节点名称、Secret、Jenkins 认证要求,以及代理是否正确传递 Upgrade 与 Connection。
  • PKIX path building failed:通常是 Java 信任链问题;需检查使用的 JDK/JRE 和 CA truststore。
  • 200/CHDIR 且已写了 ExecStartPre=mkdir:仍可能失败,因为进入工作目录发生在执行服务命令之前;应在服务启动前创建目录。
  • Agent 反复退出:用 journalctl -u jenkins-agent 检查实际错误;不要仅依赖自动重启掩盖问题。

八、连接验收、构建权限与后续维护

1. 验收清单

  • curl -vI 的 TLS 校验通过,没有使用 -k。
  • test -s /opt/jenkins-agent/agent.jar 成功,Java 可正常执行。
  • findmnt /data 与实际磁盘规划一致,/data/jenkins 存在。
  • systemctl status jenkins-agent 显示进程运行,并且日志出现成功连接信息。
  • Jenkins Nodes 页面中节点为 Online,实际运行一次构建。

2. 验证构建用户

pipeline {
    agent { label 'Linux-Agent-D13' }
    stages {
        stage('Verify Agent') {
            steps {
                sh '''
                    whoami
                    id
                    java -version
                    pwd
                '''
            }
        }
    }
}

安全提醒:本示例按照现场需求使用 root 身份运行,意味着该节点执行的 Pipeline Shell 命令具有宿主机 root 权限。只应将它分配给经过审核的可信任务,严格控制 Jenkins 用户的配置、脚本修改权限及外部 PR 的执行范围。更普遍的生产建议是使用最小权限专用用户。

3. Jenkins 升级后更新 agent.jar

curl -fSL \
  https://jenkins.example.com/jnlpJars/agent.jar \
  -o /opt/jenkins-agent/agent.jar.new
test -s /opt/jenkins-agent/agent.jar.new

systemctl stop jenkins-agent
mv /opt/jenkins-agent/agent.jar.new \
  /opt/jenkins-agent/agent.jar
chmod 644 /opt/jenkins-agent/agent.jar
systemctl start jenkins-agent
journalctl -u jenkins-agent -n 50 --no-pager

此示例不在每次 systemd 启动时实时下载 JAR,以免 Controller 短暂不可用时阻碍本地服务启动。

参考资料

本文将已确认的本机错误与可复用配置分开记录。示例命令需要按照自己的 Jenkins URL、节点名称、CA 指纹和磁盘挂载方式调整,未经验证的步骤不应视为已经在现场执行成功。