原文作者:Rich Burroughs of F5
原文链接:利用混沌工程提高微服务的弹性
转载来源:NGINX 官方网站
NGINX 唯一中文官方社区 ,尽在 http://nginx.org.cn/
微服务备受服务开发和部署团队的欢迎。在微服务架构下,开发人员可以使用更小、更具针对性的代码库,并且在服务部署的时间和方式上拥有更大的独立性。与单体架构相比,这些优势很明显。
然而,世上没有免费的午餐。随着从单体架构过渡到微服务,复杂性并不会消失,只是位置稍有转移。较小的代码库有助于更轻松地开发单个微服务,但是在生产中来操作微服务的时候复杂性会成倍的增加。
在使用微服务构建的系统中,可能运行着更多的主机和/或容器——更多的负载均衡器、更多的防火墙规则等。您可能会根据不同的微服务将 NGINX 用于不同的目的(Web 服务、反向代理、负载均衡)。随着服务数量从数十扩展到数百甚至数千,了解系统和预测系统行为变得愈发困难。此外,这些服务都通过网络相互通信,而不是通过单体架构内部的模块间调用。
我们如何验证基于微服务的系统不仅能够在正常条件下按设计运行,而且还能够应对环境中的意外故障或性能下降问题呢?混沌工程是一种不错的验证方法。混沌工程实践可帮助团队更好地管理生产环境中运行的应用并提高系统弹性。
什么是混沌工程?
我们将混沌工程定义为深思熟虑、计划周密的实验,旨在发现系统缺陷。我们经常将混沌工程比作疫苗接种,疫苗接种是将潜在的有害物质注射到体内,以防未来发生感染。在混沌工程实验中,我们主动将“故障”注入系统,以测试系统的弹性。我们采用科学有效的实验方法:提出假设,开展实验,然后观察是否验证了假设。
可注入系统的故障类型包括关闭主机或删除容器、增加 CPU 负载或内存压力,以及增加网络延迟或丢包等。这些只是举例说明,当然还有其他可注入的故障类型。
提出假设
混沌工程实验的第一步是提出假设。根据注入的故障,我们会对系统可能产生的影响做出一个预期,这个就是假设。请记住,我们的目的是测试系统的弹性。一般来说,我们假设系统能够应对我们注入的故障类型。但有时我们会发现假设有误,这时我们可以利用获得的信息来改善系统的弹性。
举例来说,假设有一个无状态 HTTP 服务在 NGINX 上运行,后者将一个 REST API 暴露给了其他一些服务。该服务的一个实例同时在生产环境中的 10 台主机上运行,可以确保在当前业务负载下,不会耗尽每台主机的 CPU 资源。我们可通过主动关闭一台主机,测试我们的系统是否足够健壮。
在这种情况下,我们的假设是“系统能够应对一台主机的故障,不会对其他服务或使用该系统的人员产生影响。”接下来我们就可以通过实验,来确认我们的假设是否正确。
爆炸半径、量级和中止条件
在规划实验时要牢记三个重要概念:爆炸半径、量级和中止条件。下面我们来详细了解一下这三个概念。
- 爆炸半径—— 是我们实验所用主机(或容器)的占比。这是一个非常重要的概念,即使在非生产环境中,我们也需要最大限度减少实验对用户的潜在影响。我们先从较小的爆炸半径开始(比如一台主机或一个容器),然后随着对实验的了解及掌控程度越来越深,逐渐增加爆炸半径。
- 量级—— 是我们给各个主机(或容器)施加的压力大小或故障程度。举例来说,如果我们要测试一个 CPU 攻击对 NGINX Web 服务器的影响,我们可能会在实验一开始多施加 20% 的 CPU 负载(量级),之后随着时间的推移不断增加。我们可以观察增加 CPU 负载对响应时间等服务指标的影响,从而确定系统在性能崩溃之前可以承受多大的攻击。
- 中止条件—— 是导致我们中断实验的条件。最好能提前知道当系统承受不住实验无法继续时,会出现哪种类型(或程度)的影响。可能是错误率或延迟的增加,也可能是监控软件生成的某个警报。您可以根据需要定义中止条件,可以根据实际的实验环境而不同。
爆炸半径、量级和中止条件能够让我们安全地进行混沌工程实验。在制定混沌工程实验计划时,务必将系统用户始终牢记在心,避免对系统用户造成负面影响。我们必须着眼于用户,努力提高系统弹性并改善用户体验。
使用黑洞攻击验证依赖项
从单体架构迁移到微服务后,复杂性加剧的表现之一是依赖项增加。单体应用中包含了系统的所有业务逻辑,而迁移到微服务后则会变成多项服务依赖共存。您的微服务还可能依赖其他外部服务,例如来自云提供商的 API,或用作基础架构一部分的 SaaS 服务。
当这些外部或内部依赖项出现故障时会发生什么?您在代码中实施的保护措施能否缓解这些故障?超时和重试等逻辑是否针对系统在生产环境中的实际运行方式进行了调优?
黑洞攻击是测试您能否处理好故障依赖项的好方法。黑洞攻击会拦截主机或容器对特定主机名、IP 地址和/或端口的访问,模拟资源不可用时会发生的情形。这种方法能够有效模拟网络或防火墙相关中断以及网络分区。
在存在外部依赖项的情况下,假设我们正在运行一个使用 Twilio API 向客户发送短消息的服务。我们知道,我们与 Twilio 的通信随时都有可能中断,因此我们将微服务设计为读取队列中的消息,并且只在这些消息通过 Twilio API 成功发送到 Twilio 后才将它们从队列中删除。
如果 Twilio API 不可用,消息将在我们这一端排队等候(比如使用 Kafka 或 ActiveMQ 等消息总线),当与 Twilio 的通信一旦恢复,它们将被立即发送。听起来很不错吧?
但在实际测试之前,我们如何知道在与 Twilio 的网络连接中断时,服务的实际表现如何呢?我们如何知道,它在生产环境中的实际运行方式是否同我们在白板上设计的一样?
我们可通过运行黑洞攻击,拦截该服务访问 Twilio API,观察它的实际表现。这可以解答我们的很多问题,例如:消息队列是否正确?我们设定的超时时间是否合理?随着消息队列的增长,服务是否继续正常运行?我们不再查看代码和推断这些问题的答案,而是实际注入故障,观察会发生什么。
在这种情况下,我们可以假设“网络连接断开时消息队列正确,并且在网络恢复后消息传递正常”。我们可通过运行黑洞攻击,检验这个假设是否正确。如果最终证明假设有误,我们可能会获取一些有用的信息,帮助我们提高系统弹性。
黑洞攻击也可以用来验证内部依赖项发生故障时会出现什么状况,以及发现隐藏的依赖项。隐藏依赖项是个十分常见的问题,最好能在它们引发事件之前发现它们。在服务中添加新的依赖项时会引入隐藏依赖项,但这些依赖项在组织内并无相关记录或传达。
举例来说:服务 A 更新后,现在依赖于服务 B,但运行服务 B 的团队并不知道这一点。他们中止服务 B 以进行维护,突然服务 A 意外中断。如果服务 A 是关键服务(例如登录服务),或者是支持客户购买商品的服务,那么这种中断的代价将非常高昂。对于团队来说,这种问题并不罕见,因为服务依赖项的映射和可视化通常比较困难。
通过定期对服务运行黑洞攻击,可暴露这些隐藏的依赖项,从而让相关团队知道它们的存在。如能清楚地了解外部依赖项,您还可以获得其他好处。比如某项服务在其依赖的服务无法访问时会如何响应?超时和重试配置是否正确?基于微服务的分布式系统如何处理网络分区?这些问题都可通过黑洞攻击予以解答。
结语
我们定义了混沌工程,并介绍了混沌工程如何帮助构建更具弹性的微服务架构。我们还讨论了如何利用黑洞攻击来了解服务如何响应外部和内部依赖项的故障。
黑洞攻击可以很好地帮助我们了解到服务在应对依赖项故障时的弹性,但是在微服务环境中,还有其他相关实验可以适用。比如关闭主机、增加网络延迟或丢包、破坏 DNS 解析以及增加 CPU 或内存压力都是不错的微服务弹性测试方法。
想要通过 NGINX Plus 试用混沌工程?立即点击下载 30 天免费试用版,或与我们联系以讨论您的用例。
NGINX 唯一中文官方社区 ,尽在 http://nginx.org.cn/
更多 NGINX 相关的技术干货、互动问答、系列课程、活动资源:
开源社区官网:https://www.nginx.org.cn/