在AWS Spot实例上设计可扩展的API

2020-07-23 18:55:37

我们的后端系统构建在AWS之上。今天,我将告诉您我们如何通过在生产环境中使用Spot实例将服务器成本削减了三倍。我还将向您介绍如何配置弹性伸缩。首先,您将看到它的工作原理概述,然后我们将提供启动它的说明。

Amazon EC2 Spot实例是AWS云中的空闲计算能力,可享受大幅折扣。亚马逊表示,他们可以达到90%,根据我们的经验,这是~3倍,根据地区、AZ或实例类型的不同而有所不同。它们与常规的主要不同之处在于,它们可以随时关闭。

这就是为什么在很长一段时间里,我们认为可以将它们用于DEV环境,或者用于计算任务,并将中间结果保存到S3或数据库,但不用于PROD。有第三方解决方案允许使用SPOT进行生产,但我们没有实现它,因为我们的案例中有许多杂乱无章的东西。本文中描述的方法完全在标准AWS功能内工作,不需要额外的脚本、cron等。

接下来,我将提供一些显示Spot实例定价历史的屏幕截图。

在欧盟-西部-1地区(爱尔兰)很大。价格在三个月内基本稳定,现在节省的成本是原来的2.9倍。

在美国东部-1地区(弗吉尼亚州北部)很大。价格在三个月内不断变化,根据可用区的不同,现在的成本节约从2.3倍到2.8倍不等。

T3.在美国东部-1地区(弗吉尼亚州北部)较小。三个月内价格稳定,现在节约了3.4倍的成本。

下图描述了本文将讨论的服务的基本体系结构。

应用负载均衡器(ALB)用作均衡器。它向EC2目标组(TG)发送请求。TG负责为ALB打开实例上的端口,并将其连接到弹性容器服务(ECS)的容器端口。ECS类似于AWS中的Kubernetes,管理Docker容器。

一个实例可以有多个具有相同端口的正常运行的容器,这就是为什么我们不能为它们分配固定端口的原因。ECS向TG报告它启动了一个新任务(在Kubernetes术语中称为“Pod”)。它检查实例上的空闲端口,并将其中一个分配给新的启动任务。此外,TG还会定期检查实例和API是否在进行健康检查。如果它发现任何问题,它就会停止向那里发送请求。

在上图中,未指明EC2 Auto Scaling Groups(ASG)服务。我们可以从标题中了解到,它负责弹性伸缩。然而,直到最近,AWS才具备从ECS管理正在运行的计算机数量的内置功能。ECS允许扩展任务数量,例如,基于CPU或RAM使用率或请求数量。但是,如果任务占用了所有可用实例,则新机器不会自动启动。

这种情况随着ECS容量提供商(ECS CP)的出现而改变。ECS中的每项服务现在都可以与ASG链接。而且,如果任务没有足够的运行实例,将启动更多实例(但在ASG的既定限制内)。反之亦然。如果ECS CP发现没有任务的闲置实例,它将命令ASG将其关闭。ECS CP可以指示实例的目标容量百分比,以便始终有一定数量的机器可用于快速任务扩展。我们以后再谈这件事。

在描述基础设施的创建之前,我要讨论的最后一个服务是EC2启动模板。它允许创建一个模板来启动所有机器,以避免每次从头开始重复。您可以在这里选择启动机器的类型、安全组、磁盘镜像以及许多其他参数。还可以指示将上载到所有正在运行的实例的用户数据。您可以在用户数据中运行脚本,例如,您可以编辑ECS代理配置文件的内容。

本文中最重要的配置参数之一是ECS_ENABLE_SPOT_INSTANCE_DRAINING=TRUE。如果打开此参数,则一旦ECS接收到正在获取Spot实例的信号,它就会将其所有正在运行的任务切换到正在排出状态。不会为此实例分配任何新任务。如果此实例上有任何挂起的任务,ECS会取消它们。它也不再接收来自平衡器的请求。您会在实例发生前两分钟收到有关删除实例的通知。在这一点上,如果您的服务没有执行任务超过两分钟,并且没有将任何内容保存到磁盘,您可以使用Spot实例,而不会丢失任何数据。

谈到磁盘,AWS最近将弹性文件系统(EFS)与ECS结合使用成为可能。即使使用这种方法的磁盘也不是障碍,但我们没有对其进行测试,因为我们不需要磁盘来存储状态。默认收到SIGINT后(在任务切换到正在排出的时刻发送),所有正在运行的任务都会在30秒内停止,即使它们还没有完成。您可以使用ECS_CONTAINER_STOP_TIMEOUT参数更改此时间。主要规则是,对于现货机器,设置时间不能超过2分钟。

让我们直接开始创建所描述的服务。我将描述一些上面没有提到的有用功能。

一般来说,这是一个循序渐进的指导,但我不会提到任何基本或更具体的情况。所有操作都在AWS管理控制台中进行,但您可以使用CloudForformation或Terraform获得相同的结果。我们在Adapty中使用Terraform。

使用此服务,您可以创建计算机配置。您可以在EC2→实例→启动模板中管理模板。

亚马逊机器映像(AMI)。指定实例将使用的磁盘镜像。对于ECS,在大多数情况下,最好使用来自Amazon的优化映像。它会定期更新,并包含ECS运行所需的所有内容。要查找相关的映像ID,请转到Amazon ECS优化的AMI页面,选择区域,然后为其复制AMI ID。例如,对于US-EAST-1区域,编写本文时的相关映像ID-ami-00c7c1cf5bdc913ed。您需要粘贴此ID以指定自定义值部分。

实例类型。指定实例类型。选择最适合您的任务。

网络设置。指定网络设置。大多数情况下,网络平台应为私有网络(VPC)。安全组用于保护您的实例。由于我们将在实例前面使用平衡器,因此我建议指定只允许来自平衡器的入站连接的组。这意味着您将有两个安全组:一个用于平衡器,允许通过80(Http)和443(Https)端口从任何地方进行入站连接;另一个用于机器,允许通过来自平衡器组的任何端口进行入站连接。我们必须通过TCP协议在两个组中打开到所有地址的所有端口的出站连接。我们可以限制出站连接的端口和地址,但在这种情况下,有必要不断监控您是否试图通过关闭的端口在任何地方寻址。

存储(卷)。指定计算机的磁盘参数。磁盘容量不能小于AMI中指定的容量,ECS优化后为30GiB。

购买选择权。它询问我们是否要购买Spot实例。我们有,但不勾选此处的复选框。我们会在Auto Scaling Group中配置它,那里有更多选项。

IAM实例配置文件。指定实例的角色。实例需要访问权限才能在ECS内运行。访问权限通常在ecsInstanceRole中定义。在某些情况下,该角色存在于您的AWS帐户中。如果没有,那么这里有关于如何做的说明。创建后,在模板中指定。

接下来,有很多参数,基本上可以保留默认值,但每个参数都有易于理解的说明。如果我们使用可突发实例,我总是打开EBS优化实例和T2/T3无限参数。

用户数据。指定用户数据。我们将编辑存储ECS代理配置的/etc/ecs/ecs.config文件。

#!/bin/bashecho ECS_CLUSTER=DemoApiClusterProd>;>;/etc/ecs/ecs.configecho ECS_ENABLE_SPOT_INSTANCE_DRAINING=true>;>;/etc/ecs/ecs.configecho ECS_CONTAINER_STOP_TIMEOUT=1M>;>;/etc/ecs/ecs.configecho ECS_ENGINE_AUTH_TYPE=docker>;;>;:\";用户名\";,\";password\";:\";password\";}}";&>;>;/etc/ecs/ecs.config。

Ecs_cluster=DemoApiClusterProd-此参数指示实例属于具有给定名称的集群,这意味着该集群将能够将其任务放在此服务器上。我们仍未创建群集,但我们将在创建群集时使用此名称。

ECS_ENABLE_SPOT_INSTANCE_DRAINING=TRUE-该参数指出,在接收到Spot实例关闭信号后,所有任务都必须切换到正在排出状态。

ECS_CONTAINER_STOP_TIMEOUT=1M-该参数表示收到SIGINT信号后,所有任务在被终止前有一分钟的时间。

ECS_ENGINE_AUTH_DATA=...-这些是连接到私有容器注册表的参数,您所有的Docker映像都存储在该注册表中。如果它们是公开的,则不需要指定任何其他内容。

在本文中,我将使用来自Docker Hub的公共映像,不需要指定ECS_ENGINE_AUTH_TYPE和ECS_ENGINE_AUTH_DATA参数。

很高兴知道:他们建议定期更新AMI,因为新版本有最新版本的Docker、Linux、ECS代理等。您可以设置更新通知。您可以收到电子邮件通知或手动更新它,或者您可以编程一个lambda函数,它将自动创建一个带有更新AMI的新版本的启动模板。

弹性伸缩组负责实例的启动和伸缩。您可以在ec2→Auto Scaling→Auto Scaling组中管理组。

购买选项和实例类型。指定集群的实例类型。坚持启动模板使用启动模板中的实例类型。结合购买选项和实例类型,可以设置灵活的实例类型。我们将使用后者。

可选的按需基础。*始终上线的是多个常规实例(不是Spot实例)。On-Demand Percent Over Base是普通实例和Spot实例的百分比,50-50平均分配,每个常规实例20-80启动4个Spot实例。这里我会选择50-50,但实际上,我们大多选择20-80,有时甚至是0-100(结合大于零的按需基数)。

实例类型。您可以在此处指定将在集群中使用的高级实例类型。我们从来没有用过它,因为我不太明白它的意义。

网络。这些都是网络设置,选择机器的私有网络和子网,大多数情况下最好选择所有可用的子网。

负载均衡。这些是平衡器设置,不要在这里做任何事情。我们稍后将配置负载均衡器和运行状况检查。

组大小。指定群集中的计算机数量限制和开始时所需的计算机数量。机器数量永远不会少于指定的最小值或大于指定的最大值,即使在根据指标进行扩展的情况下也是如此。

伸缩策略。我们将使用基于ECS运行任务的伸缩,在这种情况下,我们将在稍后配置伸缩。

实例扩容保护。缩容时实例不会被删除。打开它,这样ASG就不会删除有运行任务的计算机。ECS容量提供商将关闭对没有任何任务的实例的保护。

添加标签。您可以为实例添加标签(标记新实例复选框中必须有标记)。我建议使用名称标签,这样从群组启动的所有实例都将具有相同的名称,很容易在控制台中看到它们。

创建组后,打开它并转到高级配置。在设计控制台时,您看不到所有选项。

终止政策。这些是删除实例时要考虑的规则。它们是按顺序应用的。我们通常使用的政策如下图所示。首先,他们删除具有最旧启动模板的实例(例如,如果我们更新了AMI,我们就得到了新版本,但并不是所有实例都有时间移动到那里)。然后根据计费选择距离下一次退房最近的实例。然后按发布日期选择最老的。

很高兴知道:要更新集群中的所有机器,使用实例刷新很方便。如果您将其与上一步中的lambda-function相结合,您将拥有一个完全自动化的系统来更新实例。在更新所有机器之前,您需要关闭组内所有实例的实例扩容保护。需要关闭的是防护,不是群组中的设置,可以在实例管理中关闭。

我们将使用应用程序负载均衡器。您可以在Serviсe网页上阅读不同型号平衡器的比较。

听众。选择80和443个端口并稍后使用平衡器规则从80重定向到443是有意义的。

配置安全设置。这里我们为均衡器指定SSL证书,最有用的解决方案是在ACM中制作证书。您可以在此处阅读有关安全策略的内容。您可以保留默认的ELBSecurityPolicy-2016-08。创建平衡器后,您将看到其DNS名称。您将需要它来为您的域创建CNAME。例如,这是它在CloudFlare中的外观。

安全组。为均衡器创建或选择一个安全组(上面的说明在ec2启动模板→网络设置中)。

目标群体。创建一个组,负责将请求从平衡器路由到机器,并检查它们的可用性,以便在出现问题时替换它们中的任何一个。目标类型为实例,您可以选择任意协议和端口,如果您使用HTTPS进行均衡器与实例的交互,则需要在那里上传证书。在本例中,我们不会这样做,只需保留端口80即可。

健康检查。这些是服务运行状况检查参数。在实际服务中,它应该是实现业务逻辑重要部分的单独请求。在本例中,我将保留默认设置。接下来,您可以指定请求间隔、超时时间、成功码等。这里我们会指明成功码200-399,因为要使用的docker镜像会返回304码。

注册目标。在这里,您可以为组选择计算机,但在我们的示例中,ECS将执行此操作,只需跳过此步骤。

很高兴知道:在平衡器级别,您可以使用某种格式打开将保存在S3中的日志。您可以将它们导出到第三方分析,或者在雅典娜的帮助下直接从S3数据发出SQL请求。它很简单,不需要任何额外的代码就可以工作。我建议您在指定时间段后配置从S3存储桶中删除日志。

在前面的步骤中,我们已经创建了与服务基础设施相关的所有内容。现在我们转到将要启动的容器的配置。您可以在ECS→任务定义中执行此操作。

任务执行IAM角色。选择ecsTaskExecutionRole。它用于写入日志、访问秘密变量等。

Image是指向带有项目代码的镜像的链接,在本例中,我将使用带有Docker Hub bitnami/node-example:0.0.1的公共镜像。

硬限制。如果容器超出指定值,则将运行docker kill命令,容器将立即死亡。

软限制。容器可能会超出指定值,但在计算机上部署任务时会考虑此参数。例如,如果计算机具有4 GiB的RAM,而软限制容器为2048 MiB,则此计算机使用此容器最多可以有2个正在运行的任务。实际上,4 GiB的RAM略低于4096 MiB。您可以在集群的ECS实例部分看到它。软限制不能超过硬限制。重要的是要了解,如果一个任务有多个容器,则会汇总它们的限制。

端口映射。在主机端口中指定0。这意味着端口将动态分配,目标组将对其进行监视。容器端口是您的应用程序在其上运行的端口。它通常在命令中设置为执行,或者在您的应用代码、Dockerfile等中分配。让我们使用3000作为我们的示例,因为它在使用的镜像的Dockerfile中指明。

健康检查是容器健康检查参数,不要与目标组中配置的参数混淆。

环境是环境设置。CPU单元就像内存限制。每个处理器核心包含1024个单元,因此如果服务器有一个双核处理器,并且容器的值为512,则使用该容器的4个任务可以在一台服务器上运行。CPU单位始终与内核数量相对应,它们不能像内存那样稍微少一些。

命令是在容器内启动服务的命令。指定用逗号分隔它们的所有参数。它可以是Guricorn、NPM等。如果未指定,将使用Dockerfile中的CMD指令的值。指定npm,start。

环境变量是容器环境变量。它可以是文本数据,也可以是Secrets Manager或Parameter Store中的秘密变量。

存储和记录。配置记录到CloudWatch日志(AWS日志服务)。选中自动配置CloudWatch日志复选框就足够了。创建任务定义后,将在CloudWatch中创建日志组。默认情况下,日志将永久存储在那里。我建议将保留期从永不过期更改为确切的保留期。在CloudWatch日志组中执行此操作,只需单击当前时段并选择新时段即可。

转到ECS→群集以创建群集。选择EC2 Linux+Networking作为模板。

群集名称。在ECS_CLUSTER参数中,为集群指定与启动模板中相同的名称非常重要,在我们的示例中,它是DemoApiClusterProd。勾选一个复选框,创建一个空群集。或者,您可以打开Container Insight以在CloudWatch中按服务查看指标。如果您做的一切都是正确的,那么在ECS实例中,您将看到由Auto Scaling组管理的机器。

转到Capacity Providers选项卡并创建一个新的。您需要它来管理机器的创建和关闭,具体取决于运行的ECS任务的数量。重要提示:提供程序只能链接到一个组。

目标容量%负责具有任务的机器的负载百分比。如果您指定100%,则所有计算机都将始终忙于运行任务。如果您指定50%,一半的机器将始终是空闲的。在这种情况下,如果负载急剧增加,新任务将立即转到空闲的机器上,而不需要等待实例启动。

受管终止保护。打开它。此参数允许提供程序在需要时移除实例保护。

要创建服务,您需要转到我们之前创建的集群的Services选项卡。

Number of Tasks是服务中所需的运行任务数。此参数按比例管理,但仍需指定。

最小健康百分比和最大百分比定义部署期间任务的行为。默认值为100和200。他们的意思是,在部署任务数量时,

.