GitHub Actions已经成为CI/CD领域最受欢迎的工具之一。对于大多数开发者来说,基本的GitHub Actions工作流并不难配置——跑个测试、构建个项目、部署到服务器。然而,当项目规模增长、团队扩大、部署环境增多时,如何优化工作流的执行效率、降低成本、提高可靠性,就成为了一个值得深入探讨的话题。
本文将分享GitHub Actions的高级用法,包括矩阵构建策略、缓存优化技巧、自定义Action开发、环境管理以及多环境自动化部署的最佳实践。
矩阵构建允许你同时测试多个版本组合,确保代码在不同环境下都能正常工作:
name: Matrix Build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
django-version: ['4.2', '5.0']
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install django==${{ matrix.django-version }}
pip install -r requirements.txt
- name: Run tests
run: python -m pytest tests/ -v
注意:GitHub Actions中使用
${{ expression }}语法引用变量和上下文。上文中为了兼容Jekyll渲染,使用了转义写法,实际使用时请写为标准形式${{ matrix.python-version }}。
使用include和exclude可以更精细地控制矩阵:
strategy:
matrix:
include:
- python-version: '3.12'
django-version: '5.0'
database: 'postgresql'
db-version: '16'
- python-version: '3.12'
django-version: '5.0'
database: 'mysql'
db-version: '8.0'
- python-version: '3.11'
django-version: '4.2'
database: 'sqlite'
db-version: '3'
exclude:
- python-version: '3.10'
django-version: '5.0' # Django 5.0不支持Python 3.10
strategy:
fail-fast: false # 某个组合失败不会取消其他组合
matrix:
node-version: [18, 20, 22]
设置fail-fast: false可以确保即使某个版本组合的测试失败了,其他组合仍然会继续执行。这对于全面了解兼容性问题非常重要。
缓存是提升CI/CD速度最有效的手段之一:
- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.local/lib/python*/site-packages
# 缓存键:基于操作系统和依赖文件hash
key: pip-runner.os-hashFiles-requirements.txt
restore-keys: |
pip-runner.os-
pip-
实际写法:
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
- name: Cache Node.js dependencies
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
# 缓存键:基于操作系统和package-lock.json的hash
key: node-runner.os-hashFiles-package-lock.json
restore-keys: |
node-runner.os-
实际写法:
key: node-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
对于Docker构建,缓存可以显著减少构建时间:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
# 缓存键:基于操作系统和Dockerfile的hash
key: docker-runner.os-hashFiles-Dockerfile
restore-keys: |
docker-runner.os-
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: myapp:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
当现有Action无法满足需求时,可以开发自定义Action:
// action.yml
name: 'My Custom Action'
description: 'A custom GitHub Action'
inputs:
api-token:
description: 'API token for authentication'
required: true
environment:
description: 'Target environment'
required: false
default: 'staging'
outputs:
result:
description: 'Action result'
runs:
using: 'node20'
main: 'dist/index.js'
// src/index.js
const core = require('@actions/core');
const github = require('@actions/github');
async function run() {
try {
const token = core.getInput('api-token');
const environment = core.getInput('environment');
// 业务逻辑
const result = await deployToEnvironment(token, environment);
core.setOutput('result', JSON.stringify(result));
core.info('Successfully deployed to ' + environment);
} catch (error) {
core.setFailed('Action failed: ' + error.message);
}
}
run();
对于简单的操作,使用Composite Action更加轻量:
# .github/actions/setup-python-env/action.yml
name: 'Setup Python Environment'
description: 'Set up Python with caching and dependency installation'
inputs:
python-version:
description: 'Python version'
required: false
default: '3.12'
requirements-file:
description: 'Path to requirements file'
required: false
default: 'requirements.txt'
runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
# 引用Action输入参数
python-version: inputs.python-version
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-runner.os-hashFiles-inputs.requirements-file
- name: Install dependencies
shell: bash
run: pip install -r inputs.requirements-file
实际写法:
python-version: ${{ inputs.python-version }},key: pip-${{ runner.os }}-${{ hashFiles(inputs.requirements-file) }}
GitHub Environments提供了环境级别的保护和变量管理:
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Deploy to production
env:
# 引用环境密钥(实际使用双花括号语法)
API_KEY: secrets.PROD_API_KEY
DB_URL: secrets.PROD_DB_URL
run: |
./deploy.sh --env production
实际写法:
API_KEY: ${{ secrets.PROD_API_KEY }}
在GitHub仓库设置中,可以为每个环境配置保护规则:
- name: Rotate deployment key
run: |
NEW_KEY=$(openssl rand -hex 32)
echo "::add-mask::$NEW_KEY"
# 更新远程服务的密钥(实际使用双花括号语法引用密钥)
curl -X POST "$API_URL/rotate-key" \
-H "Authorization: Bearer secrets.CURRENT_KEY" \
-d "{\"new_key\": \"$NEW_KEY\"}"
实际写法:
-H "Authorization: Bearer ${{ secrets.CURRENT_KEY }}"
name: Multi-Environment Deployment
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- staging
- production
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build application
run: |
npm ci
npm run build
npm run test
- name: Build Docker image
run: |
# 使用commit SHA作为镜像标签
docker build -t myapp:COMMIT_SHA .
- name: Push to registry
run: |
# 引用密钥进行登录(实际使用双花括号语法)
echo secrets.REGISTRY_PASSWORD | docker login registry.example.com \
-u secrets.REGISTRY_USER --password-stdin
docker push registry.example.com/myapp:COMMIT_SHA
deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
ssh deploy@staging.example.com << 'EOF'
docker pull registry.example.com/myapp:COMMIT_SHA
docker-compose up -d
EOF
deploy-production:
needs: [build, deploy-staging]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
ssh deploy@prod.example.com << 'EOF'
docker pull registry.example.com/myapp:COMMIT_SHA
docker-compose up -d
EOF
- name: Run smoke tests
run: |
python scripts/smoke_test.py --env production
- name: Notify deployment
if: always()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Production deployment completed for commit COMMIT_SHA"
}
实际写法:将
COMMIT_SHA替换为${{ github.sha }},将secrets.XXX替换为${{ secrets.XXX }}。
合理编排任务的执行顺序可以显著减少总执行时间:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test-unit:
runs-on: ubuntu-latest
steps:
- run: npm run test:unit
test-integration:
runs-on: ubuntu-latest
steps:
- run: npm run test:integration
build:
needs: [lint, test-unit, test-integration] # 并行任务全部完成后才构建
runs-on: ubuntu-latest
steps:
- run: npm run build
concurrency:
# 使用分支名作为并发组(实际使用双花括号语法)
group: deploy-github.ref
cancel-in-progress: true # 取消正在运行的相同工作流
实际写法:
group: deploy-${{ github.ref }}
GitHub Actions的强大之处不仅在于它的易用性,更在于它丰富的扩展能力和灵活的工作流编排。通过掌握矩阵构建、缓存优化、自定义Action开发和多环境部署等高级技巧,你可以构建出高效、可靠、安全的CI/CD管道,为团队的开发效率提供坚实保障。
持续优化CI/CD管道是一个长期过程,建议定期回顾工作流的执行时间和成本,寻找优化空间,让自动化真正成为团队的效率倍增器。
💡 推荐工具:正在寻找高质量的开发工具和模板?看看这些: