为什么需要可编程代理

点击查看目录

经常会有人问“当你们说可编程代理的时候,那么什么是可编程代理,为什么需要可编程代理”?本文从不同角度回答这个问题。首先会简单地介绍代理;然后讨论下代理在发展过程中的阶段划分;基于这些阶段的划分,讨论每一个阶段相比于上一个阶段的改进之处,以及为什么需要这些改进,同时我们讨论下“可编程”所包含的几个层面;最后我们总结下“为什么需要可编程代理”。

什么是代理及代理的功能

代理是代理服务器的简称,代理服务器通常部署在两个互相隔离的网络的中间处,既能访问一侧网络也能访问另一侧网络,通过把一侧的数据搬运到另一侧,实现了网络的连通。代理是一种串路网络设备,自从计算机网络诞生,代理就存在了。由于代理是串路的,因此代理在实现网络连通功能的同时也衍生出新的功能和使用场景:

  • 路由:代理在转发数据的时候,根据数据的特征,转发到不同的目的地
  • 负载均衡:在转发过程中,通过把数据分发到不同的目的地,提高吞吐量、避免目的地单点故障。负载均衡逐渐成为代理细分功能的一个领域
  • 故障迁移:在转发过程中,当目的地出现故障时候,代理可以把数据转发到备用的目标,对请求方提供不间断的服务
  • 访问控制:代理可以决定某些流量可以通过,哪些流量需要被拦截。WAF 是典型的代理在细分领域的应用
  • 身份识别:访问控制很多时候需要基于身份信息,因此代理通常也具有身份识别的功能
  • 网络加速:代理通过缓存数据的方式加速网络访问
  • 指标采集:代理对经过的数据进行统计,汇总给 NPM 软件用于网络优化及网络规划
  • 信息安全:除了访问控制外,代理还可以用于安全审计、TLS/SSL 卸载、数据加密等,满足安全需求

提供桥接两个网络功能的除了代理还有路由器。路由器工作在网络的 3 层;而代理工作在 3 层以上,或者说 4 层和 7 层。

软件代理的发展

软件代理服务器在发展过程中,大致上经历了如下几个阶段:

配置文件时代

代理类软件(最主要是开源软件),占到了网络基础设施类软件的大多数,这些软件在细分领域提供了不同的功能,比如针对不同协议的代理、比如侧重负载均衡的代理、比如侧重缓存加速的代理。这一大类软件,都是基于配置的。用户在配置文件中设置参数、配置规则,然后启动服务进程执行这些规则

配置语言时代

配置难于表达复杂逻辑,所以很多代理软件在配置基础上引入了很薄的脚本能力,我们一般称为“配置语言”或者说 DSL,比如 Haprxoy 的 ACLVarnish 的 VCL

脚本语言时代

当逻辑进一步复杂的时候,配置语言也会难于表达;同时,当配置语言数量多到一定程度时候,配置语言本身的管理,会有很大难度。

就像 shell 脚本可以写简单的逻辑,但是当 shell 代码多到一定程度的时候,通常会进一步选择 Perl 或者 Python 这些更加结构化的脚本语言。Proxy 支持脚本语言,既有脚本语言的便利性,也有编程语言结构化的优势。

这类的例子如 Openresty(Nginx + Lua)、Nginx Plus(Nginx + NJS). 同时,这类例子里也包括大量应用类编程语言实现的代理服务器,比如基于 NodeJS 的 StrongLoopSpring Cloud Gateway 等,这些应用类编程语言往往自己就有脚本支持能力。

集群时代

脚本语言解决了代理中复杂逻辑的模块化、结构化实现难点。此时进一步的需求是把代理和其他的管理控制工具集成,因此需要有 REST 接口。外部的控制平面可以通过 REST 接口动态的设置脚本中的逻辑。

同时,人们对代理的使用也从单实例上升到集群化,因此这一类代理通常都自身支持集群能力,比如 Enovy 和基于 Openresty 的 Kong,他们通过某种集中或者共享方式实现集群能力,同时对外提供 REST 接口。

对于这个时代的代理,通过配置管理,一般也可以实现集群管理;并且配置管理工具也可以对外暴露 REST 接口。比如使用 Ansible + Nginx 的方案,实现了和云时代的能力。相比之下,集群时代的方案需要要更多的组件形成方案,而云时代的方案更收敛。

云时代

在 #5 的基础上,代理采用分布式的方式部署,最常见的场景是为每个应用进程部署一个代理,也就是 sidecar proxy 模式

在采用分布式以后,针对不同的上游服务,采用不同的规则和策略,也就是多租户能力。不同上游服务,不仅在逻辑上有独立的规则和策略;在物理上也进一步提供了隔离,实现进程级和接口级的细粒度管理。如果我们把服务网格的控制平面和数据平面看作一个整体,那么服务网格是这个领域的代表,典型的比如 Istio+EnvoyLinkerd+Linkerd ProxyPipy 就是这个阶段的产物。

代理发展总结

在如上的各个阶段里,每一个阶段都比上一个阶段有所改进,概要的说:

配置语言时代比配置文件增加了基本的脚本能力。这种基本的脚本能力,在配置文件的基础上,增加了动态能力。比如在运行期获取请求的特征(如获取 HTTP Header),然后根据这些特征做动态的逻辑判断,进行特定的操作。

脚本语言时代比配置语言时代增加完整的脚本能力,此时可以结构化和模块化的编写脚本逻辑。在配置语言的时代,当逻辑复杂的时候,脚本的量也会大幅增加,此时结构化的脚本能力成为一种必需 集群时代比脚本语言时代多了 REST 接口和集群能力。当需要水平扩容代理能力的时候,需要把多个代理实例组成集群;集群内的实例共享配置和脚本,并且用户可以通过 REST 接口去管理配置和脚本。

云时代比集群时代多了分布式能力,主要体现在同一个集群内不同的实例所运行的脚本和配置是不同的。在集群模式下,对于不同的上游服务,通常会有不同的配置和策略,比如不同的认证方式、不同的访问控制机制等;当上游服务逐渐增多的时候,这些不同上游服务的配置在逻辑上是分离的,但是在物理上都运行在同一个代理进程里。这种逻辑上分离的配置和策略运行在同一个物理进程中的情况,带来一些弊端:更多的逻辑运行在一个进程内,带来了更多的复杂性;不同上游服务的共享 CPU 和内存等资源,导致互相影响;如果某一个上游服务的脚本出现了安全漏洞,会导致其他上游服务的配置泄漏,存在安全隐患。

云模式对于集群模式的改进在于每个上游服务的代理进程是独立的、彼此隔离的。他们受同一个集群管理者管理,但是在运行中的配置和脚本是独立和隔离的。这种隔离的特性,是多租户环境中的一种强需求 – 不同的上游服务属于不同的租户,租户之间不应该互相影响,也不应该知道彼此的配置。云时代可以认为是集群时代多集群的极限模式 – 最极端情况下,每一个进程都有自己的配置。

代理的需求演化

让我再从另一个视角看下代理的演化过程–需求的演化。

配置文件时代

第一代的代理主要是实现了代理功能,并且提供了基础的可配置能力;同时,网络设备,尤其是串路网络设备的特性,要求代理是高可靠的;网络的海量数据实时传输的特性,要求代理高吞吐、低延迟、低资源。和所有的软件一样,代理也需要支持模块化和可扩展,这个阶段的代理主要采用 C 语言开发,相应的开发扩展模块也使用 C 语言,模块在进程启动时加载。概括起来说,这个阶段的代理需求是:连通性(网络功能)、易用性(可以通过配置文件配置)、可靠性(串路设备的要求)、高性能扩展性

配置语言时代

第二代代理的改进体现在进一步提高了扩展性和灵活性,如一些动态的数据获取和配套的逻辑判断。脚本的引入,进一步增强了易用性;对于组合逻辑和动态数据获取的支持,提供了灵活性,同时改进了扩展性

脚本语言时代

第三代代理相比于第二代代理的改进主要是可管理性开发者友好可编程。脚本大量地使用,一方面是因为使用 C 语言等做扩展开发难度大、维护难度大,一方面是脚本在现场开发的效率要优于编译型语言。

开发者的开发效率和大量脚本维护带来的难度,要求这一代代理使用更为结构化的脚本语言,并且需要保持不低于上一代的性能、资源占用等核心能力。

结构化和模块化的脚本语言的使用,开启了代理的可编程时代,此时扩展代理服务器的功能就包含了两个层面和可能性,一个是使用 C 语言等开发核心模块,一个是使用脚本开发动态逻辑;或者说可编程包含了核心模块 可编程动态逻辑 可编程

集群时代

第四代代理开始了集群支持能力,属于可管理性的改进。

对 REST 接口的支持,使得代理作为网络基础设施(network infra),开始融入到了整体的管理中,是 infra as code 的一个落地点。REST 接口能力,提升了代理的被管理能力,也是管理易用性的一部分。外部接口也是可编程的一个重要特征,而 REST 作为最常用的接口形式也广泛的出现在代理服务器领域。

此时可编程就包含了三个层面:#3 中描述的核心模块可编程,动态逻辑可编程,以及提供对外接口供调用的外部接口 可编程。代理服务器集群的出现,体现了扩展性功能扩展资源扩展的变化。REST 接口的出现,为进一步的自服务托管服务提供了技术基础

云时代

第五代代理的演化是云计算普及和高速发展驱动的。云的弹性、自服务、租户、隔离、计量,要求代理服务软件具备云化的能力。

如果说第四代代理是面向系统管理员的,那么第五代代理就是面向云服务的。在充分保持了之前几代代理软件特征的同时,进一步实现了Cloud Ready

随着云计算向边缘侧拓展,第五代代理也向着硬件异构、软件异构、低能耗的方向发展,因此这一代代理开始呈现了云边一体的能力。

第五代代理在可编程方面进一步演化,从核心模块动态逻辑外部接口,增加了云化的能力;包括支持分布式、多租户、可计量等。可计量是多租户的衍生需求,多租户一方面要求隔离,另一方面要求资源可以被尽可能小的粒度进行计量

我们把如上的讨论汇总成一个表格,第一列标识代理所满足的某方面需求;第一行表示不同阶段的代理;在每个单元格里,我们用来表示是否有该类能力,以及能力的程度(1-5 个,5 个表示充分支持,1 个表示基本支持)。同时,我们还列出了各个阶段的标志性软件:

  • 配置文件阶段:Squid、Httpd、Nginx。
  • 配置语言阶段:Varnish、Haproxy
  • 脚本语言阶段:Nginx+Lua、Nginx+JS
  • 集群阶段:Kong、Envoy
  • 云时代:Istio+Envoy、Linkerd、Pipy
序号 需求 配置文件阶段 配置语言阶段 脚本语言阶段 集群阶段 云时代 备注
1 连通性 * * * * * * * * * * * * * * * * * * * * * * * * * 连通性在云时代开始使用内核技术,如 iptables 和 ebpf;之前都只有 user space 进程模式
2 可靠性 * * * * * * * * * * * * * * * * * * * * * * * * * 可靠性一直是代理软件的最重要的基础能力
3 高性能 * * * * * * * * * * * * * * * * * * * * * * 性能包括吞吐率、延迟、错误率、偏离均值的幅度。其中延迟采用 P99,P999 等度量指标。早期代理软件有长尾效应,因此 P99 以上指标没有后期软件好。采用高性能脚本的代理,在返回相同内容时,通常性能优于前一代。采用 proactive 技术的代理,在提供相同性能的同时更稳定(偏离均值的幅度更小)
4 灵活性 * * * * * * * * * * * * * * * 第五代代理相比第四代显著的增强了多协议支持能力,因此我们给这一代五星的评价。并且第五代的处理模型可以适配多种协议,具有通用性,这方面要优于第四代
5 扩展性 * * * * * * * * * * * * * 和 灵活性 类似,第五代代理除了支持核心功能扩展开发、7 层逻辑扩展开发,还支持多协议,因此我们给出比第四代多一星的评价
6 硬件兼容性 * * * * * * * * * * * * * * * * * * * * 使用 C 或者 C++ 开发的代理,通常在硬件兼容性上都更好一些,社区也更活跃的迁移程序到新的硬件架构。使用 RUST 和 Go 和 Lua 开发的代理,在硬件兼容的迁移进度向相对缓慢
7 系统兼容性 * * * * * * * * * * * * * * * * * * * 系统 主要包括两个方面,一个是操作系统,一个是云平台。在操作系统兼容性方面,每一代的代理相差不多;但是在云平台兼容性方面,第四代和第五代代理都更充分的考虑并实现了和云的兼容。相比之下,第五代比第四代显著的差异是对多租户的支持能力
8 管理易用性 * * * * * * * * * * * * * 管理易用性是指针对运维和管理员角色的功能。第一二代主要以配置文件为主,基于此使用配置管理工具实现了自动化和批量的管理。第三代除了管理配置文件,需要进一步管理脚本源文件;但本质上和第一二代的管理易用性没有有显著差异。第四代提供 REST 接口,显著提升了管理的易用性。第五代除了 REST 以外,通常提供了针对云的控制平面,用来管理代理;同时对外提供多种接口以适配其他的管理需求,如监控、审计、统计等
9 用户易用性 * * * * * * * * 前三代代理主要用户就是运维和管理员。第四代时候,管理员开始把部分功能对用户提供,开始出现 as-a-Service 的模式。第五代则更多的考虑了用户使用的场景,更多的提供了面向租户的能力
10 开发易用性 * * * * * * * * * * * * * * * 围绕代理的开发包括两个方面,一个是在代理内部实现功能,一个是在代理外边实现对代理的管理能力。前三代都提供了内部开发的接口;后两代同时提供了内外接口。第五代相比第四代显著的改进是提供了云接口
11 核心接口可编程 * * * * * 每一代代理都提供了核心接口扩展的能力,但是这些接口过于底层,掌握难度较大
12 功能扩展可编程 * * * * * * * * * * * * * * * 提供更高效的功能扩展能力,是每一代代理都在进步的部分。是 可编程 代理的核心指标
13 协议扩展可编程 * * * * * 前三代主要面向单一协议,或者固定协议。从第四代开始,用户开始寻求多协议和定制协议的支持。第五代则把协议扩展作为核心能力在设计中就做了充分的考虑
14 结构化脚本编程 * * * * * * * * * * * 第三代代理开始显著的关注脚本的结构化问题;而第四代和第五代则努力为更加结构化的编程做了准备,如 Envoy 尝试通过 WASM 提供对多语言的支持;pipy 则是引入高性能 JS 脚本,提供更好的结构化
15 配置管理可编程 * * * * * 前三代代理的配置主要面向运维管理人员,外部的配置管理工具都是基于这个前提。第四代开始支持 REST 管理接口;第五代则进一步提供了标准的云接口做配置管理
16 资源扩展可编程 * * * * * * * * * 前三代代理扩容主要是增加线程或者进程的数量。第四代提供了进程的横向扩展能力,也就是集群能力。第五代在第四代基础上,一方面提供了横向扩展能力,一方面提供了更小资源下的能力,以支持更细粒度的计量与计费;也就是不仅支持增量扩展,也提供了减量扩展的能力,而这些能力,都提供编程接口
17 租户扩展可编程 * * * 云是和第四代代理同步出现的事物,租户 作为云的核心特征,并没有在第四代中获得很好的支持。第五代则以云为大前提进行了设计,考虑和提供了租户自己编程扩展的可能性

总结

上表中的 #11~#17 是代理 可编程 的具体的多个方面,这些方面也同时构成了 Why Programming Proxy 的答案:

  • 代理的内部功能需要扩展,既包括底层核心能力的扩展,也包括支持更多协议的扩展,还包括面向七层的处理能力(转发、路由、判断、访问控制等);这些七层的处理能力,要求更为便捷的编程方式,也就是脚本化、结构化的编程能力
  • 代理需要对外部提供接口,以集成到更大的管理体系中(如云平台),包括配置管理、资源管理等
  • 代理需要提供面向不同角色的扩展能力,包括运维、管理员、资源提供者、租户,这些扩展能力在某种程度上都需要 可编程
  • 同时,就像任何 可编程 组件一样,可编程代理 需要有配套的文档、开发手册、代码管理、依赖管理、构建和部署工具,并且最好有可视化的开发、调试环境。这些得到充分满足以后,用户才能够更好的管理网络流量,以及流量之上所承载的业务
Flomesh

Flomesh

Pipy 是面向云、边缘和 IoT 的可编程网络代理。

编辑本页