作为fourTheorem的一名云架构师,我发现许多公司都在努力规模化他们的应用程序并充分利用云计算的能力。
其中一些公司既是初创公司,也是合并重组过的公司,它们以单体的形式开发产品,并最终占据了一定的市场份额。他们的业务在增长,但他们很难扩大应用程序的部署规模。
他们的服务通常部署在本地的私有服务器上或托管供应商的虚拟服务器上。随着服务需求的增长,他们的生产环境开始变得越来越慢,服务可用性出现了间隙性下降,并最终影响到服务质量和进一步增长的潜力。
此时,将产品迁移到 AWS 等云供应商可能是一个明智的解决方案。迁移到云端后,他们可以按需使用资源,并按需支付费用。云资源可以动态伸缩,以适应突发的流量,使用户体验始终保持良好的水平。
有趣的是,我接触过的一些公司认为,为了过渡到云计算,他们必须重新设计应用程序的整个架构,并转向微服务甚至无服务器。
在大多数情况下,重新设计整个应用程序的时间和成本将会令人望而却步,而这些成本原本应该用于构建有助于业务进一步增长的功能。这种想法让企业对云计算可能带来的机会持怀疑态度,他们最终倾向于选择短期的伸缩策略,也就是将应用服务器升级成更强大、更昂贵的机器。
当然,单台服务器的规模是有限的,最终,企业不得不重新开始考虑替代方案。
在本文中,我将介绍一种简单的云架构,企业可以以增量的方式将单体应用程序迁移到云端,而无需对架构进行重大修改。我们将讨论云计算可伸缩性所需的最低要求和基本组件。我们还将探讨一些常见的问题,可能涉及修改应用程序代码库。最后,我们将分析在完成云端迁移后可能出现的一些进一步改进的可能性。
我已经看到很多公司用这种方法成功地迁移到云端。在云端站稳了脚跟并让应用程序稳定下来之后,他们就可以专注于为客户提供服务,并进一步发展业务。此外,因为技术已不再是阻碍因素,他们可以开始试验,并将应用程序的一部分转成解耦的服务。这样,公司就可以开始过渡到微服务架构甚至 Lambda 函数等新技术,这些技术可以帮助他们在开发过程中实现更大的灵活性,并为业务带来额外的增长机会。
一家虚构的公司
我们举个具体的例子,引入一家虚构的公司,我们将它作为一个虚构的案例研究,以此来探索如何进行云迁移。
Eaglebox 是一家文件存储公司,他们的 Eaglebox App 是一个 Web 和移动端应用程序,为用户提供保存和组织文件的功能,并能够从多个设备上进行远程访问。
我们举几个 Eaglebox App 的具体用例:
用户登录应用程序,可以看到之前上传的所有文件。
用户上传新文档,并通过提供特定的标签(客户端 ID、案例号等)来组织文档。
用户可以搜索包含特定关键字或标签的文档。
Eaglebox App 是一个单体应用程序,使用 Django 框架开发,数据库为 PostgreSQL。
Eaglebox App 目前部署在 Eaglebox 公司本地的服务器上,用户所有的文件都保存在机器的磁盘上(是的,它们会频繁备份!)。类似地,PostgreSQL 也运行在同一台机器上。数据库数据也经常备份,但没有副本。
Eaglebox 最近与一些大公司签订了一些合同,从那时起,他们就在努力扩大他们的基础设施。他们的服务器变得越来越慢,磁盘很快就饱和了,需要大量的维护。用户体验已经变得很差,整个业务目前处于风险之中。
我们来看看如何通过重新设计可伸缩的架构来帮助 Eaglebox 迁移到云端。
面临的挑战
根据 EagleBox 的工程师告诉我们的情况,我们已经确定了几个需要解决的关键问题:
一台机器上的负载过多,导致整个应用程序缓慢和无响应,有时甚至不可用。
本地磁盘上的文件太多,快速填满磁盘,当没有更多可用磁盘空间时,会发生什么情况?
PostgreSQL 数据库作为服务与应用程序运行在同一台机器上,致使整台服务器面临更大的压力。数据库读写正在成为应用程序的另一个瓶颈。
单体虚拟机成为单点故障点。如果由于某种原因发生故障,整个应用程序就会宕机。
除了这些技术问题之外,我们还需要承认,EagleBox 团队没有云架构方面的经验,迁移到云端对他们来说将是一次学习的机会。所以,我们要控制迁移变更的数量,让团队有时间适应和吸收新知识,这一点很重要。
我们提出一种新的架构,既要解决所有现有的技术问题,又要提供通往云端的最短路径,并且不要求团队做出重大的技术变更。
一种简单且可伸缩的云架构
为了解决 EagleBox 面临的挑战,我们提出一种简单、可伸缩、弹性的云架构,并将 AWS 作为首先的云供应商。
这种架构将有以下几个部分组成:
应用程序负载均衡器(入口点);
一组 EC2 虚拟机(多个运行应用程序的实例);
文件存储(S3);
会话存储(内存缓存 Redis ElastiCache);
运行在 RDS 上的多区域可用性 Postgres 数据库。
图 1. 架构概览
在图 1 中,我们可以看到架构的高级视图。接下来我们将介绍各个组件。
数据中心和网络
在讨论各种组件的细节之前,有必要简要地探讨一下 AWS 是如何公开数据中心的,以及如何为我们的架构配置网络。我们不会讲得太详细,但需要涉及一些基础知识,知道会出现什么样的故障、在出现故障时如何保持应用程序运行,以及当流量增加时如何扩展它。
“云”并非万无一失,它也会出问题。AWS、Azure 和谷歌云等云供应商为我们提供了设计弹性架构的工具和最佳实践,但这是一种共享责任模型,我们需要了解供应商可以提供哪些保证、哪里可能会出问题以及是怎样出问题的。
说到网络,我们需要介绍一些高级概念。需要注意的是,这里我只提到 AWS,但这些概念也同样适用于 Azure 和谷歌云。
区域(Region):世界各地的物理位置(如“北弗吉尼亚”、“爱尔兰”或“悉尼”),AWS 在这些物理位置托管数据中心。区域可以提供更接近客户的基础设施,让应用程序具有更低的延迟和更快的响应。
可用性区域(Availability Zone):AWS 区域内的个体数据中心,主要是为了实现冗余的电力、网络和连接。不同可用性区域中的数据中心相互脱节,如果其中一个发生严重的中断,很少会同时影响到多个可用性区域。为了保证可用性,最好将冗余基础设施分布在一个区域内的不同可用性区域。
VPC:在一个区域内为 AWS 用户提供的私有网络。它在逻辑上与 AWS 中的其他虚拟网络隔离。每个 VPC 由一个或多个子网,每个子网都包含一定范围的私有 IP 地址。
子网:VPC 和可用性区域内的 IP 地址范围,用于在网络内启动和连接资源。子网分为公共子网和私有子网。公共子网用于运行 VPC 以外的实例,这些实例可以分配公网 IP 地址。通常,我们会将前端服务器(或负载均衡器)放在公共子网中,将其他的(后端服务、数据库等)放在私有子网中。网络流量可以通过路由表在子网之间流动,例如,公共子网中的负载均衡器可以通过路由表将流量转发给私有子网的后端实例。
在我们的架构中,我们将使用如图 2 所示的 VPC 配置。
图 2. 我们架构中的 VPC 配置
主要的想法就是选择一个离客户较近的区域,并在该区域内创建一个专用 VPC。然后,我们将使用 3 个不同的可用性区域,每个可用性区域都有一个公共子网和一个私有子网。
公共子网只用于负载均衡器,架构中的其他组件(虚拟机、缓存服务器和数据库)全部使用私有子网。
操作点:从为所选的区域内配置 VPC 开始,确保在不同的可用性区域中分别创建了公共子网和私有子网。
负载均衡器
负载均衡器是所有进入 Eaglebox 应用服务器的流量的“入口”。这是一个弹性应用负载均衡器(Layer 7),可以管理 HTTP、HTTPS、WebSocket 和 gRPC 流量。它负责将流入的流量分配给后端的虚拟机。它可以检查目标虚拟机的运行状况,确保只将流入的流量转发给运行状况良好且响应迅速的实例。
操作点:确保单体应用程序提供了一个可用于检查实例健康状况的端点。如果还没有,请在应用程序中添加。
负载均衡器与 ACM (AWS证书管理器)集成,可以使用证书并提供 HTTPS 流量,确保所有进出的流量都是加密的。
从网络方面来看,负载均衡器可以使用所有的公共子网,所以也就可以使用所有的可用性区域。负载均衡器也因此具备高可用性:如果一个可用性区域突然不可用,流量将自动通过其他可用性区域进行路由。
在 AWS 中,弹性负载均衡器能够很好地处理不断增长的流量,每个实例能够分发每秒数百万个请求。对于大多数现实中的应用程序来说,我们不需要为负载均衡器的伸缩问题操心。最后,值得一提的是,这种负载均衡器完全由 AWS 负责托管,因此我们不需要操心系统配置或软件更新方面的事情。
虚拟机
Eaglebox App 是一个用 Python 开发的 Web 应用程序,使用了 Django 框架。我们希望能够同时在不同的服务器上运行应用程序的多个实例,这样就可以根据不断增加的流量进行伸缩。理想情况下,我们希望将不同的实例分布到不同的可用性区域。同样,如果一个可用性区域变得不可用,其他区域还有实例可以处理流量并避免宕机。
要让实例可动态伸缩,可以使用自动伸缩组。我们可以定义应用程序新实例自动启动(或销毁)的条件。例如,我们可以使用平均 CPU 负载水平或每个实例的平均请求数来确定是否需要启动新实例,或者,如果已经有足够的可用容量,可以降低实例数量以节省成本。为了保证高可用性,我们需要确保每个可用性区域中至少有一个实例可用。
要使用虚拟机,就需要构建虚拟机镜像。镜像可以有效地把操作系统、所有必要的软件(例如 Python 运行时)、应用程序的源代码和所有依赖项打成包。
为了启动虚拟机实例,必须定义镜像,这里涉及的细节可能并不重要,但它确实与在本地托管软件的方式有很大不同。在本地,虚拟机通常会一直保持运行。在配置完毕后,IT 人员通常会登录到机器上给软件打补丁、重新启动服务或部署新的应用程序版本。但如果存在多个实例,并且需要自动启动和销毁,这种方式就不可行了。
在云端,虚拟机是“不可变”的:一旦启动,就不应该被修改。如果需要发布更新,就构建新的镜像并启动新实例,同时逐步销毁旧实例。
但是,这种不变性不仅影响部署或软件更新,它还影响数据(或“状态”)的管理方式。我们无法在虚拟机里存储持久状态,因为一旦机器被关闭,我们将丢失所有的数据,因此,我们不能在本地文件系统存储文件或在应用程序内存中保存会话数据。
基于这样的模型,“基础设施”和“数据”变成了相互独立的关注点,可以分开处理和管理。
在评审已有代码和构建虚拟机镜像时,我们要注意所有访问数据(文件、数据库记录、用户会话数据,等等)的代码,并在必要时做出修改,以此来确保不会在实例本地保存数据。我们将在讨论我们的架构所涉及的不同存储类型时更深入地讨论我们的可选项。
那么我们如何构建虚拟机镜像呢?
有几种不同的工具和方法可以帮助我们完成这项任务。就我个人而言,我曾经使用过的并且让我感到很满意的是 AWS 的EC2 Image Builder和 Hashicorp 的Packer。
数据库
在 AWS 中,启动关系数据库(如 PostgreSQL)的最简单的方法是使用 RDS:关系数据库服务。RDS 是一种托管服务,用户启动数据库实例,AWS 负责数据库实例的更新和备份。
RDS PostgreSQL 可以有读取副本。使用读取副本是将查询负载分摊到多个实例的一种很好的方法,即使在负载很重的情况下也能保持数据库的响应速度。
RDS 的另一个有趣的特性是可以在多可用性区域模式下运行一个 PostgreSQL 实例。这意味着数据库的主实例将运行在一个可用性区域中,然后在其他可用性区域至少会有两个备用副本,以备在主可用性区域出现故障时使用。AWS 将负责在发生故障时执行自动切换,以确保数据库在没有任何人工干预的情况下尽快恢复。
需要注意的是,多可用性区域故障转移不是瞬间完成的(通常需要 60 到 120 秒),所以你需要让应用程序能够在无法建立数据库连接时也能做一些事情(或至少向用户显示描述性的消息)。
现在的问题是,我们如何将数据从本地数据库迁移到 RDS 实例中?理想情况下,我们希望可以在不停机的情况下在两个环境之间进行逐步过渡,那么我们能做些什么呢?
AWS 提供了另一种数据库服务,叫作AWS数据库迁移服务。你可以用这个服务将所有数据从旧数据库复制到新数据库。有趣的是,它还可以在切换期间保持两个数据库同步。通常情况下,由于 DNS 的关系,可能会有一些用户可以登录到新系统,其他用户则可能仍然被路由到旧服务器。
操作点:在 RDS 上创建数据库实例,并启用多可用性区域模式。使用 AWS 数据库迁移服务迁移所有数据,并在切换期间保持两个数据库同步。
文件存储
在我们的新架构中,我们可以通过 S3 (Simple Storage Service)实现分布式文件存储。S3 是 AWS 最早提供的服务之一,也可能是最著名的服务之一。
S3 是一种持久的对象存储服务,你可以用它持久地存储任意数量的数据。对象可以存储在桶(具有惟一名称的逻辑容器)中。S3 使用了键值存储模型:桶中的每一个对象都有一个唯一“键”,内容和元数据与键关联起来。
要用 S3 来读写对象,我们需要使用 AWS SDK。它支持多种语言(包括 Python),并且提供了一个与所有 AWS 服务(包括 S3)交互的编程接口。
我们还可以通过 AWS 命令行接口(CLI)与 S3 交互。对于我们的场景,CLI 有一个特别方便的命令——sync。我们可以使用这个命令将所有已有文件复制到指定的 S3 桶中。
要在这两个环境之间顺利过渡,最好是直接从现有环境开始使用 S3。这意味着我们需要将所有本地文件同步到一个存储桶中,并确保用户上传的每个新文件也被复制到同一个存储桶中。
操作点:文件迁移。创建一个新的 S3 桶,将所有现有文件同步到桶中。在 S3 中保存每个新文件。
会话存储
在我们的新架构中,有多个后端服务器负责处理用户的请求。如果流量负载是均衡的,那么每个用户请求最终都会到达给定的后端实例,但来自同一用户的后续请求可能会由另一个后端实例提供服务。
因此,所有实例都需要访问共享会话存储。事实上,如果没有共享会话存储,当请求由不同的后端实例提供服务时,各个实例将无法正确地识别用户会话。
实现分布式会话存储的常见方法是使用 Redis 实例。
在 AWS 上启动 Redis 实例最简单的方法是使用 Elasticache 的服务。Elasticache 是 Redis 和 Memcached 的托管服务,和 RDS 一样,在使用这个服务时,你不需要操心操作系统或安装安全补丁方面的事情。
ElastiCache 可以在多可用性区域模式下启动一个 Redis 集群,并提供自动故障转移功能。与 RDS 一样,这意味着如果集群主实例所在的可用性区域不可达,Elasticache 将自动执行 DNS 故障转移,并切换到其他可用性区域的备用副本上。但它的故障转移不是瞬间完成的,所以应用程序需要考虑到在故障转移期间可能无法与 Redis 建立连接。
操作点:使用 ElastiCache 提供一个 Redis 集群,并确保所有的会话数据都保存在那里。
DNS
迁移的最后一步与 DNS 有关,也就是我们如何将流量转发到 AWS 的基础设施上?
最好的方法是在Route 53中为所有的应用程序配置 DNS。Route 53 是一个高可用、可伸缩的云 DNS 服务。
它可以将应用程序域中的所有流量转发到负载均衡器。在配置并启用了之后(并且 DNS 已传播),新的基础设施将开始接收流量。
如果你的域名已经在其他地方注册过,可以将域名转到 AWS,或者修改注册配置,使用新的 Route 53 托管区域作为域名服务器。
操作点:在 Route 53 中创建一个新的托管区域,并配置 DNS,将域指向应用程序负载均衡器。在准备切换时,将你的域名注册指向 Route 53 或将域名转到 AWS。
其他建议
正如我们所看到的,这个新的架构由大量的活动组件组成。我们该如何追踪所有这些组件,并确保所有的环境(如开发、QA 和生产)尽可能保持一致?
解决这一问题的最佳方法是使用基础设施即代码(Infrastructure as a Code,IaaC)。你可以将所有的基础设施定义为代码,这些代码可以保存在代码库中(甚至与应用程序代码库放在一起),所有的基础设施对所有开发人员都是可见的。他们可以对变更进行评审或直接修改。更重要的是,IaaC 为跨环境发布变更提供了一个可重复的过程,随着架构的演化,这种可重复的过程有助于保持一致性。
如果是在 AWS 上实现 IaaC,可以选择CloudFormation,有了这个工具,你就可以使用 YAML 指定基础设施模板。AWS 提供的另一个替代工具是云开发工具包(Cloud Development Kit,CDK),它提供了一个高级接口,可以使用编程语言(如 TypeScript、Python 或 Java)定义基础设施。
另一个常见的替代方案是第三方工具Terraform。
选择哪种工具并不重要(它们都有各自的优点和缺点),重要的是你要将所有基础设施定义成代码,确保你可以围绕如何将基础设施变更交付到云端构建一个可靠的流程。
可观测性是另一个很重要的方面。现在我们有了这么多的活动组件,该如何调试问题或者如何确保系统是健康的?关于可观测性的内容超出了本文的范围,但如果你对分布式日志、跟踪、指标、警报和仪表盘等主题感兴趣,可以了解下CloudWatch和X-Ray。
基础设施即代码和可观测性是两个非常重要的主题,它们将极大地帮助你将应用程序部署到云端并保持它们顺利运行。
接下来
现在,我们已经到了云端,那么我们的旅程结束了吗?恰恰相反,我们的旅程才刚刚开始,后面还有很多东西需要探索和学习。
既然我们身处云端,就有很多机会探索新技术和新方法。
我们可以研究下容器,甚至是无服务器。如果我们要构建一个新特性,不一定要局限于必须部署在一个单体服务器上。我们可以尝试利用新工具以一种更解耦的方式构建新特性。
例如,假设我们需要构建一个功能,当一个用户上传了一个新文档,通过电子邮件通知其他人。一种方法是使用队列和处理进程。应用程序可以将与发送通知电子邮件相关的作业定义发布到队列中,一个处理进程池将处理队列中的作业,并完成发送电子邮件的任务。
这种方法可以让后端应用程序保持快速响应,并将耗时的后台任务(如发送电子邮件)委托给异步执行的外部进程。
要在 AWS 上实现这个,可以使用SQS(队列)和Lambda(无服务器计算)。
这个例子向我们展示了云计算如何为我们打开新的可能性,让公司可以快速迭代和不断试验,并利用一套全面的工具和技术来满足需求。
迁移到云端是一段旅程,不是目的地,而且这段旅程才刚刚开始。享受这段旅程吧!
附录:将单体系统迁移到云端的检查清单
创建 AWS 帐户;
选择 IaaC 工具;
创建并配置区域的 VPC(3 个可用性区域、公共子网和私有子网);
创建 S3 桶;
更新旧代码库,将每个新文件保存到 S3;
将所有现有文件复制到 S3;
在 RDS(多可用性区域模式下)中启动数据库;
使用数据库迁移服务迁移数据;
提供 ElastiCache Redis 集群(多可用性区域);
为应用程序创建一个 AMI;
为 EC2 创建安全组和 IAM 策略;
让应用程序变得无状态;
创建健康检查端点;
创建一个自动伸缩组来启动实例;
在 ACM 中生成证书;
提供一个应用程序负载均衡器(公共子网);
配置 HTTPS、目标和健康检查;
配置 Route 53;
通过 DNS 进行流量切换。
作者简介:
Luciano Mammino 在 12 岁时在他父亲的老式 i386 机器上写了第一行代码。从那时起,他就没有停止过编程。他是 fourTheorem 的高级架构师,他的工作是帮助公司充分利用云计算。Luciano 是《Node.js 设计模式》的合著者。他还是 Fullstack Bulletin 的维护者,这是一个为全栈开发人员提供的免费每周新闻通讯。Luciano 还是一名认证的 AWS 解决方案架构师和微软 MVP。
原文链接:
评论 1 条评论