第一次看到.github/workflows/ci.yml这个文件时,你可能会有种既熟悉又陌生的感觉。作为现代软件开发中持续集成(CI)的核心配置文件,它就像一位沉默的工程师,默默地在代码提交后执行各种构建、测试和部署任务。这个看似简单的YAML文件,实际上承载着项目自动化流程的全部灵魂。
GitHub Actions的工作流文件通常存放在项目根目录的.github/workflows文件夹下,采用YAML格式编写。文件名可以自定义,但一般会使用ci.yml、build.yml或test.yml等具有描述性的名称。这个文件定义了何时触发工作流、运行在什么环境、执行哪些步骤等一系列关键配置。
一个典型的CI工作流文件由以下几个核心部分组成:
yaml复制name: CI Pipeline # 工作流名称
on: # 触发条件
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs: # 定义任务
build:
runs-on: ubuntu-latest # 运行环境
steps: # 执行步骤
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
这个基础结构展示了工作流文件的四个关键部分:name、on、jobs和steps。每个部分都有其特定的作用和配置方式。
on部分定义了工作流的触发条件,这是整个自动化流程的起点。常见的触发条件包括:
高级配置示例:
yaml复制on:
push:
branches:
- main
- develop
tags:
- v*
pull_request:
branches: [ main ]
paths:
- 'src/**'
- 'package.json'
schedule:
- cron: '0 0 * * *' # 每天午夜运行
这个配置表示:当代码推送到main或develop分支,或者推送以v开头的标签时触发;当针对main分支的拉取请求中修改了src目录下的文件或package.json时触发;以及每天午夜自动运行一次。
jobs部分是工作流的核心,定义了要执行的一个或多个任务。每个任务可以配置以下关键属性:
矩阵策略示例:
yaml复制jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
os: [ubuntu-latest, windows-latest]
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
这个配置会创建6个并行的测试任务(3个Node版本×2个操作系统),大大提高了测试覆盖率。
steps部分定义了任务中要执行的具体步骤。每个步骤可以是:
高级步骤示例:
yaml复制steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0 # 获取完整历史记录
- name: Cache node modules
uses: actions/cache@v2
id: cache
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Run tests with coverage
run: npm test -- --coverage
env:
CI: true
NODE_ENV: test
这个配置展示了几个高级技巧:完整检出代码历史、缓存node_modules以加速构建、条件安装依赖、以及设置测试环境变量。
GitHub Actions中的环境变量可以在多个层级设置,优先级从高到低为:
yaml复制env:
COMMON_VAR: value1
jobs:
job1:
env:
JOB_VAR: value2
steps:
- name: Step 1
env:
STEP_VAR: value3
run: echo "$STEP_VAR > $JOB_VAR > $COMMON_VAR"
对于密码、API密钥等敏感信息,应该使用GitHub Secrets:
${{ secrets.SECRET_NAME }}引用yaml复制steps:
- name: Deploy to production
run: ./deploy.sh
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }}
重要提示:永远不要在日志中输出秘密值,GitHub会自动屏蔽已知的秘密输出,但最好避免直接echo秘密变量。
缓存是优化CI速度的重要手段,GitHub提供了actions/cache来管理缓存:
yaml复制steps:
- uses: actions/cache@v2
id: node-cache
with:
path: |
node_modules
.cache
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
if: steps.node-cache.outputs.cache-hit != 'true'
run: npm ci
这个配置会:
不同语言的依赖安装有不同的优化策略:
对于Node.js项目:
npm ci而不是npm install,它更快速且确定对于Python项目:
yaml复制- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies
run: pip install -r requirements.txt
使用表达式可以实现复杂的条件逻辑:
yaml复制jobs:
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- run: echo "Deploying to production..."
还可以使用输出参数在任务间传递数据:
yaml复制jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.step1.outputs.test_output }}
steps:
- id: step1
run: echo "::set-output name=test_output::value1"
job2:
needs: job1
runs-on: ubuntu-latest
steps:
- run: echo ${{ needs.job1.outputs.output1 }}
对于需要特定环境的任务,可以使用自定义Docker容器:
yaml复制jobs:
build:
runs-on: ubuntu-latest
container:
image: node:14-alpine
volumes:
- /usr/local/git-crypt:/usr/local/git-crypt
options: --privileged
steps:
- uses: actions/checkout@v2
- run: npm install && npm test
GitHub提供了工作流可视化工具,但有时需要更详细的监控:
workflow_run事件触发下游工作流yaml复制on:
workflow_run:
workflows: ["CI"]
types: [completed]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_MESSAGE: "Workflow ${{ github.workflow }} completed with status ${{ github.event.workflow_run.conclusion }}"
当工作流行为不符合预期时,可以:
ACTIONS_STEP_DEBUG并设置为truegithub上下文查看可用信息yaml复制steps:
- name: Debug context
run: echo '${{ toJSON(github) }}'
问题1:权限不足
yaml复制permissions:
contents: write
pull-requests: write
问题2:缓存未生效
问题3:矩阵任务太多导致排队
fetch-depth为1needs正确表达依赖关系yaml复制- uses: actions/checkout@v2
with:
fetch-depth: 1 # 只检出最新提交
始终遵循最小权限原则:
yaml复制permissions:
actions: read
checks: write
contents: read
deployments: write
issues: read
packages: none
pull-requests: read
repository-projects: none
security-events: write
statuses: write
集成安全扫描工具:
yaml复制steps:
- uses: actions/checkout@v2
- name: Run npm audit
run: npm audit --audit-level=high
- name: Run Snyk test
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
避免将用户输入直接传递给shell:
yaml复制steps:
- name: Safe script execution
run: ./script.sh "${{ github.event.issue.title }}"
shell: bash
复杂项目通常采用多阶段构建:
yaml复制jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm run lint
- run: npm run test:unit
build:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm run build
- uses: actions/upload-artifact@v2
with:
name: dist
path: dist/
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v2
with:
name: dist
- run: ./deploy.sh
根据分支或标签决定部署环境:
yaml复制jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ contains(github.ref, 'release/') && 'staging' || 'production' }}
url: ${{ contains(github.ref, 'release/') && 'https://staging.example.com' || 'https://example.com' }}
steps:
- run: echo "Deploying to ${{ contains(github.ref, 'release/') && 'staging' || 'production' }}"
将常用操作封装为可重用的Action:
javascript复制// action.yml
name: 'Greet Someone'
description: 'Greet someone by name'
inputs:
name:
description: 'Who to greet'
required: true
default: 'World'
runs:
using: 'node12'
main: 'dist/index.js'
javascript复制// src/main.js
const core = require('@actions/core')
const name = core.getInput('name')
console.log(`Hello ${name}!`)
GitHub Marketplace提供了丰富的第三方Actions:
yaml复制steps:
- uses: actions/checkout@v2
- uses: cypress-io/github-action@v2
with:
start: npm start
wait-on: 'http://localhost:3000'
在组织中共享工作流模板:
.github仓库.github/workflow-templates目录yaml复制# .github/workflow-templates/nodejs-ci.yml
name: Node.js CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm ci
- run: npm test