60

Tinder向Kubernetes的迁移

 4 years ago
source link: https://www.tuicool.com/articles/7bYRJjy
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【编者的话】

这篇文章介绍了Tinder公司向Kubernetes转型的时候遇到的主要问题和解决方法。

为什么

大概两年前,Tinder决定转向Kubernetes。Kubernetes通过不可变部署推动Tinder Engine向容器化以及低接触式运维发展。应用程序的构建、部署以及基础架构都可以由代码定义。

我们还致力于解决扩展以及稳定性的挑战。当扩展变得至关重要时,我们通常在等待新的EC2实例上线的这几分钟里备受煎熬。容器能够在几秒中内,而不是几分钟,完成调度并且上线,这对于我们很有吸引力。

一切并不容易。2019年初的迁移里,我们的Kubernetes集群一团乱麻,并且遇到各种问题,流量,集群大小以及DNS等。我们解决了这些有意思的问题,迁移了200多个服务,并且运行了一个大规模的Kubernetes集群,一共有1000个节点,15000个pod以及48000个运行着的容器。

怎么做的

从2018年1月份起,我们就开始实验迁移的各个阶段了。首先将所有服务容器化,并且部署到一系列Kubernetes预生产环境上。从10月份起,我们开始系统性地将所有遗留服务迁移到Kubernetes上。第二年3月份,迁移工作结束,Tinder平台全都跑在了Kubernetes上。

为Kubernetes构建image

在Kubernetes集群里运行着超过30个微服务的源码仓库。这些仓库里的代码是不同语言编写的(比如,Node.js,Java,Scala,Go),同一种语言还有多个运行时环境。

build系统设计成可以为每个微服务做完整自定义的“build上下文”,通常包括Dockerfile以及一系列shell脚本。虽然内容都是全自定义的,这些build的上下文是遵循标准化的格式编写的。这样标准化的build上下文使得单个build系统可以处理所有的微服务。

3YrUZv3.png!web

图1-1 Builder容器的标准化构建流程

为了达到运行时环境的最大一致性,我们在开发和测试阶段使用相同的构建流程。当想设计一种方案来保证跨平台的build环境的一致性时,我们遇到了独特的问题。最终,所有build流程都在一个特别的“Builder”容器内执行。

Builder容器的实现要求一系列高级Docker技术。Builder容器继承本地user ID和secret(比如:SSH key, AWS认证等),因为需要访问Tinder的私有存储库。它mount了包含源码的本地目录来存储build artifact。该方案改进了性能,因为它不需要在Builder容器和宿主机之间拷贝build出来的artifact。无需任何配置,下次就可以重用存储好的build artifact。

对于某些服务来说,我们需要在Builder内创建另一个容器来实现编译环境和运行时环境的匹配(比如,安装Node.js bcrypt库生成平台特定的二进制artifact)。不同服务的编译需求可能并不相同,最终的Dockerfile是即时组装出来的。

Kubernetes 集群架构和迁移

集群大小

我们决定使用kube-aws实现Amazon EC2实例上的自动化集群预配。之前,我们在一个通用节点池里运行所有东西,很快就发现需要将工作负载放到不同大小不同类型的实例上,才能最优化地使用资源。因为同时运行一些多线程的pod,和运行大量单线程Pod相比,能够得到更平滑的性能结果。

我们最后使用:

- m5.4xlarge 用于监控(Prometheus)

- c5.4xlarge 用于Node.js 工作负载 (单线程工作负载)

- c5.2xlarge 用于 Java 和 Go (多线程工作负载)

- c5.4xlarge 用于控制平面 (3个节点)

迁移

从遗留基础架构迁移到Kubernetes的准备步骤之一,是将已有的服务-服务的通信改为通过全新的Elastc Load Banlancer(ELB)的通信,ELB是在特定的虚拟私有云(VPC,Virtual Private Cloud)子网内创建的。该子网和Kubernetes VPC对等。这让我们可以逐步迁移模块,而无需考虑服务依赖的特定顺序。

这些端点使用weighted DNS记录集来创建,包含指向全新Kubernetes服务ELB的CNAME,weight为0。然后我们设置记录集的Time To Live(TTL)为0。随后慢慢调整weight值直至最终新服务器weight达到100%。直到这些都完成了,就可以将TTL调整为更合适的值。

我们的Java模块需要较低的DNS TTL,但是Node应用程序不需要。一名工程师重写了连接池的部分代码,将其封装进一个管理器,会每隔60秒刷新一次连接池。这在我们的场景里工作得很好,对性能没有太大影响。

学习

网络Fabric限制

在2019年1月8号的早晨,Tinder平台发生了一次宕机事件。为了解决之前的平台延迟增长,我们扩展了集群里的pod和节点数量。这导致所有节点上的ARP缓存耗尽。

这是和ARP缓存相关的三个Linux值:

7bQv2uN.png!web

Credit

gc_thresh3是hard cap。如果看到日志里出现“neighbor table overflow”,这意味着即使在ARP缓存同步垃圾回收(GC)之后,也没有足够的空间存储neighbor entry。这时,kernel会直接彻底丢弃数据包。

我们使用 Flannel 作为Kubernetes的网络fabric。通过VXLAN转发数据包。VXLAN是三层网络上的二层overlay scheme。它使用MAC Address-in-User Datagram Protocol (MAC-in-UDP)封装提供扩展二层网络segment的方式。物理数据中心网络上的传输协议是IP+UDP。

6jYzyqj.png!web

图2-1 Flannel图示

muiea2n.jpg!web

图2-2 VXLAN数据包

每个Kubernetes工作节点分配自己的/24虚拟地址空间。对于每个节点来说, 有1个路由表entry,1个ARP表entry(在flannel.1接口上),以及1个转发数据库(FDB)entry。这些entry在worker节点第一次启动或者第一次发现新节点时添加

另外,节点-pod(或者pod-pod)通信最终经过 eth0 接口。对应每个相应的节点源和节点目的地,都会在ARP表里对应一条额外添加的entry。

在我们的环境里,这样的通信非常常见。对于Kubernetes服务对象,会创建一个ELB,Kubernetes将每个节点注册到ELB上。ELB不知道pod,被选中的节点也不一定是数据包的最终目的地。这是因为当节点从ELB接收到数据包时,它会评估自己服务的 iptables 规则,并且随机选择另一个节点上的pod。

宕机发生的时候,集群里有605个节点。由于上述原因,超过了默认的 gc_thresh3 值。一旦这种情况发生,不仅数据包会被丢弃,而且整个Flannel /24的虚拟地址空间都会从ARP表里丢失。节点-pod通信和DNS查询都会失败。(DNS在集群内部,本文后面会更为详细地解释这里的细节。)

要解决问题,需要提高 gc_thresh1gc_thresh2gc_thresh3 的值,并且重启 Flannel 重新注册丢失的网络。

大规模集群里运行DNS

为了实现迁移,我们重度依赖于DNS来辅助流量整型,并且渐进地将服务从遗留系统引流到Kubernetes上。我们在相关的Route53 RecordSet上设置相对较低的TTL值。当在EC2实例上运行遗留基础架构时,解析器配置指向Amazon的DNS。这是个自然的选择,没有关注我们和亚马逊服务TTL设置相对较低时的成本。

随着越来越多的服务进入Kubernetes,我们发现自己运行的DNS服务每秒需要响应250,000次请求。在应用程序里,开始遇到时不时发生但影响挺大的DNS查询超时问题。即使尝试了很多调优的方法,并且将DNS供应商切换到CoreDNS部署上,它在峰值时会消耗120个核1000个pod,但是这个问题仍然会发生。

在研究其他可能的原因和解决方案时,我们发现了一篇文章,介绍了一种竞争条件,会影响Linux数据包过滤框架 netfilter 。我们遇到的DNS超时问题,伴随着Flannel接口上insert_failed次数的增加,和这篇文章的发现很一致。

这个问题发生在源和目标网络地址翻译(SNAT和DNAT)以及后续contrack表插入的过程中。一个内部讨论并且被社区推荐的workaround是将DNS移到worker节点本身上。这时:

  • SNAT不需要了,因为流量还在节点本地。不需要通过eth0接口传输。
  • DNAT不需要了,因为目标IP对于节点来说就是本地,并不需要通过iptables规则去随机选择。

我们决定按照这种方案执行。CoreDNS作为DaemonSet部署到Kubernetes上,并且通过配置kubelet-cluster-dns命令的参数,将节点本地DNS注入到每个节点的resolv.conf文件里。这个workaround对DNS超时问题非常有效。

但是,我们仍然观察到被丢弃的数据包,并且Flannel接口的insert_failed数还在增加。这在使用了上述workaround后仍然发生,因为这个workaround仅仅避免了DNS流量的SNAT和/或DNAT。对于其他类型的流量,竞争条件仍然存在。幸运的是,我们的数据包绝大部分是TCP,当竞争发生后,数据包可以被成功地重新传输。能够解决所有类型流量的长期解决方案仍在讨论之中。

使用Envoy实现更好的负载均衡

随着后台服务向Kubernetes的迁移,我们开始遇到pod间负载不均衡的问题。我们发现因为Http的Keepalive,每次滚动部署时,ELB连接会卡在第一个ready的pod上,因此绝大部分流量会流过一小部分可用的pod。我们第一次迁移尝试在新部署里设置最差情况下使用100%MaxSurge。这非常高效,但是对于一些大型部署并不可持续。

我们使用的另一个缓解措施是人为地夸大关键服务的资源请求,以便其中的pod与其他高负载pod一起拥有更多的空间。这也不是很好,因为会浪费资源,我们的Node应用程序是单线程的,它在1个核的时候更高效。唯一有效的方案是使用更好的负载均衡技术。

我们调研了 Envoy 。我们在非常有限的范围内部署了Envoy并且得到了很好的效果。Envoy是开源的,高性能的7层代理,非常适合大型的面向服务的架构。它能够实现高级负载均衡技术,包括自动重试,断流以及全局限速。

最后我们在每个pod里以sidecar模式部署Envoy,然后连接到本地容器端口。为了最大限度地减少潜在的级联并保持较小的爆破半径,我们使用了front-proxy Envoy pod,每个服务每个可用Zone里部署一个。这就是一个小型的服务发现机制,对于给定服务返回每个AZ的pod列表。

然后front-Envoys服务将这个服务发现机制和上游的集群和路由连接起来。我们配置了合理的timeout时间,增加了所有断流设置,然后加入了最小的重试设置来解决偶发的故障,让部署更为流畅。在这些front Envoy服务之前放置了TCP ELB。即使主要的front proxy层需要pin Envoy pod保持keepalive,它们仍然能够更好地处理负载,通过least_request配置后台的负载。

对于部署,我们在应用程序和sidecar pod上都使用了preStop

hook。这个hook调用sidecar健康检查失败的admin端点,sleep一小段之后,给些时间让正在进行的连接结束并耗尽。

能够进展如此迅速的一个原因是能够轻松地将丰富的metric和我们常规的Prometheus系统集成起来。这让我们在迭代配置中能够观测到到底发生了什么,并且截断流量。

结果很明显。一开始服务非常不均衡,现在集群里最重要的12个服务之前运行了这一系统。今年我们计划改进程全服务网格,提供更先进的服务发现,断流,异常值检测,限流和跟踪日志。

3YFzUzY.png!web

图 3-1 切换到Envoy时某服务的CPU收敛过程

使用envoy前的调用链

viI7B3J.png!web

envoy服务调用链

mEVBZj7.png!web

结论

通过这些学习和研究,我们成长为一个强大的自研基础架构团队,非常熟悉如何设计,部署以及运维大型Kubernetes集群。Tinder的整个工程师团队现在都拥有了如何在Kubernetes上做容器化以及部署应用程序的知识和经验。

在我们的遗留基础架构上,当需要额外扩容时,通常需要等待几分钟新的EC2实例才能启动上线。现在容器调度并能承载流量只需要几秒钟。在单个EC2实例上调度多个容器还提供了更好的水平密度。最终,2019年里,我们比上一年节省了EC2上的花费。

整个过程花了大概2年,最终2019年3月份我们完成了迁移。Tinder Platform完全运行在Kubernetes集群里,这个集群包含200个服务,1000个节点,15000个pod以及48000个运行着的容器。

基础架构不再是运维团队的保留任务。相反,整个公司的工程师都在承担这个职责,并且任何事情都是代码,工程师们控制自己的应用程序如何构建及部署。

原文链接: Tinder’s move to Kubernetes (翻译:崔婧雯 校对:)

===========================

译者介绍

崔婧雯,现就职于IBM,高级软件工程师,负责IBM WebSphere业务流程管理软件的系统测试工作。曾就职于VMware从事桌面虚拟化产品的质量保证工作。对虚拟化,中间件技术,业务流程管理有浓厚的兴趣。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK