Py学习  »  Git

【第3549期】GitHub Actions 全面解析:工作流结构完整指南

前端早读课 • 4 月前 • 232 次点击  

前言

介绍了 GitHub Actions 的工作流程结构和优化方法,提供了一个完整的指南。今日前端早读课文章由 @Kamil Chmielewski 分享,@飘飘编译。

译文从这开始~~

通过这份全面的指南,你将掌握 GitHub Actions 的工作流结构、执行流程、优化策略以及性能监控。了解如何构建更快速、更可靠的 CI/CD 流程。

了解 GitHub Actions 工作流的组成结构

GitHub Actions 已成为自动化软件开发流程中不可或缺的工具。无论是 CI/CD 流程还是定时任务,它都能帮助开发者高效地构建、测试和部署代码。但当一个工作流被触发时,究竟发生了什么?

本文将深入剖析了一次 GitHub Actions 工作流运行的整个过程,详细介绍其结构及各个组成部分如何协同工作来完成自动化任务。

核心概念:Workflow(工作流)、Job(任务)、Step(步骤)、Action(操作)

在拆解工作流运行过程之前,我们先来了解这些核心组成部分:

1、Workflow(工作流)

一个可以配置的自动化流程,由一个或多个 Job(作业)组成。工作流通过 YAML 文件定义,通常存放在代码库中的 .github/workflows 目录。它可以由多种事件触发,例如代码推送、拉取请求或定时任务等。

2、Job(作业)

一组在同一个运行环境(runner)中执行的步骤。默认情况下,多个作业是并行运行的;如果某个作业依赖于另一个作业的成功完成,也可以配置为按顺序运行。不同作业之间运行环境是隔离的,因此需要通过文件或输出值来共享数据。

3、Step(步骤)

一个可以执行命令或调用某个操作的任务。步骤在作业中按顺序执行,共享同一个文件系统,因此可以通过文件或环境变量在步骤之间传递数据。

4、Action(操作)

一个可以复用的代码模块,用于执行特定任务。Action 可以是自己编写的,也可以从 GitHub Marketplace 或公开仓库中获取。

5、Runner(运行器)

执行作业的虚拟机或容器。GitHub 提供托管的运行器(支持 Ubuntu、Windows、macOS),你也可以使用自己部署的运行器。每个作业在一个全新的运行器实例中运行。

工作流运行的生命周期

当某个事件触发一个工作流(例如,推送到主分支),GitHub Actions 就会启动一次新的工作流运行。每次运行就是工作流的一次完整执行实例。

1、工作流运行初始化

GitHub 会读取与触发事件对应的 YAML 工作流文件,并创建一个新的工作流运行实例,准备执行作业。

2、作业执行:并行处理与依赖关系

GitHub Actions 会根据作业之间的依赖关系,将它们组织成一个有向无环图(DAG),然后高效地执行这个图中的作业。

作业依赖(needs)

可以使用 needs 关键字来定义作业之间的执行顺序。例如,jobB 依赖 jobA,那么 jobB 只有在 jobA 成功完成后才会开始执行。没有依赖的作业会立即开始执行。

并行执行

GitHub Actions 会同时启动所有没有依赖关系的作业,每个作业在独立的运行器上运行。当某些作业完成后,它们的后续依赖作业就会被触发,整个过程高效地并行运行,同时保证依赖关系的正确性。

 jobs:
   build:
     runs-on: ubuntu-latest
     steps:
       - name: Build application
         run: echo "Building..."

   test:
     runs-on: ubuntu-latest
     needs: build # test 作业依赖 build 成功
     steps:
       - name: Run tests
         run: echo "Testing..."

   deploy:
     runs-on: ubuntu-latest
     needs: [build, test] # deploy 依赖 build 和 test
     steps:
       - name: Deploy application
         run: echo "Deploying..."

可选性与条件执行(if 条件)

可以使用 if 条件来让作业(或步骤)变成可选的或按条件执行。例如,可以根据分支名、事件类型或前一个作业的结果来决定是否执行。

 jobs:
   publish_docs:
     runs-on: ubuntu-latest
     if: github.ref == 'refs/heads/main' && github.event_name == 'push'
     steps:
       - name: Publish documentation
         run: echo "Publishing docs for main branch..."

如果某个作业由于 if 条件判断为 false 而被跳过,那么任何依赖它的作业也会被跳过,除非这些作业有其他执行条件,或配置为在依赖失败时仍然执行(如使用 if: always()if: success()if: failure()if: cancelled() 等)。

在作业之间共享数据

由于每个作业运行在不同的运行器上,它们无法直接共享文件或变量。要在作业间传递数据,主要有两种方式:

方法一:Artifacts(构建产物)

可以在一个作业中上传文件,在另一个作业中下载这些文件。使用 actions/upload-artifact 和 actions/download-artifact 来实现。构建产物在工作流完成后仍会保留(公开仓库默认保留 90 天,私有仓库为 400 天),并可被同一个工作流中的其他作业共享。

 jobs:
   build:
     runs-on: ubuntu-latest
     steps:
       - name: Build app
         run: echo "build output" > dist/app.js
       - name: Upload build artifacts
         uses: actions/upload-artifact@v4
         with:
           name: build-files
           path: dist/

   test:
     runs-on: ubuntu-latest
     needs: build
     steps:
       - name: Download build artifacts
         uses: actions/download-artifact@v4
         with:
           name: build-files
           path: dist/
       - name: Test app
         run: test dist/app.js

方法二:作业输出(Job Outputs)

可以使用 outputs 关键字在作业之间传递简单的字符串值。上游作业定义输出,下游作业可以通过 needs 上下文来访问这些值。

 jobs:
   build:
     runs-on: ubuntu-latest
     outputs:
       version: ${{ steps.get_version.outputs.version }}
     steps:
       - name: Get version
         id: get_version
         run: echo "version=1.2.3" >> $GITHUB_OUTPUT

   deploy:
     runs-on: ubuntu-latest
     needs: build
     steps:
       - name: Deploy version
         run: echo "Deploying version ${{ needs.build.outputs.version }}"
3. 步骤执行:顺序执行与原子性

在每个作业(job)中,步骤(step)是按顺序执行的。每个步骤必须在前一个步骤完成后才能开始。

【第1986期】使用 Figma + GitHub Actions 完成 SVG 图标的完全自动化交付

顺序执行

在工作流的 YAML 文件中定义的步骤顺序,就是它们实际执行的顺序。

原子性(通常情况)

每个步骤通常被视为一个独立的、不可分割的执行单元。如果某个步骤失败(例如命令返回非零退出码),该作业会立即停止执行,后续步骤将不会运行,作业会被标记为失败。

你可以使用 continue-on-error: true 来改变这一默认行为,让作业在某个步骤失败的情况下继续执行后面的步骤。

 jobs:
   example_job:
     runs-on: ubuntu-latest
     steps:
       - name: First step
         run: echo "This is the first step."
       - name: Second step (might fail)
         run: exit 1
         continue-on-error: true # 即使该步骤失败,作业仍会继续执行
       - name: Third step
         run: echo "This step runs regardless of the second step's outcome."
4. 重试机制:处理偶发失败与多次尝试

有时工作流或作业会因为网络波动或外部服务暂时不可用等临时问题而失败。GitHub Actions 提供了机制来处理这类情况,即 “运行尝试(run attempts)”。

工作流运行尝试

如果整个工作流运行失败,可以在 GitHub 网页界面手动重新运行。这会创建该工作流运行 ID 下的一个新的尝试记录。

作业重试

你也可以只重新运行工作流中某个失败的作业。这对于只有部分作业因为临时问题失败的情况特别有用。

使用操作实现自动重试

虽然 GitHub Actions 的 YAML 配置不支持作业层级的自动重试,但你可以在步骤中通过 shell 脚本或社区提供的 retry 操作来实现。例如,一个步骤可以在失败后自动重试多次。

并发控制和最大并行数

对于使用 matrix 策略的作业(即在不同配置如操作系统版本、Node.js 版本等下运行同一个作业),可以使用 strategy.max-parallel 来限制同时运行的作业数量。这不是直接的重试机制,但有助于控制资源使用,减少因请求过多而导致的失败。

工作流级别并发控制

你还可以通过 concurrency 设置,确保某个工作流(或工作流组)在同一时间只执行一个实例。如果有新的运行被触发,可以自动取消还未完成的旧运行。虽然这也不是重试机制,但它能优化执行流程,避免多个运行之间发生冲突。




    
 name: CI

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

 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
   cancel-in-progress: true # 取消同组中进行中的旧运行

Run Attempt(运行尝试)指的是一次具体的工作流或作业的执行。如果你重新运行某个失败的作业,它将被视为该作业在同一个父工作流下的新一次尝试。

5. 构建产物(Artifacts)与缓存(Caching)

随着工作流复杂度的增加,你可能需要在作业之间共享构建结果、保存文件以便后续使用,或加快重复任务的执行速度。GitHub Actions 提供了两种关键机制来支持这些需求:

Artifacts(构建产物)

作业可以生成构建产物(即文件或文件集合),这些文件可以在同一工作流运行中由其他作业使用,或者保存供之后下载。使用 actions/upload-artifact 和 actions/download-artifact 可实现上传和下载。

构建产物通常在作业末尾上传,在后续依赖作业的开始阶段下载。适合用于共享构建输出、测试结果或需要保存的其他文件。

Caching(缓存)

你还可以为工作流设置缓存,以加快后续运行速度。这可以通过 actions/cache 实现。缓存适用于依赖项(如 node_modules、Python 包)或可在多个运行之间重复使用的中间构建文件,从而节省执行时间。

 - name: Cache dependencies
   uses: actions/cache@v3
   with:
     path: ~/.npm
     key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
     restore-keys: |
       ${{ runner.os }}-node-
6. 工作流运行完成

当所有作业都完成(无论是成功、失败还是被跳过),整个工作流运行将被赋予一个最终状态,例如:成功(Success)、失败(Failure) 或 已取消(Cancelled)。

你可以根据这些状态设置通知策略,例如通过邮件、Slack 或其他方式通知相关人员。

可视化工作流执行流程

理解工作流的最佳方式,就是把它看作一条时间轴,其中不同作业(jobs)在并行的 “轨道” 中执行:


GitHub Actions 的工作流时间轴可以被想象成并行的作业轨道,每个轨道中的步骤按顺序执行,不同类型的任务可以用颜色标记区分。

【第3533期】Cursor AI 最佳实践:使用“金标准文件”工作流以获得更精确的结果

  • 作业默认并行运行,除非使用 needs 设置了依赖关系。
  • 每个作业中的步骤是顺序执行的,依次完成。
  • 依赖关系会造成等待,比如 Deploy 作业会等待 Build 和 Test 作业都完成后才开始执行。

使用 CI 关键指标(CI Vitals)优化工作流性能

现在你已经了解了 GitHub Actions 的工作机制,接下来我们来看如何通过 CI Vitals 来评估和优化你的工作流性能。这三个核心指标可以揭示你的 CI/CD 流水线是否健康:

WET(Workflow Execution Time,工作流执行时间)

整个工作流运行所需的时间

NFR(Noise-to-Fix Ratio,噪声修复比)

失败中有多少是由基础设施问题导致,而非真正的代码 Bug

POT(Pipeline Overhead Time,流水线额外耗时)

排队、重试、资源浪费等导致的额外时间

当 持续集成关键指标下降:诊断指南

🏎️ WET 高(工作流执行慢):应该检查哪里?

常见现象

开发者抱怨构建时间过长、反馈周期太长、频繁切换上下文等待结果。

排查重点组件:

1、作业依赖(needs)

  • 检查是否存在不必要的串行依赖
  • 看看是否有本可以并行执行的作业被错误地串联起来
  • 分析关键路径(依赖链中执行时间最长的路径)

2、作业内步骤顺序

  • 把执行速度快、可能失败的步骤(如代码格式检查、单元测试)放在最前面
  • 把耗时较大的步骤(如构建、大型集成测试)放在前面检查通过后再执行

3、缓存策略

  • 查看工作流日志中的缓存命中率
  • 检查是否反复下载了应该被缓存的依赖
  • 确保缓存键(key)设置既具体又不会太频繁变化

4、构建产物使用

  • 查找是否有作业重复构建相同内容
  • 是否可以通过构建一次,然后使用 artifacts 在多个作业间共享

快速优化建议:

  • 删除不必要的 needs 依赖关系
  • 为依赖和构建结果添加缓存
  • 并行执行无依赖的作业
  • 使用构建产物避免重复构建
🎯 NFR 高(流水线不稳定):应该检查哪里?

常见现象

开发者频繁点击 “重新运行失败作业”,对流水线失去信任,失败后重跑就能通过。

排查重点组件:

1、步骤失败模式

  • 分析失败日志,区分是代码问题还是基础设施问题
  • 查找网络超时、运行器资源问题、外部服务不可用等错误
  • 找出不稳定的测试(有时成功,有时失败)

2、重试逻辑

  • 检查与外部服务交互的步骤是否有适当的重试机制
  • 检查自定义脚本中是否缺少错误处理
  • 确保只对临时性故障进行重试,而不是对真正失败的测试

3、外部依赖

  • 查找依赖外部服务的步骤(如包管理器、API、数据库)
  • 查看是否缺少降级策略、超时设置
  • 检查是否存在访问频率限制问题

4、运行器环境问题

  • 查看是否有运行器初始化失败的日志
  • 检查是否存在资源瓶颈(如内存、磁盘空间不足)
  • 注意并行作业之间是否存在资源冲突

快速优化建议:

  • 对网络相关步骤添加重试逻辑
  • 对非关键步骤(如上传产物、发送通知)使用 continue-on-error: true
  • 设置合适的超时时间
  • 隔离不稳定的外部依赖
🗑️ POT 高(耗时浪费多):应该检查哪里?

常见现象

工作流运行时间比预期更长,等待时间多,手动重试频繁。

排查重点组件:

1、排队时间

  • 查看工作流日志中等待运行器的时间
  • 注意高峰期是否资源紧张导致排队
  • 检查并发设置是否导致了不必要的等待

2、缓存未命中

  • 分析工作流中缓存的命中 / 未命中比
  • 检查缓存键是否太具体、更新太频繁
  • 找出是否存在重复下载的大文件

3、重复操作

  • 找出执行相同操作的作业(比如重复构建)
  • 检查是否有可以合并或移除的步骤
  • 关注不必要的文件操作或数据传输

4、重试带来的开销

  • 统计工作流被手动重试的频率
  • 衡量失败尝试中花费的时间
  • 分析重试失败的共同特征

快速优化建议:

  • 优化缓存键以提高命中率
  • 合并相关步骤,减少冗余操作
  • 使用构建产物避免重复构建
  • 配置更智能的并发策略
如何衡量 CI 指标表现?

为了有效优化工作流,需要定期监测以下指标:

WET(工作流执行时间)

跟踪关键工作流的 p75 和 p90 执行时间(75% 和 90% 的运行时间分布)

NFR(噪声修复比)

记录基础设施失败与实际测试失败的比例

*POT(流水线额外耗时)

分析排队时间、重试频率和缓存未命中率

核心要点总结

掌握 GitHub Actions 的结构,有助于你构建高效、可靠的 CI/CD 流水线:

工作流结构

作业默认并行执行,每个作业内的步骤顺序执行,依赖通过 needs 控制

性能优化

使用缓存加速依赖处理,使用构建产物共享数据,使用 matrix 策略实现多配置并行执行

提高稳定性

为清理类作业使用 continue-on-error,设置超时机制,合理使用条件执行

监控与衡量

追踪 CI Vitals(WET、NFR、POT),区分真实失败与基础设施噪声

理解 GitHub Actions 的执行机制和 CI Vitals 的表现,你将全面掌握工作流的运行方式和性能状况,从而打造提升开发效率的自动化流程,而不是成为团队的负担。

关于本文
译者:@飘飘
作者:@Kamil Chmielewski
原文:https://cimatic.io/blog/github-actions-explained

图片
这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/184329