GitLab CI中Composer负载调度依赖缓存粒度、阶段隔离与资源绑定协同控制,而非调高parallel选项;盲目并发易触发runner限流、DNS卡顿或超时。

GitLab CI 中 Composer 负载调度不是靠“并发数”调节,而是靠缓存粒度、执行阶段隔离和资源绑定三者协同控制——盲目增加并行下载反而容易触发 runner 限流或超时。
为什么不能直接调高 composer 的 parallel 选项
Composer 默认启用并行下载(parallel),但在 GitLab CI 的 shared runner 或低配专用 runner 上,这会导致:
- 多个 curl 进程争抢有限的出站连接数,触发 Connection timed out(错误码 28)
- Docker 容器内 DNS 解析压力陡增,尤其使用 alpine 镜像时易卡在 Resolving packages...
- Runner 自身 CPU/内存被打满,拖慢整个流水线队列,甚至触发 GitLab 的 job timeout(默认 1 小时)
所以不建议在 CI 中通过 composer config -g parallel 10 类方式强行提速。
用 cache key 控制依赖复用边界
真正影响负载调度的是缓存是否命中。key 设计不当,会让不同 PHP 版本、不同分支的作业互相污染或完全不复用:
- 推荐用
${PHP_VERSION}-$(sha256sum composer.lock | cut -c1-8)作 cache key,既区分环境又保证 lock 变更时自动失效 - 避免只用
composer-cache这种静态 key,否则 PHP 7.4 和 8.2 的包会混存,vendor/autoload.php可能因 opcache 不兼容报错 - 如果项目含私有 GitLab 包(
"type": "vcs"),需在 key 中加入$(git ls-remote https://gitlab.example.com/group/pkg.git HEAD | cut -d' ' -f1),否则 tag 更新后仍用旧缓存
把 heavy work 拆到专用 job 或预构建镜像
composer install 是 I/O 密集型操作,在共享 runner 上会挤占其他 job 资源。更稳的调度方式是:
- 在
before_script中只做轻量准备:composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/+export COMPOSER_PROCESS_TIMEOUT=600 - 将完整安装逻辑下沉到独立
composer-installjob,并用artifacts: [vendor/]输出,后续test/buildjob 直接 consume - 长期高频构建的项目,直接在
Dockerfile.ci里RUN composer install --no-dev --optimize-autoloader,然后image: registry.example.com/php-8.2-ci:latest—— 这样连 cache 都不用管,启动即用
runner 标签与 PHP 版本强绑定
负载调度失控的根源常不在 Composer 本身,而在 runner 分配策略:
- 注册 runner 时必须带
--tag-list "php-8.2,composer-cache",CI job 中显式写tags: [php-8.2],防止 PHP 8.1 的 job 错跑到 8.2 runner 上导致缓存错乱 - 不要给所有 PHP job 共用同一个
php-runnertag,否则composer install和phpunit会竞争同一核 CPU,I/O 延迟翻倍 - 若团队同时维护 PHP 7.4/8.1/8.2 项目,至少配置三个 tagged runner,各自挂载独立的
/root/.composer/cachevolume
最易被忽略的一点:GitLab Runner 的 concurrent 值设为 1 并不等于“单任务”,它控制的是该 runner 实例能同时跑几个 job;而每个 job 内部的 Composer 行为,仍由 cache key、镜像源、timeout 等参数决定——这两层调度必须分开看,不能混为一谈。


















