GitLab CI/CD pipeline和DevOps之道系列文章(一)
GitLab CI/CD pipeline和DevOps之道系列文章(二)
我们继续了解上节的内容。 我们了解单元测试,集成测试,以及e2e测试这些功能测试后,在第二篇中,我们了解更多的非功能测试,比如 soak test,性能测试,负载测试,fuzz 测试等,这些测试保证我们程序能够更好,更全面的工作。
功能测试只是测试的一种形式。另一种重要的形式是安全测试。
- 功能测试只是测试的一种形式,而安全测试是另一种重要的形式。
- 安全测试通常由专门的团队负责,并与质量保证(QA)部门分离。
- 安全测试的主要方法可以归结为以下三个类别:检查源代码、与正在运行的代码交互、检查项目使用的第三方依赖项。
- 安全测试的目标是查找可能导致数据丢失或被篡改的问题。
- 安全测试可以涉及静态代码分析和密钥检测等具体类型。
- 静态代码分析通过审查源代码来发现不安全的编码实践,并提出解决方案。
- 代码审查可以识别出非标准习语、不寻常的格式或笨拙的程序结构,从而帮助预防潜在的安全问题。
- 密钥检测是一种特殊的静态代码分析,用于排除敏感数据(如密码、部署密钥等)从软件源代码中。
安全测试是一种重要的测试形式,需要专门团队负责。它包括检查源代码、与运行代码交互以及检查第三方依赖项。安全测试的目标是查找可能导致数据丢失或被篡改的问题。具体的安全测试类型包括静态代码分析和密钥检测。通过这些安全测试方法,可以及早发现并解决不安全的编码实践,从而提高软件的安全性。 就像静态代码分析扫描源代码以寻找编程或安全问题一样,密钥检测可以扫描源代码以查找应该删除并存储在更安全位置的秘密信息。例如,以下Java代码包含一个社会安全号码,任何具有代码读取权限的人都可以看到:
java复制代码
String bethSSN = "555-12-1212";
if (customerSSN.equals(bethSSN)) {
System.out.println("Welcome, Beth!");
}
动态分析查看源代码很有用,但有许多软件缺陷类别更容易通过与执行代码进行交互来发现。这种交互可以采用使用应用程序的图形用户界面(GUI),就像人类一样使用,向REST API端点发送请求,或者在请求的查询字符串中使用不同的值来访问Web应用程序的各种URL。
例如,您的Web服务器可能被配置为在每个响应的标头中包含其版本号。这可能看起来是无害的信息,但它可能为恶意行为者提供关于哪些面向您的网站的Web服务器攻击是可能成功的线索,并且您的Web服务器可能对哪些攻击是免疫的。
再举个例子,您的代码中复杂的逻辑可能掩盖了这样一个事实:通过输入特定的值,您可以触发未处理的除以零错误。正如之前讨论的,这种问题可能一开始并不被视为安全风险,但是聪明的黑客通常可以找到利用简单漏洞的方法,从而暴露数据、导致数据丢失或导致拒绝服务攻击。
例如,以下Ruby代码在运行时可能会产生一个ZeroDivisionError实例,从而导致程序崩溃:
ruby复制代码
puts 'how many hats do you have?'
num_hats = gets.to_i
puts 'how many cats do you have?'
num_cats = gets.to_i
puts "you have #{num_hats / num_cats} hats per cat"
依赖扫描是一种将您产品的每个依赖项的名称和版本号与已知漏洞的数据库进行对比的做法,以确定哪些依赖项应该升级到更新版本或完全删除,以提高软件的安全性。现今几乎所有重要的软件都依赖于数十个、数百个甚至数千个第三方开源库。最受欢迎的库的源代码通常会被黑帽子黑客仔细审查,寻找可能的漏洞。这些漏洞通常很快被库的维护者修复,但如果您的项目使用旧版本、未修补的库,依赖扫描将提醒您,您的代码可能容易受到已知漏洞的攻击。
写作时存在一个需要进行此类安全扫描的典型案例。许多Java项目依赖于一个名为Log4j的开源Java库,它提供了一种方便的记录信息、警告或错误消息的方式。最近发现了一个漏洞,使得黑客可以在运行Log4j的任何计算机上远程执行命令或安装恶意软件。这是一个巨大的问题!幸运的是,这正是依赖扫描可以发现的问题。任何更新的依赖扫描工具都将告知您,如果您的软件依赖于未修补的Log4j版本(直接或通过其他依赖项),它会建议您升级到哪个Log4j版本。
容器扫描现在非常重要,因为许多软件产品以Docker镜像的形式交付。Docker镜像可以被描述为一个Linux发行版,上面安装了您的应用程序,并打包成一种可以由Docker或类似工具执行的镜像格式。如果您构建的Docker镜像包含一个过时Linux发行版中的安全漏洞,那么您的应用程序就不够安全。
容器扫描会检查您的Docker化应用程序所使用的基本Linux镜像,并通过查询已知的安全漏洞数据库来确定您打包的应用程序是否容易受到攻击。例如,由于CentOS 6在2020年停止维护,它包含的库存在许多严重的安全漏洞。容器扫描会提醒您这个问题,并建议您考虑将应用程序的Docker镜像升级为使用CentOS 7或更高版本作为基础镜像。
手动安全测试总结了各种测试方法,用于检测安全漏洞或与安全相关的问题,例如不符合编程最佳实践的问题。虽然在将一个简单的Web应用程序投入生产之前需要经过许多不同的步骤,但现在有更多的方法可以窃取信息或关闭服务,并且没有理由认为这种趋势会很快改变。因此,负责任的开发人员需要考虑并可能实施所有这些不同的安全测试方法。 其中一些测试必须手动进行。其他测试可以使用自动化工具进行辅助。但是自动化测试仍然很繁琐:您仍然需要安装安全测试工具或框架、配置测试工具、更新测试框架和依赖项、设置和维护测试环境、处理报告,并以某种集成方式展示报告。如果您尝试通过外包给外部公司或软件即服务(SaaS)工具来简化问题,您将需要学习每个工具的单独图形用户界面,为每个服务维护不同的用户账户,管理多个许可证,并执行大量其他任务以确保测试平稳运行。
本节向您展示了在GitLab之前,开发团队在开发过程中面临的困难。正如您将在接下来的章节中了解到的那样,GitLab的CI/CD流水线用快速、自动化的安全扫描器取代了先前描述的笨拙、多步骤的安全测试流程,您只需要配置一次,然后在开发软件项目期间始终受益。我们将在后面更详细地讨论这个主题。
软件打包和部署 现在,您的软件已经构建、验证和安全,是时候考虑打包和部署了。和我们之前讨论过的其他步骤一样,如果手动进行此过程,它可能成为一个繁琐的负担。将应用程序打包成可部署状态不仅取决于其所使用的编程语言,还取决于您使用的构建管理工具。例如,如果您使用Maven工具管理Java产品,您需要运行与使用Gradle工具时完全不同的一组命令。将Ruby代码打包成Ruby gem需要另一个完全不同的过程。打包通常涉及收集数十个、数百个甚至数千个文件,使用适合该语言的工具进行捆绑,并仔细检查文档和许可文件是否完整并位于正确位置,可能还需要对打包的代码进行加密签名,以表明它来自可信任的来源。
我们已经提到过指定代码所采用的许可证的任务。这导致了在将代码部署到生产环境之前需要完成的另一种测试:许可证合规性扫描。
许可证合规性扫描 大多数开源的第三方库都是在特定的软件许可证下发布的。开发人员可以选择无数个许可证,但是大部分开源库只使用其中几个,包括MIT许可证、GNU通用公共许可证(GPL)和Apache许可证。了解您所依赖库使用的许可证非常重要,因为您不被允许使用与项目整体许可证不兼容的依赖项。
两个许可证之间会不兼容的原因是什么?一些许可证,比如Peaceful开源许可证,明确禁止军事机构使用该软件。另一个常见的许可证冲突是发生在所谓的Copyleft许可证和专有许可证之间。Copyleft许可证(如GPL)规定,任何使用由GPL许可证保护的库的软件必须自身使用GPL许可证。Copyleft许可证有时被称为病毒式许可证,因为它们将其许可限制传递给使用那些类型许可证覆盖的依赖项的任何软件。
由于法律要求您确保您的主要许可证与您所使用的任何第三方库的许可证兼容,您需要在打包和部署工作流程中添加许可证扫描步骤。无论是手动完成还是使用自动化工具,您都必须识别并替换您不被允许使用的任何依赖项。
部署软件 一旦您的软件已经打包,并且您已经仔细检查了依赖项的许可证,您将面临将代码在正确的时间部署到正确的位置的障碍。
大多数开发团队将代码部署到多个环境中。每个组织的设置可能不同,但是一个典型(尽管最小)的环境结构可能如下:
- 一个或多个测试环境。
- 一个与生产环境尽可能相似(但通常规模较小)的暂存环境或预生产环境。
- 一个生产环境。
我们稍后将更详细地讨论这些不同环境的用途。现在,您只需要了解每个环境如何作为基本部署工作流程的一部分使用。一般情况下,随着代码的开发,人们通常会将其部署到测试环境中,以便质量保证团队或发布工程师可以确保它按照预期工作,并且与现有代码无缝整合,不会引起任何问题。当新代码准备好添加到生产代码库时,一般会将其部署到暂存环境中进行最后一轮测试,以确保新代码与最终运行的环境之间没有任何不兼容性。如果这些测试顺利通过,代码最终被部署到生产环境中,让真实用户从新代码引入的任何功能、错误修复或其他改进中受益。
正如您所能想象的,确保正确的代码在正确的时间部署到正确的环境中是一项棘手但至关重要的工作。而部署只是其中的一半!另一半是确保各个环境可用并且健康。这些环境必须运行在正确类型和规模的硬件上,必须配置正确的用户帐户,必须正确配置网络和安全策略,并且必须安装正确版本的操作系统、工具和其他基础设施软件。当然,还有维护任务、升级和其他系统重新配置工作,需要进行计划、执行和修复,以防止出现故障。这些任务的范围和复杂性令人难以置信,这也是为什么大型组织有整个发布工程师团队来确保一切运行顺畅,并在出现问题时进行紧急排除的原因。
通过上述描述,我们了解了在您提交新代码后发生的最常见的软件开发生命周期(SDLC)任务:
- 构建代码。
- 使用各种测试验证代码的功能、性能、资源使用情况等。
- 使用更多的测试确保代码没有安全漏洞。
- 将代码打包为可部署格式。
- 查找并解决任何与许可证不兼容的问题。
- 将代码部署到适当的环境。
现在,您应该能够感受到一个主题:在GitLab之前的生活是复杂、容易出错且缓慢的。这些形容词确实适用于接近SDLC末尾时发生的打包、许可证扫描和发布任务。但是,正如您将在以后的章节中详细了解的那样,GitLab CI/CD流水线可以为您处理这些工作中最繁重的部分。通过让流水线处理无聊和重复的工作,您可以专注于编写软件中更具创造性和满足感的部分。
手动执行软件开发生命周期实践存在一些问题。现在,您对开发人员完成编写软件和用户可使用软件之间的过程有了整体的了解,您可以开始理解这个过程有多么困难。在将安全、可工作的代码交付给用户的过程中,需要完成许多任务:
其中一些任务通常是手动完成的,而其他任务可以部分或完全自动化。然而,这两种方法都存在与之相关的问题,这使得每个任务都成为潜在的痛点。
让我们来看看手动执行这些任务的困难之处:
- 它们需要时间。它们往往需要比您预算的时间更多,即使您过去有手动执行它们的经验。在执行任何这些任务时,事情会以各种不可预见的方式出错,所有这些都需要耗费时间来进行故障排除和修复。即使一切顺利,每个任务都需要大量工作量。另外,还要记住物理学家道格拉斯·霍夫斯塔特(Douglas Hofstadter)在1979年提出的定律:即使考虑到霍夫斯塔特定律,事情总是会比你预计的时间要长。 它们容易出错。由于这些任务依赖于人类执行,而人类可能会感到疲倦、无聊或分心,因此配置错误、数据输入错误、遗漏步骤或错误的应用顺序等问题很容易发生。这只是人为错误导致问题的几种方式之一。
它们会对员工士气造成压力。没有人喜欢做例行重复的工作,尤其是在形势严峻时,需要确保一切正确无误。当质量保证工程师意识到他们必须进行第20次标准的2小时手动测试时,对他们来说可能会开始怀疑软件开发是否是明智的职业选择。
它们具有高误传或错误报告的潜力。当手动测试人员完成令人沮丧的2小时测试套件后,他们是否还能够准确记录哪些工作已完成、哪些未完成?如果我们不能依赖准确记录的结果,那么所有这些测试就没有意义。然而,任何执行过复杂手动测试计划的人都知道,结果中可能存在许多模糊之处,可能有很多意外条件会影响测试结果,以及如何向那些依赖这些报告的人解释这些因素是多么困难。更不用说记录结果时出现错误的可能性,即使结果是显而易见的。
出于以上原因,您可以看到手动任务在时间、资金和员工合作意愿方面是多么昂贵。
但是,如果我们能够自动化所描述的一些任务,那么是否可以消除我们在手动任务中面临的问题呢?嗯,这确实会解决一些问题。但是,将一系列自动化工具添加到软件开发生命周期(SDLC)中会引入一整套新问题。考虑到额外的工作量、费用以及构建定制工具链所需的所有任务:
- 为每个可自动化的任务进行研究和选择工具
- 购买并更新每个工具的许可证
- 为每个工具选择一个主机解决方案
- 为每个工具提供用户权限
- 学习每个工具的不同图形用户界面
- 管理每个工具的数据库和其他基础设施
- 将每个工具与SDLC中的其他工具集成
- 弄清楚如何在中央位置显示每个工具的状态和结果,如果可能的话
- 处理存在错误、被弃用或者市场上出现更好替代品的工具
即使处理了手动任务或自动化任务的所有问题,对于使用这种模式的团队来说,仍然存在一个无法避免的大问题:这是一个顺序工作流程,步骤一个接一个地执行。一个团队编写软件,然后将代码转交给另一个团队构建软件。然后,该团队将代码传递给负责验证软件的第三个团队。完成之后,通常又将其发送给另一组工程师进行安全测试。最后,发布团队接手将代码部署到正确的位置。这个过程可能会以很多不同的方式发生,但每次只能执行一个步骤,只有在前面的步骤完成后才会将代码传递到下一个步骤,这是许多软件开发团队共享的工作流程特点。
到目前为止,可能还不清楚为什么顺序工作流程会带来问题,所以让我们详细地解释一下。由于手动执行这些步骤的困难,或者在多个工具中保持自动化步骤的顺畅和可靠运行时的麻烦,这个工作流程通常只会偶尔发生。代码何时经过这些步骤的频率因团队而异,但是由于涉及到的时间和费用,代码更改通常会在几天、几周甚至几个月内积累,然后才能进行正确的构建、验证、安全性测试和部署。这反过来意味着,在此过程中发现的问题修复起来非常昂贵。如果功能测试失败,安全测试发现漏洞,或者集成测试揭示出一旦代码全部部署到同一个环境中就无法正常工作,要确定导致问题的代码就像在一堆干草中找针一样困难。如果有5000行代码涉及25个类的更改,已经将16个依赖项升级到较新版本,Java版本已从16版更改为17版,并且测试环境正在运行不同版本的Ubuntu,那么在追踪问题的源头并确定如何修复它时将需要调查的变量非常多。 6/27/2023, 9:22:51 PM
到目前为止,你已经对传统的、DevOps之前的软件开发有了足够多的了解。我们可以用一句话概括它所面临的最大问题:顺序工作流程中涉及手动任务或由不同工具执行的自动化任务导致开发速度缓慢,发布频率不高,结果产生的软件质量低于可能达到的水平。
然而,好消息是:DevOps的出现旨在解决这些问题。而GitLab的CI/CD流水线的出现则是为了使DevOps更易于使用。接下来,我们将详细看看这两个方面。
首先,让我们解释一下什么是DevOps。尽管这个术语在软件界至少已经被使用了10年,但如今并没有一个所有人都同意的单一标准定义。当GitLab谈论DevOps时,它指的是一种关于SDLC的全新思维方式,重点放在以下四个方面:自动化、协作、快速反馈和迭代改进。
DevOps的主要重点是尽可能自动化软件开发的任务。这消除了手动构建、测试、保护和发布所带来的挑战。然而,如果将这些挑战换成组装一系列手动工具所带来的麻烦和费用,那么这种方式的用处就显而易见了。后面我们会看到GitLab是如何解决这个问题的,但目前只需理解一个正确的DevOps工作流程是完全自动化的。
通过促进编写软件涉及的所有团队之间以及每个团队成员之间的协作,DevOps有助于消除每次代码从一个团队转移到另一个团队时可能发生的摩擦点和潜在麻烦。如果没有“壁垒”阻隔代码的传递,即流程中的每一步对所有与编写和交付软件相关的人员都是透明的,那么每个人都会对代码的整体质量感到投入,并感觉自己在同一个团队中。不同的人仍然对特定任务有主要责任,但整体文化朝着共同拥有代码和共享目标的方向发展。
快速反馈可能是DevOps最关键和革命性的要素之一。它可以被视为我们之前讨论过的两个概念的结果:并发工作流和左移。当你停下来思考时,这两个术语归结为同一件事:对开发人员检查的每批代码尽早进行构建、验证和保护任务。而不是顺序执行,同时执行所有这些任务以确保它们发生在软件开发时间线的最左侧。对于每个贡献的代码块,立即运行所有这些任务,无论其大小如何。通过及早且频繁地运行这些任务,可以将被测试的代码更小,从而更便宜更容易地排除任何软件错误、配置问题或安全漏洞。
如果能快速找到和解决问题,您将能够更频繁地向客户发布软件。通过更早地为他们提供新功能和错误修复,您帮助他们从产品的迭代改进中受益。通过以较短的间隔发布较小的代码更改,降低了破坏性和需要回滚的风险,实现了“无聊发布”的口号。在这种情况下,“无聊”是一件好事:大多数客户宁愿接受频繁的、小规模的升级,而不是偶尔的、大规模的变更,后者更容易造成混乱并需要回滚。
通过利用自动化、协作、快速反馈和迭代改进,DevOps实践可以生成更高质量、更便宜开发并更频繁交付给用户的代码。
而GitLab则是一个工具,使用我们刚刚概述的DevOps原则,实现了我们讨论过的所有软件开发任务。GitLab最重要的特点是它是一个统一了SDLC中所有步骤的单一工具。
你还记得从手动过程到自动化过程的转变解决了一些问题,但也带来了与自动化相关的一系列新问题吗?GitLab的单一工具方法也解决了这些问题。考虑以下单一统一的工具链方法的好处:
只需购买一个许可证(除非您的团队使用免费的功能限制版本的GitLab,在这种情况下无需购买许可证)
只需维护和升级一个应用
只需配置一组用户账号
只需管理一个数据库
只需学习一个图形界面
只需查看一个地方——一个dashbaord,可以看到所有构建、验证、安全保护、打包和部署步骤的报告和状态。
因此,GitLab作为一个单一工具解决了使用不同自动化工具带来的问题。更重要的是,它使用一套组件和实体,它们相互之间意识到彼此并进行良好的沟通,从而实现并促进了协作、并发、透明度和共享所有权,这些都是DevOps的关键方面。一旦有了并发任务,就能够获得快速反馈。而这反过来又通过无缝的发布实现了迭代改进。
本书剩下的大部分内容都在讨论GitLab用来实践这些DevOps原则的技术:CI/CD流水线。尽管我们暂时不定义这个术语的含义,但你将在后续章节中了解到它的全部内容。现在,你只需要知道CI/CD流水线是GitLab将构建、验证、安全保护、打包和部署这些任务集中在一起进行的地方。
我们也不得不提到GitLab中的很大一部分致力于帮助您规划、分配和管理工作。但这与CI/CD流水线是分开的,因此超出了本书的范围。尽管如此,我们会偶尔涉及一些相关话题,仅仅因为GitLab中的所有内容都与CI/CD流水线密切相关,以至于无法完全局限在CI/CD流水线的边界之内。而本书的大部分内容将解释GitLab流水线可以实现什么,以及如何使用它们。
总结一下,那些不在软件公司工作的人可能没有意识到编写软件除了编写代码之外还有更多的工作。在提交代码之后,必须遵循一系列复杂的步骤来构建、验证、保护、打包和部署代码,然后用户才能使用它。所有这些步骤可以通过手动完成,或者在某些条件下可以自动化完成。然而,手动和自动化两种方法都存在问题。
DevOps是一种相对较新的方法,用于完成这些步骤。它将自动化、协作、快速反馈和迭代改进结合起来,使团队能够以更好、更快和更便宜的方式制作软件。
GitLab是一个DevOps工具,它将所有这些任务集中在一个工具下,允许软件开发团队使用单一的图形界面完成所有工作,并在一个地方显示所有的测试结果和部署状态。它通过关注自动化解决了手动过程带来的问题,通过单一工具模型解决了自动化过程带来的问题。GitLab通过使用CI/CD流水线将所有的DevOps原则付诸实践,这也是本书剩下部分的主要内容。
但在处理CI/CD流水线之前,我们需要快速地介绍一下Git,这是GitLab构建在其之上的工具。如果对Git的基础知识没有扎实的基础,你可能会发现GitLab的概念和术语很难理解。所以,请准备好,拿起一杯你最喜欢的咖啡因饮料,让我们开始学习Git吧。