乐趣区

为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 即将首次合体落地中国

退出移动版