jenkins-agent-l3-deploy

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Jenkins-agent L3 deploy (no SSH)

Jenkins Agent L3 部署(无 SSH)

Deploy a docker-compose stack where a Jenkins inbound agent on the target host runs
deploy.py
+
docker compose
locally
. The controller never SSHes in. Runtime secrets are written to tmpfs
/run/payroll-sec
(file 0444, dir 0700)
, mounted as Docker secrets at
/run/secrets/<name>
, and a tiny entrypoint shim exports them to env at container start — so they are absent from
docker inspect
and from
.env
.
部署一个 docker-compose stack,其中目标主机上的 Jenkins inbound agent 在本地运行
deploy.py
+
docker compose
。controller 绝不会通过 SSH 登录。 运行时 secret 写入 tmpfs
/run/payroll-sec
(文件 0444,目录 0700)
,以 Docker secrets 的形式挂载到
/run/secrets/<name>
;一个轻量 entrypoint shim 会在容器启动时将它们导出为 env,因此它们不会出现在
docker inspect
或
.env
中。

WHAT

内容

Purpose và context

目的与上下文

  • Purpose: dựng/chạy pipeline Jenkins deploy một stack docker-compose QUA inbound agent trên chính target (controller không SSH vào), với secret L3: chỉ nằm trên tmpfs, mount làm Docker secret, không có trong
    docker inspect
    lẫn
    .env
    .
  • Trigger (when to use vs the SSH variant):
    • Agent (this skill, preferred): an inbound agent runs on the target →
      sh
      steps execute locally; only a
      Secret file
      credential (the cred bundle) is needed. Controller opens no SSH outbound.
    • SSH variant: controller
      ssh/scp/rsync
      into the target (needs an
      SSH Username with private key
      credential). Use only when you cannot run an agent on the target.
    • User nói "deploy via jenkins agent", "no-ssh deploy", "agent thay vì ssh", "docker secrets L3", "ẩn secret khỏi docker inspect", "secret không nằm trên đĩa", hoặc đang nối pipeline Jenkins deploy docker-compose lên host Linux.
  • Non-goals: không dựng biến thể SSH (chỉ khi không chạy được agent); không sửa code ứng dụng để đọc secret (shim entrypoint lo); không cài Jenkins controller.
  • **目的:**搭建/运行一个 Jenkins pipeline,通过目标主机自身的 inbound agent 部署 docker-compose stack(controller 不通过 SSH 登录),并采用 L3 secret:只存在于 tmpfs 上、挂载为 Docker secret,且不出现在
    docker inspect
    和
    .env
    中。
  • 触发条件(何时使用此方案而非 SSH 变体):
    • Agent(此 skill,首选): inbound agent 运行在目标主机上 →
      sh
      steps 在本地执行;只需要一个
      Secret file
      credential(cred bundle)。Controller 不会建立任何出站 SSH 连接。
    • SSH 变体: controller 通过
      ssh/scp/rsync
      进入目标主机(需要
      SSH Username with private key
      credential)。仅在目标主机无法运行 agent 时使用。
    • 用户提出“通过 Jenkins Agent 部署”“无 SSH 部署”“用 Agent 代替 SSH”“Docker secrets L3”“从 Docker inspect 中隐藏 secret”“secret 不落盘”等需求,或正在配置将 docker-compose 部署到 Linux 主机的 Jenkins pipeline。
  • **非目标:**不搭建 SSH 变体(仅在无法运行 agent 时采用);不修改应用代码来读取 secret(由 shim entrypoint 完成);不安装 Jenkins controller。

Mental model

心智模型

Architecture (L3):
secret source (cred bundle)
   → deploy provisioner writes 7 files to tmpfs /run/payroll-sec  (file 0444, dir 0700 root)
   → docker-compose `secrets:` mounts each at /run/secrets/<name> (tmpfs, ro)
   → secrets-entrypoint.sh in each image: for f in /run/secrets/*; export $(UPPER basename)=$(cat f); exec "$@"
   ⇒ secret NOT in `docker inspect` (set at runtime), NOT on disk (RAM only)
postgres
uses the official
POSTGRES_PASSWORD_FILE
; app containers use the entrypoint shim so no application code changes (avoids the Edge-runtime
fs
trap if a framework imports the secret module in middleware).
架构(L3):
secret source (cred bundle)
   → deploy provisioner writes 7 files to tmpfs /run/payroll-sec  (file 0444, dir 0700 root)
   → docker-compose `secrets:` mounts each at /run/secrets/<name> (tmpfs, ro)
   → secrets-entrypoint.sh in each image: for f in /run/secrets/*; export $(UPPER basename)=$(cat f); exec "$@"
   ⇒ secret NOT in `docker inspect` (set at runtime), NOT on disk (RAM only)
postgres
使用官方提供的
POSTGRES_PASSWORD_FILE
;应用容器使用 entrypoint shim,因此无需修改应用代码(可避免框架在 middleware 中导入 secret module 时触发 Edge-runtime
fs
陷阱)。

Input và output contract

输入与输出约定

FieldRequired?Ý nghĩa
InJenkins controller reachable
:8080
+
:50000
từ target
cóagent phải kết nối được
Intarget Linux: Java 21, user thuộc docker group + sudo NOPASSWDcóxem Gotchas 1, 4
In
Secret file
credential (cred bundle)
cókhông cần SSH credential
Inrepo app có
docker-compose.yml
,
deploy.py(.example)
, Dockerfile +
secrets-entrypoint.sh
cóqua SCM checkout hoặc workspace có sẵn
Intick chọn service (WEB/ETL/DB/NGINX/ALL)khôngselective deploy; mặc định full
Outstack chạy trên targetcóbuild console
Running on <target>
Outtín hiệu verifycó
CLEAN <c>
+
MOUNT <c>
mọi container, smoke 200, cred bundle đã shred
字段是否必填?含义
输入Jenkins controller 可从 target 访问
:8080
+
:50000
是agent 必须能够连接
输入target Linux:Java 21,用户属于 docker group 且拥有 sudo NOPASSWD是见 Gotchas 1、4
输入
Secret file
credential(cred bundle)
是不需要 SSH credential
输入应用 repo 包含
docker-compose.yml
、
deploy.py(.example)
、Dockerfile 和
secrets-entrypoint.sh
是通过 SCM checkout 获取,或使用已有 workspace
输入勾选要部署的 service(WEB/ETL/DB/NGINX/ALL)否选择性部署;默认执行完整部署
输出stack 在 target 上运行是build console 显示
Running on <target>
输出验证信号是每个容器均出现
CLEAN <c>
+
MOUNT <c>
,smoke 返回 200,cred bundle 已 shredded

Rules và capabilities

规则与能力

  • RULE-01 (MUST): Don't pass secrets via compose
    environment:
    (visible in
    docker inspect
    , written to
    .env
    ).
  • RULE-02 (MUST): Don't store the secret files 0600 (non-root container can't read) — use 0444 + dir 0700.
  • RULE-03 (MUST): Don't rely on the SSH variant if you can run an agent — agent keeps SSH closed and runs locally.
  • RULE-04 (MUST): Don't point a
    file://
    SCM URL at a path the controller can't see — use a network git URL.
  • RULE-05 (MUST): Gotchas 1–5 (mỗi cái đã làm hỏng một lần chạy thật) phải được bake vào setup/pipeline.
  • Capabilities: cấu hình node/credential/job trên Jenkins; chạy shell + docker + sudo trên target; đọc git repo (SCM). Không mở SSH outbound từ controller.
  • RULE-01(MUST):不得通过 compose
    environment:
    传递 secret(这会暴露在
    docker inspect
    中,并写入
    .env
    )。
  • RULE-02(MUST):不得将 secret 文件设为 0600(非 root 容器无法读取)——应使用 0444 文件权限和 0700 目录权限。
  • RULE-03(MUST):如果可以运行 agent,则不得依赖 SSH 变体——agent 可保持 SSH 关闭并在本地执行。
  • RULE-04(MUST):不得将
    file://
    SCM URL 指向 controller 无法访问的路径——应使用网络 git URL。
  • RULE-05(MUST):Gotchas 1–5(每一项都曾导致一次真实运行失败)必须固化到 setup/pipeline 中。
  • 能力:在 Jenkins 上配置 node/credential/job;在 target 上运行 shell + docker + sudo;读取 git repo(SCM)。不会从 controller 发起任何出站 SSH 连接。

Failure boundaries

失败边界

  • Không chạy được agent trên target → ngoài phạm vi, dùng biến thể SSH (blocked cho skill này).
  • Stage Verify in
    LEAK <c>
    hoặc
    NOMOUNT <c>
    → pipeline failed (exit 1), không coi là deploy xong.
  • Agent chết
    UnsupportedClassVersionError
    / permission denied docker-sudo / secret 0600 → failed, sửa theo bảng Gotchas rồi chạy lại.
  • Repo private chưa có deploy key / known_hosts → blocked ở checkout.
  • 无法在 target 上运行 agent → 不在此 skill 的处理范围内,应使用 SSH 变体(对此 skill 而言为 blocked)。
  • Verify stage 出现
    LEAK <c>
    或
    NOMOUNT <c>
    → pipeline failed(exit 1),不能视为部署完成。
  • Agent 因
    UnsupportedClassVersionError
    退出、docker-sudo 出现 permission denied,或 secret 权限为 0600 → failed;按 Gotchas 表修复后重新运行。
  • Private repo 缺少 deploy key/known_hosts → 在 checkout 阶段 blocked。

HOW

操作方法

Main workflow

主要工作流

StepTypeInputsActionOutputs/exitFailure/next
W01judgmenthạ tầng targetChọn agent (ưu tiên) hay SSH variantquyết địnhkhông chạy được agent → dừng, SSH variant
W02effectJenkins + targetSetup inbound agent (once): node, Java 21, agent.jar, user docker+sudo,
Secret file
credential
agent onlinegotcha 1/4 → sửa, lặp
W03effectrepo appBake
secrets-entrypoint.sh
vào mỗi image (ENTRYPOINT trước CMD); provisioner ghi secret 0444/0700
image + provisioner—
W04effectjobPipeline template (Sync → Deploy → Verify), code từ SCMjob chạycheckout lỗi → B02
W05deterministicbuildVerify success (real signals)
Running on <target>
,
CLEAN
,
MOUNT
, smoke 200, cred shredded
LEAK/NOMOUNT → failed
Chi tiết từng bước (nguồn chân lý cho W01–W05):
Step类型输入操作输出/退出状态失败/下一步
W01判断target 基础设施选择 agent(优先)或 SSH 变体决策无法运行 agent → 停止,改用 SSH 变体
W02执行Jenkins + target设置 inbound agent(一次性):node、Java 21、agent.jar、具备 docker+sudo 权限的用户、
Secret file
credential
agent onlinegotcha 1/4 → 修复后重试
W03执行应用 repo将
secrets-entrypoint.sh
固化到每个 image(ENTRYPOINT 位于 CMD 之前);provisioner 写入权限为 0444/0700 的 secret
image + provisioner—
W04执行jobPipeline 模板(Sync → Deploy → Verify),代码来自 SCMjob 运行checkout 失败 → B02
W05确定性build验证成功(真实信号)
Running on <target>
、
CLEAN
、
MOUNT
、smoke 200、cred shredded
LEAK/NOMOUNT → failed
各步骤详情(W01–W05 的事实来源):

Setup the inbound agent (once)

设置 inbound agent(一次性)

  1. Jenkins → Manage Jenkins → Nodes → New Node: Permanent Agent, label =
    <target>
    , Remote root =
    /home/<user>/jenkins-agent
    , Launch = inbound (JNLP); copy the secret.
  2. On the target (agent must reach controller
    :8080
    +
    :50000
    ):
    bash
    sudo apt-get install -y openjdk-21-jre-headless          # Java 21 — NOT 17 (see gotcha 1)
    J21=$(ls /usr/lib/jvm/java-21-openjdk-*/bin/java | head -1)
    mkdir -p ~/jenkins-agent && cd ~/jenkins-agent
    curl -sO http://<CONTROLLER>:8080/jnlpJars/agent.jar
    nohup "$J21" -jar agent.jar -url http://<CONTROLLER>:8080/ \
      -secret <AGENT_SECRET> -name <target> -workDir ~/jenkins-agent > agent.log 2>&1 &
    Run the agent as a user that is in the docker group AND has sudo NOPASSWD. For production replace
    nohup
    with a systemd unit (
    Restart=always
    ).
  3. Credential: one
    Secret file
    credential (the cred bundle). No SSH credential.
  1. Jenkins → Manage Jenkins → Nodes → New Node:创建 Permanent Agent,label =
    <target>
    ,Remote root =
    /home/<user>/jenkins-agent
    ,Launch = inbound (JNLP);复制该 secret。
  2. 在 target 上执行(agent 必须能够访问 controller 的
    :8080
    +
    :50000
    ):
    bash
    sudo apt-get install -y openjdk-21-jre-headless          # Java 21 — NOT 17 (see gotcha 1)
    J21=$(ls /usr/lib/jvm/java-21-openjdk-*/bin/java | head -1)
    mkdir -p ~/jenkins-agent && cd ~/jenkins-agent
    curl -sO http://<CONTROLLER>:8080/jnlpJars/agent.jar
    nohup "$J21" -jar agent.jar -url http://<CONTROLLER>:8080/ \
      -secret <AGENT_SECRET> -name <target> -workDir ~/jenkins-agent > agent.log 2>&1 &
    使用同时属于 docker group 且拥有 sudo NOPASSWD 权限的用户运行 agent。 生产环境应使用 systemd unit 替代
    nohup
    (
    Restart=always
    )。
  3. Credential:一个
    Secret file
    credential(cred bundle)。不需要 SSH credential。

Pipeline template (declarative)

Pipeline 模板(declarative)

groovy
pipeline {
  agent { label '<target>' }                 // runs ON the target, no ssh
  environment { APP = '/home/<user>/<app-dir>' }
  stages {
    stage('Sync') { steps { sh '''
      set -e
      cp -f docker-compose.yml deploy.py.example "$APP/"
      cp -f web/Dockerfile web/secrets-entrypoint.sh "$APP/web/"
      cp -f etl/Dockerfile etl/secrets-entrypoint.sh "$APP/etl/"
      cd "$APP" && cp -f deploy.py.example deploy.py
    ''' } }
    stage('Deploy') { steps { withCredentials([file(credentialsId:'app-cred', variable:'CRED')]) { sh '''
      set -e
      mkdir -p /dev/shm/pl && chmod 700 /dev/shm/pl
      cp "$CRED" /dev/shm/pl/cred.txt && chmod 600 /dev/shm/pl/cred.txt   # gotcha 3
      cd "$APP" && ln -sf /dev/shm/pl/cred.txt cred.txt
      sudo python3 deploy.py --no-nginx --etl-url /etl
      rm -f cred.txt; shred -uf /dev/shm/pl/cred.txt 2>/dev/null || rm -f /dev/shm/pl/cred.txt
    ''' } } }
    stage('Verify') { steps { sh '''
      set -e
      for c in <containers>; do
        docker inspect "$c" --format "{{range .Config.Env}}{{println .}}{{end}}" \
          | grep -iqE "SESSION_SECRET=|AUTH_USERS=|PASSWORD=|DATABASE_URL=" && { echo "LEAK $c"; exit 1; } || echo "CLEAN $c"
        docker exec "$c" ls /run/secrets >/dev/null 2>&1 && echo "MOUNT $c" || { echo "NOMOUNT $c"; exit 1; }
      done
    ''' } }
  }
}
Provide the workspace code via SCM checkout, or pre-populate the agent workspace.
groovy
pipeline {
  agent { label '<target>' }                 // runs ON the target, no ssh
  environment { APP = '/home/<user>/<app-dir>' }
  stages {
    stage('Sync') { steps { sh '''
      set -e
      cp -f docker-compose.yml deploy.py.example "$APP/"
      cp -f web/Dockerfile web/secrets-entrypoint.sh "$APP/web/"
      cp -f etl/Dockerfile etl/secrets-entrypoint.sh "$APP/etl/"
      cd "$APP" && cp -f deploy.py.example deploy.py
    ''' } }
    stage('Deploy') { steps { withCredentials([file(credentialsId:'app-cred', variable:'CRED')]) { sh '''
      set -e
      mkdir -p /dev/shm/pl && chmod 700 /dev/shm/pl
      cp "$CRED" /dev/shm/pl/cred.txt && chmod 600 /dev/shm/pl/cred.txt   # gotcha 3
      cd "$APP" && ln -sf /dev/shm/pl/cred.txt cred.txt
      sudo python3 deploy.py --no-nginx --etl-url /etl
      rm -f cred.txt; shred -uf /dev/shm/pl/cred.txt 2>/dev/null || rm -f /dev/shm/pl/cred.txt
    ''' } } }
    stage('Verify') { steps { sh '''
      set -e
      for c in <containers>; do
        docker inspect "$c" --format "{{range .Config.Env}}{{println .}}{{end}}" \
          | grep -iqE "SESSION_SECRET=|AUTH_USERS=|PASSWORD=|DATABASE_URL=" && { echo "LEAK $c"; exit 1; } || echo "CLEAN $c"
        docker exec "$c" ls /run/secrets >/dev/null 2>&1 && echo "MOUNT $c" || { echo "NOMOUNT $c"; exit 1; }
      done
    ''' } }
  }
}
通过 SCM checkout 提供 workspace 代码,或预先填充 agent workspace。

secrets-entrypoint.sh (baked into each app image; ENTRYPOINT before CMD)

secrets-entrypoint.sh(内置到每个 app image;ENTRYPOINT 位于 CMD 之前)

sh
#!/bin/sh
set -e
if [ -d /run/secrets ]; then
  for f in /run/secrets/*; do [ -f "$f" ] || continue
    name=$(basename "$f" | tr '[:lower:]' '[:upper:]'); export "$name=$(cat "$f")"
  done
fi
exec "$@"
The provisioner (e.g.
deploy.py
) writes the secret files to a tmpfs dir mode 0444 (files) / 0700 (dir), then
docker compose up -d
.
sh
#!/bin/sh
set -e
if [ -d /run/secrets ]; then
  for f in /run/secrets/*; do [ -f "$f" ] || continue
    name=$(basename "$f" | tr '[:lower:]' '[:upper:]'); export "$name=$(cat "$f")"
  done
fi
exec "$@"
Provisioner(例如
deploy.py
)会将 secret 文件写入 tmpfs 目录,权限为 0444(文件)/ 0700(目录),然后执行
docker compose up -d
。

Gotchas (each cost a failed real run — bake these in)

Gotchas(每一项都曾导致真实运行失败——必须全部纳入)

#SymptomFix
1Agent dies
UnsupportedClassVersionError: class 65.0 … up to 61.0
Jenkins 2.5xx agent.jar needs Java 21, not 17.
2App container crash-loops
Permission denied /run/secrets/*
Compose non-swarm bind-mounts the source file's perms; write secret files 0444 (dir 0700) so the non-root container user can read.
3
shred /dev/shm/.../cred: Permission denied
Jenkins
file
credential is delivered 0400 (read-only);
cp
then
chmod 600
, and use `shred -uf …
4
docker
/
sudo
"permission denied" inside the pipeline
The agent process user must be in the docker group and have sudo NOPASSWD.
5Reboot → containers fail to mount secret
/run
is tmpfs → secrets cleared on reboot; re-run the deploy (or a boot hook) to re-materialize before
compose up
.
#症状修复
1Agent 启动时报
UnsupportedClassVersionError: class 65.0 … up to 61.0
Jenkins 2.5xx 的 agent.jar 需要 Java 21,不能使用 17。
2App 容器反复崩溃并出现
Permission denied /run/secrets/*
Compose 的非 swarm bind-mount 会继承源文件权限;应将 secret 文件写为 0444(目录 0700),确保非 root 容器用户可以读取。
3
shred /dev/shm/.../cred: Permission denied
Jenkins
file
credential 的交付权限为 0400(只读);先执行
cp
,再执行
chmod 600
,并使用 `shred -uf …
4Pipeline 中执行
docker
/
sudo
时出现“permission denied”
agent 进程用户必须属于 docker group,并拥有 sudo NOPASSWD 权限。
5主机重启 → 容器无法挂载 secret
/run
是 tmpfs → 重启后 secret 会被清除;需重新运行 deploy(或使用 boot hook),在
compose up
前重新生成 secret。

Verify success (real signals)

验证成功(真实信号)

  • Build console shows
    Running on <target>
    (proves it ran on the agent, not controller).
  • CLEAN <c>
    for every container (secret absent from
    docker inspect
    ).
  • MOUNT <c>
    (secret present at
    /run/secrets
    ).
  • App smoke (e.g. login) returns 200.
  • The cred bundle is shredded from
    /dev/shm
    (not left on disk).
  • Build console 显示
    Running on <target>
    ,证明任务运行在 agent 上,而非 controller。
  • 每个容器都出现
    CLEAN <c>
    ,表明 secret 未出现在
    docker inspect
    中。
  • 每个容器都出现
    MOUNT <c>
    ,表明 secret 已挂载到
    /run/secrets
    。
  • App smoke(例如登录)返回 200。
  • cred bundle 已从
    /dev/shm
    中 shredded,未残留在磁盘上。

Branches

分支

IDKindGuardHành viSkip / failureRejoin
B01user_optionalchỉ deploy vài service (tick WEB/ETL/DB/NGINX thay ALL)Selective deploy:
booleanParam
→
deploy.py --services "$SVC"
+
--no-deps
; NGINX thì
docker restart
proxy riêng
ALL → full; không tick gì →
none
= secrets only, no up
W05
B02conditional_requiredcode lấy từ git (production: Pipeline script from SCM)cấu hình SCM; repo private → SSH deploy key + known_hosts trên controller VÀ agentlab không git server → agent
git clone file:///…/repo.git
W04
B03recoverytarget reboot (
/run
tmpfs xoá secret)
chạy lại deploy (hoặc boot hook) để tái tạo secret trước
compose up
—W05
ID类型条件行为跳过/失败汇合点
B01user_optional仅部署部分 service(勾选 WEB/ETL/DB/NGINX,而非 ALL)选择性部署:
booleanParam
→
deploy.py --services "$SVC"
+
--no-deps
;如选择 NGINX,则单独
docker restart
proxy
ALL → full;未勾选任何项 →
none
= 仅处理 secret,不执行 up
W05
B02conditional_required代码取自 git(生产环境:Pipeline script from SCM)配置 SCM;private repo → 在 controller 和 agent 上配置 SSH deploy key + known_hosts实验环境无 git server → agent 执行
git clone file:///…/repo.git
W04
B03recoverytarget 重启(
/run
tmpfs 清除 secret)
重新运行 deploy(或 boot hook),在
compose up
前重新生成 secret
—W05

Reference — Branch details

参考——分支详情

Selective deploy (deploy only some services, not full)

选择性部署(仅部署部分 service,而非全部)

Expose
booleanParam
checkboxes (WEB/ETL/DB/NGINX/ALL) and pass the chosen compose services to the provisioner. Use
--no-deps
so a tick rebuilds only that service (not its dependency chain):
sh
if [ "$ALL" = true ]; then SVC=""; else
  S=""; [ "$WEB" = true ] && S="$S web"; [ "$ETL" = true ] && S="$S etl"; [ "$DB" = true ] && S="$S postgres"
  [ -z "$(echo $S|xargs)" ] && SVC=none || SVC=$(echo $S|xargs)
fi
deploy.py --services "$SVC"        # "" = full ; "web etl" = those ; "none" = secrets only, no up
公开
booleanParam
复选框(WEB/ETL/DB/NGINX/ALL),并将选中的 compose services 传递给 provisioner。使用
--no-deps
,使一次勾选仅重新构建该 service,而不会构建其依赖链:
sh
if [ "$ALL" = true ]; then SVC=""; else
  S=""; [ "$WEB" = true ] && S="$S web"; [ "$ETL" = true ] && S="$S etl"; [ "$DB" = true ] && S="$S postgres"
  [ -z "$(echo $S|xargs)" ] && SVC=none || SVC=$(echo $S|xargs)
fi
deploy.py --services "$SVC"        # "" = full ; "web etl" = those ; "none" = secrets only, no up

provisioner runs: docker compose up -d --build --no-deps $SVC

provisioner runs: docker compose up -d --build --no-deps $SVC

[ "$NGINX" = true ] && docker restart <proxy-container> # proxy is a separate container, not compose
**Dependency graph (depends_on) — document it** so operators know a fresh deploy needs the chain:
`web → etl → postgres` (proxy/nginx independent). With `--no-deps`, tick web = ONLY web; for a
clean first deploy tick the whole chain (or ALL). Verified: single ticks + combos all touch exactly
the chosen services.
[ "$NGINX" = true ] && docker restart <proxy-container> # proxy is a separate container, not compose
**依赖图(depends_on)——必须记录**,以便操作人员了解全新部署需要依赖链:
`web → etl → postgres`(proxy/nginx 独立)。使用 `--no-deps` 时,勾选 web = **仅** web;对于全新首次部署,应勾选整个依赖链(或 ALL)。已验证:单独勾选及组合勾选都只会影响所选的 service。

Pulling code from git

从 git 拉取代码

Production: configure the job as Pipeline script from SCM (git repo + scriptPath); Jenkins auto-
checkout scm
into the agent workspace, then the pipeline runs. The git URL must be reachable by BOTH controller (fetch Jenkinsfile) and agent (checkout workspace) —
file://
only works on the side that owns it; in a lab without a git server, have the agent
git clone file:///…/repo.git
.
Private repo (tested with GitHub, build SUCCESS):
  • HTTPS anonymous clone fails for a private repo — needs auth.
  • Use an SSH deploy key (read-only) on the repo; put the private key in a Jenkins
    SSH Username with private key
    credential (username
    git
    ); SCM URL
    git@github.com:owner/repo.git
    with that credentialsId.
  • Host key gotcha: add
    github.com
    to known_hosts on controller AND agent (
    ssh-keyscan github.com >> ~/.ssh/known_hosts
    ) or set the git host-key strategy to "Accept first connection", else the SSH clone fails host-key verification.
  • The deploy key's public part on GitHub must match the private key in Jenkins (compare
    ssh-keygen -lf key.pub
    fingerprint vs what GitHub shows).
生产环境:将 job 配置为 Pipeline script from SCM(git repo + scriptPath);Jenkins 会自动在 agent workspace 中执行
checkout scm
,随后运行 pipeline。git URL 必须能被 controller(获取 Jenkinsfile)和 agent(checkout workspace)同时访问——
file://
仅对拥有该路径的一侧有效;在没有 git server 的实验环境中,让 agent 执行
git clone file:///…/repo.git
。
Private repo(已使用 GitHub 测试,build SUCCESS):
  • 对 private repo 使用 HTTPS 匿名 clone 会失败——必须进行身份验证。
  • 使用 SSH deploy key(只读)访问 repo;将 private key 放入 Jenkins 的
    SSH Username with private key
    credential(username 为
    git
    );SCM URL 使用
    git@github.com:owner/repo.git
    并指定该 credentialsId。
  • **Host key 注意事项:**在 controller 和 agent 上将
    github.com
    加入 known_hosts(
    ssh-keyscan github.com >> ~/.ssh/known_hosts
    ),或者将 git host-key strategy 设置为“Accept first connection”;否则 SSH clone 会因 host-key verification 失败。
  • GitHub 上 deploy key 的 public 部分必须与 Jenkins 中的 private key 匹配(比较
    ssh-keygen -lf key.pub
    指纹与 GitHub 显示的指纹)。

Validation và stopping

验证与停止条件

Stage Verify là cổng tất định: exit 1 khi có
LEAK
/
NOMOUNT
. Cần người xem: console
Running on <target>
, smoke login 200,
/dev/shm
không còn cred. Dừng khi đủ 5 tín hiệu ở "Verify success"; lỗi thuộc bảng Gotchas → sửa đúng dòng Fix rồi chạy lại build, không đoán ngoài bảng.
Verify stage 是确定性关卡:出现
LEAK
/
NOMOUNT
时 exit 1。人工复核时需要看到:console 显示
Running on <target>
、smoke login 返回 200,并且
/dev/shm
中不再存在 cred。满足“验证成功”中的 5 个信号后即可停止;若错误出现在 Gotchas 表中,应严格按照 Fix 行修复后重新运行 build,不得依据表外信息进行猜测。

Examples

示例

  • Positive: app payroll (web/etl/postgres) — agent label
    <target>
    Java 21, user trong docker group + sudo NOPASSWD, build tick ALL → console
    Running on <target>
    ,
    CLEAN
    /
    MOUNT
    cho mọi container, login 200, cred bundle đã shred khỏi
    /dev/shm
    .
  • Boundary/failure: secret ghi 0600 → container web crash-loop
    Permission denied /run/secrets/*
    (gotcha 2) → provisioner đổi sang 0444 + dir 0700, chạy lại; tick chỉ WEB lần deploy đầu → web lên một mình vì
    --no-deps
    , thiếu etl/postgres → tick cả chuỗi
    web → etl → postgres
    (hoặc ALL).
  • 正向示例: payroll app(web/etl/postgres)——agent label 为
    <target>
    ,使用 Java 21,用户属于 docker group 且拥有 sudo NOPASSWD,build 勾选 ALL → console 显示
    Running on <target>
    ,每个容器都有
    CLEAN
    /
    MOUNT
    ,login 返回 200,cred bundle 已从
    /dev/shm
    中 shredded。
  • **边界/失败示例:**将 secret 写为 0600 → web app container 发生 crash-loop,并出现
    Permission denied /run/secrets/*
    (gotcha 2)→ provisioner 改为 0444 文件权限 + 0700 目录权限,然后重新运行;首次部署仅勾选 WEB → 由于使用了
    --no-deps
    ,只有 web 启动,缺少 etl/postgres → 应勾选整个依赖链
    web → etl → postgres
    (或 ALL)。