最近为了省钱,把一些项目从 CircleCI 迁到 GitHub Actions 时,发现一件反直觉的事:在 CircleCI 上用 Docker executor 比 machine executor 快 30% 以上,但在 GitHub Actions 上加了 container: 反而比直接跑在 Ubuntu 上慢了一截。两边都是用 Docker 容器,为什么体验会完全相反?
两种执行环境
CI 平台给我们「跑命令的地方」有两种:给一台虚拟机(每次 job 新起或恢复 VM)或 在已有机器上起容器(Host 一直跑,job 来了起个容器)。CircleCI 的 machine executor 是前者,docker executor 是后者。
GitHub Actions 特殊:runs-on: ubuntu-latest 看起来像 VM,实际是从池子分配的、已就绪的 hosted runner(预装环境,启动快);加 container: 就是在这台 runner 上又起一层容器。
| 环境类型 | CircleCI | GitHub Actions | 特点 |
|---|---|---|---|
| 虚拟机 | machine: true | self-hosted runner(自建) | 新起 VM,慢 |
| 容器 | docker: - image: node:20 | container: { image: node:20 } | 在 host 上起容器 |
| 平台托管优化 | 无 | runs-on: ubuntu-latest | 池化预热,快 |
CircleCI:Docker 为什么更快
Node 项目对比:machine executor 跑完构建 3 分 20 秒,docker executor 降到 2 分 40 秒。
Machine 慢在启动 VM:新起或从快照恢复,再装 Node、跑 npm install。Docker 快在复用 host:镜像缓存、十几秒起容器,环境就绪。
跑测试更明显:machine 要 docker-compose up 等数据库初始化,docker executor 的 service 容器并行启动,测试起来时数据库已经在听端口。
GitHub Actions:Docker 反而慢了
同样的项目,ubuntu-latest 直接跑 1 分 40 秒,加 container: 后涨到 2 分 30 秒。
Runner 本身就很轻:机器已就绪、Docker daemon 在跑、actions/setup-node 几秒准备好环境和 cache,温热机器直接开跑。
加 container: 是额外开销:拉镜像、起容器、挂载 workspace。CircleCI 上这是「比 VM 轻」,GitHub 上是「给轻流程加负担」。
在容器里执行 docker build
Job 跑在容器里又要 docker build(构建镜像推 registry),会遇到「容器里没 Docker daemon」的问题。
CircleCI:setup_remote_docker
CircleCI 在另一个地方起 remote Docker environment 有 daemon,job 容器通过 DOCKER_HOST 连过去:
- setup_remote_docker:
version: 20.10.14
docker_layer_caching: true
### GitHub Actions:两种方法
**直接用 runner**(推荐):`ubuntu-latest` 自带 Docker,直接 `run: docker build` 或用 `docker/build-push-action`。
**用 service 容器**:坚持用 `container:` 就得自己搭 dind:
```yaml
jobs:
build:
runs-on: ubuntu-latest
container: node:20
services:
docker:
image: docker:dind
options: --privileged
steps:
- run: docker build -t myapp .
env:
DOCKER_HOST: tcp://docker:2375怎么选
| 场景 | CircleCI | GitHub Actions | 为什么 |
|---|---|---|---|
| 标准语言环境 | docker executor | ubuntu-latest | CircleCI docker 比 machine 轻;GitHub runner 已优化 |
| 数据库/中间件 | docker + service | ubuntu-latest + service | GitHub 不需要额外 job container |
| 构建镜像 | docker + setup_remote_docker | ubuntu-latest | Runner 自带 Docker |
| 锁定环境 | docker + 自定义镜像 | container: + 自定义镜像 | 用容器固定版本 |
| 跨平台测试 | machine(分 job) | ubuntu-latest + macos-latest | 分 job 各跑 |
看起来相反的「最佳实践」,背后逻辑一致:在给定基础设施上选最轻的路径。CircleCI 的 machine 重,docker 快;GitHub 的 runner 轻,加容器反而是负担。