写了 30 万行基础设施代码后,我们得出 5 个有用的经验
写了 30 万行基础设施代码后,我们得出 5 个有用的经验
在 Gruntwork,我们创建并维护着一个包含 30 万行基础设施代码的库,有数百家公司在他们的生产环境中使用这个库。在这篇文章中,我将分享我们在开发和维护这个库的实践过程中学到的非常重要的五课。
处于石器时代的 DevOps
虽然这个行业充斥着各种前沿的流行语——Kubernetes、微服务、服务网格、不可变基础设施、大数据、数据湖,等等——但实际情况是,当你在构建基础设施并陷入困境时,它们看起来一点都不前沿。
对我来说,DevOps 感觉更像这样:
构建生产级的基础设施其实很难,压力很大,而且非常耗时。
根据我们在与数百家不同公司合作时收集的经验数据,你大致可以估计你的下一个基础设施项目需要多长时间:
第 1 课:生产级基础设施检查清单
DevOps 项目总是比预期的要长,为什么会这样?
第一个原因是 Yak Shaving(给牦牛撸毛),那么什么是 Yak Shaving?看看这个场景你就明白了:
经理:你不是在开发用户登录功能吗?为什么现在在捣鼓一个我们根本用不到的数据库?
工程师:是啊,我是打算开发用户登录功能。然后我开始评估要用哪个库,我发现一个非常好的库,但它只支持 Postgres。于是我试着搭建一个 Postgres,看看值不值得这样做。但切换数据库会破坏索引,所以我现在在学习如何建立 Postgres 索引……这样才能把用户登录功能做出来,对吧?
第二个原因是构建生产级的基础设施涉及了太多的细节。绝大多数开发人员并不知道这些细节,因此,当你在估算项目时,你通常会忘记关键和耗时的细节。
为避免这个问题,每次你开始使用新的基础设施时,请检查以下清单:
并非每个基础设施都需要检查清单中的每个项目,但你应该有意识地记录你已实现的项目、决定跳过的项目以及相应的原因。
第 2 课:工具集
截至 2018 年,以下是我们在 Gruntwork 中用于构建和管理基础设施的主要工具:
Terraform:我们使用 Terraform 来配置所有的基础设施,包括网络、负载均衡器、数据库、用户、权限以及我们所有的服务器。
Packer:我们使用 Packer 来定义和构建在服务器上运行的虚拟机镜像。
Docker:我们的一些服务器组成了集群,上面运行着托管应用程序作的 Docker 容器。我们使用的主要 Docker 集群工具是 Kubernetes、ECS 和 Fargate。
现在,所有这些工具都很有用,但这不是重点。重点是,光是有这些工具还不够,你还需要改变团队的行为。
特别是,如果你的团队不信赖这些工具,或者你的团队没有足够的时间学习使用这些工具,那么即使是世界上最好的工具都无法为你的团队带来任何帮助。因此,关键的一点是,基础设施即代码是一项投资:需要前期预付成本,但如果你明智地进行投资,将获得长期的巨大好处。
第 3 课:大模块是有害的
基础设施即代码新手通常在单个文件或作为一个单元进行部署的一组文件中定义所有环境(dev、stage、prod 等)的所有基础设施。这是一种糟糕的做法。
以下是这种做法的一些缺点:
速度慢:如果你的所有基础设施都在同一个地方定义,那么运行任何命令都需要很长时间。我们已经看到公司的 terraform plan 需要 5-6 分钟才能运行完毕!
不安全:如果你的所有基础设施都是一起管理的,那么在更改内容时都需要可以访问所有内容的权限。这意味着几乎每个用户都必须是管理员。
风险:如果所有鸡蛋都在一个篮子里,那么任意一个错误都可能会破坏整个系统。你可能正在对 dev 中的前端应用程序进行微小更改,但由于输入错误或运行了错误的命令,可能把生产数据库给删掉了。
难以理解:在一个地方拥有的代码越多,人们理解它们的难度就越大。如果将它们捆绑在一起,你不理解的部分可能会影响到你。
难以测试:测试基础设施代码很难,测试大量基础设施代码几乎是不可能的。
难以评审:诸如 terraform plan 之类的命令的输出变得毫无用处,因为没有人想要查看数千行输出。代码评审也变得毫无用处。
你应该使用小型、独立、可重用、可组合的模块来构建代码。这不是什么有争议的新观点。你之前可能已经听过无数次了:
“一次做一件事,并把它做好”——Unix 哲学。
“函数的第一条规则是它们应该很小。函数的第二个规则是它们应该比小更小。”——《整洁代码之道》
第 4 课:没有自动化测试的基础设施代码太脆弱
如果你的基础设施代码没有经过自动化测试就很容易出问题。你只是不知道一些暗藏的问题。也就是说,测试基础设施代码很难。你没有“localhost”(例如,你无法在笔记本电脑上部署 AWS VPC),也没有“单元测试”(例如,你无法将“Terraform”代码与“外部”隔离开来,因为 Terraform 所做的事情就是与外界交互)。
因此,要正确测试你的基础设施代码,通常需要将代码部署到真实环境,运行真实的基础设施,验证它们做它们该做的事情(对于这种测试方式,请参考 Terratest,一个开源库,包括用于测试 Terraform、Packer 和 Docker 代码的工具)。因此,对于基础设施测试,你必须重新定义一些术语:
单元测试是指部署和测试来自一种基础设施的一个或少量密切相关的模块(例如,测试单个数据库模块)。
集成测试是指部署和测试来自不同类型的基础设施的多个模块,以验证它们是否能够正常协同工作(例如,测试 Web 服务模块和数据库模块)。
端到端测试是指部署并测试整个架构。
这张图是一个金字塔,我们有很多单元测试、较少数量的集成测试和极少数的端到端测试。为什么?这是由每种类型的测试所需要的时间来决定的:
基础设施测试的周期时间很慢,特别是金字塔越往上就越慢,所以你会想尽可能多地在金字塔底层捕捉到错误。这意味着你应该:
构建小巧、简单的独立模块,并为它们编写大量单元测试。
将这些小型、简单、经过实战检验的构建块组合在一起,创建更复杂的基础设施,并进行少量的集成和端到端测试。
第 5 课:发布过程
现在让我们把这一切都放在一起。以下是你从现在开始应该采用的构建和管理基础设施的方法:
对照生产级基础设施检查清单,确保你正在构建正确的东西。
使用 Terraform、Packer 和 Docker 等工具将你的基础设施定义为代码。确保你的团队有时间掌握这些工具。
使用小型、独立、可组合的模块构建代码(或使用基础设施中的现成模块作为代码库)。
使用 Terratest 为你的模块编写自动化测试。
提交拉取请求,让别人来评审你的代码。
发布新版本代码。
将你的代码从一个环境推到另一个环境。