为什么需要可编程代理
经常会有人问 “当你们说可编程代理的时候,那么什么是可编程代理,为什么需要可编程代理”?本文从不同角度回答这个问题。首先会简单的介绍代理;然后讨论下代理在发展过程中的阶段划分;基于这些阶段的划分,讨论每一个阶段相比于上一个阶段的改进之处,以及为什么需要这些改进,同时我们讨论下 “可编程” 所包含的几个层面;最后我们总结下 “为什么需要可编程代理”。
什么是代理及代理的功能
代理是代理服务器的简称,代理服务器通常部署在两个互相隔离的网络的中间处,既能访问一侧网络也能访问另一侧网络,通过把一侧的数据搬运到另一侧,实现了网络的连通。代理是一种串路网络设备,自从计算机网络诞生,代理就存在了。由于代理是串路的,因此代理在实现网络连通功能的同时也衍生出新的功能和使用场景:
路由:代理在转发数据的时候,根据数据的特征,转发到不同的目的地
负载均衡:在转发过程中,通过把数据分发到不同的目的地,提高吞吐量、避免目的地单点故障。负载均衡逐渐成为代理细分功能的一个领域
故障迁移:在转发过程中,当目的地出现故障时候,代理可以把数据转发到备用的目标,对请求方提供不间断的服务
访问控制:代理可以决定某些流量可以通过,哪些流量需要被拦截。WAF 是典型的代理在细分领域的应用
身份识别:访问控制很多时候需要基于身份信息,因此代理通常也具有身份识别的功能
网络加速:代理通过缓存数据的方式加速网络访问
指标采集:代理对经过的数据进行统计,汇总给 NPM 软件用于网络优化及网络规划
信息安全:除了访问控制外,代理还可以用于安全审计、TLS/SSL 卸载、数据加密等,满足安全需求
提供桥接两个网络功能的除了代理还有路由器。路由器工作在网络的 3 层;而代理工作在 3 层以上,或者说 4 层和 7 层。
软件代理的发展
软件代理服务器在发展过程中,大致上经历了如下几个阶段:
配置文件时代。代理类软件(最主要是开源软件),占到了网络基础设施类软件的大多数,这些软件在细分领域提供了不同的功能,比如针对不同协议的代理、比如侧重负载均衡的代理、比如侧重缓存加速的代理。这一代类软件,都是基于配置的。用户在配置文件中设置参数、配置规则,然后启动服务进程执行这些规则
配置语言时代。配置难于表达复杂逻辑,所以很多代理软件在配置基础上引入了很薄的脚本能力,我们一般称为 “配置语言” 或者说 DSL,比如 Haprxoy 的 ACL,Varnish 的 VCL
脚本语言时代。当逻辑进一步复杂的时候,配置语言也会难于表达;同时,当配置语言数量多到一定程度时候,配置语言本身的管理,会有很大难度。就像 shell 脚本可以写简单的逻辑,但是当 shell 代码多到一定程度的时候,通常会进一步选择 Perl 或者 Python 这些更加结构化的脚本语言。Proxy 支持脚本语言,既有脚本语言的便利性,也有编程语言结构化的优势。这类的例子如 Openresty(Nginx + Lua)、Nginx Plus(Nginx + NJS). 同时,这类例子里也包括大量应用类编程语言实现的代理服务器,比如基于 NodeJS 的 StrongLoop、Spring Gateway 等,这些应用类编程语言往往自己就有脚本支持能力
集群时代。脚本语言解决了代理中复杂逻辑的模块化、结构化实现难点。此时进一步的需求是把代理和其他的管理控制工具集成,因此需要有 REST 接口。外部的控制平面可以通过 REST 接口动态的设置脚本中的逻辑。同时,人们对代理的使用也从单实例上升到集群化,因此这一类代理通常都自身支持集群能力,比如 Enovy 和基于 Openresty 的 Kong,他们通过某种集中或者共享方式实现集群能力,同时对外提供 REST 接口。对于上边说的 #4 类型的代理,通过配置管理,一般也可以实现集群管理;并且配置管理工具也可以对外暴露 REST 接口。比如使用 Ansible + Nginx 的方案,实现了和 #5 类似的能力。相比之下,#4 的方案需要要更多的组件形成方案,而 #5 的方案更收敛
云时代。在 #5 的基础上,代理采用分布式的方式部署,最常见的场景是为每个应用进程部署一个代理,也就是 sidecar proxy 模式。在采用分布式以后,针对不同的上游服务,采用不同的规则和策略,也就是多租户能力。不同上游服务,不仅在逻辑上有独立的规则和策略;在物理上也进一步提供了隔离,实现进程级和接口级的细粒度管理。如果我们把服务网格的控制平面和数据平面看作一个整体,那么服务网格是这个领域的代表,典型的比如 Istio+Envoy,Linkerd+Linkerd Proxy。Pipy 就是这个阶段的产物
在如上的各个阶段里,每一个阶段都比上一个阶段有所改进,概要的说:
#2 比 #1 增加了基本的脚本能力。这种基本的脚本能力,在配置文件的基础上,增加了动态能力。比如在运行期获取请求的特征(如获取 HTTP Header),然后根据这些特征做动态的逻辑判断,进行特定的操作
#3 比 #2 增加完整的脚本能力,此时可以结构化和模块化的编写脚本逻辑。在 #2 的时代,当逻辑复杂的时候,脚本的量也会大幅增加,此时结构化的脚本能力成为一种必需
#4 比 #3 多了 REST 接口和集群能力。当需要水平扩容代理能力的时候,需要把多个代理实例组成集群;集群内的实例共享配置和脚本,并且用户可以通过 REST 接口去管理配置和脚本
#5 比 #4 多了分布式能力,主要体现在同一个集群内不同的实例所运行的脚本和配置是不同的。在 #4 模式下,对于不同的上游服务,通常会有不同的配置和策略,比如不同的认证方式、不同的访问控制机制等;当上游服务逐渐增多的时候,这些不同上游服务的配置在逻辑上是分离的,但是在物理上都运行在同一个代理进程里。这种逻辑上分离的配置和策略运行在同一个物理进程中的情况,带来一些弊端:更多的逻辑运行在一个进程内,带来了更多的复杂性;不同上游服务的共享 CPU 和内存等资源,导致互相影响;如果某一个上游服务的脚本出现了安全漏洞,会导致其他上游服务的配置泄漏,存在安全隐患。#5 模式对于 #4 模式的改进在于每个上游服务的代理进程是独立的、彼此隔离的。他们受同一个集群管理者管理,但是在运行中的配置和脚本是独立和隔离的。这种隔离的特性,是多租户环境中的一种强需求 -- 不同的上游服务属于不同的租户,租户之间不应该互相影响,也不应该知道彼此的配置。#5 可以认为是 "#4 多集群 " 的极限模式 -- 最极端情况下,每一个进程都有自己的配置
代理的需求演化
让我再从另一个视角看下代理的演化过程--需求的演化。
第一代的代理主要是实现了代理功能,并且提供了基础的可配置能力;同时,网络设备,尤其是串路网络设备的特性,要求代理是高可靠的;网络的海量数据实时传输的特性,要求代理高吞吐、低延迟、低资源。和所有的软件一样,代理也需要支持模块化和可扩展,这个阶段的代理主要采用 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
总结
上表中的 #11~#17 是代理 可编程 的具体的多个方面,这些方面也同时构成了 Why Programming Proxy 的答案:
代理的内部功能需要扩展,既包括底层核心能力的扩展,也包括支持更多协议的扩展,还包括面向七层的处理能力(转发、路由、判断、访问控制等);这些七层的处理能力,要求更为便捷的编程方式,也就是脚本化、结构化的编程能力
代理需要对外部提供接口,以集成到更大的管理体系中(如云平台),包括配置管理、资源管理等
代理需要提供面向不同角色的扩展能力,包括运维、管理员、资源提供者、租户,这些扩展能力在某种程度上都需要 可编程
同时,就像任何 可编程 组件一样,可编程代理 需要有配套的文档、开发手册、代码管理、依赖管理、构建和部署工具,并且最好有可视化的开发、调试环境。这些得到充分满足以后,用户才能够更好的管理网络流量,以及流量之上所承载的业务
版权声明: 本文为 InfoQ 作者【Flomesh】的原创文章。
原文链接:【http://xie.infoq.cn/article/0360cb7a8f5a08e8a87284360】。文章转载请联系作者。
评论