深入理解 CIMaster:大规模 CI 集群智能协调系统
在现代云原生开发中,持续集成(CI)流水线是软件交付的基石。在大规模场景下,管理共享测试基础设施成为一个关键挑战。这就是 CIMaster 发挥作用的地方——一个精巧的集群管理服务,旨在协调对共享 CI 测试集群的访问,确保资源的高效利用并防止测试冲突。
问题背景:大规模共享 CI 基础设施
在大型组织中,每天运行成百上千个 CI 任务时,测试集群是需要高效共享的昂贵资源。主要挑战包括:
- 资源竞争:多个 CI 任务竞争有限的测试集群
- 集群状态管理:跟踪哪些集群可用、被占用或被保留用于调试
- 人工干预:开发者需要保留集群进行调查而不阻塞其他人
- 动态供应:当容量不足时按需创建新集群
- 生命周期管理:使用后或过期后自动释放集群
CIMaster 通过一个集中式协调服务解决了所有这些挑战。
架构概览
CIMaster 是一个用 Go 编写的 Kubernetes 原生服务,提供集群生命周期管理的 REST API。它由几个关键组件组成:
核心组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ┌─────────────────────────────────────────────────────────────┐ │ CIMaster 服务 │ │ │ │ ┌──────────────────┐ ┌─────────────────────────┐ │ │ │ HTTP API 服务器 │ │ 集群管理器 │ │ │ │ (端口 8080) │◄─────►│ - 状态管理 │ │ │ │ │ │ - 分配逻辑 │ │ │ │ /getvacant │ │ - Hold 过期 │ │ │ │ /holdcluster │ │ │ │ │ │ /releasecluster │ │ │ │ │ │ /createcluster │ └───────────┬─────────────┘ │ │ │ ... │ │ │ │ └──────────────────┘ │ │ │ │ │ │ ┌──────────────────┐ ┌───────────▼─────────────┐ │ │ │ 指标服务器 │ │ Kubernetes ConfigMap │ │ │ │ (端口 8090) │ │ - cluster.json │ │ │ │ │ │ - 乐观锁 │ │ │ └──────────────────┘ └─────────────────────────┘ │ │ │ └────────────────┬─────────────────────────────────────────────┘ │ │ HTTP POST ▼ ┌───────────────────────┐ │ Prow Manual Trigger │ │ /manual-trigger │ └───────────────────────┘
|
1. 集群管理器 (cluster-manager.go)
系统的核心,负责:
- 集群分配:查找并分配空闲集群给 CI 任务
- Hold 管理:允许开发者预留集群用于调试(6 小时过期)
- 自动清理:定期释放过期的 hold
- 与 Prow 集成:通过 Prow 的 manual-trigger 端点触发集群创建
2. 集群操作 (cluster-ops.go)
实现 ClusterInterface 接口,包含以下操作:
OccupyVacantCluster:原子性地分配可用集群
FinishOccupiedCluster:将集群返回到可用池
HoldCluster/ReleaseCluster:手动 hold 管理
AddCluster/DeleteCluster:集群库存管理
3. 状态持久化
所有集群状态存储在 Kubernetes ConfigMap 中(ci 命名空间中的 clusters):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [ { "name": "cluster-01", "region": "us-west", "status": "testing", "lastJob": "e2e-conformance", "lastBuild": "12345", "lastTriggerName": "john", "hold": false, "disabled": false, "purpose": "tess-ci", "osimage": "centos-atomic-7.6.1810-qcow2" } ]
|
乐观锁使用 Kubernetes ResourceVersion 防止并发更新时的竞争条件。
与 Prow Manual Trigger 的集成
CIMaster 的一个强大功能是通过 manual-trigger 组件与 Prow 的集成。这使得当现有容量不足时能够动态供应集群。
什么是 Prow Manual Trigger?
Prow 是 Kubernetes 的 CI/CD 系统。manual-trigger 组件(/Users/tashen/test-infra/prow/cmd/manual-trigger)是一个 HTTP 服务器,允许在正常 GitHub webhook 流程之外以编程方式创建 ProwJob。
核心能力:
- 接受带有任务规格的 HTTP POST 请求
- 在 Kubernetes 中创建 ProwJob 自定义资源
- 支持 presubmit、postsubmit 和 periodic 任务类型
- 向任务注入环境变量(如
AUTHOR)
CIMaster 如何使用 Manual Trigger
当用户调用 CIMaster 的 /createcluster 端点时:
1
| curl "http://cimaster:8080/createcluster?user=john&branch=master&job=e2e-k8s-1.32"
|
CIMaster 执行以下流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| prowRequest := types.ProwManualTriggerRequest{ Org: "tess", Repo: "tessops", BaseRef: "master", ProwType: "postsubmit", ProwJob: "e2e-k8s-1.32", User: "john", }
resp, err := http.Post( "https://prow.tess.io/manual-trigger", "application/json", requestBody, )
|
在 Prow 端,manual-trigger 服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| func (s *server) handleManualTrigger(w http.ResponseWriter, r *http.Request) { var req triggerRequest json.NewDecoder(r.Body).Decode(&req)
postsubmits := cfg.PostsubmitsStatic[req.Org+"/"+req.Repo] for _, p := range postsubmits { if p.Name == req.ProwJob { prowJob = createProwJobFromPostsubmit(p, req) break } }
if req.User != "" { addAuthorEnvToProwJob(prowJob, req.User) }
prowJobClient.Create(ctx, prowJob, metav1.CreateOptions{})
statusLink := fmt.Sprintf("https://prow.tess.io/prowjob?prowjob=%s", prowJob.Name) logLink := fmt.Sprintf("https://prow.tess.io/log?job=%s&id=%s", req.ProwJob, buildID) }
|
请求-响应流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌────────────┐ │ 用户 │ │ CIMaster │ │ Prow Manual │ │ Kubernetes │ │ │ │ │ │ Trigger │ │ │ └────┬─────┘ └────┬─────┘ └──────┬───────┘ └─────┬──────┘ │ │ │ │ │ POST /createcluster │ │ │ user=john │ │ │ ├──────────────────►│ │ │ │ │ │ │ │ │ POST /manual-trigger│ │ │ │ {org, repo, prowjob}│ │ │ ├─────────────────────►│ │ │ │ │ │ │ │ │ 创建 ProwJob │ │ │ │ AUTHOR=john │ │ │ ├──────────────────────►│ │ │ │ │ │ │ │ ◄─────────────────── │ │ │ │ ProwJob 已创建 │ │ │ │ │ │ │ ◄────────────────────┤ │ │ │ {success, job_name, │ │ │ ◄────────────────┤ status_link} │ │ │ 集群创建已触发 │ │ │ │ │ │ │
|
触发的 ProwJob 通常运行基础设施即代码(如 Terraform 或 Ansible)来供应新的 Kubernetes 集群,一旦就绪就会被添加到 CIMaster 的池中。
集群生命周期状态机
集群在几个状态之间转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| ┌───────────┐ │ finished │ ◄───────────────────┐ │ (空闲) │ │ └─────┬─────┘ │ │ │ │ /getvacant │ │ │ ▼ │ ┌──────────┐ │ │ testing │ │ │ (占用中) │ │ └────┬─────┘ │ │ │ │ /finishtest │ │ │ └────────────────────────────┘
Hold 状态(叠加): ┌─────────┐ │ hold= │ │ false │◄──── /releasecluster ────┐ └────┬────┘ │ │ │ │ /holdcluster │ │ │ ▼ │ ┌─────────┐ │ │ hold= │ │ │ true │────────────────────────────┘ └─────────┘ (6小时后自动过期)
|
关键特性和实现细节
1. 带重试逻辑的智能分配
CIMaster 实现了带抖动的指数退避来处理并发分配:
1 2 3 4 5 6 7 8 9 10
| type RandomBackoff struct { MinBackoff time.Duration MaxBackoff time.Duration rng *rand.Rand }
func (rb *RandomBackoff) GetRetryInterval() time.Duration { delta := rb.MaxBackoff - rb.MinBackoff return rb.MinBackoff + time.Duration(rb.rng.Int63n(int64(delta)+1)) }
|
每个操作最多重试 3 次,使用随机的 50-200ms 退避时间,避免惊群问题。
2. 自动 Hold 过期
后台 goroutine 持续检查过期的 hold:
1 2 3 4 5 6 7
| func (cm *ClusterManager) runCronReleaseHeldEnvs() { for { durationUntilNextExpire, err := cm.clearExpiredHolds() timer.Reset(durationUntilNextExpire) <-timer.C } }
|
这确保了如果开发者忘记释放,集群不会被无限期锁定。
3. 多用途集群支持
CIMaster 支持不同的集群类型:
- **
tess-ci**:标准 CI 测试集群
- **
tnet-ci**:具有 OS 镜像选择的网络特定测试集群
分配时会遵守用途和 OS 镜像要求:
1 2 3 4 5 6
| if cluster.Purpose != purpose { continue } if cluster.Purpose == TnetCI && cluster.OSImage != osimage { continue }
|
4. 管理员授权
受保护的端点使用简单的基于文件的授权:
1 2 3 4 5 6 7 8 9 10
| func checkUser(h http.HandlerFunc, users []string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userName := r.URL.Query().Get("name") if !contains(users, userName) { fmt.Fprintf(w, "user %s is not authorized", userName) return } h(w, r) } }
|
管理员用户从 /botadmin/users 文件加载(分号分隔)。
5. 可观测性
- Prometheus 指标:在端口 8090 暴露(
/metrics)
- 结构化日志:所有操作都使用关联 ID 记录
- 优雅关闭:120 秒的宽限期来处理进行中的请求
API 示例
为 CI 分配集群
1 2 3 4 5 6 7 8
| CLUSTER=$(curl -s "http://cimaster:8080/getvacant?build=123&job=e2e-test&email=ci-bot@ebay.com") echo "使用集群: $CLUSTER"
curl "http://cimaster:8080/finishtest?cluster=$CLUSTER"
|
调试工作流
1 2 3 4 5 6 7 8
| curl "http://cimaster:8080/holdcluster?cluster=cluster-05&name=alice&desc=debugging+network+issue"
kubectl get pods -n test-namespace
curl "http://cimaster:8080/releasecluster?cluster=cluster-05&name=alice"
|
创建新集群
1 2 3 4 5 6 7 8 9
| curl "http://cimaster:8080/createcluster?user=admin&branch=master&job=e2e-k8s-1.32"
curl "http://cimaster:8080/addcluster?cluster=cluster-20®ion=eu-central&name=admin"
|
JSON API 支持
用于编程访问:
1
| curl -H "Accept: application/json" "http://cimaster:8080/getvacant?build=123&job=test"
|
响应:
1 2 3 4 5 6 7 8 9
| { "name": "cluster-07", "region": "us-west", "status": "testing", "lastBuild": "123", "lastJob": "test", "lastTriggerName": "john", "purpose": "tess-ci" }
|
部署
CIMaster 作为 Kubernetes Deployment 运行,具有 3 个副本以实现高可用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| apiVersion: apps/v1 kind: Deployment metadata: name: cimaster namespace: ci spec: replicas: 3 minReadySeconds: 90 strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% template: spec: containers: - name: cimaster image: hub.tess.io/tess/cimaster:v0.0.37 command: - cimaster - --manageCluster=true - --cluster-config-map=clusters - --botAdminDir=/botadmin - --prow-url=https://prow.tess.io/manual-trigger - --default-prow-job=e2e-k8s-1.32 ports: - containerPort: 8080 - containerPort: 8090
|
性能特性
- 分配延迟:约 100-300ms(包括 ConfigMap 读写周期)
- 重试开销:每次重试 50-200ms(最多 3 次尝试)
- Hold 过期检查:每 10 分钟(默认)
- 并发性:通过乐观锁对多个副本安全
实际应用影响
在 eBay 的 TESS 平台,CIMaster 管理:
- 20+ 个跨多个区域的共享测试集群
- 每天数百个来自不同团队的 CI 任务
- 6 小时自动 hold 过期防止资源锁定
- 亚秒级分配适用于大多数请求
- 通过 Prow 集成实现动态扩展
未来增强
正在考虑的潜在改进:
- 优先级队列:允许关键任务跳过分配队列
- 集群健康检查:自动禁用不健康的集群
- 使用分析:跟踪分配模式并优化容量
- Webhook 通知:hold 过期的 Slack/邮件警报
- 多集群联邦:跨多个 Kubernetes 集群协调
结论
CIMaster 展示了一个相对简单的协调服务如何解决 CI/CD 基础设施中的复杂资源管理挑战。通过结合:
- Kubernetes ConfigMap 中的有状态集群跟踪
- 用于安全并发访问的乐观锁
- 废弃 hold 的自动过期
- 用于动态供应的 Prow 集成
- 易于集成的 REST API
…它为大规模共享测试基础设施提供了坚实的基础。
与 Prow 的 manual-trigger 组件的集成特别优雅——CIMaster 不需要知道如何创建集群,只需要知道何时请求它们。这种关注点分离允许基础设施团队独立演进集群供应策略。
无论您是为大型组织构建 CI 基础设施,还是希望优化 Kubernetes 平台中的资源利用,CIMaster 展示的模式都为分布式系统协调提供了宝贵的见解。
链接和资源
本文探讨了 CIMaster 的内部架构,这是一个生产环境的集群协调服务。所有代码示例均来自实际实现。