GitHub Actions 进阶
本章深入探讨 GitHub Actions 的高级功能,包括复合 Action、可重用工作流、Docker 容器作业、服务容器等。
复合 Action
复合 Action 允许将多个步骤封装成一个可重用的 Action。
创建复合 Action
在仓库中创建 .github/actions/ 目录,添加 action.yml 文件:
name: 'Setup and Build'
description: '安装依赖并构建项目'
author: 'Your Name'
inputs:
node-version:
description: 'Node.js 版本'
required: false
default: '20'
build-command:
description: '构建命令'
required: false
default: 'npm run build'
outputs:
build-path:
description: '构建输出路径'
value: ${{ steps.build.outputs.path }}
runs:
using: 'composite'
steps:
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: 安装依赖
shell: bash
run: npm ci
- name: 构建
id: build
shell: bash
run: |
${{ inputs.build-command }}
echo "path=dist" >> $GITHUB_OUTPUT
使用复合 Action
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-and-build
with:
node-version: '18'
build-command: 'npm run build:prod'
可重用工作流
可重用工作流允许在一个工作流中调用另一个工作流。
定义可重用工作流
name: Reusable Build
on:
workflow_call:
inputs:
environment:
description: '部署环境'
required: true
type: string
config-path:
description: '配置文件路径'
required: false
type: string
default: 'config/default.json'
outputs:
build-id:
description: '构建 ID'
value: ${{ jobs.build.outputs.build-id }}
secrets:
deploy-key:
required: true
jobs:
build:
runs-on: ubuntu-latest
outputs:
build-id: ${{ steps.build.outputs.id }}
steps:
- uses: actions/checkout@v4
- id: build
run: |
echo "构建环境: ${{ inputs.environment }}"
echo "id=$(date +%s)" >> $GITHUB_OUTPUT
调用可重用工作流
name: Deploy
on:
push:
branches: [main]
jobs:
call-build:
uses: ./.github/workflows/reusable-build.yml
with:
environment: production
secrets:
deploy-key: ${{ secrets.DEPLOY_KEY }}
deploy:
needs: call-build
runs-on: ubuntu-latest
steps:
- run: echo "部署构建 ${{ needs.call-build.outputs.build-id }}"
跨仓库调用
jobs:
call-external:
uses: owner/repo/.github/workflows/build.yml@v1
with:
environment: staging
secrets: inherit
Docker 容器作业
在 Docker 容器中运行作业,确保环境一致性。
使用容器作业
jobs:
docker-job:
runs-on: ubuntu-latest
container:
image: node:20-alpine
options: --user root
env:
NODE_ENV: test
volumes:
- /data:/data
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 运行测试
run: npm test
使用服务容器
服务容器用于数据库、Redis 等依赖服务:
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: 测试数据库连接
env:
DATABASE_URL: postgres://test:test@localhost:5432/test_db
REDIS_URL: redis://localhost:6379
run: |
npm test
MySQL 服务容器
services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
MongoDB 服务容器
services:
mongodb:
image: mongo:7
ports:
- 27017:27017
环境和部署保护
配置环境
在仓库设置中创建环境,配置保护规则:
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- run: echo "部署到生产环境"
环境变量和 Secrets
环境可以配置专属的变量和 Secrets:
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo ${{ vars.API_URL }}
- run: echo ${{ secrets.STAGING_KEY }}
deploy-production:
runs-on: ubuntu-latest
environment: production
steps:
- run: echo ${{ secrets.PRODUCTION_KEY }}
审批部署
环境可以配置必需的审批者:
jobs:
deploy:
runs-on: ubuntu-latest
environment: production-with-approval
steps:
- run: echo "审批通过,开始部署"
并发控制
控制同一工作流的并发执行:
name: Deploy
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "部署中..."
按环境分组
concurrency:
group: deploy-${{ inputs.environment }}
cancel-in-progress: false
条件和表达式
比较运算符
steps:
- if: github.ref == 'refs/heads/main'
run: echo "主分支"
- if: github.event_name != 'pull_request'
run: echo "非 PR 事件"
- if: contains(github.ref, 'release')
run: echo "发布分支"
- if: startsWith(github.ref, 'refs/tags/')
run: echo "标签推送"
- if: endsWith(github.ref, '/hotfix')
run: echo "热修复分支"
逻辑运算符
steps:
- if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: echo "主分支推送"
- if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
run: echo "主分支或开发分支"
- if: "!cancelled()"
run: echo "未取消时执行"
类型转换
env:
BOOLEAN_VALUE: true
STRING_VALUE: 'hello'
steps:
- if: env.BOOLEAN_VALUE == 'true'
run: echo "布尔值比较需要字符串"
- if: fromJSON(env.BOOLEAN_VALUE)
run: echo "转换为布尔值"
函数
steps:
- name: 格式化字符串
run: echo "${{ format('Hello {0}', github.actor) }}"
- name: 连接路径
run: echo "${{ join(github.event.commits.*.message, ', ') }}"
- name: JSON 操作
env:
DATA: ${{ toJson(github.event) }}
run: echo "$DATA"
- name: 哈希文件
run: echo "${{ hashFiles('**/package-lock.json') }}"
自托管运行器
配置自托管运行器
在仓库或组织的 Settings > Actions > Runners 中添加自托管运行器。
使用自托管运行器
jobs:
build:
runs-on: self-hosted
steps:
- run: echo "在自托管运行器上执行"
使用标签选择运行器
jobs:
build:
runs-on: [self-hosted, linux, x64, gpu]
steps:
- run: echo "在带 GPU 的 Linux 运行器上执行"
运行器组
jobs:
build:
runs-on:
group: my-runner-group
labels: [linux, x64]
工作流命令
工作流命令用于与 GitHub Actions 系统交互。
设置输出变量
steps:
- name: 设置输出
id: step1
run: echo "name=value" >> $GITHUB_OUTPUT
- name: 使用输出
run: echo "${{ steps.step1.outputs.name }}"
设置环境变量
steps:
- name: 设置环境变量
run: |
echo "MY_VAR=hello" >> $GITHUB_ENV
echo "ANOTHER_VAR=world" >> $GITHUB_ENV
- name: 使用环境变量
run: echo "$MY_VAR $ANOTHER_VAR"
添加路径
steps:
- name: 添加到 PATH
run: echo "$PWD/bin" >> $GITHUB_PATH
添加系统路径
steps:
- name: 添加匹配器
run: echo "::add-matcher::.github/problem-matcher.json"
设置步骤摘要
steps:
- name: 生成摘要
run: |
echo "## 构建报告" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- 构建时间: $(date)" >> $GITHUB_STEP_SUMMARY
echo "- 构建状态: 成功" >> $GITHUB_STEP_SUMMARY
掩码敏感信息
steps:
- name: 掩码输出
run: |
SECRET_VALUE="my-secret"
echo "::add-mask::$SECRET_VALUE"
echo "secret=$SECRET_VALUE" >> $GITHUB_OUTPUT
分组日志
steps:
- name: 分组输出
run: |
echo "::group::安装依赖"
npm install
echo "::endgroup::"
echo "::group::运行测试"
npm test
echo "::endgroup::"
警告和错误注解
steps:
- name: 添加注解
run: |
echo "::warning::这是一个警告"
echo "::error::这是一个错误"
echo "::notice::这是一个提示"
echo "::warning file=app.js,line=1,col=5::文件警告"
调试工作流
启用调试日志
在仓库中设置 Secrets:
ACTIONS_RUNNER_DEBUG:设置为trueACTIONS_STEP_DEBUG:设置为true
使用 tmate 远程调试
steps:
- name: 调试
uses: mxschmitt/action-tmate@v3
timeout-minutes: 15
本地测试工作流
使用 act 在本地运行 GitHub Actions:
act push
act -j build
act -W .github/workflows/test.yml
性能优化
优化缓存策略
- name: 缓存多路径
uses: actions/cache@v4
with:
path: |
~/.npm
~/.cache/pip
node_modules
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-deps-
使用缓存 Action
- name: 恢复缓存
id: cache
uses: actions/cache/restore@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: 安装依赖
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: 保存缓存
if: steps.cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: node_modules
key: ${{ steps.cache.outputs.cache-primary-key }}
减少作业数量
合并相关步骤到单个作业:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run build
- run: npm test
并行矩阵优化
strategy:
matrix:
shard: [1/4, 2/4, 3/4, 4/4]
max-parallel: 4
steps:
- run: npm test -- --shard ${{ matrix.shard }}
完整示例:发布流程
name: Release
on:
release:
types: [published]
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: 获取版本
id: version
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
registry-url: 'https://npm.pkg.github.com'
- name: 安装依赖
run: npm ci
- name: 构建
run: npm run build
- name: 运行测试
run: npm test
- name: 上传产物
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 下载产物
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: 发布到 npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 下载产物
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://npm.pkg.github.com'
- name: 发布到 GitHub Packages
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
create-release-notes:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 生成发布说明
uses: actions/github-script@v7
with:
script: |
const version = '${{ needs.build.outputs.version }}';
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: version
});
console.log('Release notes:', release.data.body);