为Envoy v2构建Kubernetes Edge(Ingress)控制平面

作者:Daniel Bryant,Datawire产品架构师;Flynn,Datawire Ambassador首席开发者;Richard Li,Datawire首席执行官兼联合创始人

Kubernetes已经成为基于容器的微服务应用程序的事实上的运行引擎,但是这个编排框架本身并不提供运行分布式系统所需的所有基础结构。微服务通常通过第7层协议,如HTTP、gRPC或WebSockets,进行通信,因此能够制定路由决策,操作协议元数据,以及在此层进行观察至关重要。然而,传统的负载平衡器和边缘代理主要关注L3/4流量。这就是Envoy Proxy发挥作用的地方。

Envoy代理由Lyft工程团队从头开始设计为通用数据平面,用于当今分布式,以L7为中心的世界,广泛支持L7协议,用于管理其配置的实时API,一流的可观察性,在小内存空间内实现高性能。然而,Envoy庞大的功能集和操作灵活性也使其配置非常复杂 - 从查看其丰富但冗长的控制平面语法可以看出这一点。

通过开源Ambassador API网关,我们想要解决创建新控制平面的挑战,该控制平面侧重于在Kubernetes集群中部署Envoy作为前向边缘代理的用例,采用Kubernetes operator惯用的方式。在本文中,我们将介绍Ambassador设计的两个主要迭代,以及我们如何将Ambassador与Kubernetes相结合。

2019年之前的Ambassador:Envoy v1 API、Jinja模板文件和热重启

Ambassador本身作为Kubernetes服务部署在容器中,使用添加到Kubernetes Services的注释作为其核心配置模型。此方法使应用程序开发者能够将路由作为Kubernetes服务定义的一部分进行管理。由于当前Ingress API规范的限制,我们明确决定采用这种方式,我们也喜欢扩展Kubernetes服务的简单性,而不是引入另一种自定义资源类型。这里可以看到Ambassador注释的一个例子:

kind: Service
apiVersion: v1
metadata:
  name: my-service
  annotations:
    getambassador.io/config: |
      ---
        apiVersion: ambassador/v0
        kind:  Mapping
        name:  my_service_mapping
        prefix: /my-service/
        service: my-service
spec:
  selector:
    app: MyApp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376

将这个简单的Ambassador注释配置转换为有效的Envoy v1配置并不是一项简单的任务。按照设计,Ambassador的配置不是基于与Envoy配置相同的概念模型 - 我们故意想要聚合和简化操作和配置。因此,将一组概念转换为另一组概念涉及Ambassador内部的相当多的逻辑。

在Ambassador的第一次迭代中,我们创建了一个基于Python的服务,该服务监视Kubernetes API以更改Service对象。当检测到新的或更新的Ambassador注释时,这些注释从Ambassador语法转换为中间表示(intermediate representation,IR),其体现了我们的核心配置模型和概念。接下来,Ambassador将此IR转换为Envoy配置,该配置在运行的Ambassador k8s服务相关联的pod中被保存为文件。Ambassador然后“热重启”在Ambassador pod内运行的Envoy进程,这引发了新配置的加载。

这个初始实现有很多好处。所涉及的机制基本上很简单,将Ambassador配置转换为Envoy配置是可靠的,Envoy基于文件的热重启集成是可靠的。

但是,这一版本的Ambassador也面临着显着的挑战。首先,虽然热启动对我们大多数客户的用例有效,但速度并不快,一些客户(特别是那些具有大量应用程序部署的客户)发现它限制了更改配置的频率。热重启也可以丢弃连接,尤其是长期连接,如WebSockets或gRPC流。

但更重要的是,IR的第一次实施允许快速原型设计,但有点原始,事实证明很难做出实质性的改变。虽然从一开始就是一个痛点,但当Envoy转向Envoy v2 API时,它成了一个关键问题。显然,v2 API将为Ambassador提供许多好处 - 正如Matt Klein在他的博客文章中概述的“通用数据平面API” - 包括访问新功能和上述连接丢弃问题的解决方案,但同时明确表示现有的IR实施无法实现跨越式发展。

Ambassador >= v0.50:Envoy v2 API(ADS)、使用KAT测试和Golang

在与Ambassador社区协商后,Datawire团队在2018年对Ambassador内部进行了重新设计。这是由两个关键目标驱动的。首先,我们希望集成Envoy的v2配置格式,这将支持SNI、速率限制和gRPC身份验证API等功能。其次,我们还希望对Envoy配置进行更强大的语义验证,因为它的复杂性越来越高(特别是在大规模应用程序部署时)。

初始阶段

我们首先通过多通道编译器对Ambassador内部进行重组。类层次结构是为了更密切地反映Ambassador配置资源、IR和Envoy配置资源之间的关注点分离(separation of concerns)。Ambassador的核心部分也进行了重新设计,以促进Datawire以外社区的贡献。我们决定采用这种方法有几个原因。首先,Envoy Proxy是一个非常快速发展的项目,我们意识到我们需要一种方法,即看似微小的Envoy配置变更不会导致Ambassador内部的重新设计。此外,我们希望能够提供配置的语义验证。

当我们开始与Envoy v2更紧密地合作时,很快就发现了测试挑战。随着Ambassador支持越来越多的功能,Ambassador处理不太常见但完全有效的功能组合时出现了越来越多的漏洞。这促使创建一个新的测试要求,这意味着Ambassador的测试套件需要重新设计,以自动管理许多功能组合,而不是依靠人手单独编写每个测试。此外,我们希望测试套件快速,以最大限度地提高工程效率。

因此,作为Ambassador重构的一部分,我们引入了Kubernetes验收测试(KAT)框架。KAT是一个可扩展的测试框架:

  • 将一堆服务(与Ambassador一起)部署到Kubernetes集群
  • 针对启动的API运行一系列验证查询
  • 对这些查询结果执行一堆认定

KAT专为性能而设计 - 它预先批量测试设置,然后在步骤3中与高性能客户端异步运行所有查询。KAT中的流量驱动程序使用Telepresence在本地运行,这使得调试问题变得更加容易。

将Golang引入Ambassador堆栈

随着KAT测试框架到位,我们很快遇到了Envoy v2配置和热重启的一些问题,这提供了切换到使用Envoy的聚合发现服务(ADS)API而不是热重启的机会。这完全消除了重新配置更改的要求,我们发现这可能导致在高负载或长期连接下断开连接。

然而,当我们考虑转向ADS时,我们遇到了一个有趣的问题。ADS并不像人们想象的那么简单:在向Envoy发送更新时存在明确的排序依赖性。Envoy项目具有排序逻辑的参考实现,但仅限于Go和Java,而Ambassador主要使用Python。我们挣扎了一点,决定最简单的方法是接受我们世界的多语言性质,并使用Go来实施我们的ADS。

我们还发现,在KAT的情况下,我们的测试已经达到了Python在许多网络连接中的性能受到限制的程度,因此我们在这里利用Go,主要使用Go编写KAT的查询和后端服务。毕竟,当你已经投入采取了Golang,另一个依赖是什么?

通过新的测试框架、新的IR生成有效的Envoy v2配置和ADS,我们认为我们完成了0.50Ambassador的主要体系结构更改。唉,我们又遇到了一个问题。在Azure Kubernetes服务上,不再检测到Ambassador注释更改。

与高度响应的AKS工程团队合作,我们能够确定问题 - 在AKS中的Kubernetes API服务器通过一系列代理公开,要求客户端更新,以了解如何使用API的FQDN连接服务器,这是通过AKS中的变异webhook提供。不幸的是,官方Kubernetes Python客户端没有对此功能的支持,所以这是我们选择切换到Go而不是Python的第三个原因。

这提出了一个有趣的问题,“为什么不放弃所有的Python代码,只是完全用Go重写Ambassador?”这是一个有效的问题。重写的主要问题是Ambassador和Envoy在不同的概念层面上运作,而不是简单地用不同的语法表达相同的概念。确信我们已经用新语言表达了概念桥梁并不是一个微不足道的挑战,如果没有真正优秀的测试覆盖范围就无法实行。

在这一点上,我们使用Go覆盖非常具体、包含良好的函数,可以更容易地验证它们的正确性,以便我们可以验证完整的Golang重写。在将来,谁知道?但是对于0.50.0,这个功能分坼让我们既利用Golang的优势,又让我们对已经在0.50的所有变化保持更多信心。

得到的教训

我们在建立Ambassador 0.50的过程中学到了很多东西。我们的一些关键要点:

  • Kubernetes和Envoy是非常强大的框架,但它们也是极其快速移动的目标 - 有时候无法替代阅读源代码并与维护者交谈(幸运的是,所有人都非常容易访问!)
  • Kubernetes/Envoy生态系统中最受支持的库是用Go编写的。虽然我们喜欢Python,但我们不得不采用Go,这样我们就不会被迫自己维护太多的组件。
  • 有时需要重新设计测试工具来推动软件的发展。
  • 重新设计测试工具的实际成本通常是将旧测试移植到新的线束实现中。
  • 为边缘代理用例设计(和实现)一个有效的控制平台一直是一个挑战,来自Kubernetes、Envoy和Ambassador的开源社区的反馈非常有用。

将Ambassador迁移到Envoy v2配置和ADS API是一个漫长而艰难的过程,需要大量的架构和设计讨论,以及大量编码,但结果的早期反馈是积极的。Ambassador 0.50现已发布,因此你可以使用参加测试,并在我们的Slack频道Twitter上与社区分享你的反馈。


KubeCon + CloudNativeCon中国论坛提案征集(CFP)2月22日截止

KubeCon + CloudNativeCon 论坛让用户、开发人员、从业人员汇聚一堂,面对面进行交流合作。与会人员有 Kubernetes、Prometheus 及其他云原生计算基金会 (CNCF) 主办项目的领导,和我们一同探讨云原生生态系统发展方向。

Open Source Summit中国提案征集(CFP)2月22日截止

在Open Source Summit中国,与会者将共同合作及共享信息,了解最新和最有趣的开源技术,包括Linux、IoT、区块链、AI、网络等;并获得如何在开源社区中导向和引领的信息。

大会日期:

  • 提案征集截止日期:太平洋标准时间 2 月 22 日,星期五,晚上 11:59
  • 提案征集通知日期:2019 年 4 月 8 日
  • 会议日程通告日期:2019 年 4 月 10 日
  • 会议活动举办日期:2019 年 6 月 24 至 26 日

提醒:这是一场社区会议。因此,让我们尽量避开公然推销产品和/或供应商销售宣传。

KubeCon + CloudNativeCon和Open Source Summit赞助方案出炉

KubeCon + CloudNativeCon和Open Source Summit多元化奖学金现正接受申请

KubeCon + CloudNativeCon和Open Source Summit即将首次合体落地中国

相关推荐