在 AWS Spot 实例上构建可扩展的 API

大家好! 我叫基里尔,是 Adapty 的首席技术官。 我们的大部分架构都在AWS上,今天我将讨论如何通过在生产环境中使用现货实例将服务器成本降低3倍,以及如何设置其自动扩展。 首先将概述其工作原理,然后提供详细的入门说明。

什么是 Spot 实例?

实例是其他 AWS 用户当前闲置的服务器,他们以很大的折扣出售它们(亚马逊的折扣高达 90%,根据我们的经验约为 3 倍,具体取决于区域、可用区和实例类型)。 它们与普通的主要区别在于它们可以随时关闭。 因此,很长一段时间我们认为,将它们用于原始环境,或者用于计算某些内容的任务,中间结果保存在 S3 或数据库中,这是正常的,但不用于销售。 有第三方解决方案允许您在生产中使用点,但我们的案例有很多拐杖,因此我们没有实施它们。 本文中描述的方法完全在标准 AWS 功能内运行,无需额外的脚本、皇冠等。

以下是一些显示现货实例价格历史记录的屏幕截图。

m5.large 位于 eu-west-1(爱尔兰)区域。 价格近 3 个月基本稳定,目前节省 2.9 倍。

在 AWS Spot 实例上构建可扩展的 API

m5.large 位于 us-east-1 区域(弗吉尼亚北部)。 价格在 3 个月内不断变化,目前可节省 2.3 倍到 2.8 倍,具体取决于可用区域。

在 AWS Spot 实例上构建可扩展的 API

t3.small 位于 us-east-1 区域(弗吉尼亚北部)。 价格已稳定 3 个月,目前节省 3.4 倍。

在 AWS Spot 实例上构建可扩展的 API

服务架构

我们将在本文中讨论的服务的基本架构如下图所示。

在 AWS Spot 实例上构建可扩展的 API

应用程序负载均衡器 → EC2 目标组 → 弹性容器服务

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

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

EC2 Auto Scaling 组 + ECS 容量提供商

上图未显示 EC2 Auto Scaling Groups (ASG) 服务。 从名字上就可以理解,它负责实例的伸缩。 然而,直到最近,AWS 还没有内置的功能来管理 ECS 中正在运行的机器的数量。 ECS 可以扩展任务数量,例如通过 CPU 使用率、RAM 或请求数量。 但如果任务占用了所有空闲实例,则不会自动创建新机器。

随着 ECS 容量提供商 (ECS CP) 的出现,这种情况发生了变化。 现在,ECS 中的每个服务都可以与 ASG 关联,如果任务不适合正在运行的实例,则会引发新任务(但在既定的 ASG 限制内)。 这也适用于相反的方向,如果 ECS CP 看到没有任务的空闲实例,那么它将向 ASG 命令关闭它们。 ECS CP 能够指定实例负载的目标百分比,以便始终有一定数量的机器空闲用于快速扩展任务;稍后我会讨论这一点。

EC2 启动模板

在详细介绍创建此基础设施之前,我要讨论的最后一项服务是 EC2 启动模板。 它允许您创建一个模板,所有机器将根据该模板启动,以免每次都从头开始重复。 在这里您可以选择要启动的机器类型、安全组、磁盘映像和许多其他参数。 您还可以指定将上传到所有已启动实例的用户数据。 您可以在用户数据中运行脚本,例如,您可以编辑文件的内容 ECS代理配置.

本文最重要的配置参数之一是 ECS_ENABLE_SPOT_INSTANCE_DRAINING=真。 如果启用此参数,那么一旦 ECS 收到 Spot 实例正在被删除的信号,它就会将所有在该实例上运行的任务转移到 Draining 状态。 不会向该实例分配新任务;如果现在有任务想要部署到该实例,它们将被取消。 来自平衡器的请求也停止了。 实例删除通知会在实际事件发生前 2 分钟发出。 因此,如果您的服务执行任务的时间不超过 2 分钟,并且没有将任何内容保存到磁盘,那么您可以使用竞价实例而不会丢失数据。

关于磁盘 - AWS 最近 可以将弹性文件系统(EFS)与ECS一起使用;使用这种方案,即使磁盘也不是障碍,但我们没有尝试这样做,因为原则上我们不需要磁盘来存储状态。 默认情况下,收到 SIGINT(当任务转移到 Draining 状态时发送)后,所有正在运行的任务将在 30 秒后停止,即使它们尚未完成;您可以使用参数更改此时间 ECS_CONTAINER_STOP_TIMEOUT。 主要是现货机不要设置超过2分钟。

创建服务

让我们继续创建所描述的服务。 在这个过程中,我会额外描述一些上面没有提到的有用的点。 一般来说,这是一个分步说明,但我不会考虑一些非常基本的情况,或者相反,非常具体的情况。 所有操作均在 AWS 可视控制台中执行,但可以使用 CloudFormation 或 Terraform 以编程方式重现。 在 Adapty,我们使用 Terraform。

EC2 启动模板

该服务创建将使用的机器的配置。 模板在 EC2 -> 实例 -> 启动模板部分中进行管理。

亚马逊机器映像 (AMI) — 指定将用于启动所有实例的磁盘映像。 对于 ECS,在大多数情况下值得使用 Amazon 的优化镜像。 它定期更新并包含 ECS 工作所需的一切。 要查找当前图像 ID,请转到页面 Amazon ECS 优化的 AMI,选择您正在使用的区域并复制其 AMI ID。 例如,对于 us-east-1 区域,写入时的当前 ID 为 ami-00c7c1cf5bdc913ed。 必须将此 ID 插入指定自定义值项中。

实例类型 — 表示实例类型。 选择最适合您任务的一项。

密钥对(登录) — 指定一个证书,您可以使用该证书通过 SSH 连接到实例(如有必要)。

网络设置 — 指定网络参数。 联网平台 在大多数情况下,应该有虚拟私有云 (VPC)。 安全组 — 您的实例的安全组。 由于我们将在实例前面使用平衡器,因此我建议在此处指定一个组,仅允许来自平衡器的传入连接。 也就是说,您将有 2 个安全组,一个用于平衡器,允许来自端口 80 (http) 和 443 (https) 上任何位置的入站连接,第二个用于计算机,允许来自平衡器组的任何端口上的传入连接。 必须使用 TCP 协议打开两个组中所有端口到所有地址的出站连接。 您可以限制传出连接的端口和地址,但是您需要不断监视您是否没有尝试访问已关闭端口上的某些内容。

存储(卷) — 指定机器的磁盘参数。 磁盘大小不能小于 AMI 中指定的大小;对于 ECS Optimized,磁盘大小为 30 GiB。

高级细节 — 指定附加参数。

购买选择 — 我们是否要购买现货实例。 我们想要,但我们不会在这里选中此框;我们将在 Auto Scaling 组中配置它,那里有更多选项。

IAM 实例配置文件 — 指示将启动实例的角色。 为了让实例在 ECS 中运行,它们需要权限,这些权限通常可以在角色中找到 ecs实例角色。 在某些情况下可以创建,如果没有,那么这里 指令 关于如何做到这一点。 创建完成后,我们在模板中注明。
接下来有很多参数,基本上到处都可以保留默认值,但每个参数都有明确的说明。 我始终启用 EBS 优化实例和 T2/T3 Unlimited 选项(如果使用) 可爆裂的 实例。

用户时间 — 表示用户数据。 我们将编辑该文件 /etc/ecs/ecs.config,其中包含 ECS 代理配置。
用户数据的示例如下:

#!/bin/bash
echo ECS_CLUSTER=DemoApiClusterProd >> /etc/ecs/ecs.config
echo ECS_ENABLE_SPOT_INSTANCE_DRAINING=true >> /etc/ecs/ecs.config
echo ECS_CONTAINER_STOP_TIMEOUT=1m >> /etc/ecs/ecs.config
echo ECS_ENGINE_AUTH_TYPE=docker >> /etc/ecs/ecs.config
echo "ECS_ENGINE_AUTH_DATA={"registry.gitlab.com":{"username":"username","password":"password"}}" >> /etc/ecs/ecs.config

ECS_CLUSTER=DemoApiClusterProd — 该参数表示该实例属于给定名称的集群,即该集群将能够将其任务放置在该服务器上。 我们还没有创建集群,但是我们在创建集群时会使用这个名称。

ECS_ENABLE_SPOT_INSTANCE_DRAINING=true — 该参数指定当收到关闭 Spot 实例的信号时,该实例上的所有任务都应转为 Draining 状态。

ECS_CONTAINER_STOP_TIMEOUT=1m - 该参数指定在收到SIGINT信号后,所有任务在被杀死之前有1分钟的时间。

ECS_ENGINE_AUTH_TYPE=docker --该参数表示采用Docker方案作为授权机制

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 组负责启动和扩展实例。 组在 EC2 -> Auto Scaling -> Auto Scaling Groups 部分中进行管理。

启动模板 — 选择上一步中创建的模板。 我们保留默认版本。

购买选项和实例类型 — 指定集群的实例类型。 坚持启动模板使用启动模板中的实例类型。 结合购买选项和实例类型,您可以灵活配置实例类型。 我们会用它。

可选的按需底座 — 始终有效的常规非 Spot 实例的数量。

高于基础的按需百分比 — 普通实例和现货实例的比例,平均分配50-50个,每个普通实例20-80个,增加4个现货实例。 出于本示例的目的,我将指定 50-50,但实际上我们通常会指定 20-80,在某些情况下指定为 0-100。

实例类型 — 在这里您可以指定将在集群中使用的其他实例类型。 我们从来没有使用过它,因为我不太明白这个故事的含义。 也许这是由于特定类型实例的限制,但可以通过支持轻松增加它们。 如果您知道该应用程序,我将很高兴在评论中阅读)

在 AWS Spot 实例上构建可扩展的 API

商业网络 — 网络设置,为机器选择 VPC 和子网,大多数情况下您应该选择所有可用的子网。

负载均衡 - 平衡器设置,但我们将单独进行此操作,我们不会在这里触及任何内容。 健康检查 稍后也会配置。

团体人数 — 我们在开始时指出集群中机器数量的限制以及所需的机器数量。 集群中的机器数量永远不会小于指定的最小值且不会大于最大值,即使根据指标进行扩展也是如此。

扩展政策 — 伸缩参数,但是我们会根据正在运行的ECS任务进行伸缩,所以稍后我们会配置伸缩。

实例缩容保护 — 缩小规模时保护实例不被删除。 我们启用它,以便 ASG 不会删除正在运行任务的机器。 ECS Capacity Provider 将禁用对没有任务的实例的保护。

添加标签 — 您可以为实例指定标签(为此,必须选中标记新实例复选框)。 我建议指定Name标签,那么组内启动的所有实例都将具有相同的名称,并且可以方便地在控制台中查看它们。

在 AWS Spot 实例上构建可扩展的 API

创建组后,打开它并进入高级配置部分。为什么在创建阶段不是所有选项都在控制台中可见。

终止政策 — 删除实例时考虑的规则。 它们按顺序应用。 我们通常使用下图中的那些。 首先,具有最旧启动模板的实例将被删除(例如,如果我们更新了 AMI,我们创建了一个新版本,但所有实例都设法切换到它)。 然后选择最接近下一个计费时间的实例。 然后根据发布日期选择最旧的。

在 AWS Spot 实例上构建可扩展的 API

好知道:更新集群内所有机器,使用方便 实例刷新。 如果将此与上一步中的 Lambda 函数结合起来,您将拥有一个完全自动化的实例更新系统。 在更新所有机器之前,您必须为组内的所有实例禁用实例缩容保护。 不是在组中进行配置,而是对计算机本身进行保护,这是在“实例管理”选项卡上完成的。

应用程序负载均衡器和 EC2 目标组

均衡器在 EC2 → 负载均衡 → 负载均衡器部分中创建。 我们将使用应用程序负载均衡器;不同类型均衡器的比较可以阅读 服务页面.

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

可用区 - 在大多数情况下,我们会为每个人选择无障碍区域。

配置安全设置 — 此处显示了平衡器的 SSL 证书,最方便的选项是 制作证书 在 ACM 中。 关于差异 安全政策 可以读入 文件资料,您可以默认选择它 ELBSecurityPolicy-2016-08。 创建平衡器后,您将看到它 DNS名称,您需要为您的域配置 CNAME。 例如,这就是它在 Cloudflare 中的样子。

在 AWS Spot 实例上构建可扩展的 API

安全组 — 为平衡器创建或选择一个安全组,我在上面的 EC2 启动模板 → 网络设置部分中写了更多相关内容。

目标组 - 我们创建一个小组,负责将请求从平衡器路由到机器,并检查它们的可用性,以便在出现问题时替换它们。 目标类型 必须是实例, 协议 и 港口 任何,如果平衡器和实例之间使用 HTTPS 进行通信,那么您需要向它们上传证书。 出于本示例的目的,我们不会这样做,我们将简单地保留端口 80。

健康检查 — 用于检查服务功能的参数。 在实际服务中,这应该是一个单独的请求,用于实现业务逻辑的重要部分;出于本示例的目的,我将保留默认设置。 接下来,您可以选择请求间隔、超时、成功代码等。在我们的示例中,我们将指示成功代码 200-399,因为将使用的 Docker 映像返回 304 代码。

在 AWS Spot 实例上构建可扩展的 API

注册目标 — 这里选择了该组的汽车,但在我们的例子中,这将由 ECS 完成,因此我们只需跳过此步骤。

好知道:在平衡器级别,您可以启用将在特定时间内保存在 S3 中的日志 格式。 从那里它们可以导出到第三方服务进行分析,或者您可以直接对 S3 中的数据进行 SQL 查询 使用雅典娜。 它很方便并且无需任何额外代码即可工作。 我还建议设置在指定时间段后从 S3 存储桶中删除日志。

ECS任务定义

在前面的步骤中,我们创建了与服务基础设施相关的所有内容;现在我们继续描述我们将启动的容器。 这是在 ECS → 任务定义部分中完成的。

启动类型兼容性 - 选择 EC2。

任务执行 IAM 角色 - 选择 ecsTaskExecutionRole。 使用它,可以写入日志,提供对秘密变量的访问权限,等等。

在“容器定义”部分中,单击“添加容器”。

图片 — 链接到包含项目代码的镜像;在本例中,我将使用 Docker Hub 中的公共镜像 bitnami/节点示例:0.0.1.

内存限制 — 容器的内存限制。 硬限制 --硬限制,如果容器超出指定值,将执行dockerkill命令,容器将立即死亡。 软极限 -- 软限制,容器可以超出指定值,但是在机器上放置任务时会考虑到该参数。 例如,如果一台机器有 4 GiB RAM,并且容器的软限制为 2048 MiB,则该机器最多可以有 2 个正在运行的任务。 实际上,4 GiB RAM 略小于 4096 MiB,这可以在集群中的 ECS 实例选项卡上查看。 软限制不能大于硬限制。 重要的是要理解,如果一项任务中有多个容器,那么它们的限制将被汇总。

端口映射 - 在 主机端口 我们指示 0,这意味着该端口将动态分配并由目标组监控。 集装箱港口 — 应用程序运行的端口通常在执行命令中指定,或者在应用程序代码、Dockerfile 等中指定。 对于我们的示例,我们将使用 3000,因为它列在 Dockerfile 正在使用的图像。

健康检查 — 容器运行状况检查参数,不要与目标组中配置的参数混淆。

环境 - 环境设置。 CPU单元 - 与内存限制类似,仅与处理器有关。 每个处理器核心为 1024 个单元,因此如果服务器具有双核处理器并且容器设置为 512,则可以在一台服务器上启动 4 个具有该容器的任务。 CPU 单元始终与核心数量相对应;核心数量不能少一点,内存也是如此。

命令 — 在容器内启动服务的命令,所有参数均以逗号分隔指定。 这可能是gunicorn、npm 等。 如果未指定,将使用 Dockerfile 中的 CMD 指令的值。 我们表明 npm,start.

环境变量 — 容器环境变量。 这可以是简单的文本数据或来自的秘密变量 秘密经理 или 参数存储.

存储和日志记录 — 在这里,我们将在 CloudWatch Logs(来自 AWS 的日志服务)中设置日志记录。 为此,只需启用自动配置 CloudWatch Logs 复选框即可。 创建任务定义后,CloudWatch 中将自动创建一组日志。 默认情况下,日志无限期地存储在其中;我建议将保留期限从永不过期更改为所需的期限。 这是在 CloudWatch Log 组中完成的,您需要单击当前周期并选择一个新周期。

在 AWS Spot 实例上构建可扩展的 API

ECS 集群和 ECS 容量提供商

进入 ECS → 集群部分创建集群。 我们选择 EC2 Linux + Networking 作为模板。

集群名称 - 非常重要,我们在这里使用与启动模板参数中指定的名称相同的名称 ECS_CLUSTER,在我们的例子中 - DemoApiClusterProd。 选中创建空集群复选框。 或者,您可以启用 Container Insights 以查看 CloudWatch 中服务的指标。 如果您正确执行了所有操作,那么在 ECS 实例部分中,您将看到在 Auto Scaling 组中创建的计算机。

在 AWS Spot 实例上构建可扩展的 API

转到选项卡 产能供应商 并创建一个新的。 提醒一下,需要根据运行的ECS任务数量来控制机器的创建和关闭。 请务必注意,一名提供者只能分配给一个组。

自动缩放组 — 选择之前创建的组。

托管扩展 — 启用它以便提供商可以扩展服务。

目标容量% — 我们需要多少百分比的机器来加载任务。 如果指定 100%,则所有计算机将始终忙于运行任务。 如果您指定 50%,那么一半的汽车将始终是免费的。 在这种情况下,如果负载急剧增加,新的出租车将立即到达空闲车辆,而无需等待实例部署。

托管终止保护 --enable,该参数允许提供者取消对实例的删除保护。 当计算机上没有活动任务且允许目标容量% 时,会发生这种情况。

ECS 服务和扩展设置

最后一步:) 要创建服务,您需要在“服务”选项卡上转到之前创建的集群。

发射类型 — 您需要单击切换到容量提供商策略并选择之前创建的提供商。

在 AWS Spot 实例上构建可扩展的 API

任务定义 — 选择之前创建的任务定义及其修订版本。

服务名称 — 为了避免混淆,我们始终指示与任务定义相同的内容。

服务型 - 始终是复制品。

任务数 — 服务中所需的活动任务数量。 该参数由缩放控制,但仍必须指定。

最低健康百分比 и 最大百分比 — 确定部署期间任务的行为。 默认值是100和200,表示在部署时任务数量会增加数倍,然后返回到想要的值。 如果你有1个任务正在运行,min=0,max=100,那么在部署过程中它将被杀死,之后会引发一个新的任务,即会停机。 如果有 1 个任务正在运行,min=50,max=150,那么部署根本不会发生,因为 1 个任务不能被分成两半或增加一倍半。

部署类型 — 留下滚动更新。

展示位置模板 — 在机器上放置任务的规则。 默认是 AZ Balanced Spread - 这意味着每个新任务都将放置在新实例上,直到所有可用区中的机器都上升。 我们通常采用 BinPack - CPU 和 Spread - AZ;通过此策略,任务尽可能密集地放置在一台机器上的每个 CPU 上。 如果需要创建新机器,则在新的可用区中创建。

在 AWS Spot 实例上构建可扩展的 API

负载均衡器类型 — 选择应用程序负载均衡器。

服务 IAM 角色 - 选择 ecsServiceRole.

负载均衡器名称 — 选择之前创建的平衡器。

健康检查宽限期 — 推出新任务后执行健康检查之前暂停,我们通常将其设置为 60 秒。

容器到负载均衡 — 在“目标组名称”项中,选择之前创建的组,所有内容都会自动填写。

在 AWS Spot 实例上构建可扩展的 API

服务自动缩放 — 服务扩展参数。 选择配置服务自动缩放以调整服务的所需数量。 我们在缩放时设置最小和最大任务数。

服务 Auto Scaling 的 IAM 角色 - 选择 AWSServiceRoleForApplicationAutoScaling_ECSService.

自动任务扩展策略 — 缩放规则。 有2种类型:

  1. 目标追踪 — 跟踪目标指标(CPU/RAM 使用情况或每个任务的请求数量)。 例如,我们希望平均处理器负载为85%,当它变得更高时,将添加新的任务,直到达到目标值。 如果负载较低,则任务将被删除,反之,除非启用了防止缩减的保护(禁用缩减).
  2. 步骤缩放 - 对任意事件的反应。 在这里您可以配置对任何事件(CloudWatch Alarm)的反应,当事件发生时,您可以添加或删除指定数量的任务,或者指定确切的任务数量。

一个服务可能有多个扩展规则,这可能很有用,主要是确保它们不会相互冲突。

结论

如果您按照说明操作并使用相同的 Docker 映像,您的服务应该返回如下页面。

在 AWS Spot 实例上构建可扩展的 API

  1. 我们创建了一个模板,根据该模板启动服务中的所有机器。 我们还学习了如何在模板更改时更新机器。
  2. 我们已经配置了对 Spot 实例停止信号的处理,因此在收到该信号后的一分钟内,所有正在运行的任务都会从机器中删除,因此不会丢失或中断任何内容。
  3. 我们升高了平衡器,将负载均匀地分配到机器上。
  4. 我们创建了一个在现货实例上运行的服务,这将机器成本降低了大约 3 倍。
  5. 我们配置了双向自动扩展,以处理增加的工作负载,而不会产生停机成本。
  6. 我们使用容量提供程序,以便应用程序管理基础设施(机器),而不是相反。
  7. 我们很棒。

如果您的负载出现可预测的峰值,例如您在大型电子邮件营销活动中投放广告,则可以通过以下方式设置扩展: 时间表.

您还可以根据系统不同部分的数据进行扩展。 例如我们有这样的功能 发送个人促销优惠 移动应用程序的用户。 有时,一个营销活动会发送给 1 万以上的人。 经过这样的分配后,由于许多用户同时登录应用程序,对 API 的请求总是会大幅增加。 因此,如果我们看到队列中有明显更多的标准指标用于发送促销推送通知,我们可以立即启动几个额外的机器和任务来为负载做好准备。

如果您在评论中告诉我使用 Spot 实例和 ECS 的有趣案例或有关扩展的信息,我会很高兴。

很快就会有文章介绍我们如何在主要无服务器堆栈上(有钱)每秒处理数千个分析事件,以及如何使用 GitLab CI 和 Terraform Cloud 进行服务部署。

订阅我们,这会很有趣!

只有注册用户才能参与调查。 登录拜托

您在生产中使用现货实例吗?

  • 22,2%是6

  • 66,7%18号

  • 11,1%我从一篇文章中了解了它们并计划使用它们3

27 位用户投票。 5 名用户弃权。

来源: habr.com

添加评论