多可用区均衡是数据类型业务在高可用场景下常用的部署方式。当业务压力增大时,有多可用区均衡调度策略的应用希望可以自动扩容出多个可用区的实例来满足集群的调度水位。本文介绍如何在多可用区实现快速弹性扩容。
前提条件
已选择多个可用区,并在每个可用区创建好vSwitch,需要在哪些可用区弹出就需要这些可用区至少有一个vSwitch。关于创建可用区和vSwtich的具体操作,请参见创建ACK托管集群。
背景信息
容器服务节点自动伸缩组件可通过预调度判断服务能否部署在某个伸缩组上,然后将扩容实例个数的请求发给指定伸缩组,最终由ESS伸缩组生成实例。但这种在一个伸缩组上配置多个可用区vSwtich的模型特点,会导致以下问题:
当多可用区业务Pod因集群资源不足无法调度时,节点自动伸缩服务会触发该伸缩组扩容,但是无法将需要扩容的可用区与实例的关系传递到ESS弹性伸缩组,因此可能会连续弹出某一个地域的多个实例,而非在多个vSwitch同时弹出,这样无法满足在多可用区同时扩容的需求。
解决方案
为了解决同时扩容多可用区节点的问题,容器服务ACK引入了ack-autoscaling-placeholder组件,通过少量的资源冗余方式,将多可用区的弹性伸缩问题转变为并发节点池的定向伸缩问题。具体操作,请参见基于ack-autoscaling-placeholder实现容器秒级伸缩。
具体原理如下:
首先为每个可用区创建一个节点池,并分别在各个节点池打上可用区的标签。
通过配置可用区标签nodeSelector的方式,使用ack-autoscaling-placeholder为每个可用区创建占位Pod,默认的占位Pod具有比较低权重的PriorityClass,应用Pod的优先级高于占位Pod。
这样业务应用Pod Pending后,会抢占各个可用区占位Pod,带有可用区nodeSelector的多可用区占位Pod处于Pending后,节点自动伸缩组件感知到的调度策略就从多个可用区的antiAffinity变成了可用区的nodeSelector,从而可以轻松处理发出扩容区域的节点的请求。
以下图两个可用区为例,介绍利用现有架构上能满足多可用区同时扩容的方法。
利用ack-autoscaling-placeholder作为业务应用和节点自动伸缩组件之间的桥梁,为每个可用区创建占位Pod,占位Pod的调度优先级需要低于实际业务应用的调度优先级。
应用Pod Pending后会迅速抢占占位Pod,并部署在各个可用区的已有节点上,同时被抢占的占位Pod会处于Pending状态。
由于占位Pod是带有可用区nodeSelector调度策略的,节点自动伸缩组件就可以并发扩容到对应的可用区。
步骤一:为可用区创建节点池并配置自定义节点标签
登录容器服务管理控制台,在左侧导航栏选择集群。
在集群列表页面,单击目标集群名称,然后在左侧导航栏,选择 。
在节点池页面右上角,单击创建节点池。
在创建节点池页面,设置创建节点池的配置项。本文以在可用区I创建开启自动伸缩的节点池auto-zone-I为例。
以下为重要配置项说明。其余配置项说明,请参见创建节点池。
配置项
说明
节点池名称
auto-zone-I
虚拟交换机
选择可用区I的虚拟交换机。
自动伸缩
开启自动弹性伸缩。
节点标签
设置节点标签的键为avaliable_zone,值为i。
单击确认配置。
在节点池页面,查看节点池列表。当auto-zone-I节点池状态为已激活,表示该节点池创建成功。
重复以上步骤,在每个需要扩容的可用区创建开启自动伸缩的节点池。
步骤二:部署Placeholder及占位Deployment
在控制台左侧导航栏,选择 。
在应用目录页签,搜索ack-autoscaling-placeholder,然后单击ack-autoscaling-placeholder。
在ack-autoscaling-placeholder页面,单击一键部署。
在创建面板,选择集群和命名空间,然后单击下一步。选择Chart版本,编辑参数,然后单击确定。
创建成功后,在
页面,可查看到该应用状态为已部署。在集群管理页左侧导航栏,选择 。
在Helm页面,单击ack-autoscaling-placeholder-defalut操作列的更新。
在更新发布面板中,更新YAML,然后单击确定。将每个可用区都设置Placeholder,每个区域需要定义一个占位Deployment。
本文以在可用区I、K、H创建占位Deployment为例,YAML示例如下:
deployments: - affinity: {} annotations: {} containers: - image: registry-vpc.cn-beijing.aliyuncs.com/acs/pause:3.1 imagePullPolicy: IfNotPresent name: placeholder resources: requests: cpu: 3500m # 调度单元CPU。 memory: 6 # 调度单元内存。 imagePullSecrets: {} labels: {} name: ack-place-holder-I # 占位Deployment名称。 nodeSelector: {"avaliable_zone":i} # 可用区标签(需要与步骤一创建节点池中配置的标签一致)。 replicaCount: 10 # 每次扩容可快速弹出Pod数量。 tolerations: [] - affinity: {} annotations: {} containers: - image: registry-vpc.cn-beijing.aliyuncs.com/acs/pause:3.1 imagePullPolicy: IfNotPresent name: placeholder resources: requests: cpu: 3500m # 调度单元CPU。 memory: 6 # 调度单元内存。 imagePullSecrets: {} labels: {} name: ack-place-holder-K # 占位Deployment名称。 nodeSelector: {"avaliable_zone":k} # 可用区标签(需要与步骤一创建节点池中配置的标签一致)。 replicaCount: 10 # 每次扩容可快速弹出Pod数量。 tolerations: [] - affinity: {} annotations: {} containers: - image: registry-vpc.cn-beijing.aliyuncs.com/acs/pause:3.1 imagePullPolicy: IfNotPresent name: placeholder resources: requests: cpu: 3500m # 调度单元CPU。 memory: 6 # 调度单元内存。 imagePullSecrets: {} labels: {} name: ack-place-holder-H # 占位Deployment名称。 nodeSelector: {"avaliable_zone":h} # 可用区标签(需要与步骤一创建节点池中配置的标签一致)。 replicaCount: 10 # 每次扩容可快速弹出Pod数量。 tolerations: [] fullnameOverride: "" nameOverride: "" podSecurityContext: {} priorityClassDefault: enabled: true name: default-priority-class value: -1
更新成功后,在各个可用区可以查看到占位Deployment已创建。
步骤三:创建实际负载的PriorityClass
使用以下YAML示例,创建名为priorityClass.yaml的文件。
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000000 # 配置优先级,比步骤二的工作负载默认优先级高。 globalDefault: false description: "This priority class should be used for XYZ service pods only."
如果不希望在负载上配置PriorityClass,可以通过设置全局PriorityClass的方式进行全局默认设置,这样只需要下发默认配置后,抢占能力便自动生效。
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: global-high-priority value: 1 # 配置优先级,比步骤二的工作负载默认优先级高。 globalDefault: true description: "This priority class should be used for XYZ service pods only."
执行以下命令,部署工作负载的PriorityClass。
kubectl apply -f priorityClass.yaml
预期输出:
priorityclass.scheduling.k8s.io/high-priority created
步骤四:创建实际工作负载
以可用区I为例。
使用以下YAML示例,创建名为workload.yaml的文件。
apiVersion: apps/v1 kind: Deployment metadata: name: placeholder-test labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: nodeSelector: # 节点选择。 avaliable_zone: "i" priorityClassName: high-priority # 这里写入步骤三配置的PriorityClass名称,如果全局配置开启,则为非必填。 containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 resources: requests: cpu: 3 # 实际负载的资源需求。 memory: 5
执行以下命令,部署实际的工作负载。
kubectl apply -f workload.yaml
预期输出:
deployment.apps/placeholder-test created
结果验证
部署后在
页面可以发现,由于实际负载的PriorityClass比占位Pod的要高,在弹出节点上,抢占的占位Pod会运行起来。而被抢占的占位Pod会触发节点自动伸缩组件的并发扩容,为下次实际负载扩容做准备。在
页面,可以看到负载Pod运行在之前占位Pod的节点上。