Mail.ru云解决方案平台中如何实现容错Web架构



哈Ha! 我是Mail.Ru Cloud Solutions(MCS)系统管理团队负责人Artyom Karamyshev。 在过去的一年中,我们推出了许多新产品。 我们希望API服务能够轻松扩展,具有容错能力,并为快速增加用户负载做好准备。 我们的平台是在OpenStack上实现的,我想告诉您为了获得容错系统,我们必须关闭哪些组件容错问题。 我认为这对于那些也在OpenStack上开发产品的人来说很有趣。

平台的总体容错性由其组件的稳定性组成。 因此,我们将逐步了解并发现风险的所有层次。

该故事的视频版本(其来源是由ITSumma组织的Uptime第4天会议的报告 )可以在Uptime Community YouTube频道上观看

物理架构的容错


现在,MCS云的公共部分基于两个Tier III数据中心,它们之间有一条自己的暗光纤,通过不同的路由保留在物理层上,吞吐量为200 Gb / s。 第三层级别提供了物理基础架构的必要弹性级别。

在物理和逻辑级别都保留了深色光纤。 通道保留过程是反复进行的,出现了问题,并且我们正在不断改善数据中心之间的通信。

例如,不久前,当在一个数据中心附近的一口井中工作时,一台挖掘机在管道上打了孔,在该管道内既有主电缆又有备用光缆。 事实证明,我们与数据中心的容错通信通道在一口井中很脆弱。 因此,我们失去了一些基础设施。 我们得出了结论,并采取了许多措施,包括在相邻的井上铺设其他光学仪器。

在数据中心中,存在通信提供商的存在点,我们通过BGP向其广播前缀。 对于每个网络方向,都会选择最佳度量标准,从而允许不同的客户提供最佳的连接质量。 如果通过一个提供商的通信断开,我们将通过可用提供商重建路由。

如果提供者发生故障,我们将自动切换到下一个。 如果其中一个数据中心发生故障,我们将在第二个数据中心中拥有我们服务的镜像副本,这将使自己承担所有负担。


物理基础设施的弹性

我们用于应用程序级容错的内容


我们的服务基于许多开源组件。

ExaBGP是一项服务,该服务使用基于BGP的动态路由协议来实现许多功能。 我们积极使用它来宣布我们的白色IP地址,用户可以通过该IP地址访问API。

HAProxy是一个高负载的平衡器,可让您配置非常灵活的规则,以平衡OSI模型不同级别上的流量。 我们使用它来平衡所有服务:数据库,消息代理,API服务,Web服务,我们的内部项目-一切都在HAProxy背后。

API应用程序 -用python编写的Web应用程序,用户可以使用它来控制其基础设施和服务。

工作程序应用程序 (以下简称为工作程序)-在OpenStack服务中,它是基础结构守护程序,允许您将API命令转换为基础结构。 例如,在worker中创建了一个磁盘,并且在应用程序API中创建了一个请求。

标准OpenStack应用程序架构


为OpenStack开发的大多数服务都尝试遵循一个范例。 服务通常由两部分组成:API和工作程序(后端执行程序)。 通常,API是一个Python WSGI应用程序,可以作为独立进程(守护程序)运行,也可以使用现成的Nginx Web服务器Apache运行。 API处理用户的请求,并将进一步的指令传递给工作程序。 传输是使用消息代理(通常是RabbitMQ)进行的,其余的则很少得到支持。 当消息到达代理时,它们将由工作人员处理,并在必要时返回响应。

这种范例意味着孤立的常见故障点:RabbitMQ和数据库。 但是RabbitMQ被隔离在一个服务中,并且从理论上讲,每个服务可以是单独的。 因此,我们在MCS尽可能共享这些服务,对于每个单独的项目,我们都创建一个单独的数据库,即一个单独的RabbitMQ。 这种方法之所以不错,是因为在某些脆弱点发生事故时,并非所有服务中断,而只是部分中断。

辅助应用程序的数量是无限的,因此API可以轻松地在平衡器后面水平扩展,以提高生产率和容错能力。

一些服务需要服务内部的协调-当API和工作程序之间发生复杂的顺序操作时。 在这种情况下,使用单个协调中心,即Redis,Memcache等的集群系统,该系统允许一个工作人员告诉另一人该任务已分配给他(“请不要接受”)。 我们使用etcd。 通常,工作人员会主动与数据库进行通信,并从中写入和读取信息。 作为数据库,我们使用在多主群集中拥有的mariadb。

这种经典的单用户服务是以OpenStack普遍接受的方式组织的。 可以将其视为封闭系统,其缩放和容错方法非常明显。 例如,对于API的容错能力,只需在它们前面放置一个平衡器即可。 通过增加工人数量实现规模扩大。

整个方案的弱点是RabbitMQ和MariaDB。 他们的体系结构值得单独写一篇文章,在本文中,我要重点介绍API的容错能力。


Openstack应用程序架构 云平台平衡和弹性

使HAProxy Balancer与ExaBGP保持弹性


为了使我们的API具有可扩展性,快速性和容错性,我们在它们前面设置了一个平衡器。 我们选择了HAProxy。 我认为,它具有我们任务的所有必要特征:在多个OSI级别进行平衡,管理界面,灵活性和可伸缩性,大量的平衡方法,对会话表的支持。

需要解决的第一个问题是平衡器本身的容错能力。 仅仅安装平衡器也会造成故障:平衡器损坏-服务下降。 为了防止这种情况,我们将HAProxy与ExaBGP一起使用。

ExaBGP允许您实施一种检查服务状态的机制。 我们使用此机制来检查HAProxy的功能,并在出现问题的情况下从BGP禁用HAProxy服务。

ExaBGP + HAProxy方案

  1. 我们在三台服务器ExaBGP和HAProxy上安装必要的软件。
  2. 在每台服务器上,我们创建一个环回接口。
  3. 在所有三台服务器上,我们为该接口分配相同的白色IP地址。
  4. 通过ExaBGP在互联网上宣布了一个白色IP地址。

容错是通过从所有三台服务器中宣布相同的IP地址来实现的。 从网络角度来看,可以从三个不同的下一个希望访问同一地址。 路由器看到三个相同的路由,根据自己的度量标准选择它们中最优先的路由(通常是同一选项),流量仅流向其中一台服务器。

如果HAProxy操作出现问题或服务器出现故障,ExaBGP会停止宣布路由,并且流量会平稳地切换到另一台服务器。

因此,我们达到了平衡器的容错能力。


HAProxy平衡器的容错能力

该方案证明是不完善的:我们学习了如何保留HAProxy,但没有学习如何在服务内部分配负载。 因此,我们对该方案进行了一些扩展:我们继续在多个白色IP地址之间进行平衡。

基于DNS的Balancing Plus BGP


HAProxy之前的负载平衡问题仍未解决。 但是,可以像在家里一样简单地解决它。

要平衡这三台服务器,您将需要3个白色IP地址和一个良好的旧DNS。 这些地址中的每一个都在每个HAProxy的环回接口上定义,并在Internet上宣布。

OpenStack使用服务目录来管理资源,从而设置服务的端点API。 在此目录中,我们指定了一个域名-public.infra.mail.ru,该域名通过DNS解析,具有三个不同的IP地址。 结果,我们通过DNS获得了三个地址之间的负载平衡。

但是由于宣布白色IP地址时,我们不控制服务器选择优先级,因此到目前为止,这并不是平衡的。 通常,由于BGP中未指定任何度量标准,因此仅按IP地址的优先级选择一台服务器,而其他两台则处于空闲状态。

我们开始通过ExaBGP提供具有不同指标的路由。 每个平衡器都宣布所有三个白色IP地址,但其中一个(此平衡器的主要IP地址)以最小度量标准宣布。 因此,当所有三个平衡器都在运行时,第一个IP地址的呼叫将落在第一个平衡器上,第二个到第二个,第三个到第三个。

当其中一个平衡器跌落时会发生什么? 万一任何平衡器根据其基础发生故障,仍会从其他两个平衡器中通告该地址,并重新分配它们之间的流量。 因此,我们通过DNS一次为用户提供了多个IP地址。 通过平衡DNS和不同的指标,我们在所有三个平衡器上获得了均匀的负载分配。 同时我们也不会失去容错能力。


基于DNS + BGP的HAProxy平衡

ExaBGP与HAProxy之间的交互


因此,基于路由通告的终止,我们在服务器离开的情况下实现了容错功能。 但是,HAProxy也可能由于服务器故障以外的其他原因而断开连接:管理错误,服务故障。 在这种情况下,我们想从负载下移除损坏的平衡器,并且需要另一种机制。

因此,通过扩展先前的方案,我们在ExaBGP和HAProxy之间实现了心跳。 当ExaBGP使用自定义脚本检查应用程序状态时,这是ExaBGP与HAProxy之间交互的软件实现。

为此,必须在ExaBGP配置中配置运行状况检查程序,该检查程序可以检查HAProxy的状态。 在我们的示例中,我们在HAProxy中配置了运行状况后端,并且从ExaBGP的一端,我们使用一个简单的GET请求进行检查。 如果公告不再发生,则HAProxy很可能无法正常工作,因此也无需公告。


HAProxy健康检查

HAProxy对等:会话同步


接下来要做的是同步会话。 通过分布式平衡器工作时,很难组织有关客户端会话的信息的存储。 但是,由于Peers功能-在不同HAProxy进程之间传输会话表的能力,HAProxy是能够执行此操作的少数平衡器之一。

有不同的平衡方法:简单(如round-robin )和高级(平衡),用于记住客户端会话时以及每次它到达与以前相同的服务器时。 我们想实现第二种选择。

HAProxy使用stick-tables为该机制保存客户端会话。 它们保存客户端的源IP地址,选定的目标地址(后端)和一些服务信息。 通常,棍子表用于保存源IP +目标IP对,这对于在切换到另一个平衡器时(例如,在RoundRobin平衡模式下)无法传输用户会话上下文的应用程序特别有用。

如果指导棒表在不同的HAProxy进程之间进行切换(在它们之间进行平衡),那么我们的平衡器将能够使用一个池表。 这样,当其中一个平衡器崩溃时,就可以无缝切换客户端网络,并且与客户端会话的工作将在先前选择的相同后端上继续进行。

为了正确运行,必须解析从其建立会话的平衡器的源IP地址。 在我们的情况下,这是回送接口上的动态地址。

对等体的正确操作仅在某些条件下才能实现。 也就是说,TCP超时必须足够大或交换机应该足够快,以使TCP会话没有时间中断。 但是,这允许无缝切换。

IaaS的服务基于同一技术。 这是一个负载均衡器,作为OpenStack的一项服务,称为Octavia。 它基于两个HAProxy进程,最初包含对等支持。 他们已经在这项服务中证明了自己。

该图示意性地显示了三个HAProxy实例之间的对等表移动,建议使用配置,该配置如何配置:


HAProxy对等(会话同步)

如果实施相同的方案,则必须仔细测试其工作。 这并非事实会在100%的情况下以相同的方式起作用。 但是,当您需要记住客户端的源IP时,至少不会丢失摇杆表。

限制来自同一客户端的同时请求数


公共领域中的任何服务(包括我们的API)都可能会出现大量请求。 从用户错误到针对性攻击,其原因可能完全不同。 我们定期在IP地址上进行DDoS。 客户经常在脚本中犯错误;他们使我们成为小型DDoS。

一种或另一种方式,必须提供额外的保护。 显而易见的解决方案是限制API请求的数量,而不是浪费CPU时间来处理恶意请求。

为了实现这种限制,我们使用基于HAProxy的速率限制,并使用相同的stick-tables。 限制的配置非常简单,并允许您通过对API的请求数来限制用户。 该算法会记住发出请求的源IP,并限制来自一个用户的同时请求的数量。 当然,我们计算了每个服务的平均API负载配置文件,并将限制设置为该值的≈10倍。 直到现在,我们继续密切监视局势,我们始终保持同步。

在实践中看起来像什么? 我们有不断使用我们的自动缩放API的客户。 他们在接近早上的时候创建了大约两三百个虚拟机,并在接近晚上的时候删除了它们。 对于OpenStack,创建一个虚拟机,同时还要使用PaaS服务,至少要请求1000个API请求,因为服务之间的交互也是通过API进行的。

这样的任务抛出会导致相当大的负担。 我们估计了此负载,收集了每天的峰值,将峰值增加了十倍,这成为我们的速率限制。 我们将手指放在脉搏上。 我们经常看到机器人,扫描仪试图查看我们,我们是否可以运行任何CGA脚本,我们会积极地削减它们。

如何为用户谨慎地更新代码库


我们还在代码部署流程级别实现容错功能。 部署期间会发生崩溃,但可以将其对服务可用性的影响降至最低。

我们会不断更新服务,并应确保更新代码库的过程不会对用户造成影响。 我们使用HAProxy管理功能和服务中的“正常关机”功能设法解决了这一问题。

为了解决此问题,有必要提供平衡器控制和服务的“正确”关闭:

  • 对于HAProxy,控制是通过stats文件完成的,该文件本质上是一个套接字,并在HAProxy配置中定义。 您可以通过stdio向其发送命令。 但是我们的主要配置控制工具是可用的,因此它具有用于管理HAProxy的内置模块。 我们正在积极使用。
  • 我们的大多数API和引擎服务都支持正常的关机技术:关机后,它们会等待当前任务完成,无论是http请求还是某种实用程序任务。 工人也会发生同样的事情。 他知道自己所做的所有任务,并在成功完成所有任务后结束。

由于这两点,我们部署的安全算法如下。

  1. 开发人员构建一个新的代码包(我们有RPM),在开发环境中进行测试,在阶段中进行测试,并将其保留在阶段存储库中。
  2. 开发人员将任务放在部署中,并带有“工件”的最详细描述:新软件包的版本,新功能的描述以及有关部署的其他详细信息(如果需要)。
  3. 系统管理员开始升级。 启动Ansible剧本,依次执行以下操作:
    • 它从阶段存储库中获取软件包,并使用它更新产品存储库中的软件包版本。
    • 列出更新的服务的后端。
    • 关闭HAProxy中的第一个更新的服务,并等待其过程结束。 由于正常关机,我们有信心所有当前客户端请求都将成功完成。
    • 在API,工作程序和HAProxy完全停止之后,将更新代码。
    • Ansible推出服务。
    • 对于每项服务,它都会拉某些“笔”,以对许多预定义的关键测试进行单元测试。 对新代码进行基本检查。
    • 如果在上一步中未发现错误,则激活后端。
    • 转到下一个后端。
  4. 更新所有后端后,将启动功能测试。 如果还不够,那么开发人员将查看他所做的任何新功能。

至此部署完成。


服务更新周期

如果我们没有一条规则,则该方案将无法正常工作。 我们在战斗中同时支持新旧版本。 预先规定,在软件开发阶段,即使服务数据库中发生更改,它们也不会破坏先前的代码。 结果,代码库逐渐更新。

结论


分享我对容错WEB架构的看法,我想再次指出其关键点:

  • 物理容错
  • 网络容错(平衡器,BGP);
  • 使用和开发的软件的容错能力。

所有稳定的正常运行时间!

Source: https://habr.com/ru/post/zh-CN474180/


All Articles