CircleCI的客户工程团队帮助用户优化其配置文件的设置方式。每天,他们可以为您的项目找到最有用的功能,平衡您的时间和信用消耗。在为我们20多个企业级客户进行了数十次的配置审查后,我们的客户工程团队已经把这一切变成了一门科学。因为你和你的团队可能并不总是有时间与专家合作,我们已经把配置优化指南放在一起,供你自己使用。这个教程包含了CircleCI的客户工程师的最佳技巧和最有价值的建议。当你需要一个专家时,我们就在这里,但我们也想让像你这样的团队更容易优化你自己的配置。
从配置灾难到配置构建更快
在这篇文章中,我们将介绍六种最有效的方法来优化你的配置文件,以便你可以更快地构建。你将学习选择正确的执行器、并行化作业、缓存、使用工作区、秘密管理以及在配置中使用轨道的最佳做法。
选择正确的执行器
许多CI管道将受益于我们的闪电式Docker便利镜像舰队之一。使用docker yaml key在Docker容器中运行,将以最快的速度提供基本的东西。
我们将这些发布到Docker Hubcimg profile。如果你的应用程序需要其他工具,考虑运行一个自定义的Docker镜像。这里有一个例子,使用一个较老的、体积较大的镜像,钉在某个版本的Node上。这个执行器是通过指定一个Docker镜像在每个作业下定义的,比如在这个test 作业。
test:
docker:
- image: circleci/node:9.9.
有了下一代的CircleCINode镜像,你可以舍弃层级,获得更快的构建。更新到下一代执行器就像更新镜像名称一样简单。
目前的配置是在Node 9.9.0中构建和测试的,但我们希望它能使用最新版本的Node来构建。要做到这一点,我们用我们的下一代图像替换执行容器使用的图像,像这样。
docker:
- image: cimg/node:latest
如果你对跨多个环境的测试感兴趣,我们也有能力通过Node orb来设置矩阵作业。这允许你在基本的Node Docker层之上指定不同版本的Node进行测试。
平行性的最佳实践
将你的作业配置为在多个容器中并行运行,可以加快你的构建速度。例如,如果你有一个长期运行的测试套件,其中有数百个独立的测试,可以考虑将这些测试分散到执行器中同时运行。一个真正优化的配置意味着明智地使用并行性。你应该仔细考虑你有多少个正在运行的并行执行器,以及分割任务所节省的时间是否值得多个容器的旋转时间。还要确保你在这些执行器上正确地分割你的测试。
考虑下一个例子。这个测试工作的主要活动是用npm run test 命令运行测试。
test:
…
parallelism: 10
steps:
...
- run: CI=true npm run test
虽然使用并行性是一个正确的方向,但这并不是最佳的写法。这个命令将简单地在所有10个容器上运行相同的测试。为了在多个容器之间分配测试,这个配置需要使用CircleCI CLI的circleci tests split 命令。当按文件名、类名或计时数据分割时,测试可以在这些容器中自动分配。按时间数据分割是理想的并行化,因为它将测试均匀地分散到各容器中运行,所以没有运行较快的容器空闲地等待一个运行较长的测试容器。
最后,考虑这是否是这个测试套件的正确的并行化水平。如果启动环境需要30秒,但测试在每个容器中只需要30秒,那么可能值得考虑降低并行性,以便在所有作业运行中花费更少的时间进行设置。测试运行时间和启动时间没有黄金比例,但应该考虑到最佳构建。下面是一个优化的配置,当优化到按文件名和时间分割测试,并在一个给定的容器中运行更多的测试时,是什么样子。
test:
…
parallelism: 5
steps:
...
- run: |
TESTFILES=$(circleci tests glob "test/**/*.test.js" | circleci tests split --split-by=timings)
CI=true npm run test $TESTFILES
缓存的最佳实践
用缓存来加快构建速度。这允许你从耗时的获取操作中重新使用数据。下面的例子使用缓存来恢复以前工作运行中的npm依赖关系。因为npm依赖性被缓存了,npm install 步骤将只需要下载package.json 文件中描述的新依赖性。这种依赖性缓存通常与npm、Yarn、Bundler或pip等包依赖性管理器一起使用,它依赖于两个特殊的步骤,指定为restore_cache 和save_cache 。这个例子显示了在test 工作中是如何使用这些缓存步骤的。
test:
...
steps:
…
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: v1-deps-{{ checksum "package-lock.json" }}
paths:
- node_modules
请注意,restore_cache 和save_cache 步骤都使用了键。key是一个唯一的标识符,用于定位你的缓存。save_cache 步骤指定了在这个键下要缓存哪些目录。在这种情况下,我们要保存node_modules 目录,以便这些Node依赖可以在以后的工作中使用。restore_cache 步骤使用这个键来寻找要恢复到作业中的缓存。该键是一个字符串,包含识别缓存的版本和写为checksum “package-lock.json” 的依赖清单文件的插值哈希。
虽然这是恢复和保存缓存的标准模式,但你可以使用回避键进一步优化它。回避键允许你识别一组可能的缓存,以增加缓存命中的可能性。例如,如果一个单一的包被添加到这个应用程序的package.json ,由checksum生成的字符串将改变,整个缓存将被错过。然而,添加一个具有更广泛的可能的密钥匹配的回退密钥可以识别其他可用的缓存。下面是一个例子,说明在添加了回退键之后,这个缓存的恢复会是什么样子。
test:
...
steps:
…
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- v1-deps-
请注意,我们只是在键的列表中增加了一个元素。让我们回到这样的场景:在我们的package.json ,一个单一的包改变了。在这种情况下,第一个键会导致一个缓存丢失。然而,第二个键允许恢复以前保存在旧的package.json 文件中的缓存。依赖性安装步骤,npm install ,将只需要获取改变的软件包,而不是对所有的软件包使用不必要的和昂贵的获取操作。请访问我们的文档,了解更多关于回退键和部分缓存恢复的信息。
选择性地持久化到工作区
下游作业可能需要访问在前一个作业中生成的数据。工作空间允许你在工作流的整个生命周期内存储文件。下一个例子的配置说明了这个概念。build 工作建立了一个Node应用程序。工作流中的下一个作业将部署该应用程序。这个配置将整个工作目录持久化到build 中的工作空间,然后附加到deploy 中的目录,因此部署可以访问已构建的应用程序。
build:
...
steps:
...
- run: npm run build
- persist_to_workspace:
root: .
paths:
- '*'
deploy:
...
steps:
....
- attach_workspace:
at: .
build 工作中创建的应用程序目录可以被deploy 工作访问。这很有效,但并不理想。工作区本质上只是创建tarballs并将其存储在blob存储中,而附加工作区需要下载和解压这些tarballs。这可能是一个耗时的过程。选择性地保存以后工作需要的文件会更快。这个例子中的npm run build 步骤产生了一个build 目录,该目录可以被压缩,然后存储在工作区进行部署。下面是这个配置的一个优化版本。
build:
...
steps:
...
- run: npm run build
- run: mkdir tmp && zip -r tmp/build.zip build
- persist_to_workspace:
root: .
paths:
- 'tmp'
deploy:
...
steps:
....
- attach_workspace:
at: .
带有构建工件的tmp 目录现在将被加载到项目的工作目录。这个配置不是上传和下载整个工作目录,而是有选择地存储压缩的、构建的应用程序,以节省归档、上传和下载工作区的时间。压缩的文件可以存储在工作区的一个临时目录中。任何与工作区相连的下游作业现在都可以访问这个压缩文件。你可以在这个深入了解CircleCI工作区的过程中了解更多。
秘密管理的最佳实践
你不希望将你的秘密检查到版本控制中,而且秘密不应该以纯文本形式写入你的配置中。CircleCI为您提供了对上下文的访问。这些允许你在你的组织中的项目中保护和共享环境变量。上下文本质上是一个秘密商店,你可以将环境变量设置为名/值对,在运行时注入。为了更好地理解这一点,请看下一个代码例子,一个不安全的配置。这个配置包括一个deploy 工作,这个工作是用纯文本写的AWS秘密定义的。
deploy:
…
steps:
...
- run:
name: Configure AWS Access Key ID
command: |
aws configure set aws_access_key_id K4GMW195WJKGCWVLGPZG --profile default
- run:
name: Configure AWS Secret Access Key
command: |
aws configure set aws_secret_access_key ka1rt3Rff8beXPTEmvVF4j4DZX3gbi6Y521W1oAt --profile default
注意:这些是假的凭证,仅用于演示目的。
这段文字对所有能访问你在CircleCI上的项目的开发者来说是可见的。相反,这些秘密应该作为环境变量存储在一个上下文中。将秘密密钥和访问ID添加到名为aws_secrets 的上下文中,作为键/值对,可以作为环境变量访问。然后,这个上下文可以被应用到工作流程中的工作。这个配置的安全版本看起来像这样。
deploy:
…
steps:
...
- run:
name: Configure AWS Access Key ID
command: |
aws configure set aws_access_key_id ${AWS_ACCESS_KEY_ID} --profile default
- run:
name: Configure AWS Secret Access Key
command: |
aws configure set aws_secret_access_key ${AWS_SECRET_ACCESS_KEY} --profile default
workflows:
test-build-deploy:
...
- deploy:
context: aws_secrets
requires:
- build
请注意,秘密已经从纯文本变成了环境变量,并且上下文被应用到工作流程中的工作。为了增加安全性,我们采用了秘密屏蔽,以防止用户意外地打印出秘密的值。
球体和可重用的配置元素
所以你已经为你的构建选择了正确的执行器,你正在适当地分割你的测试,并且你正在持久化到工作区以避免重复工作。现在你必须为你所有的其他项目做这些。多么痛苦啊,我说的对吗?如果有一种方法可以在多个构建之间重复使用你的配置文件的共享元素就好了。好了,好消息!Circle CI提供了一个被称为 "或 "的功能。
Circle CI提供了一个被称为orbs的功能,允许你在一个中心位置定义配置元素,并在多个项目中快速而轻松地重用它们。不仅如此,你还可以将参数传入轨道。你可以制作一个球体,根据你传递给它的参数,在不同的项目中做多种不同的事情。
使用我们的2.1配置版本,你还可以定义可重复使用的配置元素,以便在同一管道的多个作业中重复使用,从简单的作业步骤到重复使用整个执行器。你还可以在这些可重用的元素中传递参数。当你需要在管道的多个不同部分重复使用一个配置文件的多个元素时,这很有用。
orbs在实践中是什么样子的?好吧,这里有一个部署到S3桶的例子,完全是在配置文件中写的,没有使用我们的AWS S3部署兽皮。
- deploy:
name: S3 Sync
command: |+
aws s3 sync \
build s3://my-s3-bucket-name/my-application --delete \
--acl public-read \
--cache-control "max-age=86400"
这就可以完成工作了。但这里是使用S3 orb后的情况。
- aws-s3/sync:
from: bucket
to: ‘s3://my-s3-bucket-name/my-application’
arguments: |
--acl public-read \
--cache-control "max-age=86400"
你不需要为你的S3部署声明一个单独的部署阶段。你可以简单地从轨道上调用S3同步,作为你配置文件中的一个步骤。请注意,许多相同的信息仍然包括在内,但它现在被表示为参数,被传递到轨道,而不是在配置文件中的脚本。这不仅更紧凑,而且也使你很容易根据需要增加、删除或改变参数来改变你的S3部署。这个版本更容易掌握,一目了然,而且你可以通过更新兽皮来扩展到多个项目。 从这里提到的所有提示中,最重要的收获是,D.R.Y(不要重复自己)不仅仅是一个审美的东西。对于球体,跨项目复制的能力是黄金。祝你的球体优化工作顺利!
配置回顾
这篇文章提供了6种自己优化配置的方法。如果你需要帮助,你应该知道,我们在黄金和白金高级支持计划中提供了配置审查的高级服务。通过配置审查服务,CircleCI DevOps客户工程师将与您的团队合作,建立一个新的配置或审查一个现有的配置。我们将审查您的需求,并提供建议,以获得CircleCI的功能的最大收益。要了解更多关于高级服务和支持计划,请联系我们:cs@circleci.com。
如果没有整个客户工程团队的共同努力,就不可能有这个帖子。Anna Calinawan, Johanna Griffin, 和Grant MacGillivray。