人们经常问到的一个问题是:“什么是可编程代理,我们为什么需要它?”本文试图从不同的角度来回答这个问题。我们将从代理的简单定义开始,然后讨论代理在不同阶段是如何演化的,它们满足了哪些需求,以及它们在每个阶段提供了哪些好处。最后,我们将讨论可编程性的几个方面,并概述我们为什么需要可编程代理。
什么是代理?
代理服务器通常部署在两个独立的网络之间,负责将数据从一端传输到另一端,让它们看起来像是一个单独的网络。最简单的代理形式自计算机网络诞生以来就存在,作为用户和互联网之间的网关。不过,代理不仅可以充当网络连接器,它还支持许多其他的功能和用例,如:
路由。代理根据传输数据的特征将数据转发到不同的目标。
负载均衡。在转发过程中,数据被分发到不同的目标,提高了吞吐量,避免了单点故障。Layer4 或 Layer7 负载均衡就是代理服务器的用例之一。
故障转移。当转发到指定目标失败时,代理可以选择另一个目标,为请求者提供不间断的服务。
访问控制。代理可以决定允许哪些流量通过,需要阻止哪些流量。Web 应用程序防火墙(WAF)就是一个典型的例子。
身份管理。访问控制通常基于身份信息,因此代理通常也具有身份管理功能。
网络加速。代理通过缓存数据来加速网络访问。
指标收集。代理捕获数据统计,并将其汇总到网络性能监控(Network Performance Monitor,NPM)软件,用于网络优化和网络规划。
信息安全。除了访问控制,代理还可以用于安全审计、TLS/SSL 卸载和数据加密,以满足安全方面的需求。
位于 ISO/OSI 模型 Layer4 和 Layer7 的代理有时被称为“路由模式”代理。大多数代理服务是开源的,它们占到了网络基础设施软件很大一部分比例,在不同的领域提供专门的功能,例如特定于协议的代理、负载均衡代理、缓存加速代理,等等。
代理软件的演变
代理服务器经历了不同的发展阶段。
配置文件时代
这一代代理完全是基于配置的。用户设置一些参数,在配置文件中配置规则,然后启动服务进程来执行这些规则。
配置 DSL 时代
静态配置文件很难表达复杂的逻辑,因此许多代理在配置文件之上引入了精简脚本功能。这些通常被称为“配置语言”或领域特定语言(简称 DSL),例如 Haprxoy 的 ACL 或 Varnish 的 VCL。
脚本语言时代
随着逻辑变得越来越复杂,使用配置语言就变得越来越困难。当同一网络中使用的不同配置语言的数量达到一定的阈值时,脚本变得很难管理。
例如,使用 shell 脚本可以编写简单的逻辑,但是当 shell 代码达到一定的复杂性时,通常需要升级到更结构化的脚本语言,如 Perl 或 Python。
这些语言带来了脚本的便利性和成熟编程语言的结构优势,例如 OpenResty(Nginx+Lua)和 Nginx Plus(Nginx + NJS),还包括使用应用程序编程语言实现的代理服务器,例如基于 Node 的 StrongLoop Microgateway 和基于 Java 的 Spring Cloud Gateway,它们通常都提供了自己的脚本功能。
集群时代
脚本语言解决了模块化和结构化逻辑所固有的复杂性。这一阶段的需求是将代理与其他管理控制工具集成,因此要求代理提供 REST 或类似的接口,外部控制面板可以通过这些接口动态地更新脚本中的逻辑。
与此同时,代理的使用已经从单一实例发展到了代理集群。事实上,一些代理软件,如 Envoy 和基于 OpenResty 的 Kong,通常都支持集群功能,它们以某种集中或共享(通过 RDBMS 等)的方式实现集群功能,并提供 REST 管理接口来管理配置。
对于这个时代之前的代理,通常需要通过配置管理来实现集群管理。配置管理工具也可以公开 REST 接口。例如,Ansible + Nginx 实现了与云时代代理类似的功能。相比之下,集群时代的代理需要更多的组件来形成方案,而云时代的代理消除了管理活动组件的负担,所以后者是首选。
云时代
在云时代,代理通过分布式的方式进行部署。最常见的场景是按照边车代理模式为每个应用程序流程部署一个代理。
在集群模式下,对于不同的上游业务,通常会有不同的配置和策略,如认证方式、访问控制机制等。随着上游服务的增长,这些不同的配置在逻辑上是分开的,但在物理上仍然运行在同一个代理进程中。这种方案存在一些缺点:在同一个进程中运行更多的逻辑会带来更多的复杂性。另外,不同的上游业务共享 CPU、内存等资源会相互影响。如果某个上游服务的脚本存在安全漏洞,可能会导致配置泄露,存在安全风险。
在云时代,每个上游服务的代理进程是相互独立和隔离的。分布式代理为不同的上游服务(即多租户功能)使用不同的规则和策略打开了大门。
各种上游服务不仅具有逻辑上独立的规则和策略,还提供了物理隔离,可以在流程和接口级别进行细粒度管理。在多租户环境中,这种隔离性是必需的——不同的上游服务属于不同的租户,租户之间不应该相互影响或知道彼此的配置。
服务网格是这个时代的代表。服务网格由两个关键的架构组件组成,一个数据平面和一个控制平面。服务网格其实不是指“服务的网格”,而是代理的网格,可以将服务插入其中,从而将网络完全抽象出去。典型的例子有 Istio+Envoy、Linkerd+Linkerd Proxy。
Pipy(创建者也是本文作者之一)也是这个时代的产物,它就属于这个类别。Pipy 是一种轻量级、高性能、模块化、可编程的开源网络代理,可用于云端、边缘和物联网。Pipy 可用在各种场景中,包括(但不限于)边缘路由器、负载均衡器和代理解决方案、API 网关、静态 HTTP 服务器、服务网格边车和其他应用程序。Pipy 的开发很活跃,并由一些全职提交者和贡献者在维护,尽管它仍然是一个早期版本,但已经经过了实战测试,并被几个商业客户用在生产环境中。
从上面的讨论可以清楚地看出,每一个阶段都是对前一个阶段的改进。
代理软件需求及其演变
再让我们来看看代理的需求是如何演变的。
配置文件时代
第一代代理主要实现了用户与服务之间的网关功能,并提供了基本的可配置功能。海量数据的实时传输需要高吞吐量、低时延和低资源占用。与所有软件一样,代理也需要支持模块化和可扩展性。
在这个阶段,代理软件主要是用 C 语言开发的,扩展模块也是。
总之,这个阶段的代理需求包括连接性(网络功能)、易用性(可通过配置文件进行配置)、可靠性(交叉链接设备的需求)、高性能和可伸缩性。
配置 DSL 时代
第二代代理在可扩展性和灵活性方面得到了进一步的改进,比如动态数据获取和对获取的数据执行一些逻辑决策的能力。基于 DSL 的脚本进一步增强了可用性。可组合逻辑和动态数据获取不仅提供了灵活性,也提升了可伸缩性。
脚本语言时代
与第二代代理相比,第三代代理的主要改进是可管理性、开发者友好和可编程性。
开发者生产力和维护大量脚本的复杂性要求这一代代理使用结构化脚本语言,并保持性能、低资源利用率和上一代代理的核心功能。
之所以广泛使用脚本功能,主要是因为很难用 C 语言开发和维护扩展。事实上,与编译语言相比,脚本语言更容易学习,并提供更快的周转。
结构化和模块化脚本语言的使用引领了可编程代理的时代,代理需要提供两个层次的可编程性:用 C 语言开发核心模块,用脚本语言编写动态逻辑。换句话说,可编程代理为用户提供了开发核心模块和动态逻辑的能力。
集群时代
第四代代理从支持集群开始,提高了可管理性。
因为有了 REST 接口,代理成为网络基础设施的一部分和基础设施即代码(IaC)的起点。REST 接口除了提高代理的可管理性外,还有助于简化代理的管理。外部接口也是可编程代理的一个重要特性,而 REST 是最常见的接口形式。
此时的可编程性由可编程核心模块、可编程动态逻辑和可编程外部接口 3 层组成。代理服务器集群的出现反映了可伸缩性视角的变化,从功能扩展变成了资源扩展(用户可以将功能模块化到多个实例,而不是编写一个单体脚本)。REST 接口的出现为自助服务和托管服务提供了技术基础,例如用于配置管理的控制面板。
云时代
第五代代理的演变是由云计算的普及和快速发展推动的,带来了弹性、自助服务、多租户、隔离和度量的需求。
如果说第四代代理的用户是系统管理员,那么第五代代理的目标是云服务。在完全保持前几代代理软件特性的同时,第五代代理为云计算做好了准备。
随着云计算向边缘扩展,第五代代理需要支持异构硬件、异构软件、低能耗,以优化云与边缘的集成。
第五代代理在可编程性方面也向前进了一步:从核心模块、动态逻辑和我们之前看到的外部接口,到云计算能力、对分布式、多租户和度量的支持。度量是多租户的派生需求,一方面要求隔离性,另一方面需要以尽可能小的粒度度量资源。
接下来,我们将上面的讨论总结成一个表格,其中的行对应于特定的需求,列对应于不同阶段的代理。对于每个演化阶段,我们还在括号中提供了典型的例子或已知的软件。在每个单元格中,我们使用*表示这些功能是否可用以及在多大程度上可用(5 个*表示完全支持,1 个*表示基本支持)。
表中的第 11 行到第 17 行都与可编程代理有关,它们构成了为什么要使用可编程代理的答案:
可以扩展代理的内部功能,包括底层核心能力、支持的协议、Layer7 处理能力(转发、路由、判断、访问控制等)。Layer7 处理能力需要一种更方便的编程方式,即脚本编程和结构化编程。
代理需要提供外部接口来集成更大的管理系统(如云平台),包括配置管理、资源管理等。
代理需要为不同的角色提供可扩展功能,包括操作员、管理员、资源提供者和租户,所有这些角色都需要一定程度的可编程性。
同样,与可编程组件一样,可编程代理需要附带的文档、开发手册、代码管理、依赖关系管理、构建和部署工具,最好还有可视化的开发和调试环境。只有充分满足这些需求,用户才能有效地管理网络流量和业务。
总结
在本文中,我们解释了什么是可编程代理。为此,我们从什么是代理以及它的关键特征的定义开始,然后我们扩展了讨论的内容,包括代理的演进阶段,解释了在每个阶段添加的特性和功能。最后,我们总结了对代理特性的讨论,将其分为 17 个不同的类别,并对每一代代理进行排名。这样的分类有助于我们识别可编程代理的关键特征和属性。
作者简介:
蔡书是开源极客,Flomesh 创始人。
Ali Naqvi 是一位拥有 20 多年 IT 行业经验的 IT 专业人士。他是一个狂热的开发者和 OSS 贡献者。他的兴趣包括软件开发、软件架构和 DevOps。他经常发表演讲,也是在社区/分会中传播 OSS、DevOps 和敏捷的活跃成员。
原文链接:
评论