切换主题
资源与凭据
本文档介绍 CICD 自动打包部署平台中与机器、环境、凭据相关的配置实体:环境资源、目标服务器、镜像仓库、Git 密钥,以及它们在敏感数据加密与产物存储上的处理方式。
这些实体由管理端统一维护,在「资源」菜单下分「环境资源」「目标服务器」「镜像仓库」三个页面管理,读接口对 admin 与拥有任一 manage 授权的用户开放,写接口仅 admin。项目通过 ID 引用它们,任务执行时由管理端解析为运行计划(RunPlan)下发。项目配置本身见 项目与配置,执行机与部署节点的接入见 执行机、部署节点与集群,打包与部署流程见 构建与部署。
环境资源
定义与用途
环境资源(env_resource)登记的是一套构建环境配置(JDK、Go、Node、Docker、HBuilderX 的安装路径与相关设置)。它的意义在于「一套同配置的资源可关联多台执行机」:项目只关心「需要什么环境」,不关心「跑在哪台机器上」,由平台在触发时自动挑机。
资源类型 kind 共五种:jdk、go、node、docker、hbuilderx,前四种与构建步骤类型一一对应,hbuilderx 供 uni-app 步骤的 App 云打包使用。
字段
| 字段 | 说明 |
|---|---|
id | 主键 |
kind | 资源类型:jdk / go / node / docker / hbuilderx |
name | 资源名称,如同一台机器上区分 JDK 8 与 JDK 17 |
description | 说明(可选) |
agentIds | 关联执行机列表(列表接口另回传 agentNames 供展示);新增时至少选择一台 |
config | 类型相关配置(JSON),见下表 |
createdAt / updatedAt | 时间戳 |
config 内的字段随类型不同(键名即真实落库字段):
| 类型 | 配置字段 | 说明 |
|---|---|---|
jdk | jdkPath | JDK 安装目录 |
mavenPath | Maven 可执行文件路径(可选) | |
settingsXml | 上传的 Maven settings.xml 文件内容,密文入库、不回显 | |
go | goPath | go 可执行文件路径(留空走 PATH) |
goRoot | GOROOT(可选) | |
goBin | GOBIN(可选) | |
node | nodePath | node 可执行文件路径 |
npmPath | npm 可执行文件路径 | |
docker | dockerPath | docker 可执行文件路径 |
hbuilderx | hbuilderxPath | HBuilderX 安装目录(目录内含 cli(Linux)/ cli.exe(Windows)可执行文件),供 App 云打包调用 |
| 通用 | env | 资源自带的任务变量键值对,注入任务时范围固定为 scope=all(本地与远端都注入) |
任务变量合并顺序为「资源携带 → 分组 → 项目 → 触发临时」,同名后者覆盖前者,因此项目变量始终优先于资源自带变量;资源变量的详细合并规则见 构建与部署。
settingsXml上限 512KB,且必须包含<settings字样;任务执行时若代码仓库内已有settings.xml(本步骤构建目录或仓库根),优先使用仓库中的,否则用此处上传的内容。
HBuilderX 资源用于 uni-app 步骤选择「云打包」方式时调用 HBuilderX CLI(走 DCloud 云打包)。安装方式见 HBuilderX Linux CLI 安装文档;
hbuilderxPath填安装目录(如/opt/HBuilderX),平台据此定位cli可执行文件(Windows 为同目录下的cli.exe),执行机也可用agent.hbuilderxPath兜底。云打包的配置与执行流程见 构建与部署。
删除资源时若仍被项目的构建步骤引用(config.build.steps[].jdkId / goId / nodeId / dockerId / hbuilderxId),接口返回 409 并列出引用它的项目名。
关联执行机与任务调度
项目不直接选择执行机,而是由各构建步骤引用环境资源。触发任务时按以下规则自动选机:
- 候选集 = 各构建步骤引用的全部环境资源所关联执行机的交集(
IntersectResourceAgents/CandidateAgentsForProject);所有步骤均未引用任何环境资源时,全部执行机均为候选。 - 派发时从「在线且未满载」的候选中取最闲优先:比较各机当前已派发任务数,最少者优先,并列时取 ID 最小。
- 交集为空时任务直接失败并提示「没有匹配的执行机(请检查项目引用的环境资源是否已关联执行机)」;候选机全部被禁用时同样失败。配置类错误不会被无限排队掩盖。
- 同项目串行:工作目录是独占资源,同一项目已有
pending/running任务时,新任务继续排队。 - 计划中的工作目录、Git 密钥等与执行机相关的字段会在派发时按实际选中的执行机刷新(候选机为同配置机器)。
与构建步骤的引用方式
环境资源在每个构建步骤卡片内各自选择(字段位于 project.config.build.steps[]),步骤按自身类型只展示用到的资源行:
| 构建步骤类型 | 引用的资源字段 | 资源 kind |
|---|---|---|
| Java / Maven | steps[].jdkId | jdk |
| Go | steps[].goId | go |
| 前端 | steps[].nodeId | node |
| Docker | steps[].dockerId | docker |
| uni-app | steps[].nodeId(本地构建)+ steps[].hbuilderxId(云打包) | node + hbuilderx |
未配置对应资源时该类型步骤不可用,界面会提示「本步骤类型缺少环境资源」。保存项目时会校验各步骤所选资源存在且类型匹配。uni-app 步骤在云打包模式下额外要求该步骤已选择 hbuilderxId(否则该步骤不可用),离线打包只用 nodeId。项目构建页的区域划分见 项目与配置。
目标服务器
目标服务器(target_server)描述部署目标的连接信息,可被多个项目复用,项目用 deploy.targetIds 引用(可多选,逐台部署)。
字段
| 字段 | 说明 |
|---|---|
id | 主键 |
name | 名称 |
description | 说明 |
mode | 连接方式:ssh(默认)或 node |
nodeId | mode=node 时绑定的部署节点(执行端记录的 ID),ssh 模式恒为 0 |
host | 主机地址(node 模式下仅作展示) |
port | SSH 端口,默认 22 |
username | 登录用户 |
authType | 认证方式:password(默认)或 key |
keySource | 密钥来源(authType=key 时有效):upload(默认,上传私钥内容)或 path(执行机上已存在的私钥路径) |
switchMode | 部署前切换用户:none(默认)/ su / sudo |
deployUser | 切换到的目标用户(switchMode≠none 时使用) |
timeout | 连接超时(秒),默认 15 |
config | 凭据 JSON:password / privateKey / passphrase / switchPassword(密文)、privateKeyPath(明文路径) |
createdAt / updatedAt | 时间戳 |
接口只回显「是否已设置」(passwordSet / privateKeySet / passphraseSet / switchPasswordSet)并把对应字段值替换为掩码 ******,永不回显明文。
连接方式:ssh 与 node
ssh(SSH 直连):由执行机通过 SSH 连接目标机执行上传 / 部署 / 启动。需填写主机地址、登录用户与认证凭据;保存时校验主机地址与登录用户非空。列表页提供「测试连接」,由选定的执行机发起 SSH 连通性探测(POST /api/targets/test,支持测试已保存或未保存的连接信息)。node(部署节点):由目标机上运行的cicd_node在本机落地产物,目标机免 SSH。选择该模式时必须选择一个已注册在该目标机上的部署节点(校验节点存在且kind=node);此时:- SSH 凭据字段(
password/privateKey/passphrase/switchPassword)会被清空并从配置中移除,不再参与部署; - 列表的「地址」列展示绑定节点、「登录用户 / 认证方式 / 凭据」列显示为节点本地落地;
- 仅 SSH 模式提供「测试连接」。
- 删除节点前需先解除目标机绑定(
CountTargetServersByNode守卫);重置节点令牌后需同步更新节点config.yaml并重启。
- SSH 凭据字段(
项目部署页只需选择目标服务器(可多台),任务执行时按每台的 mode 自动改走 SSH 直连或节点中转链路,并按选择顺序逐台完成部署。
分组级可见性范围
目标服务器的可选范围由项目分组收窄:分组的 targetIds 字段是「可选目标服务器 ID 列表」。
group.targetIds | 行为 |
|---|---|
[](空,默认) | 不限制,组内项目可选全部目标服务器 |
| 非空 | 组内项目只能从中选择;保存项目时逐个校验 deploy.targetIds 均在范围内,否则返回 400「所选目标服务器不在项目分组的可选范围内」 |
分组保存时校验所选目标服务器存在;项目部署页的下拉按所选分组的范围同步收窄,切换分组后会移除不在范围内的目标。分组表单见 项目与配置。
GET /api/groups对非 admin 只返回targetIds(不下发分组任务变量与通知),因此普通用户在项目配置页仍能基于可见的分组范围收窄下拉。
项目部署配置中的引用
项目用 project.config.deploy.targetIds(列表)引用目标服务器。管理端在合成 RunPlan 时逐个读取每台记录,展开为 PlanDeploy.Targets(每项各自决定链路):
mode=ssh→ 该目标展开为mode=ssh,解密 SSH 凭据下发(port缺省22、timeout缺省15、authType缺省password、keySource缺省upload、switchMode缺省none);mode=node→ 该目标展开为mode=node并带上nodeId/nodeName,任务详情显示「部署节点」。
删除目标服务器时若仍被项目引用($.deploy.targetIds 数组包含),返回 409 并列出引用项目名。
镜像仓库
定位
镜像仓库(docker_registry)保存的是与机器无关的登录凭据:同一套凭据可以在三台执行机上 docker push、在五台目标机上 docker pull,语义完全一致,因此独立建表、独立菜单、独立接口,项目用 Docker 构建步骤的 registryId 引用。
- 做:为
docker push(执行机)与docker pull(目标机)提供登录鉴权。 - 不做:不管理镜像清单、不做镜像清理、不代理 registry 流量、不做仓库探活。
它不放进环境资源:环境资源的语义是「关联执行机 + 路径 + 参与候选机匹配」,而镜像仓库既不关联执行机,也不参与候选执行机匹配。
数据结构
| 字段 | 说明 |
|---|---|
id | 主键 |
name | 名称(唯一) |
server | 仓库地址,如 registry.example.com;留空表示 Docker Hub |
username | 登录用户 |
password | 登录密码,AES-256-GCM 密文 |
description | 备注 |
createdAt / updatedAt | 时间戳 |
接口回显时 password 替换为掩码并附 passwordSet 标记;新建时密码必填,编辑时留空表示不修改。项目侧引用的字段是 Docker 构建步骤的 registryId(project.config.build.steps[].registryId,与环境资源字段并列),留空(0)表示匿名推送 / 拉取。Docker 构建步骤卡片内提供「镜像仓库」下拉(数据源 GET /api/registries,可清空)。
凭据链路
- 管理端:合成 RunPlan 时按 Docker 步骤的
registryId读表并解密密码,装入PlanRegistry{Server, Username, Password},挂在部署计划的Registry上(执行机与目标机共用同一份)。 - 不下发
docker login、不写全局配置:两端各自把凭据渲染成临时 docker 配置目录——目录内config.json的auths[host].auth = base64(user:password),用环境变量DOCKER_CONFIG指向它。好处:并发任务互不覆盖、任务结束随临时目录清理、密码不进命令行(ps)与 shell 历史、不落远端 env 文件。 - 执行机 push:写临时目录下的
config.json(0600),把DOCKER_CONFIG注入docker push命令环境,用后删除。 - 目标机 pull:
registry交付时把config.json写到暂存目录下的docker-auth/,执行DOCKER_CONFIG=<dir> docker pull <image>(内联赋值,优先于 env 文件中可能存在的同名变量)。 deploy.mode=node中转:凭据随节点部署计划下发给目标机上的部署节点,节点侧同样注入临时 docker 配置。- Docker Hub 特例:
server留空时,config.json的键须写成官方索引地址https://index.docker.io/v1/,否则 docker 客户端匹配不到凭据(DockerAuthConfig内部处理)。
引用保护与导入导出
- 删除保护:被项目的 Docker 构建步骤引用(
$.build.steps[*].registryId)时返回 409 并列出引用它的项目名。 - 保存校验:Docker 步骤的
registryId > 0时校验记录存在,不存在报「所选镜像仓库不存在」。 - 导出 / 导入:项目导出把各步骤引用的仓库记录为
refs.steps[].registryName(与config.build.steps按位置对应),使用独立于环境资源的名称映射;导入时按名称优先匹配本环境的仓库,未找到则告警「未找到镜像仓库「x」,已置空」(回退到文件中的原 ID,两者都无效时置 0)。项目配置的复制 / 导入导出入口见 项目与配置。
Git 密钥
Git 密钥由执行端自建并上报公钥,用于 git clone / fetch 的 SSH 传输;它只用于 Git 取码,与部署目标机登录凭据互不复用(后者来自目标服务器)。完整的取码流程与缓存规则见 构建与部署,接入与指令下发见 执行机、部署节点与集群。
自建与上报
| 项 | 说明 |
|---|---|
| 生成时机 | 执行端启动、注册之前调用 ensureGitKey:检查密钥文件是否存在,不存在则生成 |
| 生成方式 | Go 原生 crypto/ed25519 或 crypto/rsa,序列化为 OpenSSH 私钥与 authorized_keys 公钥行,不依赖构建机安装 ssh-keygen |
| 默认算法 | ssh-ed25519(agent.gitKeyType 可改为 ssh-rsa,RSA 4096,兼容老旧 Git 服务) |
| 文件名 | id_ed25519 / id_rsa |
| 私钥 | 仅保存在执行机本地,文件权限 0600、目录 0700(Windows 下尽力收紧);永不上报、永不入库、永不写日志 |
| 上报内容 | {scope, projectId, name, keyType, publicKey, fingerprint, keyPath, comment}(fingerprint 形如 SHA256:xxx;公钥备注默认 cicd-agent@hostname) |
| 上报时机 | 首次随注册报文携带;后续新增 / 轮换时调 POST /api/agent/keys 增量上报;公钥与指纹属于公开信息,落 agent_git_key 表 |
| 存放路径 | 执行机级 {gitKeyRoot}/agent/id_ed25519;项目级 {gitKeyRoot}/projects/{项目ID}/id_ed25519 |
首次使用流程:在管理端「执行机 → Git 密钥」查看并一键复制自建公钥,把它添加到 Git 平台(GitHub / GitLab / Gitee 的 SSH Key 或 Deploy Key)。若跳过配置直接运行任务,执行端会在取码阶段生成密钥并上报,管理端在任务日志与详情中展示公钥与「请先配置公钥」提示,任务以明确原因失败。常见失败:Permission denied (publickey)(公钥未配置)、分支不存在、主机指纹变更(Host key verification failed,需清理 known_hosts 中对应记录)。
轮换与吊销:前端「轮换密钥」下发 rotate_key 指令 → 执行端生成新密钥对覆盖本地并上报新公钥 → 管理端更新记录;旧公钥需用户到 Git 平台手动删除,系统无法代删。「吊销」仅把记录置 revoked,之后该范围的取码立即拒绝使用。
keyScope:执行机级与项目级
Git 平台对 Deploy Key 有「同一公钥不可重复添加到多个仓库」的限制,因此提供两档密钥范围(project.config.git.keyScope):
| 取值 | 密钥 | 适用 | 说明 |
|---|---|---|---|
agent(默认) | 执行机级,一对密钥全局复用 | 推荐:把公钥加到 Git 服务器的账号级 SSH Keys | 一台执行机维护一对密钥,运维最简,该账号拥有的仓库都能拉取 |
project | 项目级 Deploy Key,每个项目一对 | 需按仓库严格授权隔离的场景 | 首次执行前需先在项目页生成并配置公钥 |
项目级密钥的推荐流程:项目编辑页「生成项目密钥」→ 管理端下发 rotate_key(scope=project)→ 执行端生成并上报公钥 → 用户在 Git 平台把该公钥添加为对应仓库的 Deploy Key → 「测试连接」(下发 git_probe 执行 git ls-remote)通过后再运行任务。
项目
authType=https时改用username+password(Token),密码密文存储、合成计划时解密下发;执行端通过临时凭据文件(0600,任务结束即删)提供,不写入命令行与日志。
工作目录缓存与清理
工作目录根由执行端 agent.workspaceRoot 指定,仓库目录固定为 {workspaceRoot}/{项目ID}(以项目 ID 命名,天然避免同名项目互相覆盖);仓库内子目录由各构建步骤的 subDir 指定,但缓存与清理的单位始终是整个仓库目录。
项目配置 git.cache 控制任务结束后是否保留工作目录:
git.cache | 任务开始前 | 任务结束后 | 适用场景 |
|---|---|---|---|
true(默认) | 目录存在 → fetch + checkout + reset(增量,保留已有产物);不存在 → clone | 保留仓库目录 | 高频构建,复用 node_modules、target/、前端依赖缓存 |
false | 若存在残留目录 → 先整体删除,再 clone | 删除仓库目录 | 磁盘紧张、要求每次全新环境 |
缓存模式下的相关开关(project.config.git):
resetHard(默认true):拉取后执行git reset --hard,保证代码与目标提交一致;cleanUntracked(默认false):执行git clean -fdx,刻意默认不删未跟踪文件,这正是缓存复用的价值所在;开启会清掉target/等未跟踪产物,需自行权衡。
清理方式:
| 方式 | 触发 | 说明 |
|---|---|---|
| 手动清理 | 项目操作列「清理工作目录」(下发 clean_workspace,可指定项目或该执行机全部项目) | 用于手动释放磁盘;执行端回报释放的字节数与目录数量 |
| 兜底清理 | 执行端 agent.workspaceTtlDays > 0 | 启动后每天扫描一次 workspaceRoot,删除超过该天数未被任何任务使用的项目目录;默认 0 表示不自动清理,避免误删 |
| 单次覆盖 | 触发任务时传 cache: false | 仅对本次任务生效,不改项目配置 |
清理范围仅限 {workspaceRoot}/{项目ID},不影响 ~/.m2、npm 全局缓存、Docker 镜像层等执行机级缓存。
敏感数据与掩码
加密存储
库内敏感字段统一使用 AES-256-GCM 加密,密文形如 enc:v1:<base64(nonce||cipher)>,由管理端统一实现;存储层不感知加解密,由 runner 在合成 RunPlan 时解密。密钥来源为 auth.encryptKey(32 字节,长度不符会拒绝启动),生产环境应改用环境变量 CICD_ENCRYPT_KEY;集群部署时各实例的 encryptKey 必须完全一致,否则凭据解密失败(见 执行机、部署节点与集群)。解密失败会记日志并返回空值(通常因 encryptKey 被更换)。
用户密码用 bcrypt(不可逆);执行机 Token 用 SHA-256 存
agent.token_hash,明文仅在创建 / 重置时返回一次;执行端 Git 私钥只留在执行机本地,任何报文都不携带。
掩码回显与保留规则
- 接口永不回显明文:读取敏感字段时统一替换为掩码
******(protocol.Mask),并以布尔标记或直接以掩码值表示「已设置」。 - 提交时的保留(
keepSecret):值传空串或掩码******时保留库中原密文,否则加密新值。因此用户在编辑表单里「不填 / 留空」即表示不修改,密文不会被清空或重复加密;传入已加密值会报「值已是密文,无需重复加密」。 - 清除:显式传空串表示清除(环境资源
settingsXml的语义即为「显式空串 = 清除」;目标服务器 / 镜像仓库 / 通知渠道 / 项目凭据则以空或掩码为「保留」)。
敏感字段清单
| 实体 | 敏感字段 | 存储 / 回显 |
|---|---|---|
| 环境资源 | config.settingsXml | AES 密文;接口回显 ****** |
| 目标服务器 | config.password / config.privateKey / config.passphrase / config.switchPassword | AES 密文;回显掩码 + xxxSet 标记(config.privateKeyPath 为明文路径,不加密) |
| 镜像仓库 | password | AES 密文;回显掩码 + passwordSet |
| 项目 | config.git.password | AES 密文;回显掩码 |
| 项目 | config.webhook.secret | AES 密文;回显掩码 |
| 项目 / 分组任务变量 | env[].value(仅 masked=true 的项) | AES 密文;回显掩码;日志与 task_log 落库前把值替换为 ****** |
| 通知渠道 | config.secret / config.smtpPassword | AES 密文;回显掩码 |
| 执行机 | agent.token_hash | SHA-256(非 AES),明文只在生成时返回一次 |
| 执行端 Git 私钥 | 执行机本地文件 | 不上报、不入库,仅本地文件 0600 |
产物存储后端
两种后端与切换
任务产物归档与部署中转产物原先一律落管理端本机磁盘,容器化部署时磁盘不持久、多副本无法共享。现在「写到哪里」变成配置项,本机磁盘与 S3 并存(不是替换),由 backend/config.yaml 的 storage 段决定:
yaml
storage:
artifact: local # 任务产物归档的写入后端:local(默认) | s3
relay: local # 部署中转产物的写入后端:local(默认) | s3
s3:
endpoint: "" # 自定义端点(兼容服务);留空用 AWS 官方端点
region: "" # 区域,如 ap-east-1(留空按 us-east-1)
bucket: ""
accessKey: "" # 生产用 CICD_S3_ACCESS_KEY 覆盖
secretKey: "" # 生产用 CICD_S3_SECRET_KEY 覆盖
sessionToken: "" # 临时凭据的会话令牌(可选)
prefix: "" # 对象键前缀,如 cicd(可选)
pathStyle: false # 按路径寻址:MinIO 等自建服务通常需要 true1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
storage.artifact/storage.relay只能为local或s3(留空按local);- 任一取
s3时,storage.s3的bucket+accessKey+secretKey必须齐全,否则启动失败; - 密钥支持环境变量覆盖(
CICD_S3_ACCESS_KEY/CICD_S3_SECRET_KEY);endpoint去尾斜杠、prefix去首尾斜杠; - 启动日志打印两个后端,如
产物存储后端:归档=local,中转=s3。
本机后端分别使用 artifact.dir(归档根)与 agent.relayDir(中转根),S3 客户端两者共用一份。
引用前缀分发
数据库中的 task_artifact.path / task_relay_artifact.path 是带后端标识的引用:
s3://{bucket}/{key}→ 对象存储;- 其余 → 管理端本机路径(原样保留,字段长度不变)。
写入用哪个后端由配置决定;读取与删除只看引用本身(blob.IsRemote)。因此:
- 切到 S3 后,历史本机路径仍可下载、可清理;
- 切回本机后,历史
s3://对象只要storage.s3仍配置完整同样可读可删。
为避免引用失效,S3 凭据在三要素齐全时就初始化客户端,即使当前写入后端是 local。对象键布局两侧一致:归档为 [prefix/]{projectId}/{taskId}/{seq}-{安全文件名},中转产物为 [prefix/]{taskId}/{seq}.tar.gz。对象不存在时统一返回 blob.ErrNotFound,接口层据此回 404。
巡检与保留
产物归档的保留策略由 artifact.* 控制(默认保留 30 天、单任务上限 2GB、总量上限 20GB,超出按最旧优先回收),过期与超量产物由管理端巡检自动清理,删除任务时产物一并删除;中转产物在中转保留期到期或任务终态后回收。下载入口与任务产物的查看见 构建与部署 与 任务。
集群部署建议把
storage.artifact与storage.relay都设为s3:否则下载请求被负载均衡转到其它实例时会 404。