测试超过200,000行基础架构代码的经验教训


IaC (基础架构即代码)是一种现代方法,我相信基础架构就是代码。 这意味着我们应该对基础架构使用与软件开发相同的理念。 如果我们说基础架构就是代码,那么我们应该重用基础架构开发中的实践,即单元测试,结对编程,代码审查。 阅读本文时,请牢记这个想法。


俄语版


这是我在DevopsConf 2019-05-28上的演讲( 视频RU )的翻译



基础设施作为重击历史



让我们想象您正在参与一个项目,并且听到类似的消息:“我们使用基础架构作为代码方法”。 不幸的是,它们真正的意思是将基础架构称为bash历史记录或将Document作为bash历史记录 。 这几乎是真实情况。 例如,丹尼斯·利森科(Denis Lysenko)在他的演讲中描述了这种情况,即如何更换基础设施并避免烦恼(RU) 。 Denis分享了有关如何将bash历史转化为高级基础架构的故事。


让我们检查源代码定义: a text listing of commands to be compiled or assembled into an executable computer program 。 如果我们愿意,我们可以将基础结构作为bash历史记录(如代码)呈现。 这是文本和命令列表。 它描述了服务器的配置方式。 此外,它是:


  1. 可重现 :您可以获得bash历史记录,执行命令并可能获得有效的基础结构。
  2. 版本控制 :您知道谁登录,何时登录以及完成了什么。
    不幸的是,如果丢失服务器,则将无法执行任何操作,因为没有bash历史记录,而服务器丢失了它。

该怎么办?


基础架构即代码



一方面,这种异常情况( 基础架构作为bash历史记录 )可以表示为基础架构作为代码 ,但另一方面,如果您想做比LAMP服务器更复杂的事情,则必须管理,维护和修改代码。 让我们聊聊基础设施即代码开发和软件开发之间的相似之处。


干燥



我们正在开发SDS(软件定义的存储)。 SDS由定制的OS分布式高端服务器和许多业务逻辑组成,因此必须使用真实的硬件。 定期有一个子任务安装SDS 。 在发布新版本之前,我们必须先安装并签出。 最初,它看起来像是一个非常简单的任务:


  • SSH托管并运行命令。
  • SCP一个文件。
  • 修改配置。
  • 运行服务。
  • ...
  • 赢利!

我认为Make CM(不是bash)是一个好方法。 但是,bash仅在极端,有限的情况下使用,例如在项目开始时。 因此,bash在项目一开始就是一个不错的选择。 时间在流逝。 我们面临着以稍微不同的配置创建新安装的不同要求。 我们通过SSH进入安装程序,并运行命令来安装所有必需的软件,通过脚本编辑配置文件,最后通过Web HTTP rest API配置SDS。 毕竟,安装已配置并且可以运行。 这是很常见的做法,但是有很多bash脚本,并且安装逻辑每天都在变得越来越复杂。


不幸的是,每个脚本就像是一片雪花,取决于谁将其粘贴。 当我们创建或重新创建安装时,这也是一个真正的痛苦。


我希望您已经有了主要想法,在这个阶段,我们必须不断调整脚本逻辑,直到服务正常为止。 但是,有一个解决方案。 干了



有DRY(请勿重复自己)方法。 主要思想是重用已经存在的代码。 听起来非常简单。 在我们的例子中,DRY的意思是:拆分配置和脚本。


SOLID for CFM



该项目正在发展,因此,我们决定使用Ansible。 原因如下:


  1. Bash不应包含复杂的逻辑
  2. 我们在Ansible中拥有一定的专业知识。

Ansible代码中包含大量业务逻辑。 有一种方法可以在软件开发过程中将其放入源代码中。 它称为SOLID 。 从我的角度来看,我们可以将SOLID for Infrastructure复用为Code 。 让我逐步解释。


单一责任原则



一个类应该只负责一个职责,也就是说,只有对软件规范的一部分进行更改才能影响该类的规范。


您不应在基础结构代码中创建Spaghetti代码。 您的基础架构应由简单的可预测砖块组成。 换句话说,将巨大的Ansible剧本拆分为独立的Ansible角色可能是一个好主意。 它将易于维护。


开闭原则



软件实体...应该可以扩展,但是可以关闭以进行修改。


最初,我们是在虚拟机上部署SDS,稍后又将部署添加到裸机服务器 。 我们做到了。 对于我们来说,这很容易,因为我们仅添加了针对裸机特定零件的实现,而无需修改SDS安装逻辑。


里斯科夫替代原则



程序中的对象应该可以用其子类型的实例替换,而不会改变该程序的正确性。


让我们持开放态度。 SOLID通常可以在CFM中使用,这不是一个幸运的项目。 我想描述另一个项目。 这是一种现成的企业解决方案。 它支持不同的数据库,应用程序服务器以及与第三方系统的集成接口。 我将使用此示例描述SOLID的其余部分


例如,在我们的案例中,基础架构团队内部达成协议:如果部署ibm java角色或oracle java或openjdk,则将具有可执行的Java二进制文件。 我们需要它,因为顶级* Ansible角色依赖于此。 另外,它允许我们交换Java实现而无需修改应用程序安装逻辑。


不幸的是,Ansible剧本中没有语法糖。 这意味着在开发Ansible角色时必须牢记这一点。


接口隔离原理



许多特定于客户端的接口比一个通用接口要好。


最初,我们将应用程序安装逻辑放入单个手册中,试图涵盖所有情况和前沿技术。 我们遇到了难以维护的问题,因此我们改变了方法。 我们了解到,客户需要我们提供的界面(即443端口上的https),并且我们能够为每个特定环境组合Ansible角色。


依赖倒置原则



一个人应该“依赖抽象,而不依赖混凝土”。


  • 高级模块不应依赖于低级模块。 两者都应依赖抽象(例如接口)。
  • 抽象不应依赖细节。 细节(具体实现)应取决于抽象。

我想通过反模式来描述这个原理。


  1. 有一个拥有私有云的客户。
  2. 我们正在请求云中的VM。
  3. 我们的部署逻辑取决于VM所位于的管理程序。

换句话说,我们无法在另一个云中重用IaC,因为顶层部署逻辑取决于底​​层实现。 请不要这样做


互动互动



基础结构不仅是代码,而且还涉及交互代码<-> DevOps,DevOps <-> DevOps,IaC <->人。


总线系数



让我们想象一下,有DevOps工程师John。 John了解您的基础架构的所有知识。 如果约翰被公交车撞到,您的基础设施将会如何? 不幸的是,这几乎是真实的情况。 有时事情会发生。 如果发生了这种情况,并且您在团队成员之间没有共享有关IaC,基础架构的知识,您将面临很多不可预测和尴尬的后果。 有一些解决方法。 让我们聊聊他们。


配对DevOpsing



就像结对编程一样。 换句话说,有两名DevOps工程师,他们使用单个笔记本电脑\键盘来配置基础结构:配置服务器,创建Ansible角色等。 听起来不错,但是它对我们没有用。 在某些情况下,它有些起作用。


  • 入职 :导师和新人从积压的工作中获得真正的任务,并一起工作-将知识从导师转移到该人。
  • 突发事件呼叫 :在故障排除期间,有一群工程师正在寻找解决方案。 关键是要由一个人领导这一事件。 该人共享屏幕和想法。 其他人正在仔细关注他,并注意到bash的把戏,错误,日志解析等。

代码审查



在我看来, 代码审查是在团队内部共享有关基础结构的知识的最有效方法之一。 如何运作?


  • 有一个存储库,其中包含您的基础结构描述。
  • 每个人都在专门的分支机构中进行更改。
  • 在合并请求期间,您可以查看基础架构中的更改增量。

最有趣的是,我们正在轮换一名审稿人。 这意味着每隔几天我们会选出一位新的审阅者,而该审阅者正在查看所有合并请求。 结果,从理论上讲,每个人都必须接触基础架构的新部分,并且对我们的基础架构具有一般的了解。


代码风格



时间在流逝,我们有时在审阅过程中争论不休,因为审阅者和提交者可能使用不同的代码样式:2个空格或4个, camelCasesnake_case 。 我们实现了它,但是,这不是野餐。


  • 第一个想法是建议使用短绒。 每个人都有自己的开发环境:IDE,OS ...同步和统一所有内容非常棘手。
  • 这个想法演变成一个松懈的机器人。 每次提交后,该机器人会检查源代码并放入带有问题列表的松弛消息中。 不幸的是,在大多数情况下,消息后没有源代码更改。

绿色建筑大师



接下来,最痛苦的步骤是限制所有人推送到master分支。 仅通过合并请求和绿色测试即可。 这就是所谓的绿色建筑大师 。 换句话说,您100%确保可以从master分支部署基础结构。 这是软件开发中非常普遍的做法:


  • 有一个存储库,其中包含您的基础结构描述。
  • 每个人都在专门的分支机构中进行更改。
  • 对于每个分支,我们都在运行测试。
  • 如果测试失败,则无法合并到master分支中。

这是一个艰难的决定。 希望,由于在审核过程中,没有关于代码样式的争论,并且代码气味的数量正在减少。


IaC测试



除了代码样式检查之外,您还可以检查是否可以在沙箱中部署或重新创建基础结构。 做什么用的 这是一个复杂的问题,我想分享一个故事,而不是一个答案。 是用Powershell编写的AWS自定义自动缩放器。 自动缩放器没有检查输入参数的最前沿,结果创建了大量的虚拟机,客户对此感到不满意。 这是一个尴尬的情况,希望可以尽早发现它。


一方面,可以测试脚本和基础结构,但另一方面,您正在增加代码量,并使基础结构更加复杂。 但是,真正的原因是您将有关基础架构的知识用于测试。 您正在描述事物应该如何协同工作。


IaC测试金字塔



IaC测试:静态分析


您可以为每个提交从头开始创建整个基础结构,但是通常会遇到一些障碍:


  • 价格是平流层。
  • 需要很多时间。

希望有一些技巧。 您的基础中应该有很多简单,快速,原始的测试。


重击很棘手


让我们看一个非常简单的例子。 我想创建一个备份脚本:


  • 从当前目录获取所有文件。
  • 将文件复制到另一个具有修改名称的目录中。

第一个想法是:


 for i in * ; do cp $i /some/path/$i.bak done 

还不错 但是,如果文件名包含空格怎么办? 我们是聪明人,我们使用引号:


 for i in * ; do cp "$i" "/some/path/$i.bak" done 

完成了吗 不! 如果目录为空怎么办? 在这种情况下,连接失败。


 find . -type f -exec mv -v {} dst/{}.bak \; 

我们吃完了吗 尚未...我们忘记了文件名可能包含\n字符。


 touch x mv x "$(printf "foo\nbar")" find . -type f -print0 | xargs -0 mv -t /path/to/target-dir 

静态分析工具


您可以通过Shellcheck捕获先前示例中的一些问题。 有很多类似的工具,它们被称为linters,您可以找到最适合您的IDE,堆栈和环境的工具。


语言能力工具工具
重击Shellcheck
红宝石鲁科科普
蟒蛇皮林特
AnsibleAnsible皮棉

IaC测试:单元测试



如您所见,短绒棉不能捕捉所有东西,它们只能预测。 如果我们继续考虑软件开发和基础设施即代码之间的相似之处,我们应该提到单元测试。 有很多单元测试系统,例如shunitJUnitRSpecpytest 。 但是您是否听说过Ansible,Chef,Saltstack和CFengine的单元测试?


当我们谈论SOLID for CFM时,我提到我们的基础架构应由简单的模块/模块组成。 现在是时候了:


  1. 将基础架构分为简单的模块/中断,即角色。
  2. 创建一个环境,即Docker或VM。
  3. 将您的一个简单的break /模块应用于环境。
  4. 检查一切正常。
    ...
  5. 赢利!

IaC测试:单元测试工具


什么是CFM和您的基础架构测试? 也就是说,您可以只运行脚本,也可以使用生产就绪型解决方案,例如:


CFM工具工具
AnsibleTestinfra
主厨检验
主厨服务器规格
盐堆高斯

让我们看一下testinfra,我想检查一下用户test1test2存在,并且它们是否属于sshusers组:


 def test_default_users(host): users = ['test1', 'test2' ] for login in users: assert host.user(login).exists assert 'sshusers' in host.user(login).groups 

最好的解决方案是什么? 这个问题没有唯一的答案,但是,我创建了热图并比较了此项目在2018-2019年期间的变化:



IaC测试框架


之后,您可能会遇到一个问题:如何一起运行? 一方面,如果您有足够的优秀工程师,则可以自己完成所有事情 ;但是,另一方面,您可以使用开放源代码的生产就绪型解决方案:


CFM工具工具
Ansible分子
主厨测试厨房
地貌Terratest

我创建了热点图并比较了该项目在2018-2019年期间的变化:



分子与 Testkitchen



一开始,我们尝试通过hyper-v内部的testkitchen测试ansible角色


  1. 创建虚拟机。
  2. 应用Ansible角色。
  3. 运行检查。

25-35岁的Ansible角色花了40-70分钟。 对我们来说太久了。



下一步是使用Jenkins / docker / Ansible / molecular。 大致相同的想法:


  1. Lint Ansible剧本。
  2. Lint Ansible角色。
  3. 运行一个Docker容器。
  4. 应用Ansible角色。
  5. 运行testinfra。
  6. 检查幂等性。


排练40个角色并测试其中10个角色大约需要15分钟。



最好的解决方案是什么? 一方面,我不想成为最终的权威,但另一方面,我想分享我的观点。 没有灵丹妙药,但是,如果Ansible分子是比testkitchen更合适的解决方案,则不存在。


IaC测试:集成测试



IaC测试金字塔的下一个层次上,有集成测试 。 基础结构的集成测试看起来像单元测试:


  1. 将基础架构分为简单的模块/中断,即角色。
  2. 创建一个环境,即Docker或VM。
  3. 将简单的break / module 组合应用于环境。
  4. 检查一切正常。
    ...
  5. 赢利!

换句话说,在单元测试期间,我们检查基础结构的一个简单模块(即Ansible角色,python脚本,Ansible模块等),但是在集成测试的情况下,我们检查整个服务器配置。


IaC测试:端到端测试



IaC测试金字塔的顶部,有端到端测试 。 在这种情况下,我们不会检查基础架构的专用服务器,脚本,模块; 我们检查整个基础架构是否正常工作。 不幸的是,还没有开箱即用的解决方案,或者我还没有听说过它们(如果您知道它们,请标记我)。 通常,人们会重新发明轮子,因为对基础架构的端到端测试存在需求。 所以,我想分享我的经验,希望对大家有用。



首先,我想描述一下上下文。 它是一种开箱即用的企业解决方案,它支持不同的数据库,应用程序服务器以及与第三方系统的集成接口。 通常,我们的客户是一个拥有完全不同环境的庞大企业。 我们了解不同的环境组合,并将其存储为不同的docker-compose文件。 另外,在docker-compose文件和测试之间存在匹配,我们将其存储为Jenkins作业。



当在openshift研究期间我们试图将其迁移到Openshift时,该方案一直处于安静的日志记录时间段。 我们使用了大致相同的容器(再次挂起DRY),仅更改了周围环境。



我们继续研究,发现了APB (Ansible Playbook Bundle)。 主要思想是将所有需要的东西打包到一个容器中,然后在Openshift中运行该容器。 这意味着您具有可重现和可测试的解决方案。



一切都很好,直到我们遇到另一个问题:我们必须维护用于测试环境的异构基础结构。 结果,我们在Jenkins作业中存储了有关如何创建基础结构和运行测试的知识。


结论



基础设施即代码,是以下各项的组合:


  • 代号
  • 人际互动。
  • 基础架构测试。

Source: https://habr.com/ru/post/zh-CN467169/


All Articles