Skip to content

指南 - 其他

对比 / 性能 / 安全 / 常见陷阱

速查

  • vs GitLab CI/CD:YAML 相似但 GHA 没 stages、jobs 默认并行;GHA 复用走 Composite + Reusable Workflow
  • vs Jenkins:GHA YAML vs Groovy;GHA 仓库一体 vs Jenkins 独立 server
  • 性能优先级:concurrency cancel > cache > matrix 控量 > self-hosted Runner
  • 安全雷区:第三方 Action 锁 SHA / fork PR 别给 secrets / GITHUB_TOKEN 默认 read-only / OIDC 替代密钥
  • 常见陷阱:cron UTC 时间 / setup-X 自带 cache / artifacts 同名覆盖 / Composite Action 必填 shell

与 GitLab CI/CD 对比

维度GitHub ActionsGitLab CI/CD
配置位置.github/workflows/*.yml(多文件).gitlab-ci.yml(单文件,可 include)
默认 job 顺序全并行按 stages 串行
依赖控制needs:(声明依赖)stages: + needs:(默认 stages)
复用机制Composite Action + Reusable Workflowextends + include
触发器on: 极丰富(含 workflow_dispatch / repository_dispatch)rules: + workflow:
Marketplace15000+ Actions 开箱即用GitLab Components(较新,规模小)
SecretsRepo / Env / Org SecretsProject / Group / Instance Variables
OIDC第一类公民,多云完整支持支持,配置稍复杂
自托管 Runnerlabel-based 调度tag-based + Executor 类型选择多
Container 内置GHCR + ${{ secrets.GITHUB_TOKEN }} 自动注入Container Registry 同样集成

决策建议

  • 个人 / 开源:GHA 默认选;公开仓库永久免费 + Marketplace 杀手锏
  • 公司代码在 GitLab:当然用 GitLab CI/CD,没必要把 CI 切出去
  • 公司代码在 GitHub:直接 GHA,私有仓配额够用 + 不用自托管
  • 混合(GitHub + 私有部署):GHA + self-hosted Runner,鱼和熊掌兼得

与 Jenkins 对比

维度GitHub ActionsJenkins
部署SaaS 为主独立服务器,需 Java
配置YAMLJenkinsfile(Groovy DSL)
复用Composite + Reusable WorkflowShared Library(Groovy 类)
调度runs-on labelsMaster-Agent + label
学习曲线低(YAML 即可)高(Groovy + 插件 + 运维)
Marketplace15000+ Actions1800+ 插件(覆盖更深)
触发集成与仓库原生融合需配 webhook + Branch Source 插件
调试act 本地模拟同 Jenkins 实例上调

何时选 Jenkins 不选 GHA

  1. 已经有成熟 Jenkins 集群和插件 / Shared Library 投入大量人力
  2. 强自托管 / 内网 / 合规要求
  3. 复杂动态 pipeline(运行时根据条件生成 stage)

性能优化清单

按收益从高到低:

1. concurrency cancel-in-progress

yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

PR 同分支频繁 push 时,自动取消旧 workflow,省 minutes 明显。

2. 用对 cache(lockfile-based key)

yaml
- uses: actions/setup-node@v4
  with:
    node-version: '22'
    cache: 'pnpm'                        # 内置 cache,自动按 lockfile

或者手写:

yaml
- uses: actions/cache@v4
  with:
    path: ~/.local/share/pnpm/store
    key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
    restore-keys: ${{ runner.os }}-pnpm-

错的:

yaml
key: ${{ github.sha }}                   # ❌ 每个 commit 一份 key,等于禁用

3. matrix 控量 + fail-fast

yaml
strategy:
  matrix:
    os: [ubuntu-latest, macos-latest, windows-latest]
    node: ['18', '20', '22']
  max-parallel: 4                        # 同时最多 4 个,省 minutes
  fail-fast: true                        # 一个失败立刻取消其它

矩阵爆炸时(os × node = 9)要么 max-parallel 限并发、要么 exclude 砍冗余组合。

4. paths filter:monorepo 友好

yaml
on:
  push:
    paths:
      - 'apps/web/**'                    # 只有改了 apps/web 才触发
      - 'packages/ui/**'
      - 'pnpm-lock.yaml'

monorepo 项目不写 paths 等于每次 push 都跑全部 workflow,是浪费大头。

5. larger runner(付费)或 self-hosted

CPU bottleneck 时升级到 ubuntu-latest-8-cores / ubuntu-latest-16-cores——按比例多收 minutes 但速度翻倍。或者自托管,物理机能多猛就多猛。

6. Docker build 用 buildx + GHA 缓存

yaml
- uses: docker/build-push-action@v6
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max

GHA 缓存对 Docker layer 极有效,第二次构建可能从 5 分钟降到 1 分钟。

7. 用对 setup-X 自带的 cache

yaml
- uses: actions/setup-node@v4
  with:
    node-version: '22'
    cache: 'pnpm'                        # ✅ 自带,省去自己写 actions/cache

- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'

- uses: actions/setup-go@v5
  with:
    go-version: '1.22'
    cache: true

安全实践

1. 第三方 Action 锁 commit SHA

yaml
# ❌ 浮动 tag,作者可以重打覆盖(曾被供应链攻击)
- uses: third-party/action@v1

# ✅ 锁 commit SHA + Dependabot 自动 PR 升级
- uses: third-party/action@a1b2c3d4e5f6789012345678901234567890abcd

@v4 这种浮动 tag 让作者可以悄悄修改内容;commit SHA 不可篡改。

仓库根放 .github/dependabot.yml

yaml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Dependabot 会自动开 PR 升级 Action 到最新 SHA,你审一下合并即可。

2. fork PR 永远不应拿 secrets

仓库 Settings → Actions → "Fork pull request workflows from outside collaborators" → 选 Require approval for all outside collaborators

或在 workflow 里显式判断:

yaml
jobs:
  build:
    if: github.event.pull_request.head.repo.full_name == github.repository
    # 只有同仓 PR(非 fork)才跑

如果 fork PR 也要 CI,单独写个无 secrets 的 lint/test workflow 给 fork 用,部署 workflow 限内部 PR。

3. GITHUB_TOKEN 默认 read-only

2023+ GitHub 推动新仓库默认 GITHUB_TOKEN: read-only。已有仓库可手动开启:

Settings → Actions → General → Workflow permissions → 选 Read repository contents permission

之后想写权限要 yml 里显式声明:

yaml
permissions:
  contents: write
  pull-requests: write

4. OIDC 替代长期密钥

长期 access key / token 一旦泄露损失大。OIDC 拿短期 token(默认 1 小时过期),且 cloud 侧可按 repo / branch / environment 精细限制 sub 字段。详见 高级 - OIDC 部署

5. secrets 不要 echo

bash
# ❌ 即使 GitHub 会 mask 完整值,截取后会泄露
echo "Token starts with ${TOKEN:0:5}..."
echo $TOKEN | base64

# ✅ 用了但不 echo
curl -H "Auth: $TOKEN" https://api.example.com

debug 时永远 echo 操作结果而非密钥本身。

6. 限制 self-hosted Runner 的访问

仓库 Settings → Actions → Runners → 配置 Runner 时勾选 "Restrict access to the repositories that are allowed to use this runner"。

不限制的话,可能你私有 Runner 给某仓库用,结果某个 fork PR 在你机器上跑 cat ~/.aws/credentials | curl ...

常见陷阱

1. cron 用的是 UTC

yaml
schedule:
  - cron: '0 4 * * 1-5'                  # UTC 04:00 = 北京 12:00

GitHub 不支持 timezone,要按本地时间排自己换算。

2. setup-X 自带 cache 别再叠

yaml
- uses: actions/setup-node@v4
  with: { node-version: '22', cache: 'pnpm' }  # 已经管 pnpm cache

- uses: actions/cache@v4                # ❌ 重复,可能冲突
  with:
    path: ~/.local/share/pnpm/store
    key: ...

只需要一个;优先用 setup-X 自带的(自动按 lockfile)。

3. artifacts 同名会冲突(v4 起)

upload-artifact@v4 起,同 workflow 内同名 artifact 上传会报错(v3 是覆盖)。matrix 多 job 上传时要加后缀:

yaml
- uses: actions/upload-artifact@v4
  with:
    name: build-${{ matrix.os }}-${{ matrix.node }}   # 多维度后缀
    path: dist/

4. Composite Action 的 run 必填 shell

yaml
runs:
  using: composite
  steps:
    - run: pnpm install                 # ❌ 缺 shell
      # → "shell" is required for run steps in composite actions

    - run: pnpm install                 # ✅
      shell: bash

5. needs 上游 cancelled / skipped 时下游也 skip

yaml
jobs:
  test: { ... }
  notify:
    needs: test
    if: failure()                       # 仅在 test 失败时跑
    steps: [...]

但如果 test 被 cancel(concurrency 取消),notify 也 skip。要强制跑写 if: always()if: !cancelled()

6. 反向 needs:取上游 outputs

yaml
deploy:
  needs: build
  runs-on: ubuntu-latest
  steps:
    - run: echo "${{ needs.build.outputs.version }}"
    # 注意是 needs.build.outputs,不是 needs.build.steps...

job-level outputs 需要在被 needs 的 job 里显式声明 outputs:,光在 step 里 echo 到 $GITHUB_OUTPUT 不够。

7. workflow_dispatch input 都是字符串

yaml
inputs:
  count:
    type: number                         # GitHub 这么标了

${{ inputs.count }} 拿到的还是字符串 "5",做数学要 fromJSON

yaml
- run: echo $((${{ fromJSON(inputs.count) }} + 1))

8. concurrency 把 deploy 一起取消了

yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true              # ❌ 部署 workflow 用这个会被 push 取消

部署类 workflow 应该:

yaml
concurrency:
  group: deploy-prod
  cancel-in-progress: false             # 排队,永不取消

9. paths filter 是 OR 不是 AND

yaml
on:
  push:
    paths:                              # 任一匹配就触发
      - 'apps/web/**'
      - 'packages/ui/**'

要 AND 逻辑("既改了 web 又改了 ui 才触发")做不到——拆成两个 workflow,或者第一个 step 里自己 git diff 判断。

10. 你的 secret 也藏不住

GHA 自动 mask 完整 secret 值,但只 mask 完整匹配。把 secret 写到文件再 cat、base64 编码、截取前几位——这些都会泄露。生产里永远不要打印涉及 secret 的中间状态。

个人 GitHub 用法 — 真要省事的话

把这套放一边后,个人项目用 GHA 的最佳实践其实很简单:

  1. 只用官方 + 一线第三方 Actionactions/* + docker/* + pnpm/action-setup 这些
  2. 永远 actions/checkout@v4 起步,不要简化掉这步
  3. cache 走 setup-X 自带cache: 'pnpm' / 'npm' / 'pip' 就够
  4. 不需要 OIDC / 自托管 Runner / Composite Action——个人项目用不上
  5. 配额够用:开源仓永久免费,私有 2000 分钟/月够个人
  6. trigger 用 push + pull_request,schedule 偶尔加(每周 dependabot 类的)
  7. 写一个 release.yml 配合 workflow_dispatch 做手动发包

GHA 的好处就是"刚学就能用,深入用也撑得住"。个人不需要把这本指南通读,先跑起来再说。