从 Jenkins 与 GitLab CI 迁移到 GitHub Actions

从 Jenkins 与 GitLab CI 迁移到 GitHub Actions 实战:概念映射表、Jenkinsfile 的 stage 与 agent 对应 job 与 runs-on、GitLab 的 stages 与 rules 与 artifacts 对应 needs 与 if 与 upload-artifact、共享库迁移到 reusable workflow 与 composite action、凭据映射到 Secrets 与 OIDC、缓存与矩阵迁移、条件语义差异、双跑灰度与常见坑


一、迁移的动机与前置评估

成本维度
  Jenkins 需要自建 master 与 agent,运维成本随规模线性上升
  GitLab CI 需要自建 runner,且与 GitLab 实例绑定
  GitHub Actions 托管 runner 按分钟计费,公共仓库免费

维护维度
  Jenkins 插件生态老化,升级插件常引发兼容问题
  Jenkinsfile 的 Groovy 沙箱限制多,调试体验差

不迁移的理由(同样要评估)
  Jenkins 上有大量难以迁移的定制插件与脚本
  GitLab 的 DAG 与父子流水线有独特能力
  已有自建 GPU 集群且成本已摊薄
迁移前必须盘点的内容
  [ ] 流水线总数与复杂度分布(简单 / 中等 / 复杂各多少)
  [ ] 使用的 Jenkins 插件清单,逐个确认有无 Actions 替代
  [ ] 凭据清单与类型(用户名密码 / SSH 密钥 / 证书 / token)
  [ ] 自托管 agent 的硬件需求(GPU、大内存、特殊操作系统)
  [ ] 构建产物的存储位置与保留策略
  [ ] 平均与峰值并发,用于估算 Actions 分钟数与并发上限
  [ ] 与外部系统的集成点(制品库、通知、部署平台)

迁移顺序
  第 1 批  最简单、无外部依赖的构建与测试流水线
  第 2 批  带制品发布的流水线,验证凭据迁移与制品存储
  第 3 批  带部署的流水线,验证环境门禁与 OIDC 免密
  第 4 批  复杂的多阶段流水线,验证 reusable workflow 与矩阵
  最后     Jenkins 上残留的定时任务,迁移到 schedule 与 workflow_dispatch

二、概念映射总表

2.1 Jenkins 到 GitHub Actions

Jenkins 概念              GitHub Actions 对应
Jenkinsfile               .github/workflows/*.yml
pipeline { agent any }    jobs.<id>.runs-on
stage('Build')            job(用 needs 串起来)
steps { sh 'x' }          steps: - run: x
parallel { }              jobs 之间的隐式并行(无 needs 即并行)
environment { }           env(workflow / job / step 三级)
post { always { } }       if: always()
when { branch 'main' }    if: github.ref == 'refs/heads/main'
parameters { }            workflow_dispatch.inputs
triggers { cron('') }     on.schedule
credentials('id')         secrets.NAME
withCredentials([...])    env: { X: ${{ secrets.Y }} }
stash / unstash           upload-artifact / download-artifact
archiveArtifacts          actions/upload-artifact
shared library            reusable workflow 或 composite action
agent { label 'gpu' }     runs-on 的 group 与 labels
retry { }                 无原生支持,用第三方 action 或脚本
timeout(time: 20)         timeout-minutes

2.2 GitLab CI 到 GitHub Actions

GitLab CI 概念             GitHub Actions 对应
.gitlab-ci.yml             .github/workflows/*.yml
stages: [build, test]      jobs 之间用 needs 表达依赖
stage: build               needs 指向的前置 job
script:                    steps: - run:
before_script              Actions 无对应,每个 job 重复或用 composite action
after_script               if: always() 的收尾步骤
rules:                     on: 与 job 级 if: 的组合
only: / except:            if:(GitLab 已废弃 only/except)
artifacts:                 actions/upload-artifact
dependencies:              needs 与 download-artifact
cache:                     actions/cache
variables:                 env / vars
CI/CD Variables            secrets / vars
extends: / include:        无直接对应,用 reusable workflow 或 YAML 锚点
parallel: 4                矩阵 strategy.matrix
environment:               environment
when: manual               environment 的 required reviewers
allow_failure: true        continue-on-error: true
tags: [gpu]                runs-on 的 labels
services: / image:         services 或 container:

2.3 心智模型的差异

Jenkins:「一台机器上顺序执行若干阶段,阶段内部用 Groovy 编程」
  强项是脚本能力,条件分支与循环随意写;弱项是状态多、难复现

GitLab CI:「一组 job 按 stage 分组,stage 之间串行,stage 内并行」
  强项是结构清晰,include 与 extends 复用方便;弱项是 stage 是隐式依赖

GitHub Actions:「一组 job 组成 DAG,每个 job 在干净的 runner 上执行」
  强项是默认隔离、默认并行、依赖显式;弱项是 job 间无共享文件系统

最关键的差异是 job 之间不共享工作目录。Jenkins 的 stage 共享 workspace,上一步的文件下一步直接可用;GitLab 的 job 会重新 clone,但可以通过 artifacts 传递;Actions 的 job 各自在全新的 runner 上,产物必须显式上传为 artifact 再下载,或者干脆把多个步骤合并到同一个 job 里。


三、Jenkinsfile 迁移实战

3.1 原始结构

pipeline {
  agent any
  environment { REGISTRY = 'registry.example.com' }
  options { timeout(time: 30, unit: 'MINUTES') }
  stages {
    stage('Install') { steps { sh 'npm ci' } }
    stage('Test') {
      parallel {
        stage('Unit') { steps { sh 'npm run test:unit' } }
        stage('Lint') { steps { sh 'npm run lint' } }
      }
    }
    stage('Build') {
      steps {
        sh "docker build -t $REGISTRY/app:$GIT_COMMIT ."
        sh "docker push $REGISTRY/app:$GIT_COMMIT"
      }
    }
  }
  post {
    always { junit 'reports/**/*.xml' }
    failure { slackSend channel: '#ci', message: "Build failed: ${env.BUILD_URL}" }
  }
}

3.2 等价的工作流

name: CI

on:
  push:
    branches: [main]
  pull_request:

env:
  REGISTRY: registry.example.com

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        target: [unit, lint]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run ${{ matrix.target == 'unit' && 'test:unit' || 'lint' }}
      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: junit-${{ matrix.target }}
          path: reports/**/*.xml

  build:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/app:sha-${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

失败通知改用独立的汇总 job:

  notify:
    needs: [test, build]
    if: always() && contains(needs.*.result, 'failure')
    runs-on: ubuntu-latest
    steps:
      - run: |
          curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -H 'Content-Type: application/json' \
            -d '{"text":"Build failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'

3.3 映射要点

1) parallel 块 → 矩阵或独立的 job
   Jenkins 的 parallel 内嵌 stage 无法一对一映射,
   最简单的做法是把并行分支拆成独立 job(无 needs 即并行)

2) post { always } → if: always()
   Actions 没有 post 块,每个需要「总是执行」的步骤单独加 if

3) junit 插件 → dorny/test-reporter 或 upload-artifact
   Actions 不会自动解析测试报告,需要显式上传与渲染

4) archiveArtifacts → actions/upload-artifact
   artifact 默认保留 90 天,numToKeepStr 需换算成 retention-days

5) timeout(time: 30) → timeout-minutes: 30
   Actions 的默认值是 360 分钟,必须显式收紧

6) GIT_COMMIT → github.sha
   github.sha 是完整 40 位,需要短哈希时自己截取

四、GitLab CI 迁移实战

4.1 原始结构

stages: [build, test, deploy]

build:
  stage: build
  script: [npm ci, npm run build]
  artifacts:
    paths: [dist/]
    expire_in: 1 week

test:unit:
  stage: test
  script: [npm ci, npm run test:unit]
  artifacts:
    reports:
      junit: reports/junit.xml
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

deploy:prod:
  stage: deploy
  script: [./deploy.sh]
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

4.2 等价的工作流

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7

  test-unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci && npm run test:unit
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: junit
          path: reports/junit.xml

  deploy-prod:
    needs: [build, test-unit]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - run: ./deploy.sh

4.3 关键差异

1) stage 是隐式依赖,needs 是显式依赖
   GitLab 里所有 build 完成后 test 才开始
   Actions 里不写 needs 就完全并行,写 needs 才有顺序
   迁移时最容易出错的地方:漏写 needs 导致 job 乱序

2) rules 与 on 的职责不同
   GitLab 的 rules 同时决定「是否触发」与「何时算成功」
   Actions 的 on 决定触发,if 决定是否执行
   复杂的 rules 条件要拆成 on 与 job 级 if 两部分

3) when: manual 没有直接对应
   Actions 用 environment 的 required reviewers 替代,
   语义更接近「审批」而非「手动点击执行」

4) extends 与 YAML 锚点
   Actions 不支持 extends,只能用 YAML 锚点或 reusable workflow
   锚点可用但可读性差,跨文件复用必须用 reusable workflow

5) artifacts 与 reports
   GitLab 的 artifacts:reports:junit 会自动在 MR 中渲染
   Actions 需要额外的 action 才能达到同等体验

4.4 条件语义对照

GitLab                               GitHub Actions
$CI_COMMIT_BRANCH == "main"          github.ref == 'refs/heads/main'
$CI_PIPELINE_SOURCE == "merge_request_event"  github.event_name == 'pull_request'
$CI_COMMIT_TAG                       startsWith(github.ref, 'refs/tags/')
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME github.base_ref
$CI_COMMIT_MESSAGE                   github.event.head_commit.message
rules:changes                        无原生对应,用 paths 过滤器或 dorny/paths-filter
# rules:changes 的替代:只在特定路径变更时触发
on:
  push:
    branches: [main]
    paths:
      - "src/**"
      - "package.json"

需要按目录分别跳过下游 job 时,用 dorny/paths-filter 产出布尔输出,再让下游 job 用 needs 加 if 判断。


五、共享库到可复用工作流

5.1 Jenkins 共享库的形态

// vars/buildAndPush.groovy,在 Jenkinsfile 中用 @Library 引入
def call(Map config) {
  def image = "${config.registry}/${config.name}:${env.GIT_COMMIT}"
  sh "docker build -t ${image} ${config.context ?: '.'}"
  sh "docker push ${image}"
  return image
}

5.2 对应到 reusable workflow

# my-org/ci-workflows/.github/workflows/build-and-push.yml
on:
  workflow_call:
    inputs:
      registry: { type: string, required: true }
      name:     { type: string, required: true }
    outputs:
      image:
        description: "推送后的镜像引用"
        value: ${{ jobs.build.outputs.image }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image: ${{ steps.meta.outputs.image }}
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - id: meta
        run: echo "image=${{ inputs.registry }}/${{ inputs.name }}:sha-${{ github.sha }}" >> "$GITHUB_OUTPUT"
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.image }}
# 调用方
jobs:
  publish:
    uses: my-org/ci-workflows/.github/workflows/build-and-push.yml@v1
    with:
      registry: registry.example.com
      name: app

5.3 对应到 composite action

# .github/actions/build-and-push/action.yml
name: Build and push
inputs:
  registry: { description: 镜像仓库地址, required: true }
  name:     { description: 镜像名, required: true }
outputs:
  image:
    value: ${{ steps.meta.outputs.image }}
runs:
  using: composite
  steps:
    - id: meta
      shell: bash
      run: echo "image=${{ inputs.registry }}/${{ inputs.name }}:sha-${{ github.sha }}" >> "$GITHUB_OUTPUT"
    - uses: docker/setup-buildx-action@v3
    - uses: docker/build-push-action@v6
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.image }}
维度              reusable workflow        composite action
粒度              整条流水线                一组步骤
runs-on           内部自己声明              由调用方决定
secrets           通过 secrets 显式传入     不直接接收,用 env 传递
适用              多仓库共享标准流水线       共享一段可复用的步骤
限制              最多 4 层嵌套             不能包含 runs-on

从 Jenkins 共享库迁移的建议
  1) 共享库里的「整条流水线」方法 → reusable workflow
  2) 共享库里的「工具方法」(如计算版本号)→ composite action
  3) 共享库里的「常量」→ 组织级 Variables
  4) 共享库里的「凭据读取」→ 全部改为 OIDC 或 Secrets

六、凭据迁移

6.1 Credentials 到 Secrets

Jenkins 凭据类型              GitHub 对应
Username with password        两个 Secret(用户名 + 密码)
Secret text                   单个 Secret
Secret file                   把文件内容作为 Secret,用时写到磁盘
SSH Username with private key SSH_PRIVATE_KEY Secret + ssh-agent
Certificate                   证书内容作为 Secret,用时解码
GitHub App                    直接用内置 GITHUB_TOKEN

Jenkins 的 withCredentials 把值注入环境变量,Actions 直接引用 Secret 即可:

- uses: docker/login-action@v3
  with:
    registry: registry.example.com
    username: ${{ secrets.REG_USER }}
    password: ${{ secrets.REG_PASS }}

6.2 SSH 密钥与 OIDC

- uses: webfactory/ssh-agent@v0.9.0
  with:
    ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

- name: Add known hosts and deploy
  run: |
    mkdir -p ~/.ssh
    ssh-keyscan -H deploy.example.com >> ~/.ssh/known_hosts 2>/dev/null
    ssh deploy@deploy.example.com 'cd /srv/app && ./pull-and-restart.sh'

必须处理 known_hosts,否则首次连接会因主机密钥确认而卡住;ssh-keyscan 有中间人风险,生产环境应把已知主机密钥写死。更彻底的做法是用 OIDC 加云 API 替代 SSH 部署,消除长期密钥。

- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
    aws-region: ap-northeast-1
迁移动作
  [ ] 在云侧创建 OIDC 提供者与角色
  [ ] 信任策略限定到具体仓库与分支
  [ ] workflow 声明 permissions: id-token: write
  [ ] 删除 Secret 中的长期 AccessKey
  [ ] 审计日志确认旧密钥已无调用

凭据可见性分层
  仓库级 Secret      仅该仓库可见,适合仓库独有的部署密钥
  组织级 Secret      按 all / private / selected 分发,适合通用凭证
  Environment Secret 仅绑定该环境的 job 可见,适合生产凭证
  Variables          非敏感配置,可明文读回

七、缓存与矩阵迁移

7.1 缓存迁移

# GitLab
cache:
  key:
    files: [package-lock.json]
  paths: [node_modules/]
# Actions
- uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-
映射要点
  1) GitLab 的 key.files 相当于 hashFiles
  2) GitLab 无 restore-keys,Actions 的 restore-keys 是额外能力
  3) Actions 的缓存总量按仓库限制(默认 10GB),需要关注淘汰
  4) 语言级 setup action 大多内置缓存,优先用它们而非手写 cache
     actions/setup-node 的 cache: npm
     actions/setup-python 的 cache: pip
     actions/setup-go 的 cache: true

Jenkins 缓存迁移
  Jenkins 通常直接把 workspace 留在 agent 上作为缓存,
  迁移后必须显式用 actions/cache,
  否则每次都在全新的 runner 上从零安装依赖。

7.2 矩阵与服务容器

# Actions 的 matrix,对应 GitLab 的 parallel:matrix
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node: [18, 20, 22]
        include:
          - node: 20
            coverage: true
        exclude:
          - node: 18
            coverage: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci && npm test
矩阵能力对照
  能力                  GitLab              Actions
  笛卡尔积              支持                支持
  显式组合              matrix 数组          include
  排除                 不支持                exclude
  动态矩阵              不支持                fromJSON 表达式
  fail-fast             不支持                fail-fast 参数
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: app
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - run: npm test
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/app
服务容器的关键差异
  1) GitLab 的服务容器与 job 共享网络命名空间,可用服务名直连
  2) Actions 的服务容器需要显式 ports 映射,用 localhost 访问
  3) Actions 必须配置 health check,否则应用可能先于数据库就绪而失败

八、双跑灰度与回退预案

双跑阶段的关注点
  1) 结论一致性:两者成功失败判定是否相同
  2) 产物一致性:构建产物的 sha256 是否一致
  3) 耗时对比:Actions 与 Jenkins 的 P50 与 P90 差异
  4) 覆盖完整性:Jenkins 上跑的检查是否都已迁移
  5) 退出标准:连续 20 次结论一致即可关闭 Jenkins 侧

做法是在迁移期让同一次提交同时触发两边,各自把产物摘要写入 artifact,再由一个对比 job 拉取两边的摘要做 diff。摘要一致说明构建可复现,不一致则需要逐个排查工具链版本与构建参数。

灰度切换
  阶段 1  仅观察      Actions 跑但不阻塞合并,暴露环境差异与脚本兼容问题
  阶段 2  并行阻塞    两者都作为必需检查,确认判定一致
  阶段 3  切换必需检查 Jenkins 降级为观察,Actions 独立承载
  阶段 4  关闭 Jenkins 保留只读一段时间以便回查

回退触发条件
  1) Actions 出现长时间不可用
  2) 关键流水线的结论与 Jenkins 持续不一致
  3) 有检查项无法在 Actions 中实现

回退动作
  1) 把必需检查切回 Jenkins
  2) 保留 Actions 流水线继续观察
  3) 记录本次回退原因,作为下一轮迁移的输入

前提
  在阶段 4 之前,Jenkins 侧的流水线配置必须保持可运行,
  不要提前删除 Jenkinsfile。

九、常见坑清单

工作目录与文件传递
  1) 假设 job 之间共享工作目录
     Jenkins 的 stage 共享 workspace,Actions 的 job 各自独立。
     表现:build job 生成的 dist/ 在 deploy job 中不存在。
     解法:用 upload-artifact 与 download-artifact 传递,
           或把相关步骤合并到同一个 job。
  2) checkout 深度默认是 1
     依赖 git 历史的步骤(版本号推导、changelog)会失败。
     解法:with: fetch-depth: 0。
  3) cd 不会跨步骤保留
     每个 run 步骤都从 GITHUB_WORKSPACE 开始。
     解法:用 working-directory 参数,或每个步骤内显式 cd。
环境变量与 shell 行为
  4) env 的作用域容易搞混
     workflow 级所有 job 可见;job 级该 job 内可见;step 级仅该步骤可见
  5) 步骤间传递变量必须用 GITHUB_ENV
     在 run 里 export FOO=bar 只对当前步骤有效
  6) 传递输出必须用 GITHUB_OUTPUT
     旧写法 ::set-output 已废弃
  7) 默认 shell 与 set -e
     Actions 的 run 步骤在 Linux 上默认使用 bash -e {0},
     即已开启 -e,但不开启 -u 与 -o pipefail
  8) 管道中的失败被吞掉
     没有 pipefail 时,cmd1 | cmd2 中 cmd1 失败不会被察觉,
     排查「明明失败了却显示成功」时优先检查这一项
- name: Set version
  id: ver
  run: |
    VERSION=$(node -p "require('./package.json').version")
    echo "VERSION=$VERSION" >> "$GITHUB_ENV"
    echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Strict script
  shell: bash
  run: |
    set -euo pipefail
    echo "version is $VERSION and ${{ steps.ver.outputs.version }}"
触发与条件
  9) fork PR 拿不到 secret
     GitHub 对来自 fork 的 pull_request 不下发任何 secret,
     Jenkins 与 GitLab 在自建环境下通常没有这个限制。
     解法:用 pull_request_target(注意安全)或 workflow_run 两段式。
 10) paths 过滤器在分支保护上的副作用
     配置了 paths 的 workflow 若未命中路径则不触发,
     作为必需检查时会一直显示 pending。
     解法:搭配一个总是运行的「汇总 job」作为必需检查。
 11) on.schedule 使用 UTC
     cron 表达式一律按 UTC 解释,需要换算本地时间。
 12) workflow_dispatch 的 inputs 都是字符串
     布尔与数字类型在表达式中仍可能被当成字符串比较,
     解法:显式用 == 'true' 比较,或声明 type 后用 fromJSON 转换。
制品与保留
 13) artifact 默认保留 90 天
     Jenkins 的 logRotator 与 GitLab 的 expire_in 语义不同,
     解法:按需设置 retention-days,并在组织级设置上限。
 14) artifact 名称冲突
     矩阵中多个 job 上传同名 artifact 会失败,
     解法:在 name 中加入 ${{ matrix.* }} 区分。
 15) 缓存与 artifact 混用
     缓存用于加速(可丢失),artifact 用于交付(必须可靠),
     不要把构建产物只放在缓存里,缓存淘汰后无法恢复。

9.1 迁移检查清单

代码层
  [ ] 所有 Jenkinsfile 与 .gitlab-ci.yml 已盘点
  [ ] 每个 stage / job 都有对应的 Actions job
  [ ] parallel 块已拆成独立 job 或矩阵
  [ ] post / after_script 已改为 if: always() 的步骤

凭据层
  [ ] 所有 Credentials 已映射为 Secrets 或 OIDC
  [ ] 长期云 AccessKey 已删除并确认无调用
  [ ] Environment secret 已用于生产凭证
  [ ] GITHUB_TOKEN 权限已按 job 最小化

能力层
  [ ] 缓存已迁移且命中率正常
  [ ] 矩阵与 include / exclude 已配置
  [ ] 服务容器已配置 health check
  [ ] 测试报告已能渲染,制品已归档且保留期合理

流程层
  [ ] 双跑对比已连续 20 次结论一致
  [ ] 必需检查已切换为 Actions
  [ ] 回退预案已书面化
  [ ] Jenkins 已保留只读一段时间

总结

从 Jenkins 与 GitLab CI 迁移到 GitHub Actions,表面上是 YAML 语法转换,实质上是心智模型的替换:Jenkins 的阶段共享同一台机器与工作目录,GitLab 用 stage 表达隐式依赖,而 Actions 把每个 job 放在全新的隔离 runner 上,依赖必须用 needs 显式声明、产物必须用 artifact 显式传递。抓住这条主线,迁移中最常见的翻车点,比如上一步生成的文件下一步找不到、漏写 needs 导致乱序、fork PR 拿不到 secret,就都能提前预判。工具层面,Jenkins 共享库对应 reusable workflow 或 composite action,Credentials 对应 Secrets 与 OIDC,stash 与 artifacts 对应 upload-artifact 与 download-artifact,cache 对应 actions/cache 与语言级 setup action 的内置缓存。流程层面,务必先用双跑对比确认结论与产物一致,再分四个阶段灰度切换,并在阶段四之前保持 Jenkins 可运行,让回退始终是一个选项。

延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「github-actions」更多文章

  1. 多云部署编排与基础设施漂移检测
  2. 文档站与静态站点发布流水线
  3. AI 代码审查与 PR 助手集成