跳到主要内容

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:设置为 true
  • ACTIONS_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);

参考资源