创建CI/CD工作流程的最佳实践:依赖性缓存

446 阅读5分钟

让我们面对现实吧。创建最佳的CI/CD工作流程并不总是一个简单的任务。事实上,编写有效和高效的配置代码是许多开发人员在其DevOps旅程中面临的最大障碍。但你不需要成为一个专家来建立一个快速、可靠的测试和部署基础设施。通过一些简单明了的技术,你可以优化你的config.yml 文件,并释放你的CI/CD管道的全部潜力。

在这个系列中,我们将深入探讨我们的解决方案工程师在与企业级客户进行一对一的配置审查时提出的一些常见建议。今天,我们重点讨论依赖性缓存,这是一种强大的技术,可以在作业之间保存构建数据,并加快你的工作流程。

什么是依赖性缓存?

在一般的计算术语中,缓存是一个过程,在这个过程中,经常使用的数据被存储在内存中,以便在将来使用时可以快速检索到。在CI/CD管道中,你经常需要在构建阶段安装相同的依赖项--应用程序运行所需的库或包。为了避免每次运行工作流时重复下载相同的依赖项,你可以使用依赖项缓存来保存这些包,使它们在未来的工作中可用。

在工作之间缓存你的依赖关系可以从你的构建中减少几秒钟甚至几分钟。提高构建速度可以减少浪费的时间,加快测试的反馈速度,使你和你的团队能够快速有效地将变化发送给你的用户。

什么时候应该使用依赖性缓存?

缓存依赖关系是我们向希望加快工作流程的用户提出的最常见建议之一。这种技术对于依赖软件包管理器的项目特别有用,比如Node.js的npm和Yarn,Python的pip,以及Ruby的Bundler,在构建过程中安装多个二进制文件。

例如,用React和Node.js编写的全栈式网络应用程序可以迅速增加几十、几百甚至几千个依赖项和子依赖项。如果没有依赖性缓存,以下config.yml 文件将在每次运行build_and_test 工作时下载应用程序所需的每一个包。

jobs:
  build_and_test:
    docker:
      - image: cimg/node:16.11.1
    steps:
      - checkout
      # install dependencies
      - run:
          name: install dependencies
          command: npm install
      # run test suite	
      - run:
          name: test
          command: npm run test

这个配置缺乏依赖性缓存。它将CircleCI的Node.js Docker镜像设置为执行环境,并在安装必要的依赖项和运行为该项目定义的测试之前,从相关的Git仓库中检查出应用的代码。由于没有设置依赖性缓存,build_and_test 工作将在每次工作流运行时从新开始,不必要地重复安装相同的依赖性。

如何在你的管道中添加依赖性缓存

在你的CircleCI工作流中添加一个依赖性缓存,就像为你创建的每个版本的缓存设置restore_cachesave_cache 步骤以及唯一的标识符,或键一样简单。下面是你在上面看到的同样的配置,这次是用依赖性缓存进行了优化。

...
jobs:
  build_and_test:
    docker:
      - image: cimg/node:16.11.1
    steps:
      - checkout
      # look for existing cache and restore if found
      - restore_cache:
          key: v1-deps-{{ checksum "package-lock.json" }}
      # install dependencies    
      - run:
          name: install dependencies
          command: npm install
      # save any changes to the cache
      - save_cache:
          key: v1-deps-{{ checksum "package-lock.json" }}
          paths: 
            - node_modules   
      # run test suite
      - run:
          name: test
          command: npm run test

这个配置现在包括依赖性缓存。restore_cache 步骤检查现有的缓存,如果发现,将其恢复到作业中。在这种情况下,npm install 将只安装那些不在缓存中的依赖项。然后,在项目的所有依赖被加载后,save_cache 步骤将把更新的依赖树保存到node_modules 目录下的新缓存中。

请注意,restore_cachesave_cache 都包括关键标识符。密钥的{{ checksum "package-lock.json" }} 部分是一个动态值,称为模板。这个特定的模板计算项目的package-lock.json 文件内容的SHA256哈希值,并将v1-deps- 预加到结果中。如果package-lock.json (或任何你在缓存密钥中指定的依赖性管理文件)发生变化,那么restore_cache 将会丢失,新的缓存将由save_cache 创建。

关于这个例子和一般的依赖性缓存,还有一些重要的事情需要注意。

  • 缓存是不可改变的。一旦save_cache 写到一个给定的键,它就不能被覆盖了。

  • 重要的是你运行测试之前包括save_cache ,这样即使你的测试失败,你的依赖也会被保存。

  • 除了依赖性管理文件的校验值,你还可以在你的模板中使用其他动态信息,包括正在构建的VCS分支、CircleCI工作编号和构建的纪元时间。欲了解更多信息,请查阅关于使用键和模板的文档。

最后,如果只有少数依赖关系发生了变化,但缓存的其他部分是有效的,可以通过设置一个回退键来恢复缓存的一部分。

- restore_cache:
        keys:
          - v1-deps-{{ checksum "package-lock.json" }}
          - v1-deps-

在这个例子中,CircleCI将首先尝试加载一个与当前版本package-lock.json 相关的缓存。如果由于添加了一个依赖关系,锁文件发生了变化,那么将找不到缓存。接下来,CircleCI将使用静态回退键v1-deps- ,加载最近的有效缓存,前缀为v1-deps- 。一旦之前的缓存被加载,npm install 将下载任何缺失的依赖。

关于部分缓存恢复的更多信息,请查看文档

总结

依赖性缓存是优化你的CircleCI配置的最直接和有效的方法之一。通过对你的工作流程的一些小调整,你可以节省大量的构建时间,让你专注于做你最擅长的事情:快速为你的用户提供价值。

依赖性缓存只是你可以对你的配置进行的许多不同的优化中的一个。其他基于缓存的优化包括在工作空间中持久化数据,以及设置Docker层缓存以加快Docker构建的速度。在本系列的下一期中,请留意对这些功能和其他功能的深入研究。