Perfetto CI 架构解析:基于 GCE 自托管 Runner 与 GitHub Actions 的弹性持续集成系统
【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto
导读
本文基于 Perfetto 仓库的 continuous-integration.md 设计文档,深入解析 Perfetto 项目用于补充 Android TreeHugger 的持续集成体系:它以 GitHub Actions 为调度入口,在 Google Cloud(GCE + GAE)上运行自托管 Runner,通过"VM → worker 容器 → sandbox 容器"三级分层实现无状态、可弹性伸缩、安全隔离的构建与测试。读完本文,你将掌握这套 CI 的整体架构、启动链路、核心配置参数、日常运维 Playbook 及其安全模型,并可在自己的项目中复用其设计思路。
Perfetto 的 CI 定位十分明确:它叠加在 Android TreeHugger 之上(而非替代),目的是为 TreeHugger 不支持的其它操作系统和较老 Android 设备提供早期测试信号与覆盖。整套 CI 基于 GitHub Actions,入口工作流为 .github/workflows/analyze.yml。
一、CI 整体架构:GitHub Actions + GCE 自托管 Runner
Perfetto CI 由两个主要部分构成:
- 调度层:GitHub Actions 工作流(位于 .github/workflows/),负责根据变更文件类型决定触发哪些测试;
- 执行层:注册在 GitHub 项目上的自托管 Runner,托管于名为
perfetto-ci的 Google Cloud 项目中,源码位于 infra/ci。
与直接使用 GitHub 托管的 runner 不同,自托管方案让 Perfetto 能够完全掌控 runner 的运行环境(Docker 镜像、临时磁盘、网络隔离、服务账号权限),并借助 GCE 的自动扩缩容能力按需拉起计算资源,同时将成本函数与待处理任务队列长度绑定。
从仓库结构看,infra/ci 下包含三类核心组件:
- frontend/:基于 AppEngine 的控制器(
ci.perfetto.dev),负责编排队列与推送扩缩容指标; - worker/:运行在每台 GCE VM 上的特权容器与相关脚本;
- sandbox/:真正执行 GitHub Action Runner 的隔离容器。
下图展示了这套 CI 的整体结构(图片来源为仓库内的 CI 架构图):
二、入口工作流 analyze.yml:按变更类型扇出的作业调度
.github/workflows/analyze.yml 是整个 CI 的入口。它负责在 push(postsubmit,main分支)和 pull_request(presubmit,main与dev/**/*分支,类型为opened与synchronize)时被触发,并设置了concurrency组以在新提交推送时取消同一 PR/分支上的旧任务。
该工作流中的analyzejob 特意运行在GitHub 的ubuntu-latest公共池上(而非自托管 sandbox)。注释给出了两点刻意设计的原因:
analyze不需要 sandbox 容器提供的任何特殊能力,公共池更快;- 当 CI 自动扩缩容降到 0 时,等待自托管资源就绪会很久,从而延迟子任务的排队、进而延迟扩容——把
analyze放在公共池能尽快扇出子任务。
analyzejob 通过git diff --name-only对比上游分支,产出三个关键输出(outputs),用于驱动下游作业的条件执行:
| 输出 | 判定逻辑(仓库内egrep正则) | 含义 |
|---|---|---|
TRIVIAL_CHANGE | 变更文件全部落在^(docs\|infra\|tools\|test/data\|[.]github)/内 | 纯文档/基建变更,跳过全部构建测试 |
UI_ONLY_CHANGE | 变更文件全部落在^(ui\|test/data/ui-screenshots)/内 | 仅 UI 变更,跳过 linux/android 等重型测试 |
BIGTRACE_CHANGE | 存在^ui/src/bigtrace/下的变更 | 额外触发 bigtrace 测试 |
随后analyze按条件扇出以下子工作流(均为needs: analyze的可复用工作流):
- linux-tests.yml:非 UI-only 且非 trivial 时触发;
- android-tests.yml:同上;
- ui-tests.yml:非 trivial 时触发;
- bazel-tests.yml 与 fuzzer-tests.yml:非 UI-only 且非 trivial 时触发;
- bigtrace-tests.yml:仅
BIGTRACE_CHANGE == '1'时触发; - repo-checks.yml 与 rust-sdk-tests.yml:分别无条件/条件触发。
工作流末尾的ensure-ci-is-greenjob 汇总所有(可能被跳过的)子工作流结果:success与skipped视为通过,cancelled会取消整个 run,其余视为失败——该 job 供仓库的 "require CI to pass" ruleset 使用,是合入门禁的最终裁决点。
三、自托管 Runner 的三级分层:VM → worker → sandbox
Perfetto CI 的执行资源按"三级容器/进程"组织,职责逐级收窄、权限逐级收缩。
3.1 GCE VM 层:无状态、只读系统盘
Worker GCE VM 的数量可变(由GCE_VM_TYPE等参数驱动,见 infra/ci/config.py),由 Google Cloud Autoscaler 动态拉起,上限为MAX_VMS_PER_REGION。每台 VM 的整机系统镜像是只读的,VM 本身完全无状态:除上传到 Google Cloud Storage 的 UI 产物与 GitHub 缓存外,不持久化任何状态;SSD 仅作为 scratch 盘用于 tmpfs/swap,每次重启即清空。
VM 的动态伸缩以ci.perfetto.devAppEngine 推送的 Stackdriver 自定义指标为成本函数,该指标即"排队中 + 运行中的 pull-request 数量"。
3.2 worker 容器层:VM 的"管家"
每台 GCE VM 上运行一个特权 Docker 容器worker。它负责 VM 的基础设置,除此之外不做任何事——只通过 supervisord 确保始终有 N 个 sandbox 在运行。worker 容器是特权容器,但注释明确说明:它不是用于隔离的(隔离是 sandbox 的职责),只是便于部署。
3.3 sandbox 容器层:真正执行 GitHub Action Runner
每个 sandbox 容器运行一个 GitHub Action Runner 实例,构建与测试都在 Action Runner 内完成。sandbox 运行在受限网络与受限服务账号下,是安全边界所在(详见第六节)。
3.4 服务账号划分
| 账号 | 使用方 | 权限 |
|---|---|---|
gce-ci-worker@perfetto-ci.iam.gserviceaccount.com | GCE VM 与特权 worker 容器 | 云平台基础权限(见GCE_SCOPES) |
gce-ci-sandbox@perfetto-ci.iam.gserviceaccount.com | sandbox 容器 | 仅允许创建(不可删除/覆盖)gs://perfetto-ci-artifacts中的产物 |
这一设计确保即使 sandbox 被攻破,攻击者也无法覆盖或删除已有 CI 产物。
四、从开机到测试运行的完整链路(Sequence Diagram)
原文档给出的启动序列如下(左侧为层次,右侧为实际执行的脚本/工作流):
make -C /infra/ci start-workers ┗━ gcloud start ... [GCE] # From /infra/ci/worker/gce-startup-script.sh docker run worker ... [worker] # From /infra/ci/worker/Dockerfile ┗━ /infra/ci/worker/worker_entrypoint.sh ┗━ supervisord ┗━ [N] /infra/ci/worker/sandbox_runner.py ┗━ docker run sandbox-N ... [sandbox-X] # From /infra/ci/sandbox/Dockerfile ┗━ /infra/ci/sandbox/sandbox_entrypoint.sh ┗━ github-action-runner/run.sh ┗━ .github/workflows/analyze.yml ┣━ .github/workflows/linux-tests.yml ┣━ .github/workflows/ui-tests.yml ... ┗━ .github/workflows/android-tests.yml结合源码,可将每一层的关键实现展开如下:
1.make -C infra/ci start-workers(Makefile)Makefile 先通过include $(shell python3 config.py makefile)从 config.py 生成配置(这就是"所有变量只存一处"的设计),再依次执行:构建 GCE 实例模板(instance-templates create,注入startup-script-url、num-workers、sandbox-img、worker-img等 metadata,机器类型为c2d-standard-32,配 2 块 NVMe local SSD 与 100GB PD-SSD 启动盘)、创建托管实例组并配置 autoscaling。
2. GCE 启动脚本(gce-startup-script.sh)从 metadata server 拉取worker-img镜像名;将 NVMe 盘组 RAID0 并格式化为无日志 ext4(注释说明这是最快配置,-E nodiscard避免 TRIM 开销),挂载为/tmp作为构建/检出/缓存的 scratch 空间;关闭内核地址空间随机化(kernel.randomize_va_space=0),因为新版内核会阻止 sandbox 内调用personality(ADDR_NO_RANDOMIZE),若不关闭则需把 sandbox 改为--privileged,从而破坏隔离初衷;最后以--privileged --net=host挂载 docker.sock 拉起 worker 容器。
3. worker 入口(worker_entrypoint.sh)从 metadata 读取num-workers与sandbox-img;docker pull最新 sandbox 镜像;创建名为sandbox的受限 bridge 网络,并用 iptables拒绝该网络访问169.254.0.0/16(GCE metadata server 网段),防止 sandbox 窃取 metadata 或模拟服务账号;随后生成 supervisord 配置,以numprocs=${NUM_WORKERS}启动 N 个sandbox_runner.py进程,并设置autorestart=true保证常驻。
4. sandbox 运行器(sandbox_runner.py)为每个 sandbox 分配GCE_HostName-N名称;通过gcloud auth application-default print-access-token --impersonate-service-account=为沙箱生成1 小时短时效的降权令牌(create_sandbox_token,且每 50 分钟刷新一次);向 GitHub 申请注册令牌后,docker run启动嵌套 sandbox 容器:--cap-add SYS_PTRACE(供调试工具使用)、--network sandbox --dns 8.8.8.8、将/tmp/sandbox-N以 tmpfs 方式挂载为容器内/tmp,并通过环境变量注入GITHUB_TOKEN_PATH与SVC_TOKEN_PATH。脚本同时用/tmp/perfetto_ci_lastrun的 mtime 记录最近活动,供闲置关机决策使用。
5. sandbox 入口(sandbox_entrypoint.sh)将github-action-runner复制到 tmpfs(因为 Action Runner 会在_work子目录检出仓库);以--unattended --ephemeral --replace模式注册一次性 Runner(--ephemeral表示跑完一个 job 即退出);配置 gcloud 使用降权令牌文件;注册 SIGTERM 清理钩子(cleanup()中执行config.sh remove优雅摘除 Runner);最后后台运行./run.sh等待 job。
6. 作业执行Runner 被 GitHub 分配任务后执行 analyze.yml,再按变更分类扇出上节列出的各测试工作流。
五、核心配置参数一览(infra/ci/config.py)
所有 CI 相关变量集中定义在 infra/ci/config.py,既被 Python 脚本 import,也可用python3 config.py makefile生成 Makefile 片段(config.mk),或python3 config.py js生成前端使用的 JSON。关键参数如下:
| 参数 | 值 | 说明 |
|---|---|---|
GITHUB_REPO | google/perfetto | 关联的 GitHub 仓库 |
GITHUB_APP_ID/GITHUB_APP_INSTALLATION_ID | 1184402/62928975 | 用于以 GitHub App 身份申请 Runner 注册令牌 |
PROJECT | perfetto-ci | Google Cloud 项目 ID |
SANDBOX_IMG/WORKER_IMG | us-docker.pkg.dev/perfetto-ci/containers/gh-sandbox/gh-worker | 容器镜像仓库地址 |
CI_SITE | https://ci.perfetto.dev | 前端控制台 |
GCS_ARTIFACTS | perfetto-ci-artifacts | 产物桶名 |
JOB_TIMEOUT_SEC/CL_TIMEOUT_SEC | 45 * 60/3h | 单 job / 单 change-list 超时 |
LOGS_TTL_DAYS | 15 | 日志保留时长 |
TRUSTED_EMAILS | ^.*@google.com$ | 可信提交者邮箱正则 |
GCE_REGIONS | us-west1 | 部署区域 |
GCE_VM_TYPE | c2d-standard-32 | VM 机器类型(32 vCPU 计算优化型) |
MAX_VMS_PER_REGION | 8 | 每区域 VM 上限 |
NUM_WORKERS_PER_VM | 4 | 每台 VM 上的 sandbox 数量 |
AUTOSCALER_MIN | 0 | 可缩容至零(闲置成本为零) |
SANDBOX_SVC_ACCOUNT | gce-ci-sandbox@perfetto-ci.iam.gserviceaccount.com | sandbox 降权账号 |
Makefile 中的 autoscaling 配置同样值得注意:实例组以 Stackdriver 自定义指标custom.googleapis.com/perfetto-ci/ci_job_queue_len为利用率信号,目标值为 0.1(gauge 类型),冷却期 60 秒——队列越长,扩的 VM 越多。
六、日常运维 Playbook
6.1 前端(JS/HTML/CSS/py)变更
- 本地测试:
make -C infra/ci/frontend test - 部署:
make -C infra/ci/frontend deploy
前端位于 infra/ci/frontend/,内含 AppEngine 配置(app.yaml、cron.yaml、index.yaml)、main.py、stackdriver_metrics.py(推送扩缩容指标)等。
6.2 Worker / Sandbox 变更
- 构建并推送新的 Docker 容器:
make -C infra/ci build pushbuild依次构建 worker 与 sandbox 镜像(构建前会把config.py、common_utils.py复制进构建上下文),push推送至us-docker.pkg.dev(若推送失败,Makefile 提示先执行gcloud auth configure-docker us-docker.pkg.dev)。 - 重启 GCE 实例组(手动或执行):
make -C infra/ci restart-workers该目标等价于
stop-workers start-workers:先删除托管实例组,再重新创建并配置自动扩缩容。
其余辅助目标:start-workers(含gce-template依赖,重建实例模板并创建实例组)、stop-workers、autoscaler-debug(查看当前扩容状态)、start-worker-for-testing/stop-worker-for-testing(测试用单 VM)、cli(交互式调试客户端)。
七、安全设计考量
原文档明确列出并逐一论证了这套 CI 的安全模型,核心要点如下:
gs://perfetto-ci-artifacts桶全局可读,但仅 GAE 与 GCE 服务账号可写;sandbox 账号只可创建、不可删除或覆盖。- 该项目中没有任何账号拥有值得一提的特权:worker 与 sandbox 服务账号在 CI 项目之外无任何特殊能力,即便被攻破,能做的事情也不会超过自己另开一个 GCP 项目。
- 该 CI只做功能与性能测试,不涉及任何形式的持续部署;GitHub Actions 仅对
perfetto-team与perfetto-contributors自动触发。 - sandbox 并不难逃逸(Docker 是唯一边界),因此 pre-submit 与 post-submit 的构建产物均不被视为可信,只用于功能正确性与性能回归判定。
- CI 构建出的二进制不会在 CI 项目之外的任何机器上运行,也刻意不推送至 GCS。
- 唯一被保留(最长 30 天)并上传至 GCS 桶的构建产物是UI 产物,目的仅为获得 HTML 变更的可视化预览;且这些 UI 产物通过 GCS per-bucket API 从与生产 UI 不同的源提供服务,避免与生产环境共享 origin。
从代码实现上看,这些设计均有落地:sandbox 网络被 iptables 阻断 metadata server(worker_entrypoint.sh)、沙箱令牌为 1 小时降权短令牌且定期刷新(sandbox_runner.py)、GCE 启动脚本关闭 ASLR 以免被迫给 sandbox 提权(gce-startup-script.sh)。
八、总结与延伸阅读
Perfetto CI 的核心设计哲学可以概括为:调度与执行分离、执行资源完全无状态、权限按层收敛、成本随队列弹性伸缩。GitHub Actions 负责按变更分类扇出任务,GCE 自托管 Runner 以"VM 只读无状态 + worker 管家 + 一次性 sandbox"的三层结构承载构建测试,而 AppEngine 前端则以任务队列长度驱动 Autoscaler 在 0~8 台 VM 之间伸缩。
如需进一步了解 Perfetto 的测试策略与相关实现,可继续阅读仓库内的以下资源:
- 测试策略总览:docs/contributing/testing.md(原文档指定入口)
- CI 入口工作流:.github/workflows/analyze.yml
- 各测试工作流:.github/workflows/(linux-tests、ui-tests、android-tests、bazel-tests、fuzzer-tests、rust-sdk-tests、bigtrace-tests 等)
- CI 源码:infra/ci(含 config.py、Makefile、worker/sandbox 容器源码)
- GCS 缓存 Action:.github/actions/cache-on-google-cloud-storage/(自托管 runner 无法使用 GitHub 自带 cache 时的 GCS 缓存方案)
【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考