复制 ECS 后云效 Flow 部署串机:Runner 身份被一并复制

复制一台正在承担部署任务的 ECS 后,云效 Flow 开始把制品随机部署到原机器或复制机。两台机器的 ECS 实例 ID 不同,但在 Flow 看来,它们使用的是同一个 Runner 身份。

最后的止血动作很简单:停止并禁用复制机上的 Runner。真正值得记录的是,为什么复制磁盘会复制部署身份,以及 Flow Runner 到底怎样领取和执行任务。

现象

原 ECS 承担前端发布。复制实例启动后,流水线构建仍然成功,但主机部署阶段不再稳定:

1
2
3
4
5
Flow 构建成功
-> 生成并上传制品
-> 进入主机部署阶段
-> 有时部署到原 ECS
-> 有时部署到复制 ECS

仓库里的 Webhook 脚本只负责触发流水线,并不携带 ECS 地址:

1
2
3
curl -X POST -H "Content-Type: application/json" \
--data '{}' \
'https://flow-openapi.example.com/pipeline/webhook/<webhook-id>'

因此问题不在业务代码或 Webhook,而在主机部署阶段使用的 Runner。

现场证据

两台 ECS 的真实实例 ID 不同:

1
2
原 ECS:   i-original
复制 ECS: i-cloned

但两台机器上存在完全相同的 systemd 服务:

1
runner-v0.3.0-be-<tenant>.service

Runner 配置位于:

1
/root/yunxiao/<tenant>/runner/config/config.yml

配置结构包含:

1
2
3
4
5
6
7
8
9
uuid: <runner-uuid>
url: <flow-server>
token: <runner-token>
tenant: <tenant>
instanceId: i-original
workspace: /root/yunxiao/<tenant>/runner
scanInterval: 5
concurrency: 50
autoUpgrade: true

关键证据有两个:

  1. 两台机器的 config.yml SHA-256 完全相同。
  2. 复制机配置中的 instanceId 仍然是原 ECS,而不是复制机自己的实例 ID。

这说明 ECS 复制过程把 Runner 的 UUID、Token、租户和实例绑定信息一起复制了。hostname 相同只会增加运维混淆,真正导致串机的是 Runner 注册身份相同。

Flow Runner 怎么工作

云效官方将 Runner 定义为安装在构建机或部署主机上的本地服务。它通过长轮询从 Flow 服务端拉取任务,再同步执行状态和日志。流水线 Runner

可以把它理解成一个常驻任务 Worker:

Flow 概念 后端开发中的对应概念
Flow 服务端 调度中心
主机组 Worker 分组和部署范围
Runner 常驻任务消费者
Runner UUID、Token Worker 身份和凭证
Build UUID 单次任务实例 ID
Step 任务中的执行步骤
Shell Executor 本机任务执行器

现场使用的二进制自报版本为 v0.3.0,支持 registerinstallrunexecupgrade 等命令。systemd 启动 runner run 后,它会持续执行下面的链路:

1
2
3
4
5
6
7
8
9
10
11
Runner 常驻进程
-> POST /api/v2/builds/request 长轮询任务
-> 没有任务:返回 204,继续轮询
-> 收到任务:取得 Build UUID 和 Steps
-> 创建临时工作目录和 Shell 脚本
-> PUT /api/v2/builds/{uuid} 上报 RUNNING
-> 下载流水线制品
-> 使用 Shell Executor 执行部署脚本
-> POST /steps/{index}/log 分片上传日志
-> PUT /api/v2/builds/{uuid} 上报 SUCCESS 或 FAILED
-> 标记任务完成并延迟清理目录

一次任务在本机留下的结构大致如下:

1
2
3
4
5
6
7
8
9
10
/root/yunxiao/<tenant>/runner/
-> config/config.yml
-> __flow_work/__flow_temp/<build-uuid>/
-> scripts/step-0.sh
-> scripts/step-1.sh
-> set_env.command
-> __flow_logs/builds/<build-uuid>/
-> step_0.log
-> step_1.log
-> finish

当前主机部署任务的实际动作也很直接:创建制品目录、下载压缩包、生成用户命令脚本、解压到站点目录,然后把退出码和日志回传给 Flow。

主机组决定部署范围。Flow 可以把上游制品下载到主机组中的机器,并支持分批发布;YAML 流水线则通过 machineGroup 指定主机组。流水线关键概念

为什么会随机部署

复制完成后,实际形成了两个使用相同身份的消费者:

1
2
3
4
5
6
7
8
9
10
原 ECS Runner
-> 使用身份 A 长轮询

复制 ECS Runner
-> 也使用身份 A 长轮询

Flow 服务端
-> 为身份 A 准备部署任务
-> 两个进程同时请求
-> 任务可能被任意一个进程领取

Flow 服务端如何处理重复 Runner 身份没有公开实现,无法从源码确认;但两台机器的配置完全相同、日志显示它们使用相同 Runner 标识轮询,而且部署记录确实分别落到两台机器,已经足以确认故障链路。

修复与验证

复制机暂时不需要参与 Flow 部署,因此直接停止并禁用它的 Runner:

1
sudo systemctl disable --now runner-v0.3.0-be-<tenant>.service

验证结果:

1
2
3
4
5
6
7
8
9
原 ECS
-> Runner active/running
-> enabled
-> 正常领取部署任务

复制 ECS
-> Runner inactive/dead
-> disabled
-> 不再请求 Flow 任务

后续流水线重新稳定部署到原 ECS,说明止血动作与根因一致。

停用不等于彻底清理

disable --now 只停止进程并取消开机启动,复制机磁盘中仍然保留原 Runner Token 和配置。有人重新启用该服务,问题还会复发。

长期处理应根据复制机用途二选一:

  • 不需要 Flow:卸载复制过来的 Runner,并删除对应注册配置。
  • 需要独立部署:清理旧 Runner,再从 Flow 控制台重新接入,让它获得新的 UUID、Token 和实例绑定,并加入独立主机组。

阿里云的主机部署故障文档也建议:通过镜像生成的新主机,不应继续使用复制的代理身份,应卸载后重新添加。主机部署问题

以后复制 ECS 或制作系统镜像时,应该额外检查这些有注册身份的常驻代理:

  • CI/CD Runner
  • GitLab Runner
  • 监控和日志采集 Agent
  • 服务注册 Agent
  • 节点证书和机器级 Token

这类组件不是普通软件包。复制磁盘不仅复制了程序,也可能复制它在外部控制平面中的身份。

  • 写正文
  • 本地预览:npm run server
  • 发布:bash bin/publish.sh -m "post: ..."

复制 ECS 后云效 Flow 部署串机:Runner 身份被一并复制
https://willfordzhan.github.io/2026/08/17/aliyun-flow-runner-cloned-ecs/
作者
詹文杰
发布于
2026年8月17日
许可协议