切换主题
构建与部署
本文档介绍 CICD 自动打包部署平台的构建步骤模型、任务变量与资源引用、构建产物归档、部署配置(产物条目 / 上传 / 落地 / 停部启脚本)、两种部署链路,以及代码获取、工作目录缓存与 RunPlan 快照。
一次任务被抽象成「一次取码 → 有序构建步骤 → 上传 → 部署 → 启动」的流水线:管理端在入队时把项目配置合成为一份不可变的 RunPlan 快照,执行端只按快照执行、不再做配置解析。相关主题见 项目管理、资源与目标服务器、任务与队列、执行机与部署节点。
四阶段模型
任务执行分为四个阶段,按固定顺序串行、不可跳跃:
| 阶段 key | 名称 | 执行内容 | 进度区间 |
|---|---|---|---|
build | 打包 | 取码(clone / fetch + checkout)→ 按 build.steps 顺序逐步构建(java / go / frontend / uniapp / docker)→ 汇总各步产物 | 0–25 |
upload | 上传 | 按 artifacts 逐条目上传到远端临时目录并校验(node 模式下先经管理端中转暂存);Docker deliver=registry 时改为 docker push | 25–45 |
deploy | 部署 | 逐条目备份被覆盖的现有目标 → 执行停止步骤 → 按 mode 落地替换(含权限)→ 执行部署步骤 | 45–70 |
start | 启动 | 执行启动步骤 → 等待 startWait 秒;未配置启动步骤时该阶段直接跳过(不算失败,如静态站点) | 70–100 |
- 进度按阶段区间均分,阶段内按步骤数 / 字节数细分。
- 执行范围可在触发时只选部分阶段(打包 / 部署 / 启动),
fromStage用于失败后从某阶段重跑,之前的阶段标记为skipped并沿用上次结果。 upload与deploy联动:项目启用部署时options.Upload = options.Deploy;项目关闭部署(deploy.enabled=false)时上传、部署、启动一律跳过,但若 Docker 步骤交付方式为「推送镜像仓库」,推送不依赖目标服务器,仍需执行上传阶段(此时Upload=true、Deploy=false)。- 人工卡点固定放在上传完成、部署开始前,详见 人工卡点。
Docker 交付
Docker 步骤的交付方式(deliver)决定镜像如何流转:
| 交付方式 | 行为 |
|---|---|
tar(默认) | 构建阶段 docker build 后 docker save | gzip 导出镜像 tar,走 artifacts 上传到目标机;无论上传成功与否都删除执行机本地镜像 |
registry | 上传阶段执行 docker push;无论推送成功与否都删除执行机本地镜像;远端启动前执行 docker pull |
镜像仓库凭据来自 Docker 步骤引用的镜像仓库(该步骤的 registryId,见 资源与目标服务器),留空按匿名 push / pull。执行端与目标机各自把凭据渲染成临时 docker 配置目录(DOCKER_CONFIG 指向该目录),不修改机器上的全局 ~/.docker/config.json,凭据不出现在命令行、shell 历史与环境变量文件中。
构建步骤
项目不再有单一类型:项目维护一份有序构建步骤列表(build.steps),一次取码后按列表顺序串行执行,各步骤产物统一汇入后续的上传 / 部署阶段。每个步骤可配置 name(步骤名,可选)、type、subDir(相对仓库根的子目录,留空为仓库根)、本步骤引用的环境资源(按类型只填用到的字段,如 jdkId / goId / nodeId / dockerId / hbuilderxId)、env(步骤级环境变量)以及该类型的参数;docker 步骤另有 registryId(镜像仓库)。
步骤类型与默认值
| 类型 | 构建工具 | 需要的环境资源 | 可填参数 | 默认值 |
|---|---|---|---|---|
java | Maven | JDK | Maven 命令、产物匹配 | goals=clean package -DskipTests、artifact=target/*.jar |
go | go | Go | 构建命令、产物匹配 | build=go build ./...、artifact=bin/* |
frontend | npm | Node | 安装命令、构建命令、产物目录 | install=npm install、build=npm run build、dist=dist |
uniapp | uni-app CLI | Node(+ 云打包时 HBuilderX) | 构建平台、安装命令、构建命令、产物目录、小程序直传、App 打包(离线 / 云打包) | install=npm install、build=npm run build:<平台>、dist=dist/build/<平台> |
docker | docker CLI | Docker | Dockerfile、镜像名、镜像 Tag、交付方式 | dockerfile=Dockerfile、tag=latest、deliver=tar |
- 「产物匹配」(
artifactPattern)与「产物目录」(distDir)是相对本步骤构建目录的 glob,主要用于构建后的产物概览与校验(以及 Dockertar交付定位归档);实际上传内容以部署阶段的artifacts[].source为准。 - Maven 的 mvn 与 JDK 来自 JDK 环境资源(或执行机本机路径兜底),不再单独配置 mvn 路径;
settings.xml按「代码仓库内 → JDK 资源上传的内容 → 默认~/.m2/settings.xml」优先级自动选取:- 先找本步骤构建目录下的
settings.xml,未找到再找仓库根,命中即用该文件(忽略资源上传的内容); - 否则用 JDK 环境资源上传的
settings.xml内容(密文入库,随 RunPlan 下发),执行时写入临时文件并以mvn -s <临时文件>引用,任务结束即删除; - 两者都没有则不传
-s。
- 先找本步骤构建目录下的
uni-app 构建与小程序直传
uniapp 步骤用于 CLI 工程(src/ + npm scripts 驱动的 uni-app 项目;HBuilderX 图形工程不适用)。它复用 node 环境资源,按 platform(h5 / mp-weixin / mp-alipay / mp-toutiao / app-android)自动给出默认构建命令与产物目录:
| platform | 默认构建命令 | 默认产物目录 |
|---|---|---|
h5 | npm run build:h5 | dist/build/h5 |
mp-weixin | npm run build:mp-weixin | dist/build/mp-weixin |
mp-alipay | npm run build:mp-alipay | dist/build/mp-alipay |
mp-toutiao | npm run build:mp-toutiao | dist/build/mp-toutiao |
app-android | npm run build:app | dist/build/app |
- 多端发布:一个步骤只产出一个平台的产物,多端需配置多个
uniapp步骤(同一步骤的installCmd只在第一个步骤填,后续步骤留空即可跳过重复安装)。 - H5 交付:产物目录仍是普通目录,按部署条目(
artifacts[].source,如dist/build/h5)上传到 nginx 等目标即可,与frontend步骤无异。 - 小程序交付:小程序产物是待上传到平台的源码包,落到服务器没有意义,应使用下方的「小程序直传」。
- App(Android / iOS)交付:
app-android平台的 CLI 产物只是 App 资源包(wgt),不是安装包;需在步骤里开启「App 打包」产出安装包——只打 Android 用离线打包(见下方「Android 离线打包」),需同时出 Android 与 iOS 用云打包(见下方「App 云打包」)。
小程序直传(构建成功后自动上传)
小程序平台的小程序步骤可开启「小程序直传」:构建成功后立即把产物上传到小程序平台,上传失败即整个步骤失败(不会出现「产物生成了但没上传」被误判为成功)。
两种上传方式(publish.mode):
| mode | 说明 | 必填项 |
|---|---|---|
weixin | 内置 miniprogram-ci 上传到微信公众平台:npx --yes miniprogram-ci@<版本> upload --pp <产物> --pkp <密钥> --appid <AppID> [-r 机器人] [--uv 版本] [--ud 备注] | AppID、代码上传密钥 |
custom | 执行端以系统 shell 执行自定义命令,用于支付宝 minidev / 抖音 tt-ide-cli 等 | 发布命令 |
- 代码上传密钥:在微信公众平台「开发管理 → 开发设置」下载,把文件内容粘贴到配置里。密钥 AES-256-GCM 密文入库、接口回显掩码
******(清空保存即保留原密文);合成 RunPlan 时解密并随计划下发到执行机,执行端写入 0600 临时文件后以--pkp引用,命令结束立即删除。密钥明文同时进入日志掩码列表,不会出现在任务日志里。 - IP 白名单:微信要求执行机出口 IP 在「开发管理 → 开发设置 → 小程序代码上传 → IP 白名单」内,否则上传会被拒绝;注意与同页「开发者 ID → IP 白名单」(仅管
AppSecret接口)不是同一个。可用curl ifconfig.me查看出口 IP。 - 自定义命令可用变量:执行端注入
UNI_PLATFORM/UNI_DIST(产物目录) /UNI_APPID/UNI_PRIVATE_KEY(密钥临时文件路径) /UNI_VERSION/UNI_DESC/UNI_ROBOT;命令按系统 shell 执行(Windows 为cmd.exe,注意语法差异)。 - 版本号 / 备注:支持
${BRANCH}、${TASK_ID}等动态表达式,在合成计划时固化。 - 上传工具:
weixin模式用npx --yes miniprogram-ci@<固定版本>按需拉取(执行机需能访问 npm 源),版本固定在agent/internal/builder的uniCIToolSpec,首次拉取后由 npx 缓存复用。上传为公网直连微信网关,执行端会注入NODE_OPTIONS=--dns-result-order=ipv4first(规避构建机 IPv6 优先但出网不通导致的socket hang up)并对传输层错误自动退避重试 3 次。
Android 离线打包(App 端)
app-android 平台的 CLI 构建产物(dist/build/app)只是 App 资源包,不能直接安装。开启步骤里的「Android 离线打包」后,执行端会把它同步进 DCloud 离线 SDK 的 Android 原生工程,再调用 Gradle 出 APK。适合只打 Android、且不使用 HBuilderX / 云打包的场景(Linux 执行机也可用)。
前置条件:
- 代码仓库内已有从 DCloud 下载的 Android 离线 SDK 原生工程(如
android/HBuilder-Integrate-AS)。 - 原生工程的 SDK 版本与本项目
@dcloudio/*编译器版本一致,否则会出现白屏 / 运行时报错。 - 在 DCloud 开发者中心申请 AppID(
__UNI__开头,需实名),并保证src/manifest.json中是同一个值。 - 执行机具备 Gradle 所需 JDK:在步骤所属项目的「环境资源」里选择 JDK 即可(执行端写入
JAVA_HOME并置于PATH最前)。
配置项(build.steps[].pack,仅 platform=app-android 且启用时有效):
| 字段 | 说明 | 默认值 |
|---|---|---|
enabled | 是否启用离线打包 | false |
appId | DCloud AppID(__UNI__...),同步写入原生工程 assets/data/dcloud_control.xml | 必填 |
templateDir | 原生工程目录,相对本步骤构建目录(仓库内路径) | 必填 |
command | 打包命令,在原生工程目录下执行 | ./gradlew assembleRelease(Windows .\gradlew.bat assembleRelease) |
artifact | APK 匹配表达式(相对原生工程目录的 glob) | 留空自动查找 |
keystore / keystoreName / storePassword / keyAlias / keyPassword | 签名配置(见下) | 留空则用原生工程自身签名 |
执行流程:
- 编译 App 资源(
npm run build:app)→dist/build/app; - 清空并拷贝到
<原生工程>/app/src/main/assets/apps/<appId>/www; - 更新
assets/data/dcloud_control.xml的appid为配置值(文件不存在则跳过); - 注入签名环境变量 → 执行
command; - 定位 APK 作为步骤主产物(
.apk会被归档上传,部署阶段可按仓库根相对路径取用)。
签名(平台注入):
- keystore 为二进制文件,配置里以 Base64 文本粘贴(如
base64 -w0 release.keystore)。 - keystore 内容与两个口令均 AES-256-GCM 密文入库、接口回显掩码
******(清空保存即保留原密文),并进入日志掩码列表。 - 合成 RunPlan 时解密,执行端写入 0600 临时文件,并以 Android Gradle Plugin 标准属性经环境变量注入:
ORG_GRADLE_PROJECT_android.injected.signing.store.file/.store.password/.key.alias/.key.password。用环境变量而非命令行-P,口令不进入进程参数与 shell 历史;无需改动原生工程的build.gradle。 - 留空 keystore 时不注入任何签名属性,直接使用原生工程自身的
signingConfig。
APK 定位:配置了 artifact 时按 glob 取第一个命中的文件;留空则在原生工程内递归查找 .apk,优先 outputs/apk 与 release 目录,其次取最近修改的。
App 云打包(DCloud,Android / iOS)
需要同时出 Android 与 iOS、或不希望维护离线 SDK 原生工程时,改用云打包:执行端通过 HBuilderX CLI(cli pack)把工程提交到 DCloud 云端编译打包,云端回传安装包下载地址。云端打包由 DCloud 侧完成,执行机只需能运行 HBuilderX CLI 并访问外网。
前置条件:
- 执行机安装 HBuilderX(含
cli可执行文件),并在「资源 → 环境资源」新建一条 HBuilderX 类型资源填写其安装目录,然后在 uni-app 构建步骤卡片内的「环境资源」中选择它(App 云打包的cli路径来自该资源;agent/config.yaml的hbuilderxPath只用于环境探测)。 - Linux 服务器可参考 DCloud 官方文档安装:https://hx.dcloud.net.cn/Tutorial/install/linux-cli
- 一个可登录的 DCloud 账号(
cli pack前会自动cli user login)。 - iOS 需具备
.p12证书 +.mobileprovision描述文件(Apple 开发者中心导出),二进制文件以 Base64 文本粘贴;Android 自有证书同理。
配置项(build.steps[].pack,mode=cloud,仅 platform=app-android 且启用时有效):
| 字段 | 说明 | 默认值 |
|---|---|---|
enabled | 是否启用打包 | false |
mode | offline 离线打包 / cloud 云打包 | offline |
username | DCloud 账号 | 必填 |
password | DCloud 密码(密文) | 必填 |
platforms | 打包平台,可多选:android / ios | 必填(至少一个) |
safeMode | 安心打包:Android 产物在本地签名生成并落到项目 unpackage/release,不依赖云端下载链接 | false |
android.packageName | Android 包名 | 勾选 Android 时必填 |
android.packType | 证书类型:0 自有证书 / 1 DCloud 公共测试证书 / 2 DCloud 老证书 / 3 云端证书 | 0 |
android.certAlias / android.certFile / android.certFileName / android.certPassword / android.storePassword | 自有证书(packType=0)的别名、证书内容(Base64)、文件名与两个口令 | packType=0 时必填 |
android.channels | 渠道包,逗号分隔(google / yyb / 360 / huawei / xiaomi / oppo / vivo) | 留空不打渠道包 |
ios.bundle | Bundle Identifier(与描述文件一致) | 勾选 iOS 时必填 |
ios.supportedDevice | 支持设备:iPhone / iPad,多个用逗号分隔 | iPhone,iPad |
ios.profile / ios.profileName | 描述文件内容(Base64)与文件名 | 勾选 iOS 时必填 |
ios.certFile / ios.certFileName / ios.certPassword | .p12 证书内容(Base64)、文件名与证书口令 | 勾选 iOS 时必填 |
执行流程:
- 与其它步骤一致先执行
installCmd安装依赖,但跳过buildCmd(App 资源由云端编译,无需本地产出dist/build/app); cli open启动 HBuilderX,并轮询cli ver直到就绪(上限 90 秒);cli user login --username <账号> --password <口令>;cli project open --path <本步骤构建目录>导入工程(已导入时提示「项目已存在」,不作为失败);- 生成
pack.json(字段与 DCloud 文档一致:project/platform/iscustom=false/safemode=<安心打包开关>/android.*/ios.*),证书与描述文件解密后写入 0600 临时文件再引用其路径; cli pack --config pack.json提交云打包(超时按 2 小时放宽,云打包排队时间较久);- 解析输出中的下载地址(
Download Link,识别路径含/download/的链接)并立即下载到临时目录;若该版本 CLI 不返回下载地址(部分 Linux CLI / 安心打包即如此),则回退取项目unpackage/release下的本地安装包,两者都拿不到才失败(错误信息附带打包输出末行,便于定位)。
产物与注意事项:
- 云端只回临时下载地址(限 5 次下载),因此执行端必须落地为本地文件后才能归档;多平台时产物为目录(含
1-android.apk、2-ios.ipa),单平台时为单个文件。 - 部分 HBuilderX CLI 版本打包成功后既不打印下载链接、也不落地产物(社区已知问题),此时请勾选「安心打包」:产物在本地签名生成并落到
unpackage/release/apk,执行端会自动取用。注意 Linux / Windows 下 iOS 不支持安心打包(仅 macOS),同时勾选 iOS 时不要开启该开关。 .apk/.ipa会被归档上传,部署阶段按仓库根相对路径取用即可。- DCloud 账号密码、Android 证书与口令、iOS 描述文件与 p12 口令均 AES-256-GCM 密文入库、接口回显掩码
******(清空保存即保留原密文),并进入日志掩码列表;合成 RunPlan 时解密、随计划下发到执行机。 cli pack cancel(HBuilderX 5.14+)可用于取消排队中的任务,当前平台未接入。
步骤级环境变量
build.steps[].env 只注入本步骤的本地构建进程,同名时覆盖项目级任务变量,不参与远端步骤(因此没有 scope)。典型用途是同一工程各步骤环境不同,例如 Go 的交叉编译:
json
{
"name": "后端", "type": "go", "subDir": "server",
"buildCmd": "go build -o bin/app ./cmd/app",
"env": [
{ "key": "GOOS", "value": "linux" },
{ "key": "GOARCH", "value": "amd64" },
{ "key": "CGO_ENABLED", "value": "0" }
]
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
执行机为 Windows 时构建命令经
cmd.exe /d /s /c执行,命令内不能写GOOS=linux go build ...(sh 语法),应改用步骤级环境变量,或写成set GOOS=linux && go build ...。
步骤间产物共享
一次取码后,所有步骤在同一份工作目录({workspaceRoot}/{项目ID},各步骤再叠加 subDir)内按列表顺序串行执行,步骤之间不做任何清理,因此前序步骤的产物对后续步骤立即可见。典型场景是「前端 + Go」同仓库工程:前端步骤(subDir=web、distDir=dist)先产出 web/dist,Go 步骤再用 //go:embed web/dist/** 把静态资源打进二进制。
- 列表顺序即依赖顺序,用「上移 / 下移」调整(前端必须排在 Go 之前)。
go:embed不支持..:产物目录必须位于 Go 模块内;若 Go 模块在子目录(如server/),可把前端步骤的产物目录填成../server/web/dist(相对该步骤构建目录,构建命令也应输出到同一路径)。git.cache=false只在任务结束删除工作目录,不影响同一任务内的步骤间传递。
不配置构建步骤
静态站点、仅分发配置 / 脚本等无需构建的项目可以不配置任何构建步骤,或关闭「启用构建」开关。此时执行端仍然取码,只是跳过构建,部署条目直接按仓库根相对路径取文件上传。任务详情的构建步骤列显示「不构建(直接用仓库文件)」。
环境资源与候选执行机
构建步骤类型在本步骤内引用对应类型的环境资源:
| 步骤类型 | 步骤配置引用 | 资源类型 |
|---|---|---|
java | steps[].jdkId | jdk |
go | steps[].goId | go |
frontend | steps[].nodeId | node |
uniapp | steps[].nodeId(本地)+ steps[].hbuilderxId(云打包) | node(+ hbuilderx) |
docker | steps[].dockerId | docker |
- 项目只引用资源,不指定执行机。一套资源(同配置,如 JDK17)可关联多台执行机(
env_resource_agent),执行时按「各步骤引用的全部资源所关联执行机的交集」自动匹配一台可用机器(最闲优先)。 - 各步骤均未引用任何资源时,全部执行机均为候选。
- 每个构建步骤卡片内只显示该步骤类型用到的资源下拉;若某步骤类型缺少对应资源,会给出告警提示。
- 镜像仓库是独立引用(Docker 步骤的
registryId),与环境资源无关,也不参与候选机匹配。
任务变量与变量展开
任务变量是任务级全局配置,一次配置对 build / upload / deploy / start 全程生效;分组级变量与触发时的临时变量也会合并进来。
注入范围
每条变量含 scope:
scope | 注入范围 |
|---|---|
all(默认) | 执行端本地命令与远端命令都注入 |
local | 仅执行端本地命令(git、mvn、go、npm、docker 与本地构建步骤) |
remote | 仅远端命令 |
- 本地注入:作为命令的环境变量追加,并注入内置
CICD_*变量。 - 远端注入:不把
export KEY=VALUE拼进命令行,而是写入远端临时 envfile({tempDir}/cicd-{taskId}/env.sh,权限0600),每条远端步骤执行前source它,避免敏感值出现在远端ps输出与 shell history;任务结束随临时目录删除。 masked=true的变量在项目配置中为密文,合成 RunPlan 时解密,接口不回显、日志落库前替换为******。
合并优先级
同名变量按下列顺序依次覆盖,越靠后优先级越高:
| 顺序 | 来源 | 说明 |
|---|---|---|
| 1 | 系统内置 | 阶段默认环境 |
| 2 | 环境资源携带 | env_resource.config.env |
| 3 | 分组级 | project_group.env(仅 enabled=true 注入,解密后展开 ${VAR}) |
| 4 | 项目级 | project.config.env |
| 5 | 触发临时变量 | 触发任务时传入,仅本次生效 |
| 6 | 运行期内置 | CICD_*,用户配置不可覆盖 |
此外,构建步骤级变量(build.steps[].env)只覆盖该步骤本地构建进程的项目级同名变量。
内置变量
运行期自动写入、用户不可覆盖:
| 变量 | 含义 |
|---|---|
CICD_TASK_ID | 任务 ID |
CICD_PROJECT | 项目名称 |
CICD_STAGE | 当前阶段(build / upload / deploy / start) |
CICD_BRANCH | 分支 / Tag |
CICD_COMMIT | 提交号 |
CICD_WORKDIR | 当前打包目录:本次任务取码后的仓库根绝对路径(如 /srv/cicd/agent/workspace/1064);各构建步骤的实际目录 = 该目录 + 步骤子目录 |
CICD_WORKDIR 的取值取决于最终选中的执行机(workspaceRoot 属执行机本机配置),因此与计划中的 RepoDir 一样在任务派发时才写入,而不是在计划合成时;引用它的任务变量值与构建命令也在派发时一并替换。
变量引用与部署占位符
- 任务变量值、构建步骤的环境变量(步骤级「环境变量」)与构建步骤的镜像名 / Tag 支持
${VAR}引用:既支持项目变量表的裸键(${APP_NAME}),也支持省略CICD_前缀的内置变量(${CICD_WORKDIR}与${WORKDIR}等价);展开在管理端合成计划时完成,涉及CICD_WORKDIR的引用因路径在派发时才确定,故在派发时展开。 - 安装 / 构建 / 发布命令里的
$VAR/${VAR}由 shell 按注入的环境变量展开:内置变量请写全名(如${CICD_WORKDIR}),CICD_WORKDIR额外支持简写${WORKDIR}。 - 部署占位符(仅用于部署步骤脚本,即停止 / 部署 / 启动脚本)在执行前替换:
${remoteDir}(目标目录)、${logDir}(日志目录)、${image}(镜像)、${container}(容器名)、${remoteFile}(文件名)、${remoteFileAbs}(文件绝对路径);其余$VAR原样保留,交由 shell 在执行该脚本时展开。 - 内容模板(
deploy.scripts[]生成的脚本文件)不用${...},而是 Go 模板语法{{ }}:远端环境变量写{{ .Vars.变量名 }}(同样支持省略CICD_前缀的简写),日志目录写{{ .Deploy.LogDir }}等,详见 项目模板与内容模板。内容模板里的${VAR}一律原样写入文件,交给 shell 执行时展开。
动态表达式(内置函数)
任务变量的值里可写动态表达式,在管理端合成计划时一次性展开为实际值(因此同一任务的所有阶段与目标机取值一致,见 §RunPlan 快照),支持与普通文本、多个表达式任意拼接。
写法有三类:
| 写法 | 说明 |
|---|---|
${KEY} | 引用其他任务变量或内置变量(${CICD_PROJECT}、${BRANCH} 等省略前缀写法同样可用);未命中时整段原样保留 |
${fn(args)} | 内置生成 / 字符串处理函数 |
${fn} | 无参函数可省略括号,如 ${now}、${date}、${uuid}、${random} |
内置函数:
| 函数 | 说明 |
|---|---|
random(n) | n 位随机数字,缺省 8 位(1~64) |
randstr(n) | n 位随机字母数字,缺省 8 位(1~64) |
uuid | 36 位 UUID v4 |
now / datetime | 当前时间 2006-01-02 15:04:05(管理端时区) |
date / time | 当前日期 2006-01-02 / 当前时间 15:04:05 |
now(layout) | 自定义 Go 参考时间布局,如 ${now(20060102)}、${now(2006-01-02_15-04-05)} |
timestamp / timestampMs | 当前秒级 / 毫秒级时间戳 |
upper / lower / trim | 转大写 / 转小写 / 去首尾空白 |
substr(s,start[,len]) | 截取子串,start 为负表示从末尾倒数,省略 len 取到末尾 |
replace(s,old,new) | 全量替换 |
函数参数支持三种形式:单引号字面量 'text'、变量名(未命中按字面量处理)、嵌套表达式 ${...};变量引用优先于同名函数。示例:
text
release-${now(20060102)}-${random(4)}
${upper(${APP_ENV})}-${substr(${CICD_COMMIT},0,7)}1
2
2
语法不合法时分段处理:只有该段表达式原样保留(如 ${upper(abc} 括号不配平、${nope(1)} 未知函数、${upper(a,b)} 参数个数不对),其余部分照常展开。表达式引用了 masked=true 的变量时,结果会级联标记为敏感,日志中一并掩码。
生效位置:分组 / 项目 / 触发临时任务变量的值、构建步骤的镜像名与 Tag、部署兜底镜像引用;部署脚本里的 ${变量名} 引用的是已展开后的值,无需再写函数。
构建产物归档与保留
除随任务上报的「主产物」外,执行端会把每个构建步骤的主产物在打包阶段就地归档到管理端长期保留,并可在任务详情与项目详情下载。
| 项 | 说明 |
|---|---|
| 归档时机 | 每个构建步骤 builder.Run 成功后立即归档(早于工作目录清理,目录仍在) |
| 归档内容 | 该步骤主产物(Java target/*.jar、前端 dist 目录、Go 输出文件等);Docker 步骤产出的是镜像引用而非文件,跳过归档 |
| 目录产物 | 执行机本机打包为 output-{taskId}-{seq}.tar.gz 后上传 |
| 文件产物 | 本身已是压缩包(.zip / .jar / .tar.gz / .7z 等按扩展名判定)原样上传;否则打一层 tar.gz 后上传 |
| 归档形态 | 统一收敛为「tar.gz 归档」或「原生压缩包」两种,已压缩产物不做二次压缩 |
| 落盘位置 | 本机:{artifact.dir}/{projectId}/{taskId}/{seq}-{文件名}[.tar.gz];对象存储:s3://{bucket}/{key} |
| 下载 | 任务详情「产物」区逐条列出并下载(需该项目 view 权限);dir 类型下载为 tar.gz |
| 失败处理 | 归档上传失败只记日志,不影响任务执行与终态 |
保留与回收由管理端配置控制(backend/config.yaml):
| 配置项 | 默认 | 说明 |
|---|---|---|
artifact.retentionDays | 30 | 产物保留天数,超过由巡检清理(含磁盘文件 / 对象) |
artifact.maxTaskBytes | 2147483648(2 GB) | 单个任务产物上限,上传前校验,超出返回 413 |
artifact.maxTotalBytes | 21474836480(20 GB) | 全部产物总量上限,超出按最旧优先回收 |
artifact.dir为落盘目录;storage.artifact可切为s3写入对象存储(storage.relay控制部署中转产物的后端)。DB 中保存的引用以s3://开头即表示对象存储,其余按本机路径处理,两种后端可共存。任务删除时随事务删除记录与落盘对象。
部署配置
部署配置全部在项目配置内维护,界面分为「部署目标」「部署文件」「部署脚本」「临时目录与备份」四个分区。「启用部署」关闭时,任务只构建并把产物归档到管理端,不要求选择目标服务器、不执行部署脚本。
部署目标与两种链路
项目中只选择「目标服务器」(target_server,可多选),部署模式由目标服务器的连接方式决定,项目管理中不提供选择:
mode | 项目配置上的表现 | 部署链路 |
|---|---|---|
ssh(默认) | 下拉展示 名称(username@host:port) | 执行机 SSH 直连目标机:按 artifacts 逐条目上传到目标机临时目录,再按项目配置执行停止 → 落地替换 → 部署 → 启动 |
node | 下拉展示 名称(部署节点 #id) | 产物经管理端中转:目标机上已安装 cicd_node,执行机把产物上传到管理端暂存,节点按下载地址拉取并在本机落地与启停;SSH 连接信息与凭据不参与 |
- 多目标:项目可同时选择多台目标服务器(
deploy.targetIds)。任务按选择顺序逐台串行部署:每台各自完成上传 → 落地 → 部署 → 启动后,再处理下一台;各台可自由混用ssh与node两种链路。多台时产物汇总名称会带上目标服务器名以区分。Docker 步骤交付方式为「推送镜像仓库」时,镜像只推送一次,各目标机各自docker pull。 mode=ssh时的认证方式、密钥来源、切换用户(su/sudo)等凭据在「目标服务器」中维护,合成 RunPlan 时解密下发(仅内存传递);参见 资源与目标服务器。mode=node时目标服务器只绑定部署节点(node_id必填)且不保存凭据;执行端内部仍保留「本机部署」实现分支,与 node 模式共用同一套落地逻辑。- 目标服务器「远端目录」(
remoteDir)是条目未指定目标目录时的默认落地目录。
部署文件条目
一次任务可上传多个条目(deploy.artifacts,数组顺序即上传顺序),单文件场景就是一条 file 条目。协议中 Artifact 共 14 个字段,界面收敛为 6 个可见列:
| 可见列 | 字段 | 说明 |
|---|---|---|
| 来源 | source | 相对仓库根的路径或 glob(如 bin/app、dist、target/*.jar),也可填执行机上的绝对路径;唯一必填项 |
| 目标目录 | targetDir | 远端目标目录(不存在则创建);留空取项目「远端目录」 |
| 目标名称 | targetName | 远端文件名或子目录名;留空时目录来源取源目录名、单文件取原文件名 |
| 落地方式 | mode | replace 覆盖(默认)/ merge 合并(保留目标已有文件)/ clean 清空后放入 |
| 权限 | permissions | 八进制三位(预置 755 可执行 / 644 普通文件 / 600 仅属主,可手填);目录来源递归设置,留空保持上传后的原权限 |
| 备份 | noBackup(反向存储) | 默认开启:落地前把本条目将覆盖的目标打成归档;关闭后该条目直接覆盖不备份(适用于日志、上传目录等) |
界面上隐藏、但协议仍保留并生效的字段(由执行端或默认值兜底):
| 字段 | 隐藏后的行为 |
|---|---|
type | 留空,由执行端按来源实际形态自动判断:命中目录即 dir,glob 恰好命中 1 个文件降级为 file,多个为 files;存量显式 type(含 image)仍然优先生效 |
name | 执行端自动取来源的文件名 / 目录名,用于日志、进度与备份文件名 |
put | 默认 subdir(在目标目录下再建一层 targetName 子目录);direct 表示内容直接放入目标目录本身、忽略 targetName(存量配置仍生效) |
dirMode | type=dir 时默认 tar(本地打包 → 上传 → 远端解包);sftp 为逐文件递归上传(目标机无 tar 时用) |
excludes | 默认无排除;相对来源根的排除 glob |
owner / group | 默认继承目标机;落地后可改属主 |
required | 新增条目默认 true:来源匹配为空即任务失败;关闭则匹配为空只跳过并告警 |
enabled | EnabledArtifacts() 只收 enabled=true 的条目,保存时显式写 true |
落地路径由
put决定:subdir时{targetDir}/{targetName}(targetName留空取源目录名 / 源文件名),direct时{targetDir}。因此type=dir默认会多一层目录名(如dist→/srv/demo/html/dist/);要让dist的内容直接落在/srv/demo/html/下,把该条目设为put=direct。注意
mode=replace在direct下会先rm -rf {targetDir}再重建(clean只清空目录内容),请确认该目录下没有需要保留的其它文件。
上传与落地
上传与落地的规则:
1) 解析来源:逐条目展开 glob 并按修改时间排序;required=true 且 0 命中 → 立即失败并指明条目名与规则
2) 上传:传到远端临时目录 {tempDir}/cicd-{taskId}/{序号}-{name}/;type=dir 且 dirMode=tar 时本地先 tar czf
3) 校验:比对远端解包后的文件数与总字节数(tar 方式用 tar -tzf 计数),不一致则失败
4) 备份:backup=true 且该条目未开启 noBackup 时,把「将被覆盖的现有目标」打成归档并按 maxBackups 轮转
5) 落地:按 put 得到 targetPath → clean 先清空 targetDir 内容 → replace/merge 以 cp -a 放入
6) 权限:chmod(目录递归,文件逐个)→ 可选 chown
7) 进度:upload 阶段进度 = 已完成条目字节 / 全部条目总字节;逐条目回报「已传 / 总量、百分比、速率」
(目录条目与中转上传按归档体积折算回源体积,保证口径一致),并按约 3 秒节流写入任务日志1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 任一条目失败:
required=true时中止整个任务(已落地的前序条目不回滚,用备份恢复,日志注明可用备份目录回退);required=false时记为警告继续。 - 产物汇总:
task.artifact_name/artifact_size记录主条目(第一个type=file,无则取全部条目合计),全部条目明细写入task.steps的upload段。 - Docker 交付:
task.image记录完整镜像引用({registry}/{镜像名}:{Tag}),任务详情「镜像」行与通知内容中的「镜像」字段取该值。
部署脚本(停 / 部 / 启)
部署脚本在协议中仍是三个独立步骤数组(stopSteps / deploySteps / startSteps,每步为 {name, script, ignoreFailure}),界面上合并为一个脚本框内的三个分段编辑,保存时按分段写回原有数组(每段合并为一个步骤),后端与执行端零改动。每段带「失败不中断」开关。
执行顺序:
部署前(停止)→ 文件落地 → 部署(可选)→ 部署后(启动)→ 等待「启动等待」秒1
| 分段 | 对应数组 | 执行时机 | 典型内容 |
|---|---|---|---|
| 部署前(停止) | stopSteps | 文件落地前 | 停旧服务 |
| 部署(可选) | deploySteps | 文件落地后、启动前 | 赋权、重载配置 |
| 部署后(启动) | startSteps | 最后执行,之后等待 startWait 秒 | 启动服务 |
- 未配置启动步骤时,
start阶段直接跳过(不算失败)。 - 每段
ignoreFailure=true表示该段命令失败不中断任务(该段内多行内容作为一个步骤整体执行)。 - 「启动等待」(
startWait,默认 0 即不等待)为启动脚本执行后等待的秒数;执行端会输出启动日志尾部若干行。
示例(jar 落地到 /srv/app/app.jar,启停脚本 app.sh 由内容模板生成):
bash
# 部署前(停止)
sh ${remoteDir}/app.sh stop
# 部署(可选,文件已落地)
chmod 755 ${remoteDir}/app.jar
sh ${remoteDir}/app.sh status
# 部署后(启动)
sh ${remoteDir}/app.sh start1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
这里的 ${remoteDir} 是部署占位符,执行前替换为项目「远端目录」(如 /srv/app);app.sh 的正文用 {{ }} 模板语法编写,见 内容模板示例。
脚本文件(内容模板)
「脚本文件」用于在部署阶段最前面按「内容模板」在目标机生成文件(先于备份与停止步骤,因此停止脚本本身也可以由模板生成):
| 字段 | 说明 |
|---|---|
| 模板 | 选择内容模板(内容以模板为准),仅可选已分配给项目所属分组的模板 |
| 文件名 | 生成的文件名,留空沿用模板默认;与模板正文一样按 {{ }} 模板语法渲染 |
| 目录 | 目标目录,留空表示项目远端目录({{ .Deploy.RemoteDir }}) |
| 权限 / 属主 | 权限缺省 0755(生成后可直接执行),属主留空沿用模板 |
| 内容 | 内容来自所选模板,按 Go 模板语法 {{ }} 渲染,见 内容模板语法;同名文件直接覆盖、不参与备份 |
临时目录与备份
| 配置项 | 默认 | 说明 |
|---|---|---|
tempDir | /tmp | 远端临时目录:本次任务使用 {tempDir}/cicd-{taskId} |
backup | 开启 | 整体备份开关;关闭时不备份任何条目 |
backupDir | {remoteDir}/.cicd-backup | 备份目录 |
maxBackups | 5 | 备份保留份数,0 表示不限 |
- 备份是逐条目独立执行的(每条目各自一个归档
{backupDir}/{name}.{yyyyMMddHHmmss}.tar.gz、各自按maxBackups轮转),因此条目级「不备份」只短路该条目,不影响其他条目的备份与保留份数,与整体backup开关是叠加关系。 - 归档内会排除备份目录自身,避免历史备份被反复打包;清理目标目录(
clean清空内容、replace替换目录)时跳过备份目录,否则刚生成的备份会被同一次部署删掉。
RunPlan 快照
管理端在创建任务时完成「变量展开(${VAR})→ 资源引用解析 → 凭据解密」,生成一份不可变的 RunPlan,落库到指令 payload 并随指令下发。执行端不再做任何配置解析。
合成要点:
- 任务变量合并:按资源携带 → 分组 → 项目 → 触发临时变量的顺序追加(后者覆盖前者),并展开
${VAR}、解密敏感值。 - 构建计划:解析
keyScope(默认agent)、authType(默认ssh)、refType(默认branch);工作目录根取执行机上报的workspaceRoot(缺省workspace),仓库根目录固定为{root}/{项目ID};ref可被触发时的临时 ref 覆盖;sshStrictHostKeyChecking=accept-new。构建步骤按项目配置顺序生成并补齐各类型默认值。 - 部署计划:
tempDir缺省/tmp、maxBackups缺省5、startWait缺省15、backup缺省开启、backupDir缺省{remoteDir}/.cicd-backup;遍历项目选择的deploy.targetIds,逐台展开为PlanDeploy.Targets[]—— 每台按target.mode决定:node→ 该目标为mode=node并带nodeId/nodeName,ssh→ 该目标为mode=ssh并解密 SSH 凭据(port缺省 22、timeout缺省 15、authType缺省password、keySource缺省upload、switchMode缺省none);解析脚本文件与镜像仓库凭据。 - 执行范围归一化:
fromStage之前的阶段置为不执行;全未选则默认全流程;项目关闭部署时上传 / 部署 / 启动按前述规则归一。 - 人工卡点:仅当本次会执行部署阶段、且项目开启卡点时固化
PlanApproval{Enabled, TimeoutSeconds}(timeoutSeconds缺省 24 小时)。
不可变语义:计划一旦落库即固化,排队期间修改项目配置(任务变量、构建步骤、部署参数、卡点超时等)不影响已入队任务;派发时管理端仅按选中的执行机覆盖执行机相关的路径(仓库目录、Git 私钥路径、known_hosts),其余一律以快照为准。
代码获取与工作目录缓存
Git 密钥范围
Git 私钥由执行端自建并上报公钥,keyScope 有两档:
| 取值 | 密钥 | 适用 |
|---|---|---|
agent(默认) | 执行机级,一对密钥全局复用 | 推荐:把公钥加到 Git 服务器的账号级 SSH Keys |
project | 项目级 Deploy Key,每个项目一对 | 需按仓库严格授权隔离的场景 |
- 私钥仅保存在执行机本地
{gitKeyRoot}下(0600),永不上报、永不写入日志;只用于 Git 取码,不用于部署目标机登录(后者用目标服务器凭据)。 - 项目级密钥首次使用需先在项目页「生成项目密钥」→ 在 Git 平台配置公钥 → 「测试连接」通过后再运行任务;跳过时执行端会在取码阶段生成并上报公钥,任务以明确原因失败。
取码流程
取码在 build 阶段最前执行,日志实时上报(命令回显对凭据自动掩码):
# SSH 传输:通过 GIT_SSH_COMMAND 指定私钥
ssh -i {keyPath} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile={gitKeyRoot}/known_hosts
# 首次(目录不存在):克隆
git clone --branch {ref} [--depth {depth}] {url} {repoDir}
# 后续(目录存在且缓存开启):增量更新
git -C {repoDir} remote set-url origin {url}
git -C {repoDir} fetch --prune origin
git -C {repoDir} checkout -f {ref}
git -C {repoDir} reset --hard {target} # workspace.resetHard=true 时执行
git -C {repoDir} clean -fdx # 仅 workspace.cleanUntracked=true 时执行1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
refType=branch时checkout -f {ref}+reset --hard origin/{ref};tag/ 指定commit时checkout -f {ref}(detached)。- HTTPS 认证通过临时
GIT_ASKPASS脚本提供用户名密码,不写入命令行与日志。 - 仓库内子目录(monorepo)由各构建步骤的
subDir指定;缓存与清理的单位始终是整个repoDir。 - 取码不可关闭:项目总是先取码再构建;仓库地址留空的项目在取码阶段以明确原因失败。
工作目录
| 层级 | 配置位置 | 示例 |
|---|---|---|
| 工作目录根 | 执行端 agent.workspaceRoot(可绝对 / 相对,相对按配置文件所在目录解析) | /data/cicd/workspace |
| 仓库根 | 推导,固定用项目 ID | {workspaceRoot}/{projectId} → /data/cicd/workspace/7 |
| 步骤构建目录 | 推导(含步骤子目录) | {repoDir}/{step.subDir}(留空即仓库根) |
同一执行机的所有项目共享 workspaceRoot;工作目录名固定为项目 ID,天然避免同名 / 改名项目互相覆盖。
缓存策略
项目配置 git.cache 控制任务结束后是否保留工作目录:
git.cache | 任务开始前 | 任务结束后 | 适用场景 |
|---|---|---|---|
true(默认) | 目录存在 → fetch + checkout + reset(增量,保留已有产物);不存在 → clone | 保留 repoDir | 高频构建,复用依赖缓存 |
false | 若存在残留目录 → 先整体删除,再 clone | 删除 repoDir | 磁盘紧张、要求每次全新环境 |
- 缓存模式下代码一致性由
resetHard=true(默认)保证;cleanUntracked=false(默认)刻意不删未跟踪文件(这正是缓存复用的价值)。若要求「代码干净但保留依赖缓存」,可单独开cleanUntracked=true(会清掉target/等未跟踪产物,需自行权衡)。 - 清理范围仅限
{workspaceRoot}/{projectId},不影响~/.m2、npm 全局缓存、Docker 镜像层等执行机级缓存。 - 单次任务可临时覆盖:触发任务时勾选「本次不缓存」(
cache=false),仅对本次生效、不改项目配置。 - 手动清理:项目操作列「清理工作目录」下发
clean_workspace指令,可指定项目或该执行机全部项目,执行端回报释放的字节数与目录数量。 - 兜底清理:执行端
agent.workspaceTtlDays > 0时,启动后每天扫描一次workspaceRoot,删除超过该天数未被任何任务使用的项目目录(默认0表示不自动清理)。