Tinder转向Kubernetes的经验分享


大约两年前,Tinder决定将其平台迁移到Kubernetes。Kubernetes为我们提供了通过不可变部署推动Tinder工程实现容器化和低成本运维的机会,应用程序构建,部署和基础结构将定义为代码。
我们还希望解决规模和稳定性的挑战。当扩展变得至关重要时,过去我们经常会花费几分钟等待新的EC2实例联机。容器调度能在几秒钟内而不是几分钟内立即提供流量。
迁移并不容易,在我们2019年初的迁移期间,我们的Kubernetes集群达到了临界点,并且由于流量,集群大小和DNS而开始遇到各种挑战。我们解决了迁移200个服务并运行Kubernetes集群的有趣挑战,该集群总计1,000个节点,15,000个pod和48,000个运行容器。
从2018年1月开始,我们一直在努力完成迁移工作的各个阶段。我们首先将所有服务集中在一起,并将它们部署到一系列Kubernetes托管的临时环境中。从10月开始,我们开始有条不紊地将所有遗留服务转移到Kubernetes。到次年3月,我们完成了迁移,Tinder平台现在仅在Kubernetes上运行。

为Kubernetes建立镜像
在Kubernetes集群中运行的微服务有30多个源代码存储库。这些存储库中的代码使用不同语言(例如,Node.js,Java,Scala,Go)编写,具有针对同一语言的多个运行时环境。
构建系统旨适合每个微服务的完全可定制的“构建上下文”,这些微服务通常由Dockerfile和一系列shell命令组成。虽然它们的内容是完全可定制的,但这些构建上下文都是按照标准化格式编写的。构建上下文的标准化允许单个构建系统处理所有微服务。上图是通过Builder容器的标准化构建过程
为了实现运行时环境之间的最大一致性,在开发和测试阶段使用相同的构建过程。当我们需要设计一种方法来保证整个平台的一致构建环境时,这就带来了一个独特的挑战。因此,所有构建过程都在特殊的“Builder”容器中执行。
Builder容器的实现需要许多高级Docker技术。此Builder容器根据访问Tinder专用存储库的需要继承本地用户ID和机密(例如,SSH密钥,AWS凭证等)。它安装包含源代码的本地目录,以便以自然的方式存储构建工件。此方法可提高性能,因为它消除了在Builder容器和主机之间复制构建的工件。下次无需进一步配置即可重复使用存储的构建工件。
对于某些服务,我们需要在Builder中创建另一个容器,以使编译时环境与运行时环境相匹配(例如,安装Node.js bcrypt库会生成特定于平台的二进制工件)。编译时要求可能因服务而异,最终的Dockerfile是即时编写的。

(..具体到Amazon EC2的迁移)
从我们的遗留基础架构迁移到Kubernetes的准备步骤之一是将现有的服务到服务通信更改为指向在特定虚拟私有云(VPC)子网中创建的新Elastic Load Balancers(ELB)。该子网与Kubernetes VPC对等。这使我们能够精细地迁移模块,而不考虑服务依赖性的特定顺序。
这些端点是使用加权DNS记录集创​​建的,其中CNAME指向每个新ELB。为了切换,我们添加了一条新记录,指向新的Kubernetes服务ELB,权重为0.然后我们将记录设置的生存时间(TTL)设置为0.然后将旧的和新的权重慢慢调整为最终在新服务器上以100%结束。切换完成后,TTL设置为更合理的值。
我们的Java模块尊重低DNS TTL,但我们的Node应用程序却没有。我们的一位工程师重写了部分连接池代码,将其包装在一个每60秒刷新一次池的管理器中。这对我们来说效果很好,没有明显的性能影响。

学习收获
1.
网络结构限制
在2019年1月8日清晨,Tinder的平台遭遇持续停机。为响应当天早上平台延迟的无关增加,在群集上缩放了pod和节点计数。这导致我们所有节点上的ARP缓存耗尽。
有三个与ARP缓存相关的Linux值:gc_thresh1 gc_thresh2 gc_thresh3
gc_thresh3是一个硬块。如果您正在获取“neighbor table overflow”日志条目,则表明即使在ARP缓存的同步垃圾收集(GC)之后,也没有足够的空间来存储邻居条目。在这种情况下,内核完全丢弃数据包。
我们使用Flannel作为Kubernetes的网络介质。数据包通过VXLAN转发。VXLAN是第3层网络上的第2层覆盖方案。它使用MAC地址在用户数据报协议(MAC-in-UDP)封装来提供扩展第2层网络段的方法。物理数据中心网络上的传输协议是IP + UDP。

2.在规模上运行DNS
为了适应我们的迁移,我们大量利用DNS来促进流量整形和从遗留到Kubernetes的增量切换,以实现我们的服务。我们在相关的Route53 RecordSets上设置了相对较低的TTL值。当我们在EC2实例上运行我们的旧基础架构时,我们的解析器配置指向了亚马逊的DNS。我们认为这是理所当然的,我们的服务和亚马逊服务(例如DynamoDB)相对较低的TTL成本在很大程度上未被注意到。
随着我们为Kubernetes提供越来越多的服务,我们发现自己运行的DNS服务每秒回复250,000个请求。我们在应用程序中遇到间歇性且有影响力的DNS查找超时。尽管进行了彻底的调整工作,并且DNS提供商切换到CoreDNS部署,同时达到峰值为1000个容量为120个核心的容器,但仍然发生了这种情况。
在研究其他可能的原因和解决方案时,我们发现了一篇描述影响Linux数据包过滤框架netfilter的竞争条件的文章。我们看到的DNS超时,以及Flannel界面上的递增insert_failed计数器,与文章的发现一致。
在源和目标网络地址转换(SNAT和DNAT)以及随后插入conntrack表期间会出现此问题。内部讨论并由社区提出的一种解决方法是将DNS移动到工作节点本身。在这种情况下:

  • SNAT不是必需的,因为流量在本地停留在节点上。它不需要通过eth0接口传输。
  • DNAT不是必需的,因为目标IP是节点的本地IP而不是每个iptables规则随机选择的pod 。

我们决定采用这种方法。CoreDNS在Kubernetes中作为DaemonSet部署,我们通过配置kubelet-cluster-dns命令标志将节点的本地DNS服务器注入到每个pod的resolv.conf中。解决方法对DNS超时有效。
但是,我们仍然看到丢弃的数据包和Flannel接口的insert_failed计数器增量。即使在上述解决方法之后,这仍将持续存在,因为我们仅针对DNS流量避免了SNAT和/或DNAT。其他类型的交通仍将出现竞争条件。幸运的是,我们的大多数数据包都是TCP,当条件发生时,数据包将被成功重传。我们仍在讨论针对所有类型流量的长期解决方案。

3. 使用Envoy实现更好的负载平衡
当我们将我们的后端服务迁移到Kubernetes时,我们开始受到不同平衡负载的影响。我们发现由于HTTP Keepalive,ELB连接停留在每个滚动部署的第一个就绪pod中,因此大多数流量都流过一小部分可用pod。我们尝试的第一个缓解措施之一是在最严重的违规者的新部署中使用100%MaxSurge。对于一些较大的部署,这是有效的,并且长期不可持续。
我们使用的另一个缓解措施是人为地夸大关键服务的资源请求,以便共置的容器与其他重型容器一起拥有更多的空间。由于资源浪费和我们的Node应用程序是单线程的,因此从长远来看这也不会成为可行的,因此有效地限制在1核心。唯一明确的解决方案是利用更好的负载平衡。
我们内部一直在寻求评估nvoy。这使我们有机会以非常有限的方式部署它并立即获益。Envoy是一个开源的高性能第7层代理,专为面向服务的大型架构而设计。它能够实现先进的负载平衡技术,包括自动重试,电路中断和全局速率限制。
我们提出的配置是在每个pod旁边放置一个Envoy边车,这个pod有一条路由和一个集群可以打到当地的Docker端口。为了最大限度地减少潜在的级联并保持较小的爆炸半径,我们使用了一组前代理Envoy吊舱,每个服务区的每个可用区(AZ)中都有一个部署。这些工作点击了我们的工程师之一的小型服务发现机制,它只返回了每个AZ中用于给定服务的pod列表。
然后,服务前端Envoys将此服务发现机制与一个上游集群和路由一起使用。我们配置了合理的超时时间,提升了所有断路器设置,然后进行了最小的重试配置,以帮助解决瞬态故障和平稳部署。我们使用TCP ELB为这些前面的Envoy服务提供了前面的服务。即使来自我们的主前代理层的keepalive固定在某些Envoy pod上,它们也能够更好地处理负载并被配置为通过least_request平衡到后端。
对于部署,我们在应用程序和sidecar pod上都使用了preStop挂钩。这个钩子称为边车健康检查失败管理端点,以及一个小睡眠,给予一些时间允许机上连接完成和消耗。
我们能够如此快速地迁移的一个原因是:由于我们能够轻松地使用Prometheus设置集成了丰富监控指标。这让我们可以看到在我们迭代配置设置并能搞清楚流量减少时发生了什么。
结果立竿见影。我们从最不平衡的服务开始,并在此时让它在我们集群中的十二个最重要的服务面前运行。今年,我们计划转向全业务网状网,具有更先进的服务发现,断路,异常检测,速率限制和跟踪。


最终结果
通过这些学习和其他研究,我们开发了一个强大的内部基础架构团队,非常熟悉如何设计,部署和操作大型Kubernetes集群。Tinder的整个工程组织现在拥有如何在Kubernetes上集成和部署应用程序的知识和经验。
在我们的传统基础架构上,当需要额外的扩展时,我们经常会等待几分钟等待新的EC2实例联机。容器现在可以在几秒钟内安排并提供流量,而不是几分钟。在单个EC2实例上调度多个容器还可以提高水平密度。因此,与去年相比,我们预计2019年EC2将大幅节省成本。
花了将近两年时间,但我们在2019年3月完成了迁移.Tinder平台专门在Kubernetes集群上运行,该集群包括200个服务,1,000个节点,15,000个pod和48,000个运行容器。基础设施不再是我们运营团队的任务。相反,整个组织的工程师共同承担这一责任,并控制他们的应用程序如何构建和部署,以及所有代码。