等待审批的工作流运行

Semaphore UI 2.19 是 2.x 系列中规模最大的一个版本。它引入了带图形化编辑器的工作流,让 runner 能够在 Docker 容器和 Kubernetes Pod 中执行任务,使 Semaphore 能够向运行中的任务签发短期 JWT 身份令牌,新增了加密密钥轮换,彻底修复了 runner 的可靠性问题,并带来了一长串安全加固变更。

本系列涵盖 v2.19.0v2.19.14 的各个版本。补丁版本列在文末。

亮点

  • 工作流 (Beta) — 将任务模板串联成带审批门控的流水线,在图形化编辑器中绘制,并在同一画布上实时观察运行。
  • Docker 和 Kubernetes 执行器 — runner 可以在全新的容器或 Pod 中执行每个任务 (Pro / Enterprise)。
  • 面向任务的 JWT 身份令牌 — 从 playbook 到 Vault、AWS、GCP、Azure 以及任何接受 OIDC 令牌的服务的无密钥认证。
  • 加密密钥轮换 — 带标签的密钥环,支持热重载和 vaults check 命令。
  • 调查变量可作为环境变量传递,并新增 inttext 类型和重新设计的 enum 类型。
  • 任务历史的真正服务端分页 — 拥有数百万任务的项目依然保持快速。
  • Runner 可靠性 — 在线/离线状态、一次性哈希注册令牌,以及自动恢复卡在失效 runner 上的任务。
  • 选择性调试日志,通过 SEMAPHORE_DEBUG_FILTER 实现。
  • 安全加固:当前密码校验、CSRF 来源验证、安全 Cookie、API 全面更严格的输入验证。
  • 移除 BoltDB — 仅支持 SQLite、MySQL 和 PostgreSQL。

工作流 (Beta)

工作流是由任务模板组成的图,作为一个整体运行。每个节点要么是任务(运行一个模板),要么是审批(暂停运行,直到用户批准或拒绝),要么是注释(永不执行的自由格式说明)。边带有条件:成功时失败时始终

工作流以新的工作流菜单项出现在项目侧边栏中,并带有 Beta 标记。它们在 Pro 版本中可用;在 beta 期间,无论何种方案都会启用。

图形化编辑器

工作流编辑器

编辑器是一个基于 Drawflow 构建的全页画布:

  • 从面板中拖出节点,通过拖动节点把手来连接它们;
  • 点击一条边可更改其条件;边按颜色区分,角落里有图例;
  • 点击一个节点可在侧边面板中编辑其属性:模板、汇聚模式(所有父节点 / 任一父节点)、审批超时和消息、注释文本;
  • 自环和环路在绘制时即被拒绝,问题面板与服务端验证保持一致,因此无法保存有问题的图;
  • 节点位置会被持久化;通过 API 创建且未指定位置的工作流会自动获得拓扑布局;
  • 使用工具栏或 Ctrl + 鼠标滚轮缩放,拖动画布平移,折叠面板以腾出空间;
  • 起始版本为运行版本号提供种子(1.4.01.4.1、……);该版本会传递给运行所启动的每个任务。

节点属性面板

实时运行视图

运行视图复用同一画布。每个节点显示其任务状态,活动节点高亮显示,待处理的审批直接在画布上渲染批准 / 拒绝按钮。停止按钮会强制停止运行中的所有任务,拒绝待处理的审批,并将运行标记为 stopped

深色主题运行视图

运行状态包括 runningapprovalsuccessfailedstopped。工作流的推进由服务端驱动:当任一工作流任务完成时,后续节点会被调度,因此没有自动运行子节点的模板不再会让运行停滞。

工作流列表

逐节点任务参数

每个任务节点都可以覆盖传递给其模板的参数(变量、清单、分支、参数、版本、消息),与手动运行的方式相同。

API

GET/POST   /api/project/{id}/workflows
GET/PUT/DELETE /api/project/{id}/workflows/{workflow_id}
POST       /api/project/{id}/workflows/{workflow_id}/run
GET        /api/project/{id}/workflows/{workflow_id}/runs
GET        /api/project/{id}/workflows/{workflow_id}/runs/{run_id}
POST       /api/project/{id}/workflows/{workflow_id}/runs/{run_id}/stop
GET        /api/project/{id}/workflows/{workflow_id}/runs/{run_id}/artifacts
GET        /api/project/{id}/workflows/{workflow_id}/runs/{run_id}/approvals
POST       /api/project/{id}/workflows/{workflow_id}/runs/{run_id}/approvals/{node_id}

Beta 的已知限制:没有小地图、撤销/重做或多选;工作流工件(set_stats 值)尚不能在远程 runner 上执行的任务之间传递。


Docker 和 Kubernetes 执行器 (Pro / Enterprise)

Runner 可以在隔离环境中执行每个任务,而不是直接在 runner 主机上执行。执行器在每个 runner 进程中通过 runner.executor.type 选择一次(localdockerk8s)。

Dockerrunner.executor.docker,Pro):

选项 环境变量 默认值
host SEMAPHORE_RUNNER_DOCKER_HOST 本地套接字
tls_verify, cert_path SEMAPHORE_RUNNER_DOCKER_TLS_VERIFY, …_CERT_PATH
image SEMAPHORE_RUNNER_DOCKER_IMAGE semaphoreui/job:latest
helper_image SEMAPHORE_RUNNER_DOCKER_HELPER_IMAGE semaphoreui/helper:latest
network SEMAPHORE_RUNNER_DOCKER_NETWORK bridge
pull_policy SEMAPHORE_RUNNER_DOCKER_PULL_POLICY if-not-present
cpu_limit, memory_limit SEMAPHORE_RUNNER_DOCKER_CPU_LIMIT, …_MEMORY_LIMIT
privileged SEMAPHORE_RUNNER_DOCKER_PRIVILEGED false
poll_interval_seconds, cleanup_grace_seconds …_POLL_INTERVAL_SECONDS, …_CLEANUP_GRACE_SECONDS 2, 30

Kubernetesrunner.executor.k8s,Enterprise):

选项 环境变量 默认值
kubeconfig SEMAPHORE_RUNNER_K8S_KUBECONFIG 集群内
namespace SEMAPHORE_RUNNER_K8S_NAMESPACE semaphore
image SEMAPHORE_RUNNER_K8S_IMAGE semaphoreui/job:latest
helper_image SEMAPHORE_RUNNER_K8S_HELPER_IMAGE semaphoreui/helper:latest
service_account SEMAPHORE_RUNNER_K8S_SERVICE_ACCOUNT default
pull_secrets SEMAPHORE_RUNNER_K8S_PULL_SECRETS
poll_interval_seconds, cleanup_grace_seconds …_POLL_INTERVAL_SECONDS, …_CLEANUP_GRACE_SECONDS 3, 30

CI 现在会发布两个新镜像:semaphoreui/job(Ansible、Terraform、OpenTofu、Terragrunt、paramiko)和 semaphoreui/helper。模板可以通过模板表单中新增的执行器镜像字段为自己的任务覆盖镜像(当执行器功能未获授权时,会显示 升级到 PRO 徽标)。

Runner 连接还新增了 runner.connection.server_ca_cert_filerunner.connection.skip_tls_verify


面向任务的 JWT 身份令牌

Semaphore 可以充当运行中任务的 OIDC 风格身份提供者,这样 playbook 无需长期凭据即可向 Vault、云 STS 端点或内部服务进行认证。

模板表单:带 JWT 的高级选项

  • 通过向任务 runner 签发 JWT 按模板启用;设置一个或多个受众TTL(上限为 jwt.max_ttl)。
  • 任务通过 SEMAPHORE_JWT 环境变量接收令牌。
  • 令牌使用 ES256(ECDSA P‑256)签名,仅携带 ID:task_idproject_idtemplate_iduser_id,以及标准的 isssubaudexpnbfiatjti 声明。
  • 公钥发布于 GET /.well-known/jwks.json

服务器配置:

"jwt": {
  "enabled": true,
  "issuer": "https://semaphore.example.com",
  "default_ttl": "1h",
  "max_ttl": "24h"
}

环境变量:SEMAPHORE_JWT_ENABLEDSEMAPHORE_JWT_ISSUERSEMAPHORE_JWT_DEFAULT_TTLSEMAPHORE_JWT_MAX_TTL


调查变量

以环境变量的形式传递变量

调查变量现在有了变量传递方式设置:应用特定的 CLI 方式(Ansible 使用 --extra-vars,Terraform/OpenTofu 使用 -var,shell 脚本使用 CLI 参数)或进程环境变量。变量名会原样使用,因此 Terraform 用户只需将其命名为 TF_VAR_region。环境变量不会出现在进程列表中,这使其成为传递密钥更安全的选择。

以环境变量形式传递的调查变量

新类型

  • int — 带验证的数字输入;
  • text — 多行文本;
  • enum — 重新设计的编辑器,支持名称/值对和默认值。

Enum 调查变量编辑器

任务对话框会按类型相应地渲染:

带类型化调查变量的新建任务对话框

存储在现有的 survey_vars JSON 中 —— 无需迁移。


模板与任务

  • 动态 playbook 选择器。 playbook 文件路径 字段会列出仓库中找到的 playbook(GET /api/project/{id}/repositories/{repository_id}/playbooks)。列表跟随所选分支,在无法读取仓库时回退为自由文本。

  • 跳过 Ansible Galaxy 安装 — 按模板设置的选项,可选择按任务覆盖,用于跳过从 requirements.yml 安装角色和集合。

  • 变量组中的类型化变量,包括数字:

    带类型化变量的变量组

  • 任务历史分页。 历史页面过去会获取最新的 200 个任务并在客户端翻页,因此更早的任务无法访问。后端现在使用键集游标(?count=20&before=<task_id>X-Has-Next 响应头)一次返回一页,不使用 COUNT(*) 也不使用 OFFSET。页脚提供 每页行数 以及上一页/下一页控件。同样的分页也适用于按模板的任务列表和仪表盘。

    带服务端分页的历史页面

  • 任务列表最多每 5 秒重新加载一次;移除了若干冗余请求。

  • 计划任务使用服务端 cron 解析器进行验证,客户端与服务端不再出现分歧。

  • 只有在模板允许时,任务中的分支覆盖才会被接受。

  • Git 操作按仓库目录串行化。 启用了 允许并行任务 的模板共享一个工作副本,并发的 git pull / git checkout 可能会损坏它。更新和检出现在构成单个临界区,本地执行和 runner 执行均适用,包括清单仓库。


Runner

带在线/离线状态的 Runner 页面

  • Runner 页面上的在线 / 离线状态,基于心跳存活状态得出。

  • 一次性注册令牌。 可以先在 UI 中创建 runner,稍后再使用 smrs_… 令牌注册;该令牌仅显示一次,仅以 SHA‑256 哈希形式存储,并在一小时后过期。对话框提供可直接复制的环境变量、配置文件和 Docker 命令。重新生成令牌会重置已注册的 runner,以便重新注册。

    Runner 注册令牌对话框

    SEMAPHORE_WEB_ROOT=https://semaphore.example.com \
    SEMAPHORE_RUNNER_REGISTRATION_TOKEN=smrs_… \
    semaphore runner register --config ./config.runner.json
    
    semaphore runner start --config ./config.runner.json
    
  • 挂起任务恢复。 Runner 会发送其进程启动时间(X-Runner-Started-At)。停止轮询的 runner 会在 runners.offline_timeout_sec(120 秒)后被标记为 离线:它不再接收新任务,其处于 starting 状态的任务会被重新分配。在 runners.task_fail_timeout_sec(420 秒)后,其处于 running 状态的任务会被标记为失败并附带明确的消息。重启后丢失内存中作业池的 runner 会被立即检测到。协调每隔 runners.reconcile_interval_sec(30 秒)运行一次。

  • 从某个 runner 重新分配走的任务会在旧 runner 上被终止。

  • 移除了过期 runner 回退机制:当所有 runner 都离线时,任务会在队列中等待,而不是被派发给长达 30 分钟未轮询的 runner。

  • 移除了每个 runner 的 RSA 加密密钥。 Runner 与服务器之间的流量依赖 TLS;这从注册和 setup 中删除了密钥交换步骤。

  • 修复了 runner 客户端中的 TCP 连接泄漏;无效的注册令牌返回 400


密钥与加密

  • 加密密钥轮换。 新的 encryption 块描述一个带标签的密钥环:内联的 keys(值或文件),或一个 keys_folder,其中每个文件都是以文件名命名的密钥,再加上指向密钥加密密钥和选项加密密钥的 active 指针。密文现在携带密钥 ID,因此无需一次性全量重新加密即可轮换密钥。encryption.keys_file 配合 keys_poll_interval(默认 15s)可热重载密钥环。新增 CLI:semaphore vaults checksemaphore vaults rekey 已围绕密钥环重写。旧版扁平的 access_key_encryption 仍然可用。
  • option_encryption — 用于数据库中存储的选项的独立密钥。
  • OpenBao 密钥存储类型(通过 Vault 提供者路由),并带有自己的图标。
  • 无需静态凭据的 AWS Secrets Manager — 新增 使用 IAM 角色 复选框。
  • Vault/OpenBao 存储的跳过 TLS 验证选项。
  • 同步的和只读的密钥字段在更新时不再被清空。

可观测性

  • 命名空间化的调试日志。 --debug-filter / SEMAPHORE_DEBUG_FILTER 用于选择哪些子系统输出调试信息,风格类似 Node.js 的 debugrunnerrunner,task_pooltask_***,-db。可用的命名空间:runnertask_pooltask_runnertask_loggergitterraformsessionldapscheduledbha。该过滤器仅在日志级别为 DEBUG 时生效;它永远不会提升日志级别。Syslog 钩子遵循同一过滤器。
  • 在 runner、任务和认证中新增了许多带上下文的调试语句;启动时会打印所选的工作区。

安全

修改密码需要输入当前密码

  • 修改密码现在需要输入当前密码(CWE‑620)。
  • 对状态变更请求进行 Origin / Referer 验证(CSRF 加固)。
  • 通过 HTTPS 提供服务时,会话 Cookie 会标记为 Secure
  • Runner 注册令牌以哈希形式存储并会过期;移除了每个 runner 的加密密钥。
  • 创建自定义角色时会检查调用者的权限(权限提升修复)。
  • Git URL 验证;向 git 传递 --end-of-options,使精心构造的 ref 无法被解析为标志;提交哈希会进行格式检查;浏览仓库前会验证分支;playbook 路径会被验证。
  • 访问密钥负载和模板的 app 字段会被验证。
  • JWT 声明仅携带 ID —— 不会向外部系统泄露名称或电子邮件。
  • Runner 令牌不再写入项目备份。
  • API 在写入错误后立即返回,而不是继续输出部分写入的响应。
  • SECURITY.md 中发布了安全 SLA;发布工件使用 [email protected] GPG 密钥签名。

UI 与本地化

包含捷克语的语言选择器

  • 捷克语翻译。
  • 模板表单中 JWT 和计划部分的下拉卡片。
  • 复制到剪贴板图标在浅色模式下可见;修复了运行中任务的加载动画;修复了模板表单的内边距;工作流 Beta 标签。
  • 集成变量提取会保留 JSON 对象和数组,而不是将其转为字符串。

升级说明

破坏性变更与行为变更

  1. BoltDB 已移除。 bolt 不再是有效的方言;服务器会拒绝启动并提示 “Bolt is not supported starting from version 2.19”。请先迁移到 SQLite、MySQL 或 PostgreSQL。
  2. 移除了 runner 加密密钥。 服务器和 runner 必须都升级到 2.19。请确保 runner 与服务器之间的流量受 TLS 保护。依赖该密钥的脚本化 runner 配置必须更新。
  3. 任务列表 API 已分页。 GET /api/project/{id}/tasks/last 接受 countbeforelimit 仍然被接受,但依赖单次响应返回最新 200 个任务的客户端必须改为分页。
  4. 不再有过期 runner 回退。 当所有 runner 离线时,任务保持排队。
  5. 注册时移除了 runner 的 active 标志
  6. 项目备份不再包含 runner 令牌。
  7. SQLite:v2.19.14 会重建 sessiontask,以添加正确的外键(修复用户删除)。孤立的会话会被移除。升级前请备份数据库。

新增配置

jwtrunnersencryptionoption_encryptionsecrets_pathdb.dialectrunner.executor.{type,docker,k8s}runner.connection.{server_ca_cert_file,skip_tls_verify}runner.registration_token_filerunner.token_file。全部为可选;现有配置继续有效。config.schema.yaml 和配置参考文档已重新生成。

数据库迁移

v2.18.6(模板 jwt_params)、v2.18.15(工作流表)、v2.19.2runner.started_at)、v2.19.11project__workflow_node.task_params_id)、v2.19.12project__template.executor_image)、v2.19.14(SQLite session/task 重建)。修复了 MariaDB 12.1 迁移兼容性。


补丁版本

版本 变更
2.19.8 修复了 SQLite 迁移;修复了用户删除(session/task 外键);数据库查询中的字符串比较不区分大小写
2.19.9 SEMAPHORE_RUNNER_EXECUTOR_TYPE 环境变量;配置 omitempty 清理
2.19.10 执行器配置修复;Docker 构建选项修复
2.19.11 日志中打印所选工作区;helper 镜像的 latest 标签;数据库迁移测试
2.19.12 修复了 runner 配置选项中的空指针;错误传播修复
2.19.14 修复了配置中旧版 secrets_path 的处理

依赖项

Go 1.26;go-git 5.19、go-oidc 3.19、golang.org/x/crypto 0.53、go-ldap 3.4.13、modernc.org/sqlite 1.52。文档已作为 git 子模块添加;THIRD-PARTY-LICENSES.md 已重新生成。

You might find this interesting