译者 | 李睿
审校 | 重楼
本文将探索OpenTelemetry的一个新用例,在Kubernetes中为各种形式的可扩展微服务测试创建轻量级环境。
OpenTelemetry项目于2019年推出,是之前就已经存在的OpenTracing和OpenCensus这两个项目的结合,其目标是成为从基于分布式微服务的应用程序中提取遥测数据的单一开放标准。该项目是一个规范、工具和库的集合,旨在帮助以日志、度量和跟踪的形式从应用程序收集遥测数据,然后可以将其转发到支持聚合、可视化和内省这些数据的可观察性工具。
本文将探索一个利用OpenTelemetry(缩写为OTel)的新用例,特别是由OTel启用的分布式场景传播,以创建称为“沙盒”的轻量级环境,然后可以用于以可扩展的方式启用各种形式的微服务测试。此外,还将研究哪种类型的应用程序最自然地使用这个模型,以及它如何帮助用户在开发生命周期的早期获得高质量的测试反馈。
超越OpenTelemetry的可观察性
OTel的设计用例之一是跟踪。跟踪提供了对分布式系统如何处理用户或应用程序请求的理解,该分布式系统涉及许多独立的组件。
跟踪的工作原理
每个微服务在处理请求时,都会发出一些被称为“span”的信息,其中包含一些关于它正在处理的请求的场景。而一旦所有这些span被发送到某个后端,它们就可以拼接在一起,告诉整个请求流,这被称为“跟踪”。跟踪可用于了解特定请求发生了什么,也可用于在聚合场景中发现和处理可能影响某些请求子集的模式。这些关于请求通过由多个微服务组成的系统所经过路径的信息,可以有效地隔离和推断生产问题发生时的根本原因。
广义场景传播
当查看下面的图表时,可以看到每个单独的服务只发出它自己的“span”,这与它在本地处理请求的方式有关。因此,为了将属于单个请求的范围绑定到单个跟踪中,需要在这些标识请求本身的服务之间传播某种形式的场景,这被称为跟踪场景。
OTel通过它的库来实现这一点,它负责沿着同步/异步请求链在服务之间传递跟踪标识符。更重要的是,对于一些动态语言,它只需要放入相应的OTel库,该库“自动检测”字节代码以透明地执行分布式场景传播。
这种沿请求流传播场景片段的想法虽然起源于跟踪,但并不是专门与跟踪相关。
它是一种更通用的机制,可以利用它在微服务之间传播更多与跟踪相关的信息。有关Baggage规格的解释明确地说明了这一点。
该规范定义了一个标准,用于表示和传播一组与分布式请求或工作流执行相关的应用程序定义的属性。这是独立于跟踪场景规范的。无论是否使用分布式跟踪,都可以使用Baggage。
该规范标准化了应用程序定义的属性的表示和传播。与其相反,跟踪场景规范标准化了启用分布式跟踪场景所需元数据的表示和传播。
现在开始利用这个机制来实现超越遥测和追踪的用例。
使用OpenTelemetry的沙盒
沙盒是一种轻量级环境,可用于测试和验证对堆栈中微服务子集的更改。沙盒与传统环境的关键区别之一是,沙盒使用一组未更改的共享依赖项(其名称为“基线”),以测试一些已经更改的服务。
这个属性使得沙盒资源的效率很高,并且设置起来也很快。反过来,这意味着用户可以启动一个沙盒来测试单个提交,或者对每个微服务的单个更改,这在具有中等规模(>20左右)的传统微服务环境中变得成本高昂。
这种方法的另一个关键优势是,这些沙盒可以部署到现有的环境中,例如部署或生产环境中,以针对最新的依赖项测试更改,从而消除了过时依赖关系的问题,以及由于它们之间的差异而不得不在“更高”的环境中反复重新测试的问题。
以下将了解如何利用OTel中的分布式场景传播在Kubernetes中创建沙盒。
请求路由
由于采用OTel库和工具,一旦应用程序能够跨请求携带任意场景片段,现在就可以开始考虑使用它来隔离请求及其相互影响。
可以通过利用请求级多租户来创建沙盒。这是通过为每个请求分配一个特定的“租户”来完成的。租户只是指与每个请求相关联的标识符,告诉系统如何处理和路由特定的请求。在下面的示例中,租赁由“租户”标头(或者说L7协议元数据)指定,并具有一个值,该值标识应将其路由到服务的哪个特定测试版本。该租赁元数据使用OTel中的Baggage机制在服务之间传递,并在后台使用HTTP/gRPC标头。
以一个简单的微服务应用程序为例,它包含一个前端和后端,可以设置如下内容:
可以教会前端根据租约有条件地将正在发出的出站请求路由到后端。在Kubernetes中,做出路由决策是很简单的,因为有几种机制可以在应用程序不参与决策的情况下处理它。例如,可以使用服务网格(如Istio),这使得这种路由操作很容易在网格本身的规则中表示。创建一个Sidecar容器,利用特使或自定义代码来拦截请求,并在每个微服务上透明地代理请求,这是非常简单的。
上面创建的这个设置也可以扩展到测试中的多个工作负载。这种设置可以很容易地在现有的登台和生产集群中进行设置,只需很少的工作。登台和生产通常已经有一个持续部署(CD)流程,用于更新服务的稳定版本(基线),并且在同一集群中部署这些沙盒们能够在不测试这些依赖关系时重用它们。
只需使用上面所示的请求隔离,这个沙盒就可以用于测试对无状态服务的更改、运行API测试等。但是它仍然有局限性,因为还没有解决隔离可能产生副作用的数据突变、通过消息队列隔离异步工作流或调用第三方API的问题。以下将解决这些问题,使沙盒更加健壮,并能够在微服务环境中支持更多类型的测试。
状态隔离
使用这种方法在更多用例中执行更复杂的测试的一个重要步骤是解决状态隔离问题。在这里需要注意的一点是,并不是每个涉及微服务测试的用例都需要额外的状态隔离。例如,在人工或使用自动化为特定流运行API或E2E测试时,如果在特定流中,能够创建和清理在测试中使用的实体,那么已经实现了气密测试,而实际上不需要额外的状态隔离。
但是,在有些情况下需要额外的隔离,例如在数据存储上执行DDL更改,或者如果可能执行一些有副作用的突变。在这些情况下,可能希望部署可能受到影响的数据存储的隔离版本,并使沙盒工作负载使用它们,而不是基线环境使用的数据存储。
人们可以看到,在实践中,尽可能地使用逻辑隔离而不是基础设施隔离要有效得多。例如,在像MySQL和PostgreSQL这样的数据库以及大多数托管云计算数据库中,数据基础设施本身提供了一个内置的逻辑隔离概念。这种逻辑隔离的选择使沙盒保持轻量级、资源高效,并且仍然能够在几秒钟内启动,而这对于大多数情况来说已经足够了。当然,如果有必要,总是可以启动新的物理基础设施来与沙盒相关联,但在实践中发现这种需求非常罕见。
建立基础设施只是解决方案的一部分;播种数据是关键,必须在环境启动时/之前执行。可以将所有这些短暂数据存储的生命周期与环境本身的生命周期联系起来,也可以选择设置和维护预先预热的数据存储,这些数据存储是根据需要提供给服务的测试版本的。后一种使用预热测试数据存储的方法可以帮助准备好高质量的数据,并且在实践中,即使对于大型微服务堆栈,需要保留的此类测试数据存储数量也不是特别多。一些沙盒方法的高级用户更进一步,实现了将租赁与特定类型的数据集相关联的机制,例如在Uber关于SLATE的博客文章中。
使用短暂的隔离数据存储,可以提供高质量的数据进行开发/测试,同时确保在环境之外没有可见的副作用。使用逻辑隔离的想法特别有助于扩展沙盒的数量,同时将额外的基础设施或运营成本降至最低。与环境选择最高隔离级别作为默认隔离级别的流行概念相反,可以根据应用程序的用例和性质为沙盒选择最合适的隔离类型。
消息队列
消息队列在现代微服务堆栈中无处不在,当涉及到这些沙盒时,它提出了一个有趣的挑战。考虑到消息队列,消息队列的大多数(如果不是全部)实现允许在消息中设置一些报头/元数据。这意味着消息本身包含一些关于它们来自哪里的请求/流的标识的信息,这提供了一些关于如何进行消息路由的想法。
对于消息队列,通常也首选逻辑隔离,而不是物理隔离。因此,如果Kafka是消息队列,更愿意设置单独的主题,而不是建立新的孤立的Kafka集群。
虽然这个解决方案保证有效,但实际上,在大型微服务环境中有许多生产者和消费者,这使得它不太理想,这是因为它可能最终要求孤立地支持每个微服务。一种更清洁的隔离方法是教会消费者更有选择性地以“租户意识”的方式消费。
这种让使用者能够感知租户的方法需要在应用程序层进行一些更改。该功能可以很容易地封装到一个“消息路由器”库中,可以与使用者客户端一起使用。这个库将根据使用者服务的标识(它是基线吗?它属于哪个沙盒?)和包含租赁信息的消息元数据。这个库需要了解集群中不同沙盒的状态信息,以便在每个使用者中做出决策,这要求它与运行在Kubernetes集群中的某些路由控制器(或服务网格)进行对接。
这种方法解决了一个长期存在的问题,即消息队列在微服务堆栈中难以测试,它为具有租户意识的消费者提供了一种多路复用不同流的干净方式,并且不需要额外的基础设施设置,这使得它的设置非常快。
第三方API
现代微服务栈利用许多第三方API和外部服务来提供重要的功能,例如支付、集成等,这些功能可以通过外部API端点和Webhook与Kubernetes中的微服务进行对接。在第三方API中,传播租户信息的方法因系统而异。
在许多情况下,这些API只是“即发即弃”,或者具有由应用程序本身控制的副作用,在这种情况下,可能不需要通过外部系统进行租赁传播。
在需要外部系统回调或Webhook在微服务中执行一些额外处理的系统中,HTTP回调URL中的查询参数可以用于传播租赁信息。Kubernetes集群内部的路由机制将查找包含租赁信息的特定查询字符串,并使用该查询字符串将这些回调请求路由到适当的沙盒工作负载。
在实践中,这种机制通常需要对应用层进行一些较小的修改。微服务还必须知道它自己的租期。也就是说,拥有关于它所属的沙盒的信息,这样它就可以在回调时设置适当的租期元数据。
在实践中实现沙盒
沙盒方法已经在Lyft、Uber和Doordash的数百个微服务中大规模实现,这允许一个非常有效和可扩展的机制来快速测试和迭代。用户在与企业合作中吸取了一些经验教训,可以帮助他们在自己的基础设施中采用沙盒。
在实践中,对于想要开始采用这种方法的用户,验证的最简单的方法是首先将沙盒集成到一些微服务的持续集成(CI)流程中。这些沙盒可以在现有的登台环境中创建,这只需要很少的额外设置,并消除了与共享登台环境相关的瓶颈。从这一点开始,随着OTel在企业内的采用和场景传播中的空白被填补,很容易扩展沙盒,以在不同微服务之间进行变更的预合并测试、端到端测试和性能测试。随着采用率的增长,随着越来越多的用例可以更快、更容易地使用沙盒解决,对额外测试环境或staging环境的需求急剧下降是很典型的。
此外还发现,服务之间的合并前测试,例如将属于不同服务的测试沙盒放在一起,并通过它们运行流量,在开发生命周期中非常有用,并且在开发迭代和反馈方面比目前常见的合并后测试要快得多。
在生产环境中使用沙盒进行测试是终极目标,它最终能够完全消除分段。一旦租赁控制和数据隔离机制在组织内足够成熟,这最终是可能的,但对于希望使用沙盒的组织来说,这通常不是最佳的起点。
虽然大多数微服务通信模式都很适合这个范例,但也有一些模式是困难的,例如,测试Kubernetes基础设施组件或在自定义协议上使用低级网络的组件。对于这些情况,更传统的设置环境的方式或不同的隔离方法可能比使用沙盒更合适。
结论
本文探索了沙盒的范例,沙盒是一种利用请求级隔离来提供可扩展的微服务测试轻量级环境。还研究了在内部实现有效沙盒解决方案的不同技术,例如使用OTel传播场景、请求路由、状态隔离以及处理消息队列和第三方API。最后还研究了对于希望在内部实现沙盒的组织的一些实际考虑。
沙盒方法已经在一些行业中被有效地使用,以帮助加快和改进开发生命周期。Signadot正在构建一个Kubernetes原生解决方案,使创建和部署沙盒变得容易,能够帮助实现这一点。
https://dzone.com/articles/sandboxes-in-kubernetes-using-opentelemetry
领取专属 10元无门槛券
私享最新 技术干货