我们都知道,Dropbox一个使用免费的文件同步、备份、共享云存储供应商。目前已经有超过一亿用户在使用Dropbox。那么其基础架构是什么样的呢,本文我们以一个方面的架构案例学习,一斑窥豹,了解其流量代理的基础架构。同时了解基于Nginx的旧的流量基础架构及其痛点,以及通过迁移到Envoy所获得的好处。
概述Dropbox老的代理层架构和其他很多公司一样,也是用的Nginx,其配置大部分是静态的,并结合使用Python2,Jinja2和YAML进行渲染。对其进行任何更改都需要完全重新部署。所有动态部分,例如上游管理和统计信息导出器,都用Lua编写。任何足够复杂的逻辑都移到了用Go语言编写的下一级代理层。
在Dropbox这种架构存在一些问题:
Dropbox内部(私有)和外部API逐渐从REST迁移到gRPC,需要来自代理的各种转码功能。
协议缓冲区实际上已成为服务定义和配置的标准。
所有软件,无论使用哪种语言,都使用Bazel构建和测试。
Dropbox工程师大量参与开源社区中的基本基础结构项目。
另外,Nginx操作维护成本非常高,主要表现在:
配置生成逻辑太灵活,无法在YAML,Jinja2和Python之间进行分配。
系统监控用的Lua,日志解析和基于系统的监控组合。
越来越依赖第三方模块会影响稳定性,性能以及后续升级的成本。
Nginx部署和流程管理与其余服务完全不同。它完全依赖于其他系统的配置:syslog,logrotate等,而无法与基本系统完全分离。
Dropbox流量移至Envoy时,Dropbox必须无缝迁移一个系统,该系统已经处理了数千万个打开的连接,每秒数百万个请求和数以兆计的带宽,这使得这Dropbox了世界上最大的Envoy用户之一。
Bandaid第二层代理:在Dropbox内部,在很大程度上依赖于基于Golang的代理Bandaid。它可以与Dropbox基础架构很好地集成,它可以访问内部Golang库的广阔生态系统:监控,服务发现,速率限制等。他们曾考虑过从Nginx迁移到Bandaid,但存在一些问题:
Golang比C/C 占用更多资源。资源的低利用率对于边缘节点尤其重要,因为无法轻松地"自动扩展"边缘节点的部署。
CPU开销主要来自GC,HTTP解析器和TLS,后者的优化程度低于Nginx/Envoy使用的BoringSSL。
"按请求分类"模型和GC开销大大增加了高连接服务中的内存需求。
Go的TLS堆栈不支持FIPS。
Bandaid在Dropbox之外没有社区,只能依靠自己进行功能开发。
架构选型从让从Nginx迁移到Envoy,Dropbox考虑了很多方面的优势,主要包括运维和开发能各个方面的维度。
性能Nginx的架构是事件驱动和多进程的。它支持SO_REUSEPORT,EPOLLEXCLUSIVE和工作者到CPU的固定。尽管它是基于事件循环的,但它不是完全非阻塞的。这样某些操作(例如打开文件或访问/错误日志记录)可能会导致事件循环停止(,aio_write和线程池),从而带来导致后续的延迟。
Envoy具有类似的事件驱动的体系结构,但是其使用线程而不是进程。它还具有SO_REUSEPORT支持(带有BPF过滤器支持),并且依赖libevent进行事件循环实现。Envoy在事件循环中没有任何阻塞的IO操作。甚至日志记录也以非阻塞方式实现,因此不会引起停顿。
通过Dropbox内部的测试结果表明,在大多数测试工作负载下,Nginx和Envoy的性能相似:每秒高请求(RPS),高带宽以及混合的低延迟/高带宽gRPC代理。但是有几个明显的差异:
Nginx显示出更高的长尾延迟。这主要是由于在I/O繁重的情况下事件循环停滞所致,尤其是与SO_REUSEPORT一起使用时,因为在这种情况下,表现为当前阻塞worker接受连接。
不带统计信息收集的Nginx性能是Envoy的一部分,Lua统计信息收集在高RPS测试中将Nginx减慢了3倍。考虑到对lua_shared_dict的依赖,这是无可避免的。
由于Envoy不会受到这两个问题的困扰,在迁移到Envoy之后,可以释放出60%的服务器。
可观察性可观察性是任何产品最基本的操作需求,但对于代理这样的基础架构而言尤其如此。在迁移期间,这一点尤为重要,以便监控系统可以检测到任何问题,出现故障时候也可以报告任何问题。
非商业Nginx带有一个"stub status"模块,支持以下7个统计信息:
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
这显然是不够的,Dropbox添加了一个简单的log_by_lua处理程序,该程序根据Lua中可用的标头和变量添加每个请求的统计信息:状态代码,大小,缓存命中率等。其简单状态统计功能如下:
function _M.cache_hit_stats(stat)
if _var.upstream_cache_status then
if _var.upstream_cache_status == "HIT" then
stat:add("upstream_cache_hit")
else
stat:add("upstream_cache_miss")
end
end
end
除了各个请求的Lua统计信息外,Dropbox还有一个非常脆弱的error.log解析器,负责upstream,http,Lua和TLS错误分类统计。
最重要的是,有一个单独的导出器来收集Nginx内部状态:自上次重新加载以来的时间,worker数量,RSS/VMS大小,TLS证书使用期限等。
一个典型的Envoy设置可以提供了数千种不同的指标(以prometheus格式),描述了代理流量和服务器的内部状态:
curl -s http://localhost:3990/stats/prometheus | wc -l
14819
包括具有不同汇总的大量统计信息:
每个群集/每个上游/每个虚拟主机的HTTP统计信息,包括连接池信息和各种时序直方图。
每个监听的TCP/HTTP/TLS下游连接统计信息。
从基本版本信息和正常运行时间到内存分配器状态和不赞成使用的功能使用情况计数器,各种内部/运行时状态。
Envoy的管理界面也非常丰富。不仅通过/certs,/clusters和/config_dump接口提供其他结构化的统计信息,而且还具有非常重要的操作功能:
通过/logging动态更改错误日志记录的能力。可以能够在几分钟内解决相当模糊的问题。
/cpuprofiler,/heapprofiler,/contention在不可避免的性能故障排除期间肯定会非常有用。
/runtime_modify 接口允许任意更改配置参数集而无需推送新配置,而新配置可用于功能控制等。
除统计数据外,Envoy还支持插入的跟踪提供程序。这不仅对拥有多个负载平衡层的Traffic团队有用,对于希望从边缘到应用服务器端到端跟踪请求延迟的应用程序开发人员也很有用。
最后但并非最不重要的一点是,Envoy能够通过gRPC流式传输访问日志。这消除了流量团队syslog-to-hive配置的负担。此外,在Dropbox生产中启动通用gRPC服务比添加自定义TCP/UDP侦听器更容易。
像其他所有操作一样,Envoy中访问日志的配置通过gRPC管理服务即访问日志服务(ALS)进行。管理服务是将Envoy数据平面与生产中的各种服务集成的标准方法。
功能集成Nginx的集成方法最好描述为"Unix-ish"。配置是非常静态的。它严重依赖文件(例如配置文件本身,TLS认证和票证,允许列表/阻止列表等)和众所周知的行业协议(通过HTTP到syslog和auth子请求)。对于小型安装而言,这样的简单性和向后兼容性是一件好事,因为可以使用几个Shell脚本轻松实现Nginx的自动化。
但是随着系统规模的扩大,可测试性和标准化变得越来越重要。
Envoy更支持流量数据平面,控制平面以及和其他基础架构集成的理念。通过提供通常被称为xDS的稳定API,鼓励使用protobufs和gRPC。Envoy通过查询一个或多个这些xDS服务来发现其动态资源。
这些支持对于Dropbox尤为有用,Dropbox的所有服务已在内部通过基于gRPC的API进行交互。Dropbox已经实现了自己的xDS控制平面版本,该版本将Envoy与Dropbox的配置管理,服务发现,密码管理和路由信息集成在一起。
以下是一些可用的xDS服务,其Nginx替代品及其应用示例:
(ALS)可以动态配置访问日志目标,编码和格式。对比一下Nginx的log_format和access_log的动态版本。
端点发现服务可提供有关群集成员的信息。这类似于Nginx配置中动态更新的上游块服务器条目列表(例如,Lua 的balancer_by_lua_block的 )。在本案例中,将其代理到内部服务发现中。
秘密发现服务提供了各种与TLS相关的信息,这些信息将涵盖各种ssl_ *指令(以及lua 的ssl *模块。),将此接口调整为适合的秘密分发服务。
运行时发现服务提供了运行时标志。基于检查来自Lua的各种文件的存在,我们在Nginx中实现此功能的方法非常笨拙。这种方法很快会在各个服务器之间变得不一致。Envoy的默认实现也是基于文件系统的,但是将RTDS xDS API指向了分布式配置存储。这样,可以一次控制整个集群(通过具有类似sysctl的界面的工具),并且在不同服务器之间不会出现意外的不一致。
(RDS)将路由映射到虚拟主机,并允许对标头和过滤器进行其他配置。
用Nginx来讲,它们类似于带有set_header/proxy_set_header和proxy_pass的动态位置块。在较低的代理层上,直接从服务定义配置中自动生成这些。
Dropbox内部的Envoy控制平面实现了越来越多的xDS API。被部署为生产中的常规gRPC服务,并充当基础结构构建块的适配器。它通过一组通用的Golang库来执行此操作,以与内部服务进行对话,并通过稳定的xDS API向Envoy公开它们。整个过程不涉及任何文件系统调用,信号,cron,logrotate,syslog,日志解析器等。
可配置性Nginx具有简单易读的配置,这是不可否认的优势。但是,随着配置变得越来越复杂,并且开始生成代码,该优势就没有了。
上面提到过,Dropbox的Nginx配置是通过Python2,Jinja2和YAML的混合生成的。一个典型的示例是:
{% for server in servers %}
server {
{% for error_page in server.error_pages %}
error_page {{ error_page.statuses|join(' ') }} {{ error_page.file }};
{% endfor %}
...
{% for route in service.routes %}
{% if route.regex or route.prefix or route.exact_path %}
location {% if route.regex %}~ {{route.regex}}{%
elif route.exact_path %}= {{ route.exact_path }}{%
else %}{{ route.prefix }}{% endif %} {
{% if route.brotli_level %}
brotli on;
brotli_comp_level {{ route.brotli_level }};
{% endif %}
...
Nginx配置生成方法存在一个很大的问题,那就是配置生成涉及的所有语言都允许替换and/or逻辑。YAML具有anchors,Jinja2有循/if和macroses。如果一二没有干净的数据模型,复杂性会迅速增加。
这个问题可以解决,但是还存在两个基本问题:
没有关于配置格式的声明性描述。如果想以编程方式生成和验证配置,则需要自己进行发明。
从C代码的角度来看,语法上有效的配置仍然可能无效。例如,某些与缓冲区相关的变量具有值限制,对齐限制以及与其他变量的相互依赖性。
为了从语义上验证配置,需要通过nginx -t运行它。
相对比Envoy具有用于配置的统一数据模型:其所有配置都在协议缓冲区中定义。这不仅解决了数据建模问题,而且还将键入信息添加到配置值中。鉴于protobuf是Dropbox最常用的组件,并且是描述/配置服务的通用方式,因此集成变得非常容易。
Envoy的配置生成器基于protobufs和Python3。所有数据建模均在原始文件中完成,而所有逻辑均在Python中进行。下面是一个例子:
from dropbox.proto.envoy.extensions.filters.http.gzip.v3.gzip_pb2 import Gzip
from dropbox.proto.envoy.extensions.filters.http.compressor.v3.compressor_pb2 import Compressor
def default_gzip_config(
compression_level: Gzip.CompressionLevel.Enum = Gzip.CompressionLevel.DEFAULT,
) -> Gzip:
return Gzip(
# Envoy's default is 6 (Z_DEFAULT_COMPRESSION).
compression_level=compression_level,
# Envoy's default is 4k (12 bits). Nginx uses 32k (MAX_WBITS, 15 bits).
window_bits=UInt32Value(value=12),
# Envoy's default is 5. Nginx uses 8 (MAX_MEM_LEVEL - 1).
memory_level=UInt32Value(value=5),
compressor=Compressor(
content_length=UInt32Value(value=1024),
remove_accept_encoding_header=True,
content_type=default_compressible_mime_types(),
),
)
在某些情况下,经过类型检查的protobuf在逻辑上可能是无效的。在上面的示例中,gzip window_bits只能取9到15之间的值。可以通protoc-gen-validate protoc插件轻松定义这种限制:
google.protobuf.UInt32Value window_bits = 9 [(validate.rules).uint32 = {lte: 15 gte: 9}];
最后,使用正式定义的配置模型的潜在好处是,它自然地导致文档与配置定义并置在一起。
可扩展性将Nginx扩展到标准配置所不能提供的范围之外,通常需要编写C模块。Nginx的开发指南对可用的构建块进行了扎实的介绍。就是说,这种方法是相对重量级的。实际上,需要相当资深的软件工程师来安全地编写Nginx模块。
就模块开发人员可用的基础架构而言,他们可以期待基本容器,例如哈希表/队列/红黑树,内存管理以及请求处理所有阶段的hook。还有一些外部库,例如pcre,zlib,openssl,当然还包括libc。
为了提供更轻量级的功能扩展,Nginx提供了Perl和Javascript接口。可悲的是,它们的能力都相当有限,大部分都局限于请求处理的内容阶段。
由社区采用最常用的扩展方法是基于第三方的lua-nginx-module模块及各种OpenResty库。这种方法几乎可以挂接到请求处理的任何阶段。
Dropbox使用log_by_lua收集统计信息,还使用balancer_by_lua进行动态后端重新配置。
Envoy的主要扩展机制是通过C 插件。过程的记录不如Nginx的那样,但它更简单。部分原因是:
干净且注释良好的界面。C 类充当自然的扩展和文档点。
C 14语言和标准库。从诸如模板和lambda函数之类的基本语言功能,到类型安全的容器和算法。通常,编写现代C 14与使用Golang并没有多大区别,或者甚至连说Python也没有什么不同。
C 14及其标准库以外的功能。由abseil库提供,这些包括来自较新C 标准的直接替换,具有内置静态死锁检测和调试支持的互斥锁,更多/更有效的容器等等。
通过简单地实现Envoy stats接口,能够仅用200行代码将Envoy与Vortex2(Dropbox的监视框架)集成在一起。
Envoy通过moonjit获得了Lua支持,moonjit是具有改进的Lua 5.2支持的LuaJIT分支。与Nginx的第三方Lua集成相比,它的功能和hook要少得多。由于开发,测试和故障排除解释代码的额外复杂性的成本,这使Envoy的Lua吸引力大大降低。本案例中,决定避免使用它,而仅将C 用于Envoy的可扩展性。
Envoy与其他Web服务器的不同之处在于它对WebAssembly(WASM)的新兴支持-一种快速,可移植且安全的扩展机制。WASM不能直接使用,而可以用作任何通用编程语言的编译目标。Envoy实现了WebAssembly for Proxies规范(还包括参考Rust和C SDK),该规范描述了WASM代码和通用L4/L7代理之间的边界。代理服务器代码和扩展代码之间的这种分隔允许安全的沙箱操作,而WASM低级紧凑二进制格式允许接近本机的效率。
最重要的是,在Envoy中,代理wasm扩展与xDS集成在一起。这样可以进行动态更新,甚至可以进行潜在的A/B测试。
借助WASM,服务提供商可以安全有效地在其边缘运行客户的代码。客户从可移植性中受益:他们的扩展可以在实现代理代理ABI的任何云上运行。另外,它允许您的用户使用任何语言,只要它可以编译为WebAssembly。这使他们能够安全有效地使用更广泛的非C 库集。
当前, Dropbox还没有使用WebAssembly。但是,当Go for SDK for proxy-wasm可用时,这可能会改变。
构建和测试体系默认情况下,Nginx是使用基于shell的自定义配置系统和基于make的构建系统构建的。这是简单而优雅的方法,但是将其集成到Bazel构建的monorepo中需要花费大量的精力,才能获得增量,分布式,密封和可复制构建的所有优点。
注:谷歌开源了由Bazel构建的Nginx,该版本由Nginx,BoringSSL,PCRE,ZLIB和Brotli库/模块组成。
在测试方面,Nginx 在单独的存储库中有一组Perl驱动的集成测试,没有任何单元测试。
鉴于Dropbox对Lua的大量使用以及缺乏内置的单元测试框架,求助于使用模拟配置和基于Python的简单测试驱动程序进行测试:
class ProtocolCountersTest(NginxTestCase):
@classmethod
def setUpClass(cls):
super(ProtocolCountersTest, cls).setUpClass()
cls.nginx_a = cls.add_nginx(
nginx_CONFIG_PATH, endpoint=["in"], upstream=["out"],
)
cls.start_nginxes()
@assert_delta(lambda d: d == 0, get_stat("request_protocol_http2"))
@assert_delta(lambda d: d == 1, get_stat("request_protocol_http1"))
def test_http(self):
r = requests.get(self.nginx_a.endpoint["in"].url("/"))
assert r.status_code == requests.codes.ok
最重要的是,通过预处理所有生成的配置(例如,用127/8替换所有IP地址,切换到自签名TLS证书等)并在结果上运行nginx -c来验证所有语法的语法正确性。
在Envoy方面,主要的构建系统已经是Bazel。因此,将其与monorepo集成起来很简单:Bazel轻松地允许添加外部依赖项。Dropbox还使用copybara脚本来同步Envoy和udpa的protobuf。当需要进行简单的转换而无需永远维护大型补丁集时,Copybara十分方便。
使用Envoy,可以灵活地使用带有一组预先编写的模拟的单元测试(基于gtest / gmock)或Envoy的集成测试框架,或同时使用两者。对于每一个小小的变化,都不再需要依靠缓慢的端到端集成测试。
开源Envoy开发要求更改以具有100%的单元测试覆盖率。通过Azure CI管道针对每个拉取请求自动触发测试。
使用google /becnhmark对性能敏感的代码进行微基准测试也是一种常见的做法是:
bazel run --compilation_mode=opt test/common/upstream:load_balancer_benchmark -- --benchmark_filter=".*LeastRequestLoadBalancerChooseHost.*"
BM_LeastRequestLoadBalancerChooseHost/100/1/1000000 848 ms 449 ms 2 mean_hits=10k relative_stddev_hits=0.0102051 stddev_hits=102.051
...
改用Envoy之后,可以完全依靠单元测试来进行内部模块开发:
TEST_F(CourierClientIdFilterTest, IdentityParsing) {
struct TestCase {
std::vector<std::string> uris;
Identity expected;
};
std::vector<TestCase> tests = {
{{"spiffe://prod.dropbox.com/service/foo"}, {"spiffe://prod.dropbox.com/service/foo", "foo"}},
{{"spiffe://prod.dropbox.com/user/boo"}, {"spiffe://prod.dropbox.com/user/boo", "user.boo"}},
{{"spiffe://prod.dropbox.com/host/strange"}, {"spiffe://prod.dropbox.com/host/strange", "host.strange"}},
{{"spiffe://corp.dropbox.com/user/bad-prefix"}, {"", ""}},
};
for (auto& test : tests) {
EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(testing::Return(test.uris));
EXPECT_EQ(GetIdentity(ssl_), test.expected);
}
}
亚秒级的测试往返对生产率产生复合影响。它使能够付出更多的努力来增加测试范围。能够在单元测试和集成测试之间进行选择,使我们能够平衡Envoy测试的覆盖范围,速度和成本。
Bazel是开发人员经历过的最好的事情之一。它的学习曲线非常陡峭,并且是一笔大笔的前期投资,但在投资上却有很高的回报:增量构建,远程缓存,分布式构建/测试等。
Bazel很少讨论的好处之一是,能够查询甚至扩展依赖图。依赖关系图的编程接口以及跨所有语言的通用构建系统是一项非常强大的功能。它可以用作linters,代码生成,漏洞跟踪,部署系统等之类的基础构建块。
安全性Nginx的代码面非常小,具有最小的外部依赖性。通常只对生成的二进制文件看到3个外部依赖项:zlib(或其更快的变体之一),TLS库和PCRE。Nginx具有所有协议解析器,事件库的自定义实现,甚至还可以重新实现某些libc函数。
在某些时候,Nginx被认为非常安全,以至于它被用作OpenBSD中的默认Web服务器。后来,两个开发社区陷入了混乱,导致创建了httpd。
这种极简主义在实践中得到了回报。Nginx在过去11年中仅报告了30个漏洞。
另一方面,Envoy拥有更多的代码,尤其是当考虑到C 代码比用于Nginx的基本C语言要密集得多时。它还包含来自外部依赖项的数百万行代码。从事件通知到协议解析器的所有内容均已卸载到第三方库。这会增加攻击面,并使生成的二进制文件膨胀。
为了解决这个问题,Envoy高度依赖现代安全实践。它使用AddressSanitizer,ThreadSanitizer和MemorySanitizer。它的开发人员甚至超越了这个范围,采用了模糊测试。但是由于代码量的膨胀带来的安全问题却仍然是个问题。2年内Envoy就出现了22个安全漏洞。
为了应对增加的漏洞风险,Dropbox内部使用了来自上游OS供应商Ubuntu和Debian的最佳二进制强化安全实践。所有边缘节点的二进制文件定义了特殊的强化构建配置文件。它包括ASLR,堆栈保护器和符号表强化:
build:hardened --force_pic
build:hardened --copt=-fstack-clash-protection
build:hardened --copt=-fstack-protector-strong
build:hardened --linkopt=-Wl,-z,relro,-z,now
在大多数环境中,像Nginx这样的分叉式Web服务器在堆栈保护器上都存在问题。由于主进程和工作进程共享同一堆栈canary,并且在canary验证失败时,工作进程被*死,因此可以在大约1000次尝试中逐位对canary进行暴力破解。使用线程作为并发原语的Envoy不受此攻击的影响。
Dropbox还尽可能加强对第三方的依赖,其在FIPS模式下使用BoringSSL,该模式包括启动自检和二进制文件的完整性检查。还考虑在某些边缘Canary服务器上运行支持ASAN的二进制文件。
功能性Nginx最初是一个Web服务器,专门用于以最少的资源消耗提供静态文件。它的功能是最重要的:静态服务,缓存和range缓存。
但是,在代理方面,Nginx缺少现代基础架构所需的功能。后端没有HTTP/2。支持gRPC代理,但没有连接多路复用。不支持gRPC转码。最重要的是,Nginx的"开放核"模型限制了可以纳入代理的开源版本的功能。结果,某些重要功能(如统计信息)在"社区"版本中不可用。
相比之下,Envoy已经发展成为一个入口/出口代理,经常用于重载gRPC的环境。但是它的Web服务功能支持是基本的:,仍在进行中的缓存,brotli或预压缩。对于这些用例,仍然有一个小的后备Nginx设置,Envoy将其用作上游群集。
Envoy还对许多与gRPC相关的功能提供了本机支持:
gRPC代理。这是一项基本功能,使Dropbox能够为应用程序(例如Dropbox桌面客户端)端对端使用gRPC。
HTTP/2到后端。使Dropbox可以大大减少流量层之间的TCP连接数,从而减少内存消耗和保持活动的流量。
gRPC→HTTP桥( 反向)这些使Dropbox能够使用现代gRPC堆栈公开旧版HTTP / 1应用程序。
gRPC-WEB。使Dropbox即使在中间层(防火墙,IDS等)尚不支持HTTP/2的环境中也可以端到端使用gRPC。
gRPC JSON转码器。这使Dropbox能够将所有入站流量(包括Dropbox公共API)从REST转换为gRPC。
此外,Envoy还可以用作出站代理。我们使用它来统一其他几个用例:
出口代理:由于Envoy 添加了对HTTP CONNECT方法的支持,因此可以用作Squid代理的替代产品。Dropbox已经开始用Envoy替换Squid,不仅极大地提高了可视性,而且还通过使用通用数据平面和可观察性统一堆栈来减少操作麻烦(不再为统计信息解析日志)。
第三方软件服务发现:Dropbox依靠软件中的Courier gRPC库,而不是使用Envoy作为服务网格。但是,在需要一次性花费很少的精力将开源服务与服务发现连接起来的情况下,Dropbox确实使用了Envoy。例如,Envoy在分析堆栈中用作服务发现辅助工具。Hadoop可以动态发现其名称和日记节点。Superset可以发现气流,预存和配置单元后端。Grafana可以发现其MySQL数据库。
社区Nginx的开发非常集中。它的大部分开发都不对外。Nginx-devel邮件列表上有一些外部活动,并且官方Bug Tracker上偶尔有与开发相关的讨论。
Envoy开发是开放和分散的:通过GitHub问题/拉动请求,邮件列表和社区会议进行协调。
很难量化开发样式和工程社区,举例以HTTP/3开发为例。Nginx的QUIC和HTTP/3实现了。该代码是干净的,零外部依赖关系。但是开发过程本身并不透明。在此之前半年,Cloudflare提出了自己的Nginx HTTP/3实现。结果,Nginx社区现在有两套的HTTP/3实验版本。
在Envoy的情况下,基于rust的"quiche"(QUIC,HTTP等)库,HTTP/3的实现也在进行中。该项目的状态在GitHub问题中进行了跟踪。在Envoy公开可用的方式完成补丁之前。
对比所见,Envoy结构更加透明,极大地鼓励了协作。对Dropbox而言,他们设法对Envoy进行了许多中小型更改,包括从操作改进和性能优化到新的gRPC转码功能和平衡更改的所有内容。
迁移现状Dropbox已经将Nginx和Envoy并排运行了半年多,并通过DNS逐步将流量从一个切换到另一个。到目前为止,已经将各种各样的工作负载迁移到Envoy:
入口高吞吐量服务。Dropbox桌面客户端的所有文件数据都通过Envoy通过端到端gRPC提供。通过切换到Envoy,由于从边缘进行更好的连接重用,还略微提高了用户的性能。
入口高RPS服务。这是Dropbox桌面客户端的所有文件元数据。获得了端到端gRPC的相同好处,并且删除了连接池,这意味着我们不受每个连接一次请求的限制。
通知和遥测服务。通过该服务,Dropbox处理所有实时通知,因此这些服务器具有数百万个HTTP连接(每个活动客户端一个)。现在可以通过gRPC流而不是昂贵的长轮询方法来实现通知服务。
高吞吐量/高RPS混合服务。API流量(元数据和数据本身)。这样可以考虑公共gRPC API。甚至可以直接在边缘节点上转换为对现有的基于REST的API进行代码转换。
出口高吞吐量代理。在本案例中,是Dropbox与AWS的通信,主要是S3。最终能够实现从生产网络中删除所有Squid代理,从而使Dropbox只有一个L4 / L7数据平面。
要迁移的最后一件事是Dropbox官网本身。迁移之后,可以开始停用边缘Nginx部署。
遇到的问题当然,迁移并非完美无缺。但这并没有导致任何明显的中断。迁移中最困难的部分是Dropbox API服务。Dropbox通过公共API与很多不同的设备进行通信-包括从curl/wget支持的shell脚本和具有自定义HTTP / 1.0堆栈的嵌入式设备,到那里的每个可能的HTTP库。Nginx是经过实践检验的事实行业标准。可以理解,大多数库都隐式地依赖于某些行为。除了api用户所依赖的Nginx和Envoy行为之间的许多不一致之外,Envoy及其库中还存在许多错误。Dropbox在社区的帮助下迅速解决了所有这些问题并将其上游。Dropbox踩到的"大坑"包括:
合并URL中的斜杠。URL标准化和斜杠合并是Web代理的非常常见的功能。Nginx但是Envoy不支持后者。Dropbox向上游提交了补丁,以添加该功能,并允许用户使用merge_slashes选项选择加入。
。Nginx允许接收两种形式的 Host标头:example.com或example.com:port。Dropbox有几个曾经依赖于此行为的API用户。首先,通过在配置中复制虚拟主机(有端口和无端口)来解决此问题,但后来在Envoy端添加了一个忽略匹配端口的选项:strip_matching_host_port。
传输编码区分大小写。出于某种未知原因,一个小型子集API客户端使用了Transfer-Encoding:Chunked(请注意大写的" C")标头。这在技术上是有效的,因为RFC7230声明 Transfer-Encoding/TE标头不区分大小写。该修复程序很简单,已提交给上游。
同时具有Content-LengthTransfer-Encoding的:chunked。以前曾经与Nginx一起使用的请求,但因Envoy迁移而中断。另一方面,代理应该只删除Content-Length标头并转发请求。Dropbox还扩展了http-parse,以允许类库用户选择支持此类请求,并且目前正在努力将支持添加到Envoy本身。
还值得一提的是,还遇到了一些常见的配置问题:
断路配置错误。根据Dropbox的经验,如果将Envoy作为入站代理运行,尤其是在HTTP /1和HTTP/2混合环境中,则错误地设置断路器会在流量高峰或后端中断期间导致意外停机。如果不使用Envoy作为网状代理,请考虑放松它们。值得一提的是,默认情况下,Envoy中的断路限制非常严格。
缓冲。Nginx允许在磁盘上进行请求缓冲。这在您具有无法理解分块传输编码的传统HTTP/1.0后端的环境中尤其有用。Nginx可以通过将它们缓存在磁盘上,将它们转换为具有Content-Length的请求。Envoy有一个Buffer过滤器,但是由于无法将数据存储在磁盘上,因此我们只能在内存中缓冲多少内存。
总结本文我们介绍了Dropbox代理基础架构由Nginx迁移到Envoy的案例,总结了两者的区别和选型的考虑的各个方面因素以及迁移过程中遇到的坑和经验。
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved