亚马逊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实例详细介绍