Jenkins 构建机与 CE 部署机分离时,自研 P4 工具链通过 rsync/SSH 发布,私钥只保存在 Jenkins Credentials。
Jenkins(私钥)
│ rsync/ssh → ce-deploy@部署机(公钥)
│ └─ scripts/toolchains/deploy-p4.sh → CE_COMPILERS_ROOT
│ └─ 9p 只读挂载 → VM /opt/compiler-explorer
└─ ssh -J ce-deploy@部署机 → ce@127.0.0.1:2223(同一公钥)
└─ sudo systemctl restart ce.service
只使用一把密钥:
- 私钥只存于 Jenkins Credential,不出现在任何服务器磁盘上。
- 公钥安装两处:部署机
ce-deploy的authorized_keys和 VM 内ce的authorized_keys。 - 不给
ce-deploy单独配 VM 私钥:Jenkins 能以ce-deploy执行命令时,第二把私钥会被直接读走,不构成隔离。 - 普通配置(主机、路径、端口)写在 Jenkinsfile 的
environment块,不进 Credentials——Credential 值在日志中会被遮蔽且不可审计。
以下示例假设仓库位于 /srv/ce/repo,按实际路径替换。
sudo useradd --system --create-home \
--home-dir /var/lib/ce-deploy --shell /bin/bash --user-group ce-deploy
sudo passwd -l ce-deploy
id ce-deploy # 应只有 ce-deploy 自己的组,无 sudo/docker/kvmDEPLOY_REPO=/srv/ce/repo
TOOLCHAIN_ROOT=/srv/ce/compilers
INCOMING_ROOT=/srv/ce/incoming
sudo install -d -o ce-deploy -g ce-deploy -m 0750 "${INCOMING_ROOT}"
# 工具链根:发布与清理旧版本需要组写
sudo chgrp -R ce-deploy "${TOOLCHAIN_ROOT}"
sudo chmod -R g+rwX "${TOOLCHAIN_ROOT}"
sudo find "${TOOLCHAIN_ROOT}" -type d -exec chmod g+s {} +目录位于其他用户家目录下时(如 /home/<user>/...):对路径上每一级父目录只授 x(可穿过、不可列出),目标目录按上表授权,不要把 ce-deploy 加入家目录属主的组。用 namei -l <路径> 检查整条路径权限。
sudo setfacl -m u:ce-deploy:--x /home/<user>
sudo setfacl -m u:ce-deploy:--x /home/<user>/<每一级父目录>sudo -u ce-deploy test -x "${DEPLOY_REPO}/scripts/toolchains/deploy-p4.sh" || {
sudo setfacl -m u:ce-deploy:r-x "${DEPLOY_REPO}/scripts"
sudo setfacl -m u:ce-deploy:r-x "${DEPLOY_REPO}/scripts/toolchains"
sudo setfacl -m u:ce-deploy:r-x "${DEPLOY_REPO}/scripts/toolchains/deploy-p4.sh"
sudo setfacl -m u:ce-deploy:r-- "${DEPLOY_REPO}/scripts/toolchains/lib.sh"
}ce-deploy 不需要读取仓库 .env,所需变量由 Jenkins 显式传递。
sudo install -d -o ce-deploy -g ce-deploy -m 0700 /var/lib/ce-deploy/.ssh
# 将 JENKINS_SOURCE_IP 换成 Jenkins 实际出口 IP,公钥为一行 ed25519 公钥
printf '%s %s\n' \
'from="JENKINS_SOURCE_IP",no-agent-forwarding,no-X11-forwarding,no-pty,permitopen="127.0.0.1:2223"' \
'ssh-ed25519 AAAA... jenkins-ce-deploy' |
sudo tee /var/lib/ce-deploy/.ssh/authorized_keys
sudo chown ce-deploy:ce-deploy /var/lib/ce-deploy/.ssh/authorized_keys
sudo chmod 0600 /var/lib/ce-deploy/.ssh/authorized_keys不能用 restrict(会禁止 TCP 转发,ProxyJump 依赖它);permitopen="127.0.0.1:2223" 已把转发限定到 VM SSH 端口。
把同一公钥放到 Compose 可读取的位置,并写入 .env:
install -D -m 0644 jenkins_ce_deploy.pub \
/srv/ce/keys/jenkins_ce_deploy.pubCE_VM_SSH_PUBKEY=/srv/ce/keys/jenkins_ce_deploy.pub以后重建 overlay 时 cloud-init 会自动注入。当前运行的 VM 用旧管理密钥手动追加一次:
cat /srv/ce/keys/jenkins_ce_deploy.pub |
ssh -i "${CE_VM_SSH_KEY}" -p "${CE_VM_SSH_PORT:-2223}" -o IdentitiesOnly=yes \
ce@127.0.0.1 '
set -eu; umask 077
install -d -m 0700 ~/.ssh
touch ~/.ssh/authorized_keys && chmod 0600 ~/.ssh/authorized_keys
IFS= read -r key
grep -qxF "$key" ~/.ssh/authorized_keys || printf "%s\n" "$key" >> ~/.ssh/authorized_keys
'没有任何旧密钥可用时,只能等新公钥随 overlay 重建注入。
- 在安全机器生成密钥:
ssh-keygen -t ed25519 -f jenkins_ce_deploy -C jenkins@ce-deploy -N ''。 - Jenkins → Credentials →
SSH Username with private key:Usernamece-deploy,IDce-deploy-host,粘贴私钥。 - 公钥按上文安装到部署机和 VM。
放在工具链构建仓库根目录,只需替换 Build 阶段的构建命令并核对 environment 中的主机与路径。
pipeline {
agent { label 'toolchain-builder' }
options {
timestamps()
disableConcurrentBuilds() // 发布串行,避免软链竞争
buildDiscarder(logRotator(numToKeepStr: '20'))
}
environment {
DEPLOY_HOST = 'ce-deploy@poweredger770'
DEPLOY_REPO = '/srv/ce/repo'
TOOLCHAIN_ROOT = '/srv/ce/compilers'
P4_RETENTION_DAYS = '7' // 日期窗口和真实 build 上限;0 = 不限
INCOMING_ROOT = '/srv/ce/incoming'
VM_SSH_PORT = '2223'
SSH_OPTS = '-o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new'
INSTALL_DIR = "${WORKSPACE}/install"
}
stages {
stage('Build') {
steps {
sh '''
set -eu
# TODO: 换成实际构建命令,把产物安装到 ${INSTALL_DIR}
echo "构建产物输出到 ${INSTALL_DIR}"
'''
}
}
stage('Validate') {
steps {
sh '''
set -eu
test -x "${INSTALL_DIR}/bin/p4c"
test -x "${INSTALL_DIR}/bin/p4mlir-opt"
chmod -R a+rX "${INSTALL_DIR}" # VM 内以只读挂载执行
'''
}
}
stage('Package') {
steps {
sh '''
set -eu
BUILD_TIMESTAMP="$(date +%Y%m%d%H%M)"
SHORT_COMMIT="$(git rev-parse --short=12 HEAD)"
PKG="p4mlir-${BUILD_TIMESTAMP}-${BUILD_NUMBER}-${SHORT_COMMIT}.tar.zst"
echo "${PKG}" > "${WORKSPACE}/.toolchain-pkg"
tar -cf - -C "${INSTALL_DIR}" . \
| zstd -T0 -q -o "${WORKSPACE}/${PKG}"
'''
}
}
stage('Upload') {
steps {
sshagent(credentials: ['ce-deploy-host']) {
sh '''
set -eu
PKG="$(cat "${WORKSPACE}/.toolchain-pkg")"
rsync -a --partial -e "ssh ${SSH_OPTS}" \
"${WORKSPACE}/${PKG}" \
"${DEPLOY_HOST}:${INCOMING_ROOT}/${PKG}"
'''
}
}
}
stage('Deploy') {
steps {
sshagent(credentials: ['ce-deploy-host']) {
sh '''
set -eu
PKG="$(cat "${WORKSPACE}/.toolchain-pkg")"
ssh ${SSH_OPTS} "${DEPLOY_HOST}" \
"CE_COMPILERS_ROOT='${TOOLCHAIN_ROOT}' \
P4_TOOLCHAIN_RETENTION_DAYS='${P4_RETENTION_DAYS}' CE_DEFER_RESTART=1 \
'${DEPLOY_REPO}/scripts/toolchains/deploy-p4.sh' \
'${INCOMING_ROOT}/${PKG}'"
'''
}
}
}
stage('Restart CE') {
steps {
sshagent(credentials: ['ce-deploy-host']) {
sh '''
set -eu
ssh ${SSH_OPTS} -J "${DEPLOY_HOST}" -p "${VM_SSH_PORT}" \
ce@127.0.0.1 \
'sudo systemctl restart ce.service && \
sudo systemctl is-active ce.service'
'''
}
}
}
stage('Verify') {
steps {
sshagent(credentials: ['ce-deploy-host']) {
sh '''
set -eu
ssh ${SSH_OPTS} -J "${DEPLOY_HOST}" -p "${VM_SSH_PORT}" \
ce@127.0.0.1 \
'/opt/compiler-explorer/p4-latest/bin/p4c --version | head -1'
'''
}
}
}
}
post {
always {
sshagent(credentials: ['ce-deploy-host']) {
sh '''
if [ -f "${WORKSPACE}/.toolchain-pkg" ]; then
PKG="$(cat "${WORKSPACE}/.toolchain-pkg")"
ssh ${SSH_OPTS} "${DEPLOY_HOST}" \
"rm -f -- '${INCOMING_ROOT}/${PKG}'" || true
fi
'''
}
cleanWs()
}
}
}要点:
CE_DEFER_RESTART=1让发布脚本只切换软链,由Restart CE阶段统一重启 VM 内的ce.service。P4_TOOLCHAIN_RETENTION_DAYS默认为7,同时表示日期窗口和真实 build 上限;p4-latest不计数。dated build 每天只保留最新一个,legacy build 填补剩余名额;设为0时不限窗口和总数,但仍每日去重。- 标准包名中的
${BUILD_TIMESTAMP}-${BUILD_NUMBER}-${SHORT_COMMIT}用于唯一标识和每日选择。菜单对 dated build 显示YYYY-MM-DD,对旧格式显示 build number;生产端应始终使用一致时区生成时间戳。 post.always清理部署机上的临时 tarball;deploy-p4.sh已把内容解压进compilers,删除 tarball 不影响已发布版本。StrictHostKeyChecking=accept-new适合首次接入,稳定后建议在 agent 上预置known_hosts并固定指纹。- 发布其他工具链时复制相应
deploy-*.sh的模式;deploy-p4.sh接收新格式p4mlir-<yyyyMMddHHmm>-<buildNumber>-<commit>.tar.zst,并兼容旧格式.tar.gz|zst。构建机和部署机处理 zst 时都需要zstd;归档必须包含bin/p4c、bin/p4mlir-opt、bin/p4mlir-translate、bin/p4mlir-to-json、bin/mlir-translate及bin/opt/bin/llc/bin/llvm-objdump/bin/llvm-cxxfilt。
# 登录与写权限
ssh -i jenkins_ce_deploy ce-deploy@poweredger770 \
'id && test -w /srv/ce/incoming'
# 不能列出他人家目录(应 Permission denied)
ssh -i jenkins_ce_deploy ce-deploy@poweredger770 'ls /home/<user>'
# ProxyJump 进 VM 并操作 CE
ssh -i jenkins_ce_deploy -J ce-deploy@poweredger770 -p 2223 \
ce@127.0.0.1 'sudo systemctl is-active ce.service'| 主体 | 能做什么 | 不能做什么 |
|---|---|---|
| Jenkins 私钥 | 仅存于 Jenkins Credentials | 不在任何服务器磁盘上 |
ce-deploy |
写 incoming/compilers、执行部署脚本、转发到 127.0.0.1:2223 |
无 sudo/docker/kvm、不能读取他人家目录、不能写仓库、不能转发到其他地址 |
VM 内 ce |
免密 restart/status/is-active ce.service |
无其他 root 权限 |
整个发布只更新工具链目录并重启 ce.service,不重建 Docker 镜像、Ubuntu 基础镜像或 CE overlay。