跳转至

CI/CD 与 GitHub Actions 自动化

1. 一句话说明

CI/CD 是"代码提交后自动测试 + 自动部署"的工程实践,GitHub Actions 是 GitHub 内置的免费自动化引擎,让你的每次 push 都能自动跑测试、检查代码、构建镜像、发布文档。


2. 什么是 CI/CD

白话解释

想象你在餐厅当厨师: - CI(持续集成)= 每做一道菜都让质检员尝一口(自动测试),发现问题立即修正,不等到客人投诉 - CD(持续部署/交付)= 菜做好质检通过后,自动送到客人桌上(自动部署),不需要你亲自端盘子

没有 CI/CD 的痛点: - 代码改了不敢 push,怕把别人的功能搞坏 - 手动跑测试太慢,经常忘记跑 - 部署要登录服务器手动操作,容易出错 - 代码格式每个人不一样,review 时吵架

有了 CI/CD: - push 自动触发测试 → 坏了立刻知道 - PR 自动检查代码风格 → 统一规范 - merge 后自动部署 → 无需人工干预 - 文档改了自动发布 → 永远最新


3. GitHub Actions 核心概念

3.1 五大组件

# Workflow(工作流):一个自动化流程,由 YAML 文件定义
# 位置:.github/workflows/xxx.yml
# 白话:一份自动化任务清单

# Job(作业):Workflow 中的一组步骤
# 白话:任务清单中的一个大项(如"跑测试")

# Step(步骤):Job 中的一个具体操作
# 白话:大项里的一个小步骤(如"安装依赖")

# Runner(运行器):执行 Job 的虚拟机
# 白话:帮你干活的机器人(GitHub 免费提供 Ubuntu/macOS/Windows)

# Trigger(触发器):什么时候启动 Workflow
# 白话:机器人开始干活的信号(push/PR/定时/手动)

3.2 层级关系

Workflow (.yml 文件)
├── Job 1 (如:lint)           ← 可并行
│   ├── Step 1: Checkout 代码
│   ├── Step 2: 安装依赖
│   └── Step 3: 跑 linter
├── Job 2 (如:test)           ← 可并行
│   ├── Step 1: Checkout 代码
│   ├── Step 2: 安装 Python
│   ├── Step 3: 安装依赖
│   └── Step 4: 跑 pytest
└── Job 3 (如:deploy)         ← 依赖 Job 1, 2 通过
    ├── Step 1: Checkout 代码
    └── Step 2: 部署到服务器

4. YAML 语法基础

# YAML 基础语法(GitHub Actions 用 YAML 写配置)

# 键值对(冒号+空格)
name: my-workflow        # 字符串值
timeout: 30              # 数字值
verbose: true            # 布尔值

# 列表(短横线+空格)
steps:
  - name: "第一步"
  - name: "第二步"

# 嵌套(用缩进表示层级,2个空格)
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: "检出代码"

# 多行字符串(竖线 | 保留换行)
run: |
  echo "第一行"
  echo "第二行"

# 环境变量引用
env:
  MY_VAR: hello
# 使用:${{ env.MY_VAR }}

# GitHub 上下文变量
# ${{ github.event_name }}    触发事件名
# ${{ github.ref }}           分支名
# ${{ github.sha }}           提交哈希
# ${{ secrets.MY_SECRET }}    仓库密钥

5. 实操教程

5.1 自动运行 pytest

# 文件:.github/workflows/test.yml
# 功能:每次 push 或 PR 自动运行 Python 测试

name: Run Tests                     # Workflow 名称(显示在 Actions 页面)

on:                                 # 触发条件
  push:                             # push 时触发
    branches: [main, develop]       # 只监听 main 和 develop 分支
  pull_request:                     # PR 时触发
    branches: [main]

jobs:
  test:                             # Job 名称
    runs-on: ubuntu-latest          # 运行环境:最新 Ubuntu

    strategy:                       # 矩阵策略:多版本测试
      matrix:
        python-version: ["3.9", "3.10", "3.11"]  # 测试3个Python版本

    steps:
      # 步骤1:检出代码
      - name: Checkout code
        uses: actions/checkout@v4    # 使用官方 checkout action

      # 步骤2:配置 Python
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}  # 矩阵变量

      # 步骤3:缓存依赖(加速后续运行)
      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip         # 缓存 pip 目录
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}

      # 步骤4:安装依赖
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest pytest-cov       # 安装测试框架

      # 步骤5:运行测试
      - name: Run tests with coverage
        run: |
          pytest tests/ --cov=src/ --cov-report=xml --cov-report=term
          # --cov=src/       统计 src 目录的覆盖率
          # --cov-report=xml 输出 XML 格式(供后续工具使用)

      # 步骤6:上传覆盖率报告
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage.xml

5.2 自动检查代码风格

# 文件:.github/workflows/lint.yml
# 功能:自动检查 Python 代码风格和类型

name: Code Quality

on: [push, pull_request]

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

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      # Black 格式检查(只检查不修改)
      - name: Check formatting with Black
        run: |
          pip install black
          black --check --diff .          # --check 不修改,只报错
          # 如果格式不对,CI 会失败,提醒开发者格式化

      # Ruff 快速 linting
      - name: Lint with Ruff
        run: |
          pip install ruff
          ruff check .                    # 检查代码问题
          ruff format --check .           # 检查格式

      # MyPy 类型检查
      - name: Type check with MyPy
        run: |
          pip install mypy
          mypy src/ --ignore-missing-imports

5.3 自动构建 Docker 镜像

# 文件:.github/workflows/docker.yml
# 功能:代码合并到 main 后,自动构建并推送 Docker 镜像

name: Build Docker Image

on:
  push:
    branches: [main]              # 只在 main 分支触发
    tags: ['v*']                  # 或打了 v 开头的 tag

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      # 登录 Docker Hub(需要在仓库 Settings > Secrets 添加凭证)
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}   # 仓库密钥
          password: ${{ secrets.DOCKER_PASSWORD }}

      # 提取元数据(自动生成 tag)
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: myuser/my-bioinfo-tool
          tags: |
            type=ref,event=branch       # 分支名作为 tag
            type=semver,pattern={{version}}  # 语义化版本

      # 构建并推送
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .                     # Dockerfile 所在目录
          push: true                     # 推送到 registry
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha           # GitHub Actions 缓存加速
          cache-to: type=gha,mode=max

5.4 自动部署文档

# 文件:.github/workflows/docs.yml
# 功能:文档更新后自动构建并发布到 GitHub Pages

name: Deploy Docs

on:
  push:
    branches: [main]
    paths: ['docs/**', 'mkdocs.yml']   # 只有文档变化才触发

permissions:
  contents: read
  pages: write                          # 写 GitHub Pages 权限
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      # 安装 MkDocs(Python 文档生成器)
      - name: Install MkDocs
        run: |
          pip install mkdocs mkdocs-material    # Material 主题
          pip install mkdocstrings[python]      # 自动从代码生成文档

      # 构建静态文档
      - name: Build docs
        run: mkdocs build                       # 生成 site/ 目录

      # 部署到 GitHub Pages
      - name: Deploy to GitHub Pages
        uses: actions/upload-pages-artifact@v3
        with:
          path: site/

      - name: Deploy
        id: deployment
        uses: actions/deploy-pages@v4

6. 生信 CI/CD 实践

6.1 自动测试 Pipeline

# 文件:.github/workflows/pipeline-test.yml
# 功能:每次改了分析脚本,自动用测试数据跑一遍

name: Test Bioinformatics Pipeline

on:
  push:
    paths: ['scripts/**', 'Snakefile', 'main.nf']  # 脚本变化才触发

jobs:
  test-pipeline:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      # 安装 conda 环境
      - name: Setup Miniconda
        uses: conda-incubator/setup-miniconda@v3
        with:
          activate-environment: bioinfo
          environment-file: environment.yml    # conda 环境文件
          auto-activate-base: false

      # 用测试数据跑 pipeline
      - name: Run pipeline with test data
        shell: bash -l {0}                     # 激活 conda
        run: |
          # 用小数据集快速测试
          python scripts/run_pipeline.py \
            --input test_data/ \
            --output test_results/ \
            --threads 2

      # 验证输出文件
      - name: Validate outputs
        run: |
          # 检查关键输出文件是否存在
          test -f test_results/abundance_table.tsv
          test -f test_results/diversity_metrics.csv
          # 检查文件不为空
          [ -s test_results/abundance_table.tsv ]
          echo "Pipeline test passed!"

6.2 自动生成分析报告

# 文件:.github/workflows/report.yml
# 功能:定时运行分析并生成报告

name: Generate Weekly Report

on:
  schedule:
    - cron: '0 2 * * 1'              # 每周一凌晨2点
  workflow_dispatch:                   # 也可以手动触发

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

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install jupyter nbconvert pandas matplotlib

      # 执行 Jupyter Notebook 生成报告
      - name: Generate report
        run: |
          jupyter nbconvert --to html --execute \
            notebooks/weekly_report.ipynb \
            --output-dir reports/

      # 上传报告为 artifact
      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: weekly-report-${{ github.run_number }}
          path: reports/
          retention-days: 30            # 保留30天

7. 常用 Actions 市场

Action用途示例
actions/checkout@v4检出仓库代码几乎每个 workflow 都用
actions/setup-python@v5配置 Python 环境指定 Python 版本
actions/cache@v4缓存依赖加速缓存 pip/conda/npm
actions/upload-artifact@v4上传产物测试报告、构建结果
actions/download-artifact@v4下载产物跨 Job 传递文件
docker/build-push-action@v5构建推送 DockerCI 构建镜像
conda-incubator/setup-miniconda@v3配置 Conda生信环境管理
softprops/action-gh-release@v2创建 Release自动发布版本
peaceiris/actions-gh-pages@v4部署 GitHub Pages文档网站
codecov/codecov-action@v4上传覆盖率代码覆盖率报告

8. 面试怎么答

Q1:什么是 CI/CD?为什么生信也需要?

"CI 是持续集成——每次代码提交自动运行测试,确保新代码不会破坏已有功能。CD 是持续交付/部署——测试通过后自动发布。生信同样需要,因为分析 pipeline 也是代码,需要保证修改不会引入 bug。比如改了质控阈值,CI 自动用测试数据跑一遍 pipeline 验证输出正确性,比手动验证可靠得多。"

Q2:GitHub Actions 的基本结构是什么?

"一个 Workflow 由 YAML 文件定义,放在 .github/workflows/ 目录下。核心结构是:Trigger(触发条件,如 push/PR/定时)→ Job(作业,跑在 Runner 上)→ Step(步骤,可以是 shell 命令或复用社区 Action)。多个 Job 默认并行,也可以设置依赖顺序。"

Q3:你在项目中怎么用 CI/CD?

"在该 T2D 宏基因组项目中,我会设置:1)push 触发 pytest 跑单元测试,确保数据处理函数正确;2)PR 触发代码风格检查(Black + Ruff),保证代码质量;3)merge 到 main 后自动用测试数据跑一遍完整 pipeline 验证输出;4)文档更新自动部署到 GitHub Pages。这样团队协作时,任何人的修改都有自动验证。"

Q4:CI 跑得太慢怎么优化?

"几个常用策略:1)缓存依赖——用 actions/cache 缓存 pip/conda 包,避免每次重装;2)路径过滤——paths 参数限制只有相关文件变化才触发;3)矩阵策略并行——多个 Python 版本同时测试而非串行;4)分层测试——push 只跑快速单元测试,merge 时跑完整集成测试;5)使用更小的测试数据集。"

Q5:如何保护 CI 中的密钥(如数据库密码、API Key)?

"用 GitHub Secrets 管理。在仓库 Settings > Secrets and variables > Actions 中添加,在 YAML 中通过 ${{ secrets.MY_SECRET }} 引用。Secrets 在日志中自动打码,PR 从 fork 来的不会暴露 secrets。对于敏感操作(如部署),还可以设置 Environment 审批流程。"


9. 速查表

# ===== 触发条件 =====
on: push                                # push 时
on: pull_request                        # PR 时
on: [push, pull_request]                # push 或 PR
on:
  push:
    branches: [main]                    # 指定分支
    paths: ['src/**']                   # 指定路径
    tags: ['v*']                        # 指定 tag
on:
  schedule:
    - cron: '0 0 * * *'                 # 定时(UTC)
on: workflow_dispatch                   # 手动触发

# ===== 常用 Runner =====
runs-on: ubuntu-latest                  # Ubuntu(最常用)
runs-on: macos-latest                   # macOS
runs-on: windows-latest                 # Windows

# ===== 矩阵策略 =====
strategy:
  matrix:
    python-version: ["3.9", "3.10", "3.11"]
    os: [ubuntu-latest, macos-latest]

# ===== 条件执行 =====
if: github.event_name == 'push'         # 只在 push 时
if: success()                           # 前面步骤成功
if: failure()                           # 前面步骤失败
if: always()                            # 无论如何都执行

# ===== Job 依赖 =====
jobs:
  test:
    ...
  deploy:
    needs: test                         # 等 test 完成才跑

# ===== 常用环境变量 =====
${{ github.sha }}                       # 提交 SHA
${{ github.ref }}                       # 分支引用
${{ github.actor }}                     # 触发者
${{ github.repository }}                # 仓库名
${{ secrets.GITHUB_TOKEN }}             # 内置 token

10. 延伸资源

资源说明
GitHub Actions 官方文档最权威参考
Actions Marketplace社区 Action 市场
act本地运行 GitHub Actions
GitHub Actions for BioinformaticsSnakemake CI Action
nf-core CI 模板nf-core 如何做 CI
GitHub Skills交互式学习 GitHub

免费额度:公开仓库无限免费;私有仓库每月 2000 分钟(Linux)。生信学术项目通常是公开仓库,完全免费。