亚马逊AWS官方博客

手把手教你快速部署 Spot 实例并配置中断邮件警告

我们知道Amazon EC2 简化了 Amazon EC2 Spot 实例的定价模型,转变为提供可预测的低价,并根据长期供需趋势逐步调整价格,而且您将继续获得与按需实例相比高达 90% 的节省比例。同时Amazon EC2 Spot 支持通过 RunInstances 函数、run-instances 命令或 AWS 管理控制台启动Spot实例。通过命令行启动Spot实例,只需简单地在调用 run-instances 命令时为 InstanceMarketOption 参数指定 Spot,如果满足容量要求,您将立即收到实例 ID。

按需实例和Spot实例之间的唯一区别是,当EC2需要退还时,Spot实例可以被Amazon EC2中断并发出两分钟的通知。中断通知作为Amazon CloudWatch Events中的事件提供,CloudWatch Events可以通过电子邮件,文本,AWS Lambda等发送两分钟警告。

在这篇博客文章中,我们将展示如何使用AWS CLI命令行快速部署Spot实例并配置中断邮件警告。

在开始之前,请确保已经安装了AWS CLI,并赋予了启动EC2的权限。AWS CLI的安装和配置,请参考https://docs.amazonaws.cn/cli/latest/userguide/cli-chap-welcome.html

使用命令行请求启动EC2 Spot实例

通过添加参数 --instance-market-options,我们可以将用于启动EC2实例的aws-cli命令转换为Spot请求。

我们将使用的选项参数未指定最高出价,因此最高出价默认为该区域中实例类型的按需价格。另外,我们指定参数SpotInstanceType='persistent'InstanceInterruptionBehavior='stop'。这样,如果实例因Spot市场价格上涨而暂时中断,然后又回落,实例将会自动重启。

一切就绪之后,我们就可以使用AWS CLI启动Spot实例了,以下命令将会请求一台Spot实例。

#请根据实际情况修改以下变量

IMAGE_ID=ami-05a85395c8ff37b18

REGION=cn-northwest-1

INSTANCE_TYPE=c5.large

KEY_NAME=key-pair-name

INSTANCE_NAME=ec2-spot-name

USER_DATA="file://path/to/user-data-script.sh"

 

#指定请求Spot实例

INSTANCE_MARKET_OPTIONS="MarketType='spot',SpotOptions={InstanceInterruptionBehavior='stop',SpotInstanceType='persistent'}"

 

#请求启动EC2 Spot实例

INSTANCE_ID=$(aws ec2 run-instances \

--region "$REGION" \

--instance-type "$INSTANCE_TYPE" \

--image-id "$IMAGE_ID" \

--key-name $KEY_NAME \

--user-data $USER_DATA \

--instance-market-options "$INSTANCE_MARKET_OPTIONS" \

--tag-specifications 'ResourceType=instance,Tags=[{Key="Name",Value="'"$INSTANCE_NAME"'"}]' \

--output text \

--query 'Instances[*].InstanceId')

 

参数IMAGE_ID是AMI的ID。示例中的ami-05a85395c8ff37b18是Amazon Linux 2的ID。

参数REGION是区域的代码。例如宁夏区域的代码是cn-northwest-1。

参数INSTANCE_TYPE是实例的类型。例如c5.large。

参数KEY_NAME是该区域下您的密钥对的名称。

参数INSTANCE_NAME是将要启动的实例的名称。

参数USER_DATA是实例启动时运行的用户脚本。

参数INSTANCE_MARKET_OPTIONS是实例的市场(购买)选项。MarketType='spot'指市场购买类型是Spot。InstanceInterruptionBehavior='stop'指Spot实例被中断时则停止(stop),默认为终止(terminate)。SpotInstanceType='persistent'指定Spot实例请求的有效期,persistent指该请求将一直处于活动状态,直到被取消或达到有效期(默认是7天)为止。

执行上述命令后,如果容量可用,该实例将立即启动。 输入命令echo $INSTANCE_ID 可输出该实例的ID。

利用CloudWatch Events配置中断邮件警告

如上所述,CloudWatch Events可以在Spot实例被中断之前发送两分钟警告。以下将介绍如何将警告发送到电子邮箱。

(1)创建一个SNS主题以接收Spot实例活动通知:

#请根据实际情况修改以下变量

EMAIL_ADDRESS=my-mail@email.com

SNS_TOPIC_NAME=my-sns-topic-name

 

#创建一个SNS主题

SNS_TOPIC_ARN=$(aws sns create-topic --region "$REGION" --name "$SNS_TOPIC_NAME" --output text --query 'TopicArn')

 

#使用电子邮箱订阅SNS主题

aws sns subscribe \

--region "$REGION" \

--topic-arn "$SNS_TOPIC_ARN" \

--protocol email \

--notification-endpoint "$EMAIL_ADDRESS"

执行上述命令后,您的电子邮箱将会收到一封订阅确认的邮件,点击“Confirm subscription”链接确定订阅上述SNS的主题。如果不点击链接确认订阅,后续将不会收到警报邮件。

(2)授予CloudWatch Events 权限以发布SNS主题:

aws sns set-topic-attributes \

--region "$REGION" \

--topic-arn "$SNS_TOPIC_ARN" \

--attribute-name Policy \

--attribute-value '{

"Version": "2008-10-17",

"Id": "cloudwatch-events-publish-to-sns-'"$SNS_TOPIC_NAME"'",

"Statement": [{

"Effect": "Allow",

"Principal": {

"Service": "events.amazonaws.com"

},

"Action": [ "SNS:Publish" ],

"Resource": "'"$SNS_TOPIC_ARN"'"

}]

}'

(3)创建一个CloudWatch Events规则,过滤该Spot实例的中断警告:

#请根据实际情况修改以下变量

RULE_NAME_INTERRUPTED="ec2-spot-interruption-$INSTANCE_ID"

RULE_DESCRIPTION_INTERRUPTED="EC2 Spot instance $INSTANCE_ID interrupted"

 

EVENT_PATTERN_INTERRUPTED='{

"source": [

"aws.ec2"

],

"detail-type": [

"EC2 Spot Instance Interruption Warning"

],

"detail": {

"instance-id": [ "'"$INSTANCE_ID"'" ]

}

}'

 

如果需要监控所有Spot实例,EVENT_PATTERN_INTERRUPTED可修改为:

EVENT_PATTERN_INTERRUPTED='{

"source": [

"aws.ec2"

],

"detail-type": [

"EC2 Spot Instance Interruption Warning"

]

}'

 

aws events put-rule \

--region "$REGION" \

--name "$RULE_NAME_INTERRUPTED" \

--description "$RULE_DESCRIPTION_INTERRUPTED" \

--event-pattern "$EVENT_PATTERN_INTERRUPTED" \

--state "ENABLED"

(4)设置警告邮件的内容,并将SNS主题添加到CloudWatch Events规则:

#变量InputTemplate是警告邮件的内容,请根据实际情况修改

SNS_TARGET_INTERRUPTED='[{

"Id": "target-sns-'"$SNS_TOPIC_NAME"'",

"Arn": "'"$SNS_TOPIC_ARN"'",

"InputTransformer": {

"InputPathsMap": {

"title": "$.detail-type",

"source": "$.source",

"account": "$.account",

"time": "$.time",

"region": "$.region",

"instance": "$.detail.instance-id",

"action": "$.detail.instance-action"

},

"InputTemplate":

"\"<title>: <source> will <action> <instance> in <region> of <account> at <time>\""

}

}]'

 

#将SNS主题添加到CloudWatch Events规则

aws events put-targets \

--region "$REGION" \

--rule "$RULE_NAME_INTERRUPTED" \

--targets "$SNS_TARGET_INTERRUPTED"

 

按上述配置后,如果该Spot实例收到2分钟中断通知,那么您的邮箱将会收到类似的警告邮件:

 

如果您还想在Spot实例的运行状态发生变化时,例如Spot实例停止,再次启动或者运行时收到邮件,则可以创建一个新的CloudWatch Events规则:

 

RULE_NAME_STATE="ec2-instance-state-change-$INSTANCE_ID"

RULE_DESCRIPTION_STATE="EC2 instance $INSTANCE_ID state change"

 

event_pattern_state='{

"source": [

"aws.ec2"

],

"detail-type": [

"EC2 Instance State-change Notification"

],

"detail": {

"instance-id": [ "'"$INSTANCE_ID"'" ]

}

}'

 

aws events put-rule \

--region "$REGION" \

--name "$RULE_NAME_STATE" \

--description "$RULE_DESCRIPTION_STATE" \

--event-pattern "$EVENT_PATTERN_STATE" \

--state "ENABLED"

并将SNS主题添加到CloudWatch Events规则:

SNS_TARGET_STATE='[{

"Id": "target-sns-'"$SNS_TOPIC_NAME"'",

"Arn": "'"$SNS_TOPIC_ARN"'",

"InputTransformer": {

"InputPathsMap": {

"title": "$.detail-type",

"source": "$.source",

"account": "$.account",

"time": "$.time",

"region": "$.region",

"instance": "$.detail.instance-id",

"state": "$.detail.state"

},

"InputTemplate":

"\"<title>: <source> reports <instance> is now <state> in <region> of <account> as of <time>\""

}

}]'

 

aws events put-targets \

--region "$REGION" \

--rule "$RULE_NAME_STATE" \

--targets "$SNS_TARGET_STATE"

(5)除了可以利用CloudWatch Events配置中断邮件警告, 您也可以从实例内部处理中断通知。

实例元数据服务是一个安全的终结点,您可以直接从实例中查询有关实例的信息。发生竞价型实例中断时,您可以通过此服务检索有关中断的数据, 从而在实例终止之前对实例执行操作(例如正常停止进程)。请记住,您执行的所有操作都必须在两分钟内完成。

要查询此服务,请首先获取访问令牌,然后将此令牌传递给Instance Metadata Service来认证您的请求。 可以通过http://169.254.169.254/latest/meta-data/spot/instance-action访问有关中断的信息。 当实例未标记为中断时,此URI返回404响应代码。 以下代码演示了如何查询实例元数据服务以检测中断。

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \

&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-type

如果实例被标记为中断,您将收到200响应代码。 您还会收到以下JSON格式的响应,其中包括在中断时采取的措施(终止,停止或休眠),以及采取该措施的时间(即2分钟警告期到期)。

{“action”: “terminate”, “time”: “2020-10-28T08:22:00Z”}

您可以利用这个中断通知来执行应用停止等脚本,例如:

#!/bin/bash

TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`

while sleep 3; do

HTTP_CODE=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s -w %{http_code} -o /dev/null http://169.254.169.254/latest/meta-data/spot/instance-action)

if [[ "$HTTP_CODE" -eq 401 ]] ; then

echo 'Refreshing Authentication Token'

TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 30"`

elif [[ "$HTTP_CODE" -eq 200 ]] ; then

# 收到中断通知,执行脚本,例如

/usr/local/shutdown-script.sh

else

echo 'Not Interrupted'

fi

done

您可以在脚本/usr/local/shutdown-script.sh里面添加需要停止应用的脚本,也可以使用下面的命令对Spot创建镜像, 并且按需启动该镜像。

aws ec2 create-image --instance-id $INSTANCE_ID --name "spot server" --no-reboot

清理资源

如果我们手工终止EC2 Spot实例,则持久Spot请求将重新启动替换实例。要永久终止,我们需要先取消竞价请求:

SPOT_REQUEST_ID=$(aws ec2 describe-instances \

--region "$REGION" \

--instance-id "$INSTANCE_ID" \

--output text \

--query 'Reservations[].Instances[].[SpotInstanceRequestId]')

 

aws ec2 cancel-spot-instance-requests \

--region "$REGION" \

--spot-instance-request-ids "$SPOT_REQUEST_ID"

然后终止EC2实例:

aws ec2 terminate-instances \

--region "$REGION" \

--instance-ids "$INSTANCE_ID" \

--output text \

--query 'TerminatingInstances[*].[InstanceId,CurrentState.Name]'

删除CloudWatch Events的中断规则:

TARGET_IDS_INTERRUPTED=$(aws events list-targets-by-rule \

--region "$REGION" \

--rule "$RULE_NAME_INTERRUPTED" \

--output text \

--query 'Targets[*].[Id]')

 

aws events remove-targets \

--region "$REGION" \

--rule "$RULE_NAME_INTERRUPTED" \

--ids $TARGET_IDS_INTERRUPTED

 

aws events delete-rule \

--region "$REGION" \

--name "$RULE_NAME_INTERRUPTED"

 

删除CloudWatch Events的状态变化规则(如果已创建):

TARGET_IDS_STATE=$(aws events list-targets-by-rule \

--region "$REGION" \

--rule "$RULE_NAME_STATE" \

--output text \

--query 'Targets[*].[Id]')

 

aws events remove-targets \

--region "$REGION" \

--rule "$RULE_NAME_STATE" \

--ids $TARGET_IDS_STATE

 

aws events delete-rule \

--region "$REGION" \

--name "$RULE_NAME_STATE"

删除SNS主题:

 

aws sns delete-topic \

--region "$REGION" \

--topic-arn "$SNS_TOPIC_ARN"

 

更多内容

更多有关Spot实例的相关内容,请大家参考AWS EC2的官方文档和FAQ:

http://docs.amazonaws.cn/AWSEC2/latest/UserGuide/using-spot-instances.html

https://www.amazonaws.cn/ec2/faqs/#spot-instances

关于如何从实例内部处理中断,也可以参考文章:成本优化利器——Amazon EC2 Spot实例详细介绍

 

本篇作者

吴金福

AWS 混合云方案架构师,负责基于AWS的混合云方案架构的咨询和设计。在加入AWS之前,就职于大型集团企业。负责私有云数据中心的方案设计和建设,在数据中心基础设施、虚拟化、高性能计算和混合云等领域有着多年的经验积累。