组合操作 Reusable Workflows
解读
国内一线互联网公司在 PHP 侧普遍采用 GitHub Actions 或自研 CI(如阿里 Aone、腾讯蓝盾、字节跳动 Flow)做持续集成。
“组合操作 Reusable Workflows”并不是让候选人背诵官方文档,而是考察两点:
- 能否把散落在各仓库的重复 YAML 抽象成统一、可版本化、可灰度发布的构建-测试-部署流程;
- 能否在 PHP 语境下(Composer 依赖、扩展编译、多版本并存、扩展缓存、OPcache 预热、Swoole/FrankenPHP 常驻进程等)落地最佳实践,兼顾安全、性能、成本。
面试官常追问:
- 如何做到“一处修改、全业务线生效”又不引发雪崩?
- 私有包、商业扩展(如 SourceGuardian)如何安全透传?
- 回滚策略是什么?
- 国内网络不稳定,job 级缓存如何设计?
答不到这些,会被认为“只会写 Hello World 级别 YAML”。
知识点
- GitHub Actions 可复用工作流语法:workflow_call、inputs、secrets、outputs。
- 组织级共享:把 reusable workflow 放在 .github 仓库的 workflows/ 目录,其他仓库通过 {owner}/.github/.github/workflows/xxx.yml@ref 引用。
- 输入参数类型:string、number、boolean、choice,支持 default;secrets 需显式声明继承。
- 输出与制品: reusable workflow 可声明 outputs,调用端用 needs.xxx.outputs.xxx 获取;制品通过 actions/upload-artifact 跨 job 传递。
- 矩阵与策略:在调用端定义 strategy.matrix,reusable workflow 内部用 {{ matrix.stability }} 实现 5.6~8.3 & low-deps/high-deps 全量覆盖。
- 缓存设计:
- Linux 国内镜像源:mirrors.aliyun.com、repo.huaweicloud.com;
- Composer cache key 用 composer.lock 哈希,fallback 到 composer.json;
- ext 缓存:把 phpize 编译后的 .so 按“php版本-扩展名-扩展版本”做 key,存在 actions/cache 的专用路径,减少每次 apt-get install 依赖。
- 安全:
- 所有 secrets 通过 inherit: true 显式继承,禁止在日志中回显;
- 私有包使用 Composer 的 bearer 认证,token 存于组织级 secret,定期轮换;
- 敏感扩展采用 GitHub Packages 私有仓库 + OIDC 信任,防止泄露到公网。
- 灰度与回滚:
- reusable workflow 强制打 tag 发布,业务线引用时固定到 v1.2.3 而非 @main;
- 发布阶段采用“双集群 + 流量 5%→30%→100%”策略,异常自动触发 rollback 脚本(php artisan down + 流量切回旧集群)。
- 性能:
- job 级并发数根据 Runner 配额动态调整,PHP 单元测试用 paratest 分片;
- OPCache 预热脚本在部署后一次性调用 200 个核心接口,确保容器热启动 < 50 ms。
- 国内合规:
- 若代码托管在码云 Gitee,需用 Gitee Go 的“共享构建模板”语法,与 GitHub Actions 略有差异,但思路一致;
- 日志与制品须保留 6 个月以上,满足等保 2.0 审计要求。
答案
下面给出一套可直接落地的最小闭环示例,覆盖“代码检查→单元测试→构建镜像→灰度部署”四段,放在组织级 .github 仓库,文件路径 .github/workflows/php-reusable.yml。
name: PHP Reusable Pipeline
on:
workflow_call:
inputs:
php-versions:
description: 'JSON array of PHP versions'
required: true
type: string
stability:
description: 'dependencies stability'
required: false
default: 'stable'
type: choice
options:
- stable
- lowest
with-coverage:
description: 'upload coverage'
required: false
default: false
type: boolean
secrets:
PACKAGIST_TOKEN:
required: true
DOCKER_REGISTRY_USER:
required: true
DOCKER_REGISTRY_PASS:
required: true
outputs:
image-tag:
description: 'final docker image tag'
value: ${{ jobs.build.outputs.tag }}
env:
COMPOSER_MIRROR: https://mirrors.aliyun.com/composer/
jobs:
lint:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: mbstring,openssl,curl
tools: php-cs-fixer,phpstan
- name: Validate composer files
run: composer validate --strict
- name: Code style
run: php-cs-fixer fix --dry-run --diff
- name: Static analysis
run: phpstan analyse --error-format=github
test:
needs: lint
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
php: ${{ fromJson(inputs.php-versions) }}
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring,pdo_mysql,redis,swoole
coverage: ${{ inputs.with-coverage && 'pcov' || 'none' }}
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: ~/.composer/cache
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install dependencies
run: |
composer config --global repo.packagist composer ${{ env.COMPOSER_MIRROR }}
composer update --prefer-${{ inputs.stability }} --no-progress --no-scripts
- name: Run tests
run: vendor/bin/paratest --coverage-clover=coverage.xml
- name: Upload coverage
if: inputs.with-coverage
uses: codecov/codecov-action@v3
with:
files: coverage.xml
fail_ci_if_error: true
build:
needs: test
runs-on: ubuntu-22.04
outputs:
tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: registry.xxx.com/app/${{ github.event.repository.name }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
- name: Login registry
uses: docker/login-action@v2
with:
registry: registry.xxx.com
username: ${{ secrets.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DOCKER_REGISTRY_PASS }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
PHP_VERSION=8.2
COMPOSER_MIRROR=${{ env.COMPOSER_MIRROR }}
deploy:
needs: build
runs-on: ubuntu-22.04
environment
(因篇幅限制,此处仅展示核心框架,实际面试时可手写完整 deploy 步骤,包括灰度、监控、回滚脚本。)
拓展思考
- 多集群蓝绿:把 deploy 拆成“canary-5%→canary-30%→stable”三个 job,每个 job 调用同一份 reusable workflow,但通过 inputs.canary-weight 区分流量比例,失败自动触发 rollback 接口。
- 私有扩展缓存:把 swoole、SourceGuardian 等编译产物做成 Docker 基础镜像,reusable workflow 先引用该基础镜像,再执行 composer install,减少 2~3 分钟编译时间。
- 动态 Runner:国内云厂商(华为云、阿里云)已提供 GitHub Actions 托管 Runner,可按需弹出 32 核机器,把 PHPUnit 分片到 16 个并发,10 万用例 3 分钟跑完;成本比常驻 Runner 降低 60%。
- 合规审计:在 reusable workflow 中加入 step 把构建日志、测试报告、镜像 SBOM 上传到内部合规系统,自动关联需求单号,满足上市券商“源码可回溯”监管要求。
- 版本策略:采用语义化版本加日历版本混合命名,如 v1.2025w22.1,既保留语义兼容,又能让业务线一眼看出发布时间,方便排障。