GitHub Actions与CI/CD - Git & GitHub 协作精通第5篇
GitHub Actions and CI/CD - Master Git & GitHub Collaboration Part 5
前言:为什么需要CI/CD?
在项目开发过程中,每次修改代码都需要重复运行测试、构建和部署的过程。一个人开发时还好,但团队协作时这个过程会变得相当繁琐。如果你曾说过"在我的电脑上是正常的啊",那么你可能已经感受到了CI/CD的必要性。
今天是Git & GitHub协作精通系列的第5篇,我们将深入探讨使用GitHub Actions的CI/CD。从初学者也能理解的基础知识开始,到可以立即应用于项目的实战工作流程,我们将一起学习。
1. 什么是CI/CD
1.1 CI (Continuous Integration) - 持续集成
CI是一种开发方法,开发者频繁且定期地将代码变更集成到主分支。不仅仅是简单地合并代码,每次集成时都会自动构建和运行测试,确认没有问题。
举个例子。假设三个开发者各自在开发不同的功能。如果没有CI,他们各自开发几周后一次性合并时可能会发生巨大的冲突。但应用CI后,每天甚至一天多次集成代码并自动验证,可以早期发现和解决问题。
1.2 CD (Continuous Delivery/Deployment) - 持续交付/部署
CD有两种含义。Continuous Delivery是指代码变更自动经过测试后准备好可部署状态,Continuous Deployment则更进一步,指自动部署到生产环境。
总结如下:
- Continuous Delivery:代码自动准备到随时可部署的状态(部署按钮由人按)
- Continuous Deployment:通过所有测试后自动部署到生产环境
1.3 CI/CD的实际好处
我在实际引入CI/CD时感受到的最大变化是"对部署的恐惧消失了"。以前很不愿意在周五下午部署。因为如果出问题,周末就要加班。但有了自动化测试和部署流水线后,随时都可以自信地部署了。
2. GitHub Actions介绍
2.1 什么是GitHub Actions?
GitHub Actions是GitHub直接提供的CI/CD平台。自2019年正式发布以来快速增长,现在已成为最流行的CI/CD工具之一。最大的优点是与GitHub仓库完美集成,无需设置单独的外部服务即可直接使用。
GitHub Actions的核心特点:
- GitHub原生:无需单独服务集成,直接在仓库中使用
- 基于YAML配置:易于理解的声明式语法
- 市场:数千个预制Action可复用
- 免费额度:公共仓库无限制,私有仓库每月2000分钟免费
- 多种运行器:支持Linux、Windows、macOS环境
2.2 与其他CI/CD工具的比较
还有Jenkins、CircleCI、Travis CI等其他CI/CD工具。各有优缺点,但如果主要使用GitHub,选择GitHub Actions在大多数情况下是好的选择。配置简单,不需要额外的基础设施建设,与GitHub的事件(Push、PR等)自然集成。
3. 理解Workflow文件结构
3.1 工作流文件位置
GitHub Actions的工作流文件位于仓库的.github/workflows/目录中。在此目录创建YAML文件后,GitHub会自动识别并执行。
my-project/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── deploy.yml
│ └── test.yml
├── src/
├── tests/
└── package.json
3.2 工作流的组成要素
工作流具有以下层次结构:
- Workflow(工作流):自动化的整个流程,由一个YAML文件定义
- Event(事件):触发工作流的触发器
- Job(作业):在同一运行器上执行的步骤集合
- Step(步骤):单个工作单位,执行shell命令或action
- Action(动作):可复用的工作单位
4. 掌握基本语法
4.1 name - 工作流名称
指定工作流的名称。这是在GitHub Actions标签页中显示的名称,请写得易于识别。
name: CI Pipeline
4.2 on - 事件触发器
定义工作流何时执行。让我们看看最常用的事件。
# Push事件
on:
push:
branches: [ main, develop ]
paths:
- 'src/**'
- '!src/**/*.md'
# Pull Request事件
on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened ]
# 定时任务(cron语法)
on:
schedule:
- cron: '0 9 * * 1' # 每周一上午9点(UTC)
# 手动执行
on:
workflow_dispatch:
inputs:
environment:
description: '选择部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
4.3 jobs - 作业定义
定义实际要执行的作业。可以定义多个job,默认情况下并行执行。
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 安装依赖
run: npm ci
- name: 构建
run: npm run build
4.4 runs-on - 指定运行器环境
指定作业运行的虚拟环境。可以使用GitHub托管的运行器,也可以使用自托管运行器。
# GitHub托管运行器
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
runs-on: windows-latest
runs-on: macos-latest
# 自托管运行器
runs-on: self-hosted
4.5 uses和run - 执行步骤
uses用于使用预制的action,run用于直接执行shell命令。
steps:
# 使用action
- name: 检出代码
uses: actions/checkout@v4
# 执行shell命令
- name: 运行测试
run: npm test
# 多行命令
- name: 构建和部署
run: |
npm run build
npm run deploy
5. 常用Actions
5.1 actions/checkout
最基本的action,将仓库代码检出到工作流环境。几乎所有工作流都在第一步使用。
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史(需要标签、分支信息时)
ref: ${{ github.head_ref }} # 检出特定分支
5.2 actions/setup-node
设置Node.js环境。可以同时指定版本和缓存设置。
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # 自动缓存node_modules
registry-url: 'https://registry.npmjs.org' # npm发布时需要
5.3 actions/cache
缓存依赖项或构建产物以缩短工作流执行时间。
- name: 恢复缓存
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
5.4 其他有用的Actions
# Python设置
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
# Java设置
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
# Docker构建和推送
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: user/app:latest
6. 实现测试自动化
6.1 基本测试工作流
这是一个在创建或更新PR时自动运行测试的工作流。
name: Test
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: Lint检查
run: npm run lint
- name: 类型检查
run: npm run type-check
- name: 单元测试
run: npm test -- --coverage
- name: 测试覆盖率报告
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
6.2 测试结果报告
将测试结果作为评论留在PR上,审查者可以快速确认。
- name: 测试结果报告
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Jest Tests
path: junit.xml
reporter: jest-junit
7. 部署自动化
7.1 GitHub Pages部署
将静态站点自动部署到GitHub Pages的工作流。
name: Deploy to GitHub Pages
on:
push:
branches: [ main ]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 构建
run: |
npm ci
npm run build
- name: 上传Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: 部署GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
7.2 Vercel部署
自动部署到Vercel的配置。可以区分Preview和Production环境进行部署。
name: Deploy to Vercel
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装Vercel CLI
run: npm install -g vercel@latest
- name: 获取Vercel项目信息
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: 构建
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: 生产环境部署
if: github.ref == 'refs/heads/main'
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
7.3 AWS S3 + CloudFront部署
name: Deploy to AWS
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 构建
run: |
npm ci
npm run build
- name: 配置AWS凭证
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: 上传到S3
run: aws s3 sync ./dist s3://${{ secrets.S3_BUCKET }} --delete
- name: 使CloudFront缓存失效
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
8. Secrets管理
8.1 Secrets设置方法
API密钥、令牌等敏感信息绝对不能直接写在代码中。使用GitHub Secrets安全管理。
设置方法:
- 进入仓库Settings
- 在左侧菜单选择"Secrets and variables" -> "Actions"
- 点击"New repository secret"
- 输入名称和值后保存
8.2 Secrets使用示例
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 部署
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
echo "API密钥已设置"
npm run deploy
8.3 按环境管理Secrets
使用Environments功能可以按staging、production等环境管理不同的secrets。
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- name: 部署到Staging
run: deploy --env staging
env:
API_URL: ${{ secrets.API_URL }} # staging环境的API_URL
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- name: 部署到Production
run: deploy --env production
env:
API_URL: ${{ secrets.API_URL }} # production环境的API_URL
9. Matrix构建
9.1 什么是Matrix策略?
使用Matrix构建可以在多个环境(操作系统、语言版本等)同时运行测试。例如,如果想在Node.js 18、20、22版本上都进行测试,不需要创建单独的job,只需用matrix定义即可。
name: Cross-version Test
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
exclude:
- os: windows-latest
node-version: 18
fail-fast: false # 即使一个失败,其余继续执行
steps:
- uses: actions/checkout@v4
- name: 设置Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
9.2 动态Matrix
也可以使用JSON动态构建matrix。
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: echo "matrix={\"include\":[{\"project\":\"api\"},{\"project\":\"web\"}]}" >> $GITHUB_OUTPUT
build:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
steps:
- run: echo "Building ${{ matrix.project }}"
10. 实战工作流示例
10.1 完整的CI流水线
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
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
type-check:
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 type-check
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 test -- --coverage
- uses: codecov/codecov-action@v3
build:
needs: [lint, type-check, 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 build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
10.2 自动发布工作流
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: 生成Release Notes
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
10.3 PR自动标签
name: PR Labeler
on:
pull_request:
types: [opened, synchronize]
jobs:
label:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
10.4 定期安全检查
name: Security Check
on:
schedule:
- cron: '0 9 * * 1' # 每周一上午9点(UTC)
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: 依赖安全检查
run: npm audit --audit-level=high
- name: 检查依赖更新
run: npm outdated || true
- name: Snyk安全扫描
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
总结:用CI/CD提高开发生产力
本文我们学习了使用GitHub Actions构建CI/CD流水线。起初配置文件编写可能感觉复杂,但一旦搭建好,之后就可以持续享受自动化的好处。
重要的是不要从一开始就追求完美的流水线。从简单的测试自动化开始,逐步扩展。我一开始也只是创建一个只运行npm test的工作流,根据需要逐渐添加lint、类型检查、部署自动化等。
下一篇第6篇将介绍如何参与开源贡献。Fork、PR工作流、贡献指南等,将详细讲解实际参与开源项目的方法。