基于 Kubernetes 的自动扩展(auto scaling)引

Kubernetes (略记为 k8s)作为开源的容器编排系统,近年来快速流行,正在成为云原生分布式系统架构中的事实标准。可扩展性(scalability)是分布式系统设计的一项重要主题,而 Kubernetes 提供了丰富的自动可扩展性支持。本文以 Kubernetes 内建特性为基础,结合 Azure Kubernetes Service (AKS)的部分专有特性,介绍 Kubernetes 为实现自动扩展提供的一些基本方法。面对不同系统更为复杂的实际生产环境,仍有必要评估是否需要更加复杂的架构来满足具体的可扩展性需求。本文不涉及扩展方案的具体设计细节。

考虑到 Kubernetes 依然是相对较新的系统,很多术语的中文翻译尚未被广泛接受,且考虑到易读性,本文会根据情况使用中文翻译或保留英文原文。建议将本文作为能了解概况的介绍,在快速阅读后搜索相关文档获取更详细的信息。

衡量标准(Metrics)

在设计自动扩展时,首要问题便是,何时扩展。为了回答这一问题,首先需要确定需要被监控的衡量标准,来判断是否需要扩展系统。

对于 Kubernetes,常见的衡量标准包括 CPU 和内存等 pod 自身的资源利用情况,也可以是 Kubernests 之外的某些队列的长度等其他外部指标。

扩展形式

根据水平(horizontal)还是垂直(vertical),基于 pod 还是基于 node,Kubernetes 的扩展大致可以分为四种类型。水平扩展指增加或减少资源,如改变 pod 或 node 的数量。垂直扩展指提高或降低资源的属性,如改变 pod 请求的和最大允许的资源上限,或改变 node 的性能。

在这四种扩展形式的组合中,node 的垂直扩展不常见。下面将简述剩余三种,以及两种 AKS 支持的额外方案。

水平 Pod 自动扩展

根据需要增减 pod 副本(replica)是应对负载变动的常用手段。Kubernetes 提供了水平 Pod 自动扩展器(Horizontal Pod Autoscaler,HPA)来自动管理 pod 的扩展。

HPA 适用于无状态应用,但也支持有状态应用(stateful set)。HPA 可以与集群自动扩展器(cluster autoscaler)结合,在满足扩展需求的同时节省开销。

HPA 控制器通过监控内部和外部衡量标准,来判断 pod 是否需要扩展。衡量标准支持以下三类:

  • pod 自身属性,如 CPU 利用率
  • 自定义衡量标准,如网络流量或其他与 pod 相关的数值
  • 外部衡量标准,与 pod 无关。如外部队列中等待处理的任务数量

HPA 控制器默认作为标准的 kube-controller-manager 运行,因此仅能监控由副本控制器创建的 pod,如 deployment、replica set,或 statful set。HPA 还需要一个衡量标准源,如 metrics-server 来监控 pod 的 CPU 使用,或者是实现了 custom.metrics.k8s.is 或 external.metrics.k8s.io 的服务来分别监控自定义或外部衡量标准。

集群自动扩展器

集群自动扩展器(Cluster Autoscaler,CA)能够根据集群使用率增减 node 的数量,帮助优化成本。

CA 持续运行两种任务来监视未被安排的 pod,并计算是否能在满足当前负载的前提下减少 node 数量。

pod 未被安排可能有以下原因:

  • CPU 或内存资源不足
  • 没有匹配 pod 的亲和性(affinity)规则,或污点(taint)和容忍度(toleration)的 node

如发现未被安排的 pod,CA 将检查 node 池,并增加新 node 来解决该问题。如果条件允许,CA 也会移动 pod 至其他 node 来优化使用。CA 在增加 node 时也会试图保持不同区域(zone)之间的平衡,但在减少 node 时则不会有额外处理。

CA 仅受如 AKS 等云端 Kubernetes 支持。这些 Kubernetes 服务往往也会处理好 CA 带来的安全问题。

水平 Pod 自动扩展器

水平 Pod 自动扩展器(Vertical Pod Autoscaler,VPA)能够改变 pod 容器所需的 CPU 和内存资源请求,来更好地分配集群资源。VPA 根据实际使用情况而非最初指定的资源请求上限预估来请求资源,因此更接近现实环境。

如果负载激增而又无法分散至不同 node,VPA 也能临时增加 pod 的资源来应对突发情况。

Kubernetes 不支持动态更改正在运行的 pod 的资源,因此,VPA 通过以下方式来实现扩展:

  • 一个用于监控使用情况并计算相应数值的 recommender,recommender 仅能用于获取负载状态而不实际创建新的 pod
  • 一个用于追回(evict)pod 使之可以按新的资源请求重建的 updater
  • 一个用于在新建 pod 时使用新的资源请求的 admission controller

VPA 也依赖 metrics-server。

一般情况下,如果衡量标准是 CPU 或内存利用率,VPA 不能与 HPA 同时使用。VPA 的性能也没有在大规模集群中经过验证,还有一些其他限制条件

借助 Azure Container Instance 快速扩展 AKS

Azure Container Instance(ACI)可与 AKS 集成实现快速扩展,来应对突发流量激增。ACI 实例是一种安全的隔离的逻辑计算资源,并以低延迟实现扩展。

Kubernetes Event-driven Autoscaling

Kubernetes Event-driven Autoscaling(KEDA)是由 Microsoft 和 Red Hat 开发的开源组件,它允许 Kubernetes 以事件驱动的架构模型来处理负载问题。KEDA 是 CNCF 官方项目,目前处于沙盒阶段。

KEDA 可以水平扩展 deployment 或 job。它构筑于 Kubernetes 的 HPA 之上,并提供了接口以便用户利用外部衡量标准来决定是否扩展。Kafka topic lag、Azure Queue 的长度、Prometheus 查询语句的返回结果等都能被 KEDA 用作衡量标准。

KEDA 有助于减少开发成本,更方便地使用外部衡量标准。不过,如果要同时使用 CA,仍需具体设计扩展方案,以免 KEDA 和 CA 行为发生冲突。

_

以上是 Kubernetes 自动扩展的一些基本形式和概念,可以根据关键字或相关链接进一步了解详情。关于 AKS 的扩展选择可以阅读官方文档

系统设计笔记

作为分类目录的一个补充,在这里按照功能类别对系统设计开发中的一点心得和笔记作一个索引。其中部分是根据自己在查找网络资料时找到的内容的整理与演绎,在此感谢所有那些无私分享经验的人们。

分布式系统

最终一致性(eventual consistency)引

一致性设计在分布式系统中是一个重要问题。如果一个系统同时使用多个子数据系统来存储与读取数据,就必须设计满足功能需求的一致性定义。如果系统对不同数据子系统进行操作的结果不一致,不但可能会使用户困惑,更可能引发更严重的数据问题或系统错误。一致性有多种级别,适用于不同的业务场景。对于金融等对数据一致性要求较高的行业,传统的事务可以提供较高的一致性保证。对于分布式系统等对性能(performance)和可用性(availability)要求较高的场景,牺牲一定的强一致性来换取更好的用户体验也可接受。最终一致性的具体行为根据实际需求不同,并无严格而精确的定义。本文仅作为一个引子,介绍最终一致性及与其相关的一些基本概念,方便读者对最终一致性有初步的认识,以便进一步阅读其他文章与资料,设计适合自身业务需求的系统数据一致性。

衍生数据系统

衍生数据(derived data)指的是可以由另一些数据通过某种可以复现的处理来再次生成的数据。通常,衍生数据用于加速读取数据库中的某些数据。索引(index)和缓存(cache)都是典型的衍生数据。如果衍生数据丢失,仍可以通过原始数据再次生成。

单个数据库系统中的索引属于其所存储数据的衍生数据。有时,数据库的索引可能会由于性能或成本考量而被维护在另一个系统之中,此时,该索引系统即为一种衍生数据系统。该索引系统中的数据可以通过数据库中存储的数据重新生成(即使往往开销过大而不实际),反之则不行。这种情况下,如何维护源数据库数据与索引数据库系统之间数据的一致性,值得系统设计者考虑。

事务与 ACID

事务(transaction)是一种传统的确保数据一致性的方式。在一个系统中,各种类型的错误都可能会破坏数据的一致性。网络问题、硬件故障、远程服务不可用等原因都会导致数据操作部分或彻底失败。

事务可以将一组数据读写操作合并为一组,构成一个逻辑单元,作为一个单独的数据操作执行。一次事务操作要么完全成功(提交),要么彻底失败(中止并回滚)。如果事务失败,它能被安全地再次执行。

ACID 是原子性(atomicity)、一致性(consistency)、隔离性(isolation)与持久性(durability)的首字母缩写,它通常用来描述事务的性质。

原子性减轻了多步操作中途出错时造成的问题。如果一此事务失败,原子性将确保没有数据发生变化,事务可以被安全地重新提交。

一致性事实上不是数据库的一种属性,而由具体的应用需求定义。一个应用程序可以借助原子性与隔离性来实现一致性,而这可能不仅仅是单个数据库系统的问题。一致性有多种等级,在此不具体展开介绍。

隔离性与竞争(race condition)及并发(concurrency)有关,用于解决多个事务同时执行时的相互干涉。

持久性在数据库系统中指数据应当在事务提交成功后能安全而长久地被保存,且不会因系统故障而丢失。

事务可以在很大程度上确保数据质量。然而,最近流行的 NoSQL 数据库大多不提供事务支持,以免它过高的性能开销损害它们试图提供的高可用性。 从事务的角度来看,数据存储与索引是不同的数据库对象。 许多数据库系统,包括微软 Azure Cosmos DB 等分布式数据库都提供了单对象事务支持,但并未提供完全的多对象事务支持。

异构分布式事务

异构分布式事务( heterogeneous distributed transaction)指的是事务的参与者可能由两种或多种不同的技术组成。例如,由不同供应商提供的数据库,各司其职的数据库与索引系统,甚至是消息分发系统等非数据库系统。异构分布式事务也需要满足提交(commit)的原子性,这比实现数据库内部的事务更为困难。

两阶段提交(2PC,Two-Phase Commit)是一种著名的强一致性保证。对于一次事务请求,所有子系统都将确保数据被写入或最新的数据被读取。对于由一个索引系统和一个数据库系统构成的异构分布式系统,在完成一次两阶段提交后,索引系统中存储的数据必然能反映数据库系统的最新情况,对数据库系统的更新操作也总会反应在索引系统中。

重试策略

重试(retry)即再次执行某一操作。在设计数据系统时,重试策略也是一个需要考虑的问题。不恰当的重试可能会导致以下问题:

  • 如果重试请求的响应因网络问题而没能返回,再次重试可能会使开销倍增
  • 如果问题在于系统超负载,重试可能会导致问题进一步恶化
  • 重试只能解决暂时性问题(如死锁、隔离性被违反/isolation violation,或短暂的网络故障等),因此在重试前有必要判断重试是否可能解决问题

因此,在设计重试策略时,超时、最大重试次数、两次重试之间的间隔等都需要纳入考虑。

最终一致性与BASE

与事务提供的 ACID 属性不同,最终一致性提供的一致性保证也被称为 BASE(Basic Available, Soft state, Eventual consistency)。BASE 确保如果系统之后没有更多的数据操作,最终对系统的所有访问都将返回相同的结果。BASE 更多地是确保系统使用可用,但在系统数据收敛之前,它可能返回不同的结果。 最终一致性的实际行为由具体应用定义,无法统一阐述。

采用最终一致性的数据系统通常不要求数据操作失败时执行回滚(rollback)。用户或系统日志将得知操作失败,但在另一次成功的操作之前,数据的不一致问题并不会被自动修复。

之所以最终一致性会出现,很大程度上是因为如今的网路应用常常要处理规模巨大的数据与请求。此时,更强的一致性往往并不现实,高可用性反而是更系统更核心的设计目标。

_

以上是讨论最终一致性之前可能需要了解的一些前置概念的简述,以帮助读者快速了解系统数据一致性设计时的一些主题,并能有目标地查阅更多资料与文献。水平有限,如果纰漏,欢迎批评指正。