多集群应用部署与应用组织指南
1. 负载均衡与多集群应用状态管理
1.1 任播网络与负载均衡层级
在网络中,任播网络利用核心路由协议从互联网多个位置进行通告。传统观念里,一个 IP 地址映射到一台单一的机器,但在任播网络中,IP 地址实际上是一个虚拟 IP 地址,它会根据你的网络位置被路由到不同的地方。流量会基于网络性能的距离而非地理距离被路由到“最近”的位置。任播网络通常能产生更好的效果,但并非在所有环境中都可用。
在设计负载均衡时,还需考虑是在 TCP 层还是 HTTP 层进行负载均衡。对于基于 Web 的应用,在 HTTP 层进行负载均衡有显著优势。如果你开发的是基于 HTTP 的应用(如今大多数应用都是如此),使用全局感知 HTTP 的负载均衡器能让你了解更多客户端通信的细节。例如,可以根据浏览器中设置的 cookie 来做出负载均衡决策。此外,感知协议的负载均衡器能做出更智能的路由决策,因为它能看到每个 HTTP 请求,而不只是 TCP 连接上的字节流。
无论选择哪种方法,最终服务的位置会从全局 DNS 端点映射到代表服务入口点的一组区域 IP 地址。这些 IP 地址通常是 Kubernetes 服务或入口资源的 IP 地址。一旦用户流量到达该端点,就会根据应用的设计在集群中流动。
1.2 多集群应用的状态管理
解决负载均衡问题后,设计多集群应用的下一个挑战是考虑状态。理想情况下,应用不需要状态,或者所有状态都是只读的。在这种情况下,支持多集群部署所需做的工作很少。可以将应用分别部署到每个集群,并在顶部添加负载均衡器,多集群部署就完成了。然而,对于大多数应用来说,必须以一致的方式在应用的副本之间管理状态。如果不能正确处理状态,用户将体验到混乱和有缺陷的服务。
以一个简单的零售店为例,来说明复制状态如何影响用户体验。如果只在多个集群中的一个集群存储客户订单,当客户的请求因负载均衡或地理位置移动而转移到不同区域时,他们可能无法看到自己的订单,这会给用户带来不好的体验。因此,用户状态需要跨区域复制。复制方法也会影响客户体验,“我能读取自己的写入内容吗?”这个问题简洁地概括了复制数据和客户体验的挑战。虽然答案似乎显而易见是“能”,但实现起来比看起来要困难。例如,客户在电脑上下单后立即在手机上查看订单,他们可能通过两个完全不同的网络访问应用,从而落在两个完全不同的集群上。用户期望能看到刚下的订单,这就是数据一致性的一个例子。
1.3 数据一致性模型
一致性决定了如何考虑数据复制。我们希望数据是一致的,即无论从哪里读取数据,都能得到相同的结果。但复杂的因素是时间:数据必须多快达到一致?数据不一致时是否会有错误指示?有两种基本的一致性模型:
-
强一致性
:保证写入操作在成功复制后才会成功。
-
最终一致性
:写入操作总是立即成功,只保证在某个稍后的时间点成功复制。
一些系统还允许客户端根据每个请求选择自己的一致性需求。例如,Azure Cosmos DB 实现了有界一致性,在最终一致性系统中对数据的陈旧程度有一定的保证。Google Cloud Spanner 允许客户端指定愿意容忍陈旧读取以换取更好的性能。
强一致性似乎是一个更简单的模型,因为数据在任何地方都是相同的,但它代价高昂。在写入时保证复制需要更多的努力,并且在无法复制时会有更多的写入失败。与最终一致性相比,强一致性成本更高,能支持的并发事务数量更少。最终一致性成本更低,能支持更高的写入负载,但对应用开发者来说更复杂,可能会让最终用户遇到一些边缘情况。
许多存储系统只支持单一的并发模型。支持多种并发模型的系统需要在创建存储系统时指定。选择并发模型对应用设计有重大影响,并且很难更改。因此,在为多个环境设计应用之前,选择一致性模型是重要的第一步。
1.4 存储层选择
部署和管理复制的有状态存储是一项复杂的任务,需要有专业领域知识的团队来设置、维护和监控。建议考虑使用基于云的存储来实现复制数据存储,这样可以将负担交给云提供商的大型团队,而不是自己的团队。在本地环境中,也可以将存储支持工作外包给专注于运行所选存储解决方案的公司。只有在大规模情况下,投资组建自己的团队来管理存储才是有意义的。
确定存储层后,下一步是构建应用设计。
1.5 复制孤岛:最简单的跨区域模型
在多个集群和多个区域复制应用的最简单方法是将应用复制到每个区域。应用的每个实例都是完全相同的克隆,无论在哪个集群中运行,看起来都一样。由于顶部有负载均衡器来分配客户请求,并且在需要状态的地方实现了数据复制,应用不需要做太多更改来支持这种模型。根据为数据选择的一致性模型,需要处理数据在区域之间可能无法快速复制的问题,但如果选择强一致性,这通常不需要对应用进行重大重构。
当以这种方式设计应用时,每个区域都是一个独立的孤岛。区域内包含所需的所有数据,一旦请求进入该区域,就完全由该集群中的容器提供服务。这种方法在降低复杂性方面有显著优势,但代价是效率较低。
考虑一个分布在全球多个地理区域的应用,目的是为用户提供极低的延迟。现实情况是,一些地理区域人口众多,而一些区域人口较少。如果应用的每个集群中的每个孤岛都完全相同,那么每个孤岛都必须根据最大地理区域的需求来配置资源。结果是,区域集群中应用的大多数副本都过度配置,应用的成本效率较低。解决这种成本过高问题的明显方法是减少较小地理区域中应用使用的资源大小。然而,调整应用大小并不总是可行的,因为可能存在瓶颈或其他要求(例如,至少维护三个副本)。
特别是将现有应用从单集群迁移到多集群时,复制孤岛设计是最简单的方法,但要明白这种方法有成本,起初可能可以承受,但最终可能需要对应用进行重构。
1.6 分片:区域数据
随着应用的扩展,使用区域孤岛方法可能会遇到一个痛点,即全局复制所有数据变得越来越昂贵和浪费。虽然为了可靠性复制数据是好事,但应用的所有数据不太可能都需要存放在部署应用的每个集群中。大多数用户只会从少数地理区域访问应用。
此外,随着应用在全球范围内增长,可能会遇到数据本地化的监管和其他法律要求。根据用户的国籍或其他因素,可能会对存储用户数据的位置有外部限制。这些要求意味着最终需要考虑区域数据分片。跨区域分片数据意味着并非所有数据都存在于应用所在的所有集群中,这显然会影响应用的设计。
例如,假设应用部署到六个区域集群(A、B、C、D、E、F),将应用的数据集分成三个子集或分片(1、2、3)。数据分片部署可能如下表所示:
| 集群 | 分片 1 | 分片 2 | 分片 3 |
| ---- | ---- | ---- | ---- |
| A | ✓ | - | - |
| B | - | ✓ | - |
| C | - | - | ✓ |
| D | ✓ | - | - |
| E | - | ✓ | - |
| F | - | - | ✓ |
每个分片在两个区域中存在以实现冗余,但每个区域集群只能提供三分之一的数据。这意味着每当需要访问数据时,必须在服务中添加一个额外的路由层。路由层负责确定请求是需要访问本地数据分片还是跨区域数据分片。
虽然可能想将数据路由作为链接到主应用的客户端库的一部分来实现,但强烈建议将数据路由构建为一个单独的微服务。引入新的微服务可能看起来会增加复杂性,但实际上它引入了一个抽象层,简化了事情。应用中的每个服务不必担心数据路由问题,而是有一个单独的服务来封装这些问题,其他服务只需访问数据服务。将应用拆分为独立的微服务在多集群环境中提供了显著的灵活性。
1.7 更好的灵活性:微服务路由
区域孤岛方法在多集群应用开发中可能会降低部署的多集群应用的成本效率,同时也会影响灵活性。创建孤岛实际上是在更大规模上创建了容器和 Kubernetes 试图打破的那种单体应用。此外,这种方法迫使应用中的每个微服务同时扩展到相同数量的区域。
如果应用规模较小且范围有限,这种方法可能有意义,但随着服务规模增大,尤其是当它们开始在多个应用之间共享时,多集群的单体方法会显著影响灵活性。如果以集群为部署单位,并且所有 CI/CD 都与该集群绑定,即使不适合,也会迫使每个团队遵循相同的部署流程和时间表。
例如,假设有一个非常大的应用部署到三十个集群,同时有一个正在开发的小应用。强迫开发小应用的小团队立即达到大应用的规模是不合理的,但如果应用设计过于僵化,就可能会出现这种情况。
更好的方法是在应用设计中将应用内的每个微服务视为面向公众的服务。虽然它可能实际上并不需要面向公众,但应该有自己的全局负载均衡器,并管理自己的数据复制服务。不同的微服务应该相互独立。当一个服务调用另一个服务时,其负载会以与外部负载相同的方式进行平衡。有了这种抽象,每个团队可以像在单集群中一样独立扩展和部署他们的多集群服务。
当然,为应用中的每个微服务都这样做会给团队带来很大的负担,并且通过为每个服务维护负载均衡器以及可能的跨区域网络流量,也会增加成本。就像软件设计中的所有事情一样,在复杂性和性能之间存在权衡,需要为应用确定添加服务边界隔离的合适位置,以及将服务分组到复制孤岛中的合理位置。就像单集群环境中的微服务一样,这种设计可能会随着应用的变化和增长而改变和适应。考虑到这种灵活性进行设计将有助于确保应用在不进行大规模重构的情况下进行适应。
2. 组织应用的原则与方法
2.1 组织应用的目标与原则
在深入探讨如何构建应用结构的具体细节之前,有必要考虑驱动这种结构的目标。开发基于 Kubernetes 的云原生应用的总体目标显然是可靠性和敏捷性,但这与如何设计应用的维护和部署有什么关系呢?以下三个原则可以指导设计最适合这些目标的结构:
-
将文件系统视为真相来源
-
进行代码审查以确保更改的质量
-
使用功能标志来分阶段进行部署和回滚
2.2 文件系统作为真相来源
刚开始探索 Kubernetes 时,通常会以命令式的方式与它交互。例如,运行
kubectl run
或
kubectl edit
命令来创建和修改集群中运行的 Pod 或其他对象。即使开始探索如何编写和使用 YAML 文件时,也是临时进行的,就好像文件本身只是修改集群状态道路上的一个中转站。实际上,在真正的生产化应用中,情况应该相反。
不应将集群的状态(etcd 中的数据)视为真相来源,而应将 YAML 对象的文件系统视为应用的真相来源。部署到 Kubernetes 集群的 API 对象是存储在文件系统中的真相的反映。
有很多原因说明这是正确的观点。首先,这在很大程度上使你可以将集群视为不可变基础设施。随着向云原生架构的转变,我们越来越习惯将应用及其容器视为不可变基础设施,但将集群视为不可变基础设施的情况较少。然而,将应用迁移到不可变基础设施的原因同样适用于集群。如果你的集群是通过临时应用从互联网下载的随机 YAML 文件创建的,那么它就像用命令式 bash 脚本构建的虚拟机一样危险。
此外,通过文件系统管理集群状态使多个团队成员之间的协作变得非常容易。源代码控制系统广为人知,并且可以轻松地让多个人同时编辑集群的状态,同时让每个人都清楚冲突(以及冲突的解决方法)。
所有部署到 Kubernetes 的应用都应首先在存储在文件系统中的文件中进行描述,这是绝对的首要原则。实际的 API 对象只是将这个文件系统投影到特定集群中的结果。
2.3 代码审查的作用
不久前,对应用源代码进行代码审查还是一个新颖的想法。但现在很清楚,在将代码提交到应用之前,让多个人查看代码是生产高质量、可靠代码的最佳实践。
令人惊讶的是,对于用于部署这些应用的配置,情况并非完全如此。审查代码的所有原因同样适用于应用配置。但仔细想想,对这些配置进行代码审查对于服务的可靠部署至关重要。根据经验,大多数服务中断是由于意外后果、拼写错误或其他简单错误造成的。确保至少有两个人查看任何配置更改可以显著降低此类错误的概率。
2.4 功能标志的使用
使用功能标志可以分阶段进行应用的部署和回滚。通过功能标志,可以在不影响所有用户的情况下,逐步向部分用户推出新功能。如果新功能出现问题,可以快速回滚,而不会对整个系统造成太大影响。例如,可以根据用户的特征(如用户 ID、地理位置等)来决定哪些用户可以看到新功能。
以下是一个简单的 mermaid 流程图,展示了使用功能标志进行部署和回滚的过程:
graph LR
A[开始] --> B[开发新功能]
B --> C[设置功能标志为关闭]
C --> D[部署新功能到生产环境]
D --> E{选择部分用户}
E -->|是| F[打开功能标志给部分用户]
E -->|否| G[打开功能标志给所有用户]
F --> H{收集反馈}
H -->|有问题| I[关闭功能标志]
H -->|无问题| J[打开功能标志给所有用户]
I --> K[修复问题]
K --> B
J --> L[结束]
通过遵循这些原则,可以更好地组织应用,提高应用的可靠性和可维护性,同时降低部署风险,实现更灵活的应用开发和管理。
2.5 组织应用的实际操作建议
在明确了组织应用的原则后,下面给出一些实际操作建议,帮助你更好地将这些原则应用到实际项目中。
2.5.1 文件系统结构规划
为了将文件系统作为真相来源,需要合理规划文件系统的结构。可以按照应用的不同组件、环境等进行划分。例如:
my-app/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
├── dev/
│ ├── kustomization.yaml
│ └── patch.yaml
├── prod/
│ ├── kustomization.yaml
│ └── patch.yaml
在这个结构中,
base
目录存放应用的基础配置文件,
dev
和
prod
目录分别存放开发环境和生产环境的定制配置。通过 Kustomize 工具,可以方便地将基础配置和环境特定配置合并,生成最终的部署文件。
2.5.2 代码审查流程建立
建立有效的代码审查流程是确保配置质量的关键。可以按照以下步骤进行:
1.
提交拉取请求(PR)
:开发人员在完成配置更改后,提交 PR 到代码仓库。
2.
自动检查
:设置自动化工具,对 PR 进行语法检查、格式检查等基本验证。
3.
人工审查
:至少邀请一名其他团队成员对 PR 进行审查。审查人员需要检查配置的正确性、是否符合最佳实践等。
4.
合并代码
:在审查通过后,将 PR 合并到主分支。
2.5.3 功能标志管理
对于功能标志的管理,可以使用专门的工具或平台。以下是一个简单的步骤:
1.
定义功能标志
:在应用中定义需要控制的功能标志,并为每个标志设置默认状态。
2.
集成到配置管理
:将功能标志的配置集成到应用的配置管理系统中,方便在不同环境中进行管理。
3.
动态调整
:在运行时,可以通过管理界面或 API 动态调整功能标志的状态,实现新功能的逐步推出和回滚。
2.6 多集群应用部署与应用组织的综合考量
在实际项目中,多集群应用部署和应用组织是相互关联的。以下是一些综合考量的要点:
2.6.1 负载均衡与应用组织
负载均衡的设计会影响应用组织的方式。例如,如果采用任播网络和 HTTP 层负载均衡,需要确保应用的配置能够适应这种负载均衡方式。可以在文件系统中为不同的负载均衡策略设置相应的配置文件。
2.6.2 状态管理与应用组织
多集群应用的状态管理需要在应用组织中得到体现。如果采用复制孤岛或分片的方式管理数据,需要在文件系统中明确各个集群的数据配置和路由规则。
2.6.3 微服务架构与应用组织
微服务架构下,每个微服务都有自己的配置和部署需求。在应用组织中,需要为每个微服务单独管理配置文件,并考虑如何协调不同微服务之间的依赖关系。
2.7 总结
多集群应用部署和应用组织虽然增加了复杂性,但对于满足现实世界的需求和用户期望是必要的。通过合理设计负载均衡、管理应用状态、选择合适的一致性模型和数据存储方式,可以提高应用的可靠性和性能。同时,遵循将文件系统作为真相来源、进行代码审查和使用功能标志等原则,可以更好地组织应用,降低维护成本,提高开发效率。
在实际项目中,需要根据应用的特点和需求,灵活选择合适的方法和策略。随着应用的发展和变化,不断调整和优化部署和组织方式,以适应新的挑战和机遇。
以下是一个表格,总结了多集群应用部署和应用组织的关键要点:
| 方面 | 要点 |
| ---- | ---- |
| 负载均衡 | 考虑任播网络、TCP 或 HTTP 层负载均衡 |
| 状态管理 | 选择合适的一致性模型,处理数据复制 |
| 数据存储 | 考虑云存储或外包,避免过度配置 |
| 应用组织 | 将文件系统作为真相来源,进行代码审查,使用功能标志 |
通过综合考虑这些要点,可以构建出高效、可靠的多集群应用系统。
下面是一个 mermaid 流程图,展示了多集群应用部署和应用组织的整体流程:
graph LR
A[开始设计应用] --> B[确定负载均衡策略]
B --> C[管理应用状态]
C --> D[选择数据存储方式]
D --> E[组织应用配置]
E --> F[进行代码审查]
F --> G[使用功能标志部署]
G --> H[监控和优化]
H --> I{是否需要调整}
I -->|是| B
I -->|否| J[结束]
这个流程图展示了一个循环的过程,强调了在应用的整个生命周期中,需要不断监控和优化,根据实际情况进行调整,以确保应用的性能和可靠性。
超级会员免费看
1047

被折叠的 条评论
为什么被折叠?



