GitHub Actions 完全指南

相关文档:Docker Compose完全指南(/docker-compose-wan-quan-zhi-nan/) Docker高级指南(/docker-gao-ji-zhi-nan/) pytest完全指南(/pytest-wan-quan-zhi-nan/) GitHub Actions 是 GitHub 内置的 CI/CD 平台,通过在仓库的 .github/workflows/ 目录下创建 YAML 文件定义自动化流程。 GitHub Actions 对 YAML 缩进敏感,uses 和 run 必须在同一级: 在 Windows Runner

分享

官方文档:https://docs.github.com/zh/actions
适用版本:GitHub Actions(2026-05-07 核实)

相关文档:Docker Compose完全指南 Docker高级指南 pytest完全指南


1. 基础概念

GitHub Actions 是什么

GitHub Actions 是 GitHub 内置的 CI/CD 平台,通过在仓库的 .github/workflows/ 目录下创建 YAML 文件定义自动化流程。

概念 说明
Workflow(工作流) 一个 YAML 文件,定义一套完整的自动化流程
Event(触发事件) 触发 Workflow 的事件(push、PR、cron 等)
Job(作业) Workflow 中的一个独立任务单元,默认并行运行
Step(步骤) Job 中的一个操作,按顺序执行
Action(动作) 可复用的步骤单元(marketplace 有大量现成的)
Runner(运行器) 执行 Job 的服务器(GitHub 托管或自托管)

免费额度(GitHub 托管 Runner)

账户类型 免费分钟数 / 月
个人免费账户 2000 分钟(Linux)
公开仓库 无限制

2. Workflow 文件结构

# .github/workflows/ci.yml
name: CI                   # Workflow 名称

on:                         # 触发条件
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:                     # Job ID
    name: Run Tests         # Job 显示名
    runs-on: ubuntu-latest  # 运行环境

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run tests
        run: pytest

3. 触发事件(on)

on:
  # 推送到指定分支
  push:
    branches:
      - main
      - "release/**"     # 通配符
    paths:
      - "src/**"         # 只有 src/ 下的文件变化才触发
    paths-ignore:
      - "docs/**"
      - "*.md"

  # Pull Request
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [main]

  # 定时任务(cron)
  schedule:
    - cron: "0 2 * * *"    # 每天凌晨 2 点 UTC

  # 手动触发
  workflow_dispatch:
    inputs:
      environment:
        description: "部署环境"
        required: true
        default: "staging"
        type: choice
        options: [staging, production]

  # 其他 Workflow 调用
  workflow_call:

  # 发布 Release
  release:
    types: [published]

4. Job 配置

jobs:
  build:
    runs-on: ubuntu-latest   # ubuntu-latest / ubuntu-22.04 / windows-latest / macos-latest

    # 并发控制:同一 PR 只保留最新的 Workflow 运行
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: true

    # 超时限制
    timeout-minutes: 30

    # 环境变量(Job 级别)
    env:
      PYTHONPATH: src
      DATABASE_URL: sqlite:///test.db

    # 矩阵策略:多版本并行测试
    strategy:
      fail-fast: false  # 一个失败不取消其他
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
        os: [ubuntu-latest, macos-latest]

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

5. 常用步骤

Checkout 代码

- uses: actions/checkout@v4
  with:
    fetch-depth: 0  # 获取完整历史(覆盖率比较需要)

设置 Python 环境

- uses: actions/setup-python@v5
  with:
    python-version: "3.12"
    cache: "pip"  # 缓存 pip 依赖

- name: Install dependencies
  run: |
    pip install -r requirements.txt
    pip install -r requirements-dev.txt

缓存依赖

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

设置 Node.js 环境

- uses: actions/setup-node@v4
  with:
    node-version: "20"
    cache: "npm"

- run: npm ci

6. 实用 Workflow 模板

Python CI(测试 + 代码质量)

# .github/workflows/ci.yml
name: Python CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip

      - name: Install dependencies
        run: pip install -r requirements.txt -r requirements-dev.txt

      - name: Lint(Ruff)
        run: ruff check src tests

      - name: Type check(mypy)
        run: mypy src

      - name: Run tests with coverage
        env:
          DATABASE_URL: postgresql+asyncpg://testuser:testpass@localhost:5432/testdb
        run: pytest --cov=src --cov-report=xml -v

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: coverage.xml

前端 CI(Lint + 构建 + 测试)

# .github/workflows/frontend-ci.yml
name: Frontend CI

on:
  push:
    branches: [main]
    paths: ["frontend/**"]
  pull_request:
    paths: ["frontend/**"]

defaults:
  run:
    working-directory: frontend

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: npm
          cache-dependency-path: frontend/package-lock.json

      - run: npm ci

      - name: Type check
        run: npm run type-check

      - name: Lint
        run: npm run lint

      - name: Build
        run: npm run build

      - name: Test
        run: npm run test

Docker 构建 + 推送

# .github/workflows/docker-publish.yml
name: Docker Publish

on:
  release:
    types: [published]
  push:
    branches: [main]

jobs:
  build-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      # 设置 Docker Buildx(多平台构建)
      - uses: docker/setup-buildx-action@v3

      # 登录 GitHub Container Registry
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 生成标签(main 分支 → latest,release → 版本号)
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-

      # 构建并推送
      - uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha    # 使用 GitHub Actions 缓存加速构建
          cache-to: type=gha,mode=max

部署到服务器(SSH)

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # 关联 GitHub 环境,可配置审批

    steps:
      - uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/myapp
            git pull origin main
            docker compose pull
            docker compose up -d --no-deps web
            docker compose exec web alembic upgrade head

7. Secrets 与变量

# 使用 Secrets(Settings → Secrets and variables → Actions)
steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    run: ./deploy.sh

# 使用 Variables(非敏感配置)
  - name: Build
    env:
      APP_VERSION: ${{ vars.APP_VERSION }}
      ENVIRONMENT: ${{ vars.ENVIRONMENT }}
    run: npm run build

内置变量

变量 说明
github.sha 触发 Workflow 的 commit SHA
github.ref 触发的分支/标签(refs/heads/main
github.ref_name 分支/标签名(main
github.actor 触发者用户名
github.event_name 事件类型(push/pull_request
github.repository 仓库名(owner/repo
github.run_id 本次运行 ID
runner.os 运行器 OS(Linux/Windows/macOS

8. Job 间数据传递

输出变量

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get_version.outputs.version }}

    steps:
      - id: get_version
        run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying version ${{ needs.build.outputs.version }}"

上传/下载 Artifact

# 上传构建产物
- uses: actions/upload-artifact@v4
  with:
    name: dist
    path: dist/
    retention-days: 7

# 在另一个 job 下载
- uses: actions/download-artifact@v4
  with:
    name: dist
    path: dist/

9. 最佳实践

固定 Action 版本,使用 SHA 而非 tag(安全)

# 不推荐(tag 可以被强制推送覆盖)
uses: actions/checkout@v4

# 推荐(SHA 不可变,防止供应链攻击)
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

最小权限原则

permissions:
  contents: read     # 只读代码
  packages: write    # 写 GitHub Packages(只有需要时才开)

耗时任务加缓存

# pip、npm、cargo、maven 依赖都应该缓存
- uses: actions/setup-python@v5
  with:
    python-version: "3.12"
    cache: pip  # 自动缓存

10. 踩坑与注意事项

YAML 缩进问题

GitHub Actions 对 YAML 缩进敏感,usesrun 必须在同一级:

# 错误
steps:
  - name: test
  uses: actions/checkout@v4  # 缩进错误!

# 正确
steps:
  - name: test
    uses: actions/checkout@v4

services 容器需要时间启动

# PostgreSQL 需要健康检查,不能直接用
services:
  postgres:
    image: postgres:16
    options: >-
      --health-cmd pg_isready
      --health-interval 10s
      --health-retries 5

Windows Runner 换行符问题

在 Windows Runner 上,shell 脚本的换行符可能导致问题,明确指定 shell:

- name: Run script
  shell: bash   # 强制使用 bash(Windows 上也有)
  run: echo "hello"

最佳实践

将机密存入 Repository Secrets,不硬编码:API 密钥、Docker 凭证等敏感信息必须存入 GitHub Secrets(Settings → Secrets and variables → Actions),通过 ${{ secrets.MY_KEY }} 引用,不出现在 YAML 或日志中。

使用 cache action 加速依赖安装:Python pip、Node npm、Go mod 每次都下载会导致 CI 耗时 3–5 分钟,缓存后通常 15–30 秒恢复。

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-${{ hashFiles('requirements.txt') }}
    restore-keys: pip-

用 matrix 策略做多版本测试:一次定义,自动在多个 Python/Node 版本上并行运行,比重复写多个 job 简洁。

strategy:
  matrix:
    python-version: ["3.11", "3.12", "3.13"]
steps:
  - uses: actions/setup-python@v5
    with:
      python-version: ${{ matrix.python-version }}

部署步骤加 environment 保护:对 production 部署的 job 设置 environment: production,可以配置审批者(required reviewers),防止误推。

concurrency 避免同一分支并行部署:PR 多次推送会触发多个 Workflow 同时运行,配置 concurrency 自动取消旧的运行。

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

常见陷阱

陷阱:secrets 在 fork 的 PR 中不可用

现象: 外部贡献者提交 PR 后,CI 中访问 secrets.DOCKER_TOKEN 报错或为空。

原因: 出于安全考虑,fork 仓库触发的 pull_request 事件无法访问父仓库的 secrets,防止恶意 PR 窃取密钥。

解决: 使用 pull_request_target 事件(谨慎使用,有安全风险)或将需要 secrets 的步骤移到需要 merge 后才触发的工作流(push 到 main)。


陷阱:job 间共享文件需要 artifact

现象: Job A 生成了一个构建产物,Job B 需要使用,但 Job B 找不到该文件。

原因: 每个 job 在独立的 runner 实例上运行,文件系统不共享。

解决: Job A 用 upload-artifact 上传,Job B 用 download-artifact 下载。

# Job A
- uses: actions/upload-artifact@v4
  with:
    name: dist
    path: dist/

# Job B(需声明 needs: [build])
- uses: actions/download-artifact@v4
  with:
    name: dist

陷阱:workflow 文件语法错误静默失败

现象: 推送后 workflow 没有触发,或 GitHub 显示 workflow 文件错误。

原因: YAML 缩进错误(用 tab 而非空格)、on 关键字不加引号被解析为布尔值、step 的 usesrun 同时存在等常见语法错误。

解决:actionlint 本地静态检查 workflow 文件;在 VS Code 中安装 GitHub Actions 扩展实时提示。

# 本地检查
brew install actionlint
actionlint .github/workflows/*.yml

参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog