paint-brush
关于持续集成、GitHub Actions 和 Sonar Cloud,您需要知道的一切经过@shai.almog
2,932 讀數
2,932 讀數

关于持续集成、GitHub Actions 和 Sonar Cloud,您需要知道的一切

经过 Shai Almog18m2023/03/24
Read on Terminal Reader

太長; 讀書

如果做得不好,CI 过程可能会将这个神奇的工具变成一场噩梦。 CI 应该让我们的生活更轻松,而不是相反。
featured image - 关于持续集成、GitHub Actions 和 Sonar Cloud,您需要知道的一切
Shai Almog HackerNoon profile picture

当 Mozilla 项目启动时,我第一次遇到了持续集成 (CI) 的概念。它包括一个基本的构建服务器作为过程的一部分,这在当时是革命性的。我正在维护一个 C++ 项目,该项目花费了 2 个小时来构建和链接。


我们很少经历一个干净的构建,这会产生复杂的问题,因为错误的代码被提交到项目中。


自从那些日子以来,发生了很多变化。 CI 产品无处不在,作为 Java 开发人员,我们享受着前所未有的丰富功能。但我有点言过其实了……让我们从基础开始吧。

持续集成是一种软件开发实践,其中以频繁且一致的方式自动构建和测试代码更改。


CI 的目标是尽快发现并解决集成问题,降低错误和其他问题进入生产的风险。


CI 通常与持续交付 (CD) 并驾齐驱,持续交付旨在自动化整个软件交付过程,从代码集成到生产部署。


CD 的目标是减少部署新版本和修补程序所需的时间和精力,使团队能够更快、更频繁地向客户交付价值。


使用 CD,通过 CI 测试的每个代码更改都被认为可以部署,使团队可以随时自信地部署新版本。我不会在这篇文章中讨论持续交付,但我会回去讨论它,因为有很多东西要讨论。


我非常喜欢这个概念,但有些事情我们需要监控。

持续集成工具

有很多强大的持续集成工具。下面是一些常用的工具:


  • Jenkins :Jenkins 是最流行的 CI 工具之一,提供范围广泛的插件和集成以支持各种编程语言和构建工具。它是开源的,并提供了一个用户友好的界面来设置和管理构建管道。


    它是用 Java 编写的,经常是我的“首选工具”。但是,管理和设置起来很麻烦。有一些“Jenkins as a service”解决方案也可以改善其用户体验,这在某种程度上是缺乏的。


  • Travis CI :Travis CI 是一个基于云的 CI 工具,与 GitHub 集成良好,是基于 GitHub 项目的绝佳选择。由于它早于 GitHub Actions,因此成为 GitHub 上许多开源项目的默认设置。


  • CircleCI :CircleCI 是一个基于云的 CI 工具,支持广泛的编程语言和构建工具。它提供了一个用户友好的界面,但它的最大卖点是构建和交付的速度。


  • GitLab CI/CD :GitLab 是一种流行的源代码管理工具,包括内置的 CI/CD 功能。 GitLab 解决方案灵活而简单;它甚至在 GitLab 领域之外也获得了一些行业牵引力。


  • Bitbucket Pipelines :Bitbucket Pipelines 是来自 Atlassian 的基于云的 CI 工具,它与他们的源代码管理工具 Bitbucket 无缝集成。由于它是 Atlassian 产品,因此它提供无缝的 JIRA 集成和非常流畅的面向企业的功能。


请注意,我没有提到我们很快就会谈到的 GitHub Actions。比较 CI 工具时需要考虑几个因素:


  • 易用性:一些 CI 工具具有简单的设置过程和用户友好的界面,使开发人员更容易上手和管理他们的构建管道。


  • 与 GitHub、GitLab 和 Bitbucket 等源代码管理 (SCM) 工具集成。这使团队更容易自动化他们的构建、测试和部署过程。


  • 支持不同的编程语言和构建工具:不同的 CI 工具支持不同的编程语言和构建工具,因此选择与您的开发堆栈兼容的工具很重要。


  • 可扩展性:一些 CI 工具更适合具有复杂构建管道的大型组织,而其他工具则更适合需求更简单的小型团队。


  • 成本:CI 工具的成本范围从免费和开源到可能很昂贵的商业工具,因此选择适合您预算的工具很重要。


  • 特性:不同的 CI 工具提供不同的特性,例如实时构建和测试结果、支持并行构建和内置部署功能。


总的来说,Jenkins 以其多功能性和广泛的插件库而闻名,使其成为具有复杂构建管道的团队的热门选择。 Travis CI 和 CircleCI 以易于使用和与流行的 SCM 工具集成而著称,是中小型团队的不错选择。


GitLab CI/CD 是使用 GitLab 进行源代码管理的团队的热门选择,因为它提供了集成的 CI/CD 功能。 Bitbucket Pipelines 是使用 Bitbucket 进行源代码管理的团队的不错选择,因为它与平台无缝集成。

云与内部部署

托管代理是选择 CI 解决方案时要考虑的重要因素。代理托管有两个主要选项:基于云的和内部部署。


  • 基于云:基于云的 CI 解决方案,例如 Travis CI、CircleCI、GitHub Actions 和 Bitbucket Pipelines,将代理托管在云中自己的服务器上。这意味着您不必担心管理底层基础架构,并且可以利用云的可扩展性和可靠性。


  • 本地:本地 CI 解决方案(例如 Jenkins)允许您在自己的服务器上托管代理。这使您可以更好地控制底层基础设施,但也需要付出更多努力来管理和维护服务器。


选择 CI 解决方案时,重要的是要考虑您团队的具体需求和要求。


例如,如果您有一个庞大而复杂的构建管道,那么像 Jenkins 这样的本地解决方案可能是更好的选择,因为它可以让您更好地控制底层基础设施。


另一方面,如果你有一个需求简单的小团队,Travis CI 等基于云的解决方案可能是更好的选择,因为它易于设置和管理。

代理状态

有状态决定了代理是否在构建之间保留它们的数据和配置。


  • 有状态代理:一些 CI 解决方案,例如 Jenkins,允许有状态代理,这意味着代理在构建之间保留它们的数据和配置。这对于需要在构建之间保留数据的情况非常有用,例如当您使用数据库或运行长时间运行的测试时。


  • 无状态代理:其他 CI 解决方案,例如 Travis CI,使用无状态代理,这意味着每次构建都会从头开始重新创建代理。这为每个构建提供了一个干净的平台,但这也意味着您需要在外部管理任何持久的数据和配置,例如在数据库或云存储中。


CI 的支持者就最佳方法展开了激烈的辩论。无状态代理提供干净且易于重现的环境。我在大多数情况下都选择它们,并且认为它们是更好的方法。


无状态代理也可能更昂贵,因为它们的设置速度较慢。由于我们为云资源付费,因此该成本可能会增加。但一些开发人员更喜欢有状态代理的主要原因是调查能力。


对于无状态代理,当 CI 进程失败时,除了日志之外,您通常别无他法。


使用有状态代理,我们可以登录机器并尝试在给定机器上手动运行该过程。我们可能会重现一个失败的问题并因此获得洞察力。


与我合作的一家公司选择了 Azure 而不是 GitHub Actions,因为 Azure 允许有状态的代理。在调试失败的 CI 过程时,这对他们很重要。


我不同意这一点,但这是个人意见。我觉得我花在故障排除错误代理清理上的时间比我从调查错误中获益的时间还多。但这是个人经历,我的一些聪明朋友不同意。

可重复构建

可重复构建是指每次执行构建时都能生成完全相同的软件工件的能力,无论环境或执行构建的时间如何。


从 DevOps 的角度来看,拥有可重复的构建对于确保软件部署的一致性和可靠性至关重要。


间歇性故障无处不在是 DevOps 的祸根,跟踪起来也很痛苦。


不幸的是,没有简单的解决办法。正如我们所希望的那样,一些脆弱性会进入具有合理复杂性的项目中。我们的工作是尽可能减少这种情况。可重复构建有两个障碍:


  • 依赖项——如果我们不使用特定版本的依赖项,即使是很小的更改也会破坏我们的构建。


  • 片状测试- 没有明显原因偶尔失败的测试绝对是最糟糕的。


在定义依赖项时,我们需要关注特定版本。有许多版本控制方案,但在过去十年中,标准的三数字语义版本控制接管了整个行业。


这个方案对于 CI 非常重要,因为它的使用会显着影响构建的可重复性,例如使用 maven 我们可以这样做:

 <dependency> <groupId>group</groupId> <artifactId>artifact</artifactId> <version>2.3.1</version> </dependency>


这是非常具体的,并且非常适合可重复性。但是,这可能很快就会过时。我们可以用LATESTRELEASE替换版本号,这将自动获取当前版本。这很糟糕,因为构建将不再可重复。


然而,硬编码的三数方法也存在问题。通常情况下,补丁版本代表对错误的安全修复。在这种情况下,我们希望一直更新到最新的次要更新,而不是更新的版本。


例如,对于之前的情况,我想隐式使用版本2.3.2而不是2.4.1 。这为次要的安全更新和错误牺牲了一些可重复性。


但更好的方法是使用Maven Versions Plugin并定期调用mvn versions:use-latest-releases命令。这会将版本更新到最新版本,以使我们的项目保持最新。


这是可重复构建的直接部分。困难在于片状测试。这是一种常见的痛苦,以至于一些项目定义了“合理数量”的失败测试,而一些项目在确认失败之前多次重新运行构建。


测试不稳定的一个主要原因是状态泄漏。测试可能会因为先前测试遗留下来的细微副作用而失败。理想情况下,测试应该自行清理,以便每个测试都可以独立运行。


在一个完美的世界中,我们会在一个完全隔离的新鲜环境中运行每个测试,但这不切实际。这意味着测试运行时间太长,我们需要等待 CI 过程的大量时间。


我们可以编写具有各种隔离级别的测试;有时我们需要完全隔离,可能需要启动一个容器进行测试。但大多数时候,我们不会这样做,而且速度差异很大。


测试后清理非常具有挑战性。有时,来自外部工具(例如数据库)的状态泄漏会导致不稳定的测试失败。为了确保失败的可重复性,通常的做法是对测试用例进行一致的排序;这确保构建的未来运行将以相同的顺序执行。


这是一个热议的话题。一些工程师认为,这会鼓励错误测试并隐藏我们只能通过随机测试顺序才能发现的问题。根据我的经验,这确实在测试中发现了错误,但在代码中没有。


我的目标不是构建完美的测试,因此我更喜欢以一致的顺序(例如字母顺序)运行测试。


保持测试失败的统计数据很重要,永远不要简单地按下重试。通过跟踪有问题的测试和失败的执行顺序,我们通常可以找到问题的根源。


大多数时候,失败的根本原因是之前测试中的错误清理,这就是顺序很重要及其一致性也很重要的原因。

开发人员体验和 CI 性能

我们在这里开发软件产品,而不是 CI 工具。 CI 工具是为了让这个过程更好。不幸的是,很多时候使用 CI 工具的体验非常令人沮丧,以至于我们最终花在物流上的时间比实际编写代码的时间还多。


通常,我会花几天时间尝试通过 CI 检查,以便合并我的更改。每次我接近时,另一个开发人员都会先合并他们的更改并破坏我的构建。


这导致开发人员体验不那么出色,尤其是随着团队规模的扩大,我们在 CI 队列中花费的时间多于合并我们的更改。我们可以做很多事情来缓解这些问题:


  • 减少测试中的重复——过度测试是我们可以使用覆盖工具检测到的常见症状。


  • 片状测试消除——我知道删除或禁用测试是有问题的。不要轻易那样做。但是,如果您花在调试测试上的时间比调试代码的时间还多,那么它的价值就值得商榷了。


  • 为 CI 过程分配更多或更快的机器。


  • 并行化 CI 过程。我们可以并行化一些构建类型和一些测试。



最终,这直接关系到开发人员的生产力。但是我们没有用于这些优化的分析器。我们必须每次都测量;这可能很辛苦。

GitHub 操作

GitHub Actions 是内置于 GitHub 中的持续集成/持续交付 (CI/CD) 平台。它是无状态的,尽管它在某种程度上允许代理的自托管。我之所以关注它,是因为它对开源项目是免费的,并且对闭源项目有相当大的免费配额。


该产品在该领域是一个相对较新的竞争者,它不像之前提到的大多数其他 CI 工具那样灵活。但是,由于它与 GitHub 和无状态代理的深度集成,对于开发人员来说非常方便。


为了测试 GitHub Actions,我们需要一个新项目,在本例中,我使用JHipster生成了这里的配置:


我在这里创建了一个单独的项目来演示 GitHub Actions 的使用。请注意,您可以在任何项目中遵循这一点;虽然我们在这种情况下包含了 maven 指令,但概念非常简单。


创建项目后,我们可以在 GitHub 上打开项目页面并移至操作选项卡。


我们会看到这样的东西:


在右下角,我们可以看到带有 Maven 项目类型的 Java。一旦我们选择了这种类型,我们就开始创建一个maven.yml文件,如下所示:


不幸的是,GitHub 建议的默认 maven.yml 包含一个问题。这是我们在此图像中看到的代码:

 name: Java CI with Maven on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B package --file pom.xml # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - name: Update dependency graph uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6


最后三行更新依赖图。但是这个功能失败了,或者至少对我来说失败了。删除它们解决了问题。其余代码是标准的 YAML 配置。


代码顶部附近的pull_requestpush声明构建将在拉取请求和推送到 master 时运行。这意味着我们可以在提交之前对拉取请求运行我们的测试。如果测试失败,我们将不会提交。


我们可以在项目设置中禁止提交失败的测试。一旦我们提交了 YAML 文件,我们就可以创建一个拉取请求,系统将为我们运行构建过程。这包括运行测试,因为 maven 中的“package”目标默认运行测试。


调用测试的代码在结尾附近以“run”开头的行中。这实际上是一个标准的 Unix 命令行。有时,创建一个 shell 脚本并从 CI 进程运行它是有意义的。


有时编写一个好的 shell 脚本比处理各种 CI 堆栈的所有 YAML 文件和配置设置更容易。


如果我们以后选择切换 CI 工具,它也更便携。在这里,我们不需要它,因为 maven 足以满足我们当前的需求。


我们可以在这里看到成功的拉取请求


为了测试这一点,我们可以通过将“/api”端点更改“/myapi”来向代码添加错误。这会产生如下所示的故障。它还会触发发送给提交作者的错误电子邮件。


当出现此类故障时,我们可以点击右侧的“详情”链接。这会将我们直接带到您在此处看到的错误消息:


不幸的是,这通常是一条无用的消息,不会为解决问题提供帮助。然而,向上滚动会显示实际的故障,通常可以方便地为我们突出显示,如下所示:


请注意,通常有多个失败,因此进一步向上滚动是明智的。在此错误中,我们可以看到失败是 AccountResourceIT 的第394行中的断言,您可以在此处看到,请注意行号不匹配。在本例中,第394行是该方法的最后一行:

 @Test @Transactional void testActivateAccount() throws Exception { final String activationKey = "some activation key"; User user = new User(); user.setLogin("activate-account"); user.setEmail("[email protected]"); user.setPassword(RandomStringUtils.randomAlphanumeric(60)); user.setActivated(false); user.setActivationKey(activationKey); userRepository.saveAndFlush(user); restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); user = userRepository.findOneByLogin(user.getLogin()).orElse(null); assertThat(user.isActivated()).isTrue(); }


这意味着断言调用失败。 isActivated()返回false并且未通过测试。这应该有助于开发人员缩小问题范围并了解根本原因。

超越

正如我们之前提到的,CI 是关于开发人员生产力的。我们可以走得更远,而不仅仅是编译和测试。我们可以执行编码标准、检查代码、检测安全漏洞等等。


在这个例子中,让我们集成 Sonar Cloud 这是一个强大的代码分析工具(linter)。它可以发现项目中的潜在错误并帮助您提高代码质量。


SonarCloud 是基于云的 SonarQube 版本,它允许开发人员持续检查和分析他们的代码,以查找和修复与代码质量、安全性和可维护性相关的问题。它支持多种编程语言,例如 Java、C#、JavaScript、Python 等。


SonarCloud 集成了流行的开发工具,例如 GitHub、GitLab、Bitbucket、Azure DevOps 等。开发者可以使用 SonarCloud 获得代码质量的实时反馈,提升整体代码质量。


另一方面,SonarQube 是一个为软件开发人员提供静态代码分析工具的开源平台。它提供了一个显示代码质量摘要的仪表板,并帮助开发人员识别和修复与代码质量、安全性和可维护性相关的问题。


SonarCloud 和 SonarQube 都提供类似的功能,但 SonarCloud 是一种基于云的服务,需要订阅,而 SonarQube 是一个开源平台,可以安装在本地或云服务器上。


为了简单起见,我们将使用 SonarCloud,但 SonarQube 应该可以正常工作。首先,我们去sonarcloud.io并注册。最好使用我们的 GitHub 帐户。然后我们会看到一个选项,可以添加一个存储库以供 Sonar Cloud 监控,如下所示:


当我们选择 Analyze new page 选项时,我们需要授权访问我们的 GitHub 存储库。下一步是选择我们希望添加到 Sonar Cloud 的项目,如下所示:


选择并进入设置过程后,我们需要选择分析方法。由于我们使用 GitHub Actions,我们需要在下一阶段选择该选项,如下所示:


设置完成后,我们进入 Sonar Cloud 向导中的最后阶段,如下图所示。我们收到一个可以复制的令牌(图像中的条目 2 是模糊的),我们将很快使用它。


请注意,一旦您单击标有“Maven”的按钮,就会出现与 Maven 一起使用的默认说明。


回到 GitHub 中的项目,我们可以移动到项目设置选项卡(不要与顶部菜单中的帐户设置混淆)。在这里,我们选择“秘密和变量”,如下所示:


在本节中,我们可以添加一个新的存储库秘密,特别是我们从 SonarCloud 复制的 SONAR_TOKEN 键和值,您可以在此处看到:


GitHub Repository Secrets 是一项功能,允许开发人员安全地存储与 GitHub 存储库相关的敏感信息,例如 API 密钥、令牌和密码,这些信息是验证和授权访问存储库使用的各种第三方服务或平台所必需的.


GitHub Repository Secrets 背后的概念是提供一种安全便捷的方式来管理和共享机密信息,而无需在代码或配置文件中公开信息。


通过使用秘密,开发人员可以将敏感信息与代码库分开,并防止其在安全漏洞或未经授权访问的情况下被暴露或破坏。


GitHub Repository Secrets 被安全地存储,并且只能由已被授予存储库访问权限的授权用户访问。机密可用于与存储库相关联的工作流、操作和其他脚本。


它们可以作为环境变量传递给代码,以便它可以以安全、可靠的方式访问和使用机密。


总体而言,GitHub Repository Secrets 为开发人员提供了一种简单有效的方法来管理和保护与存储库相关的机密信息,有助于确保项目及其处理的数据的安全性和完整性。


我们现在需要将其集成到项目中。首先,我们需要将这两行添加到 pom.xml 文件中。请注意,您需要更新组织名称以匹配您自己的名称。这些应该进入 XML 中的部分:


 <sonar.organization>shai-almog</sonar.organization> <sonar.host.url>https://sonarcloud.io</sonar.host.url>


请注意,我们创建的 JHipster 项目已经具有 SonarQube 支持,在此代码运行之前,应将其从 pom 文件中删除


在此之后,我们可以将maven.yml文件的“Build with Maven”部分替换为以下版本:

 - name: Build with Maven env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=shai-almog_HelloJHipster package


一旦我们这样做,SonarCloud 将为每个合并到系统中的拉取请求提供报告,如下所示:


我们可以看到一份报告,其中包含错误、漏洞、气味和安全问题的列表。单击这些问题中的每一个都会导致我们出现以下情况:


请注意,我们有一些选项卡可以准确解释问题的原因、解决方法等。这是一个非常强大的工具,可以作为团队中最有价值的代码审阅者之一。


我们之前看到的另外两个有趣的元素是覆盖率和重复报告。 SonarCloud 预计测试将有 80% 的代码覆盖率(在拉取请求中触发 80% 的代码),这个很高并且可以在设置中配置。


它还指出重复代码,这可能表明违反了不要重复自己 (DRY) 原则。

最后

CI 是一个庞大的主题,有很多机会可以改善项目的流程。我们可以自动检测错误。简化工件生成、自动交付等等。但以我的拙见,CI 背后的核心原则是开发人员体验。


它是为了让我们的生活更轻松。


如果做得不好,CI 过程可能会将这个神奇的工具变成一场噩梦。通过测试成为徒劳的练习。我们一次又一次地重试,直到我们最终可以合并。由于缓慢而拥挤的队列,我们等待数小时才能合并。


这个应该有帮助的工具变成了我们的克星。这不应该是这样的。 CI 应该让我们的生活更轻松,而不是相反。