【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

如何在 Amazon EC2 Container Service 上实现服务高可用的自动伸缩

  • 2019-11-13
  • 本文字数:6955 字

    阅读完需:约 23 分钟

如何在Amazon EC2 Container Service上实现服务高可用的自动伸缩

1. 简介

Amazon EC2 Container Service (ECS)是 Amazon 提供的一项 Docker 容器管理服务,可以让您轻松构建、运行、管理您的 Docker 容器服务。ECS Service 是 ECS 的重要组件,它可以在集群中运行指定数量的任务,当某个任务不可用时,它会重新启动新的任务,维持住任务的指定数量。这个特性从一定程度上保证了服务的可用性,但当面对突发流量,ECS 本身并不能动态地进行任务数量的扩展,当流量较少时,ECS 也无法动态地进行任务数量的缩减。为解决此问题,可以使用 Auto Scaling 和 Amazon CloudWatch 等实现服务的自动伸缩,保证服务高可用。本文将介绍一个运用 Auto Scaling 在 ECS 中实现服务高可用的方案,并通过对方案构建过程的剖析,让您对高可用、自动伸缩的服务架构有更进一步的了解。

2. 方案构建

2.1 整体结构图

本方案使用 AWS CloudFormation 模板声明整个资源堆栈所需资源和相关配置,并实现自动化构建。关于 AWS CloudFormation 相关知识,请通过以下链接了解:CloudFormation入门



从上面这个结构图可以看出以下主要组成部分:


  1. 初始由两个跨 AZ 的 Container Instance 组成的 ESC Cluster。

  2. 初始 ESC Service 中有两个 task。

  3. Service Auto Scaling 与 CloudWatch 结合,当 Service 维度的 CPUUtilization 与 Threshold 满足设定的触发条件时,触发 CloudWatch Alarm,CloudWatch Alarm 根据设定的 Scaling Policy 进行 Task 数量伸缩。

  4. Auto Scaling Group 与 CloudWatch 结合,当 Cluster 维度的 CPUReservation 与 Threshold 满足设定的触发条件时,触发 CloudWatch Alarm,CloudWatch Alarm 根据设定的 Scaling Policy 进行实例数量的伸缩。

2.2 准备工作

(1)准备好 CloudFormation 模板脚本


请从这里(点我)下载用于构建整个资源堆栈的模板脚本文件。


或者通过点击下面按钮来运行堆栈:



(2)准备好 Lambda Function


本方案在 Auto Scaling Group 中的实例关闭时,会调用一个实现自动切换 Draining 状态的 Lambda Function。那么在构建整个堆栈之前,需要先准备好这个 Lambda Function。这里我们使用 Python 编写了这个脚本 auto_drain.py 来实现此功能。您可以了载包含了这个脚本的压缩包(点我)


下载完成后,请将它上传到您账户上同个 Region 的 S3 Bucket 中。记住这个 S3 Bucket Name,在构建堆栈时,将它传给 Lambda Function S3 Bucket 这个参数。


2.3 构建过程


(1)构建堆栈


打开您的 CloudFormation 控制面板,使用模板脚本创建堆栈,创建成功后,打开堆栈“输出”栏,可以看到 ALBDNS 输出值如下所示:



将这个 URL 复制到浏览器访问,看到 It Works 字样,表示堆栈构建成功。


(2)测试 service auto scaling


我们在模板中定义了基于 ECS Service 维度的 CPU 使用率(CPU Utilization)指标进行自动伸缩 service 的 task 个数。初始状态下,我们设置 Service 的 Desired Count(参见模板中的 ServiceDesiredCount 参数)为 2,查看 ECS Service Management Console,选中左边栏“集群”,再选中 as-demo-cluster,点击“ECS Service 实例”。可以看到现在有两个容器实例,每个容器实例运行一个任务,总共有两个任务。



接下来我们利用 Apache ab 压测工具进行测试,向我们的 ALB 发送 30000 条请求,并发为每秒 1000。相应的 ab 命令如下:

col 1

$ ab -n 300000 -c 1000 http://as-in-publi-xxxxxxxxxxxxxx/


等待 ab 请求运行完成后,查看此时 ECS Service 中实例的状态,如下图所示,可以发现在某台实例上新增加了一个 task,总的 task 数量变成了 3 个,也就是成功触发了 Service Scale out。



等待几分钟,再次查看 ECS Service 中实例的状态,可以看到 task 总数量又变成了 2 个,即当 Service CPUUtilization 小于我们设的的 Threshold 一定时间(默认设置的是 5 分钟)后,自动触发了 Service Scale in。



(3)测试 cluster auto scaling


同样使用 Apache ab 进行压测,不过为了达到效果,需要加大请求数量和并发数量。


使用以下 ab 命令(当然您可以根据自己实际情况进行调整)

col 1

$ ab -n 1000000 -c 2000 http://as-in-publi-xxxxxxxxxxxxxx/


如果遇到 socket:Too many open files 异常。可以使用以下命令修改 linux 系统最大打开文件数

col 1

$ ulimit -n 2048


等待压测结束后,查看 ECS 中实例的状态(因为实例的启动与容器的启动不同,耗费时间比较长,可能需要等待几分钟才能看到效果),如下图所示,Cluster 自动增加了一台实例。



等待一段时间后,再次查看 ECS Service 中实例的状态,如下图所示,Cluster 自动减少了一台实例。



(4)测试自动切换实例 Draining 状态


我们使用 Lambda Function 实现在 Scale in 时自动切换实例到 Draining 状态(具体讲解见后面章节)。


当前集群中总的实例数量为 2,我们通过修改 Auto Scaling Group 的 desired count(在 EC2 管理窗中界面选中我们模板创建的 Auto Scaling Group,修改 desired count),将它设为 1,这样 Auto Scaling 会自动关闭一台实例,看看运行的任务数量有什么变化。


可以看到被停掉的实例状态自动切换成了 DRAINING,任务也被移到了另一台实例上。

3. 基于两个维度的自动伸缩

CloudWatch 监听 ECS 中的四个指标,分别是 CPUUtilization、MemoryUtilization、CPUReservation 和 MemoryReservation。这四个指标可以分为两大类,前两者是指资源使用率,后两者是指资源占有率。本方案通过集群和服务这两个维度,分别对占用率和使用率进行监控,定制不同的伸缩策略,从而实现自动伸缩。

3.1 什么是资源使用率和资源占有率

资源使用率(CPUUtilization 和 MemoryUtilization)是指 ECS 集群(或服务)中所有运行着的任务实际使用资源的总和除以 EC2 Container Service 集群(或服务)总的资源得到的数值,表现的是集群或服务中资源的使用情况


资源占有率(CPUReservation 和 MemoryReservation)是指 ECS 集群(或服务)中所有运行着的任务申请占有的资源的总和除以 EC2 Container Service 集群(或服务)总的资源得到的数值,表现的是集群或服务中资源的剩余情况

3.2 基于服务中 CPU 使用率实现服务自动伸缩

为了简单起见,本方案只监控 CPU 的使用率和占有率。


当服务面对越来越大的流量时,服务中 CPU 使用率快速上升,这时可以根据这个 CPU 使用率来动态地增加任务数量,以达到分担负载的作用。而不是根据 CPU 占有率来进行扩展,因为无论流量如何变大,CPU 占用率是与服务中任务数量相关的,任务数量不同,CPU 占用率也不会改变。


在本方案中,具体的构建过如下:


(1)首先,我们声明了对服务中 task desired count 作为 Auto Scaling 调整的对象 ScalableTarget,ResourceId 指向我们创建的 ECS Service 代码如下:

col 1

ServiceScalingTarget:


Type: AWS::ApplicationAutoScaling::ScalableTarget


DependsOn: DemoService


Properties:


MaxCapacity: !Ref ServiceMaxSize


MinCapacity: !Ref ServiceMinSize


ResourceId: !Join [”, [service/, !Ref ‘ECSCluster’, /, !GetAtt [DemoService, Name]]]


RoleARN: !GetAtt [ServiceAutoscalingRole, Arn]


ScalableDimension: ecs:service:DesiredCount


ServiceNamespace: ecs


(2)我们声明了两个 Scaling Policy,分别针对 Scale Out 和 Scale In 两种情况,当 Scale Out 时修改 ServiceScalingTarget 中的 Service Desired Count,增加 1 个 task;当 Scale In 时修改 ServiceScalingTarget 中的 Service Desired Count,减少 1 个 task。主要代码如下:

col 1

ServiceScaleOutPolicy:


Type: AWS::ApplicationAutoScaling::ScalingPolicy


Properties:


PolicyType: StepScaling


PolicyName: StepOutPolicy


ScalingTargetId: !Ref ‘ServiceScalingTarget’


StepScalingPolicyConfiguration:


AdjustmentType: ChangeInCapacity


MetricAggregationType: Average


StepAdjustments:


– MetricIntervalLowerBound: “0”


ScalingAdjustment: “1”


ServiceScaleInPolicy:


Type: AWS::ApplicationAutoScaling::ScalingPolicy


Properties:


PolicyType: StepScaling


PolicyName: StepInPolicy


ScalingTargetId: !Ref ‘ServiceScalingTarget’


StepScalingPolicyConfiguration:


AdjustmentType: ChangeInCapacity


MetricAggregationType: Average


(3)最后,我们声明两个 CloudWatch Alarm,分别在 Service 的 CPUUtilization 满足 Threshold 关系时,触发 Scale Out 和 Scale In 的 Alarm。

col 1

ServiceCPUUtilizationScaleOutAlarm:


Type: AWS::CloudWatch::Alarm


Properties:


EvaluationPeriods: !Ref ServiceCPUUtilizationScaleOutMinutes


Statistic: Average


Threshold: !Ref ServiceCPUUtilizationScaleOutThreshold


AlarmDescription: Alarm if Service CPUUtilization greater then threshold.


Period: ’60’


AlarmActions: [!Ref ‘ServiceScaleOutPolicy’]


Namespace: AWS/ECS


Dimensions:


– Name: ClusterName


Value: !Ref ECSCluster


– Name: ServiceName


Value: !GetAtt [DemoService, Name]


ComparisonOperator: GreaterThanThreshold


MetricName: CPUUtilization


ServiceCPUUtilizationScaleInAlarm:


Type: AWS::CloudWatch::Alarm


Properties:


EvaluationPeriods: !Ref ServiceCPUUtilizationScaleInMinutes


Statistic: Average


Threshold: !Ref ServiceCPUUtilizationScaleInThreshold


AlarmDescription: Alarm if Service CPUUtilization less then threshold.


Period: ’60’


AlarmActions: [!Ref ‘ServiceScaleInPolicy’]


Namespace: AWS/ECS


Dimensions:


– Name: ClusterName


Value: !Ref ECSCluster


– Name: ServiceName


Value: !GetAtt [DemoService, Name]


ComparisonOperator: LessThanThreshold


MetricName: CPUUtilization

3.3 基于集群中 CPU 占有率实现集群自动伸缩

当服务中任务的数量随着流量增大也不断增加时,集群中 CPU 的占有率也会上升,当 CPU 占有率不断上升,表示可用的资源已不多了,此时需要扩展新的实例,增加整个集群总的资源。


在本方案中,具体的构建过如下:


(1)首先声明两个 Scaling Policy,当集群 Scale Out 时修改集群中的实例数量,增加 1 个实例;当 Scale In 时修改集群中的实例数量,减少 1 个 task。主要代码如下:

col 1

ClusterScaleOutPolicy:


Type: “AWS::AutoScaling::ScalingPolicy”


Properties:


AdjustmentType: “ChangeInCapacity”


AutoScalingGroupName:


Ref: “ECSAutoScalingGroup”


PolicyType: “StepScaling”


MetricAggregationType: “Average”


StepAdjustments:



MetricIntervalLowerBound: “0”


ScalingAdjustment: “1”


ClusterScaleInPolicy:


Type: “AWS::AutoScaling::ScalingPolicy”


Properties:


AdjustmentType: “ChangeInCapacity”


AutoScalingGroupName:


Ref: “ECSAutoScalingGroup”


PolicyType: “StepScaling”


MetricAggregationType: “Average”


StepAdjustments:



MetricIntervalUpperBound: “0”


ScalingAdjustment: “-1”


(2)我们声明两个 CloudWatch Alarm,分别监听的指标是集群的 CPUReservation,当 CPUReservation 满足 Threshold 设定的关系时,触发 Scale Out 和 Scale In 的 Alarm。主要代码如下:

col 1

ClusterCPUReservationScaleOutAlarm:


Type: AWS::CloudWatch::Alarm


Properties:


EvaluationPeriods: !Ref ClusterCPUReservationScaleOutMinutes


Statistic: Average


Threshold: !Ref ClusterCPUReservationScaleOutThreshold


AlarmDescription: Alarm if Service CPUUtilization greater then threshold.


Period: ’60’


AlarmActions: [!Ref ‘ClusterScaleOutPolicy’]


Namespace: AWS/ECS


Dimensions:


– Name: ClusterName


Value: !Ref ECSCluster


ComparisonOperator: GreaterThanThreshold


MetricName: CPUReservation


ClusterCPUReservationScaleInAlarm:


Type: AWS::CloudWatch::Alarm


Properties:


EvaluationPeriods: !Ref ClusterCPUReservationScaleInMinutes


Statistic: Average


Threshold: !Ref ClusterCPUReservationScaleInThreshold


AlarmDescription: Alarm if Service CPUUtilization less then threshold.


Period: ’60’


AlarmActions: [!Ref ‘ClusterScaleInPolicy’]


Namespace: AWS/ECS


Dimensions:


– Name: ClusterName


Value: !Ref ECSCluster


ComparisonOperator: LessThanThreshold


MetricName: CPUReservation

4. Cluster scale in 时切换实例到 Draining 保证服务容量

我们的 ECS 集群并不是一创建好就一成不变的,特别是当集群空闲的时候,触发了 EC2 的 Auto Scaling Group 的缩减实例之后. 有些时候我们需要关闭其中某个实例,无论是由于系统升级、安装软件、还是为了节省运行成本。那么在关闭实例时,实例上正在运行的 task 怎么办呢?把它们全杀掉?那可不行,那样就等于减小了整个 ECS 服务的容量和负载能力,对性能有很大影响。另外就是当直接关闭 EC2 机器的时候杀死其中的 task, 可能造成运行在上面的 task 处理的请求异常终止,而影响到我们所提供服务的 SLA, 这种情况下,将实例切换到 Draining 状态就是一种解决方案。


当您要关闭集群中某个实例时,运行在这个实例上的 task 会被停止,然后 ECS 会在集群中另一个(或多个)实例上运行这些被停掉的 task,从而保证整个 ECS Service 的 task 数量保持不变,保证了整个服务容量和负载能力不受影响。同时,当实例被设置成为 Draining 状态之后,ECS 将会自动平滑关闭 EC2 宿主机中的 task 资源,利用定义 task 模版时候所定义的放置规则,将其移动到其他拥有剩余资源的集群中的宿主机上. 关于实例 Draining 的更多信息,请参见文档.


在本方案中,我们使用 AWS SNS 和 Lambda 实现了实例在集群的 Auto Scaling Group Scale in 时关联 Auto Scaling 的LifecycleHook,实现被关闭前调用 Lambda Function 自动切换 Draining 状态,将 task 转移到集群中其它实例上。


我们定义了一个 Lambda Function 运行 Python 脚本 auto_drain.py,这个脚本主要完成两个工作:


(1)判断传到 Lambda Function 中的 event 是否包含 autoscaling:EC2_INSTANCE_TERMINATING,是的话就将这个实例状态切换成 Draining,这样 ECS 就会自动停止此实例上所有运行中的 task,并在集群中别的实例启动同样数量的 task。


切换实例到 Draining 状态的代码:

col 1

containerStatus = containerInstances[‘status’]


if containerStatus == ‘DRAINING’:


tmpMsgAppend = {“containerInstanceId”: containerInstanceId}


else:


# Make ECS API call to set the container status to DRAINING


ecsResponse = ecsClient.update_container_instances_state(cluster=clusterName,


containerInstances=[containerInstanceId],status=’DRAINING’)


(2)判断实例上还有没有 task 正在运行中。如果仍然有运行中的 task,则过发送一条 SNS 通知重新触如这个 Lambda Function。如果实例上所有 task 都已经被停止,则解开 LifecyleHook,使这个实例被停掉。


根据 task 运行情况做不同处理的代码:

col 1

# If tasks are still running…


if tasksRunning == 1:


response = snsClient.list_subscriptions()


for key in response[‘Subscriptions’]:


if TopicArn == key[‘TopicArn’] and key[‘Protocol’] == ‘lambda’:


snsClient.publish(


TopicArn= key[‘TopicArn’],


Message=json.dumps(message),


Subject=’Publishing SNS message to invoke lambda again…’


)


# If tasks are NOT running…


elif tasksRunning == 0:


completeHook = 1


try:


response = asgClient.complete_lifecycle_action(


LifecycleHookName=lifecycleHookName,


AutoScalingGroupName=asgGroupName,


LifecycleActionResult=’CONTINUE’,


InstanceId=Ec2InstanceId)


logger.info(“Response received from complete_lifecycle_action %s”,response)


logger.info(“Completedlifecycle hook action”)


except Exception, e:


整个 Python 脚本代码,可以解压您在准备工作中下载的 auto_drain.zip,查看 auto_drain.py 脚本文件。


``

5. 总结

本文主要介绍了如何在 ECS 中使用 Auto Scaling、Amazon CloudWatch 和其他常用 AWS 服务实现高可用,可伸缩的服务架构,并结合 Lambda Function 和 AWS SNS 做到当集群中由于 Auto Scaling 缩减关闭某个实例时,自动切换实例状态到 Draining,将 task 转移到其他实例上,保证服务容量以及回收实例资源时候的 task 的平滑过渡。通过本套方案,可以让 ECS 服务在可用性和伸缩性上得到保证,从而为您解决实际业务场景中相类的问题提供一定的帮助和思路。您也可以在本方案基础上,结合实际业务需求,定制自己的 Scaling Policy、监控指标和监控维度,实现更符合您需求的解决方案。


作者介绍



李磊,AWS 解决方案架构师,负责基于 AWS 的云计算方案的架构设计,同时致力于 AWS 云服务在国内和全球的应用和推广。在大规模并发后台架构,电商系统,社交网络平台、互联网领域应用,DevOps 以及 Serverless 无服务器架构等领域有着广泛的设计与实践经验。在加入 AWS 之前超过十年的开发和架构设计经验, 带领团队攻克各种技术挑战,总是希望站在技术的最前沿。


本文转载自 AWS 技术博客。


原文链接:


https://amazonaws-china.com/cn/blogs/china/how-to-implement-high-availability-automatic-scaling-of-services-on-ecs/


2019-11-13 08:00568

评论

发布
暂无评论
发现更多内容

微信小程序开发系列 (四) :微信小程序的页面跳转路由设计

Jerry Wang

微信小程序 微信 前端开发 微信开发 4月月更

一种很爽的学习方法,被我Get到了!

博文视点Broadview

2021最新一次Java面试,快手三面一轮游,如今已拿意向书

爱好编程进阶

Java 程序员 后端开发

28岁程序身价过亿,从字节提前“退休

爱好编程进阶

Java 程序员 后端开发

卫剑钒:《大教堂与集市》被过誉了吗?

腾源会

开源 腾源会

Dubbo如何处理业务异常,这个一定要知道哦!

爱好编程进阶

Java 程序员 后端开发

大数据培训Spark SQL底层执行流程

@零度

Sparksql 大数据开发

想从单体架构演进到分布式架构,SBA 会是一个不错的选择

华为云开发者联盟

架构 微服务架构 分布式架构 单体架构 SBA

CXF webservice之手动启动服务方法(restful )

爱好编程进阶

Java 程序员 后端开发

字节跳动构建Data Catalog数据目录系统的实践

字节跳动数据平台

数据库 字节跳动 数据治理 数据目录

基于语义感知SBST的API场景测试智能生成

华为云开发者联盟

测试 语义感知 SBST 动态修正 ODG图

Docker Swarm从部署到基本操作

爱好编程进阶

Java 程序员 后端开发

DockerFile的编写构建镜像步骤,常用命令和案例

爱好编程进阶

Java 程序员 后端开发

20 数据存储服务器集群的伸缩性设计

爱好编程进阶

Java 程序员 后端开发

企业文档协作如何进行?

小炮

文档协同

2021最新分享Java面试题库万字精华 github上标星80

爱好编程进阶

Java 程序员 后端开发

中国全球GPU市场竞争格局分析

Finovy Cloud

人工智能 gpu 云服务器

大规模并行分布式深度学习

阿里云大数据AI技术

人工智能 深度学习 并行分布式训练

FusionStorage原理及组件

爱好编程进阶

Java 程序员 后端开发

Hexo 搭建:搭建与配置

爱好编程进阶

Java 程序员 后端开发

聊聊Kotlin中的lambda

北洋

kotlin Andriod 4月月更

20年最新金九银十面试必备,教你一份文档吊打面试官,拿到offer

爱好编程进阶

Java 程序员 后端开发

@RequestParam、@ModelAttribute、

爱好编程进阶

Java 程序员 后端开发

Docker下Prometheus和Grafana三部曲之三:自定义监控项开发和配置

爱好编程进阶

Java 程序员 后端开发

2021最新Java学习路线,自学者的福利

爱好编程进阶

Java 程序员 后端开发

小平邦彦:树懒style的世界一流数学家

图灵教育

数学 数学史 数学家

BS-XX-007基于JSP实现户籍管理系统

爱好编程进阶

Java 程序员 后端开发

dubbo实战之二:与SpringBoot集成

爱好编程进阶

Java 程序员 后端开发

hadoop

爱好编程进阶

Java 程序员 后端开发

13W字!2021最新发布互联网大厂高频面试技术点!

爱好编程进阶

Java 程序员 后端开发

18 张图,一文了解 8 种常见的数据结构

爱好编程进阶

Java 程序员 后端开发

如何在Amazon EC2 Container Service上实现服务高可用的自动伸缩_语言 & 开发_亚马逊云科技 (Amazon Web Services)_InfoQ精选文章