先验知识
AWS 是亚马逊云计算平台。
EC2 是 AWS 提供的云主机,一般采用基于 CentOS 的 Amazon Linux 系统,像大多数云主机一样使用。
ELB 是 AWS 提供的负载均衡服务,可以将请求转发到目标组中的某个 EC2 实例,也会对目标组实例进行健康检查,只将请求转发到健康的实例。
目标
AWS AutoScaling 服务允许我们设定 AutoScaling 组(ASG)。ASG 维护了一组 EC2 实例,EC2 实例可以分布在不同的可用区里以保证可用性。AutoScaling 能检查实例健康状况,自动替换不健康的实例,能根据特定规则增减需要的实例数量,并能在启动、销毁 EC2 实例时触发事件调用其他服务。AutoScaling 服务能够与 ELB 弹性负载均衡服务结合使用,实例不需要公网 IP,而且实例在启动后会自动加入 ELB 的目标组,在终止实例前会先等待 ELB 连接耗尽。
我们要实现的结构如图所示:
看上去梦寐以求,但用起来却没有这么舒服。
给 ASG 创建 EC2 的启动配置
我们需要给 ASG 提供启动配置(Launch Configuration),启动配置里指定了 EC2 的实例类型、AMI 镜像、存储设备、IAM 角色、SSH Key、用户数据(User Data)等一系列信息,用于启动新实例。
我们主要关心 IAM 角色和启动脚本,其他的按需配置。
用户数据实际上是启动脚本,当 EC2 实例启动时,在初始化的最后以 root 身份将用户数据作为脚本执行。你可以登录实例,在 /var/log/cloud-init-output.log
日志尾查看其输出。
因为每次修改启动配置都需要重新创建 ASG,而这就需要清空实例等十分繁琐,所以最好将用户数据设置为从外部获取真正的启动脚本来运行,以便修改调试:
#!/usr/bin/env bash
aws s3 cp s3://bucket/key /tmp/myscript.sh &&
chmod +x /tmp/myscript.sh && /tmp/myscript.sh
这需要给 EC2 的 IAM 角色允许 S3 访问权限。
创建 ASG
我们根据启动配置创建 ASG,设定最低、最高、所需的实例数量,将实例连接到特定的 ELB 目标组,以 ELB 的健康检查作为 ASG 中实例的健康依据等等。
不健康的实例在健康检查失败且等待超时后将被终止。具体请参考 ASG 中实例的运行状况检查。
ASG 中实例的生命周期
ASG 中实例有生命周期的概念,也就是实例有一个状态,并在不同的状态间转移。
ASG 中实例的生命周期如图所示:
生命周期钩子
在启动的 Pending 和终止的 Terminating 状态均可以添加钩子,钩子会触发事件,并等待事件完成再进入下一状态(当没有钩子时实例会自动跳到下一状态)。钩子能设置超时时限、超时结果。
可以使用 aws-cli 或 AWS SDK 等主动完成生命周期事件,或延期它。事件完成需要提供结果,主动完成和超时均有“继续”和“放弃”两种结果可选。
可以给启动或终止阶段添加多个钩子,他们会在上一个钩子完成且结果为“继续”后依次执行。在启动阶段一旦出现“放弃”的结果后,实例会跳过其他钩子,直接开始终止过程,这也会触发终止阶段的钩子;在终止阶段出现“放弃”的结果后,实例会跳过其他钩子,直接终止。
添加钩子
使用 aws autoscaling put-lifecycle-hook
命令添加沟子,有两种可选的 transition 值:
-
autoscaling:EC2_INSTANCE_LAUNCHING
实例启动时 -
autoscaling:EC2_INSTANCE_TERMINATING
实例终止时
下面这个命令的例子演示了添加“启动时钩子,超时时限 600s,超时后放弃该实例”:
aws autoscaling put-lifecycle-hook \
--auto-scaling-group-name ${ASG} \
--lifecycle-hook-name ${HOOK} \
--lifecycle-transition "autoscaling:EC2_INSTANCE_LAUNCHING" \
--heartbeat-timeout 600 \
--default-result "ABANDON"
如果使用了下文的 SNS 或 SQS 接收事件,还需要提供 role-arn
和 notification-target-arn
。AWS 提供了比较完整的文档 添加生命周期钩子 来讲述这一过程。
报告事件完成或延期
使用 aws autoscaling complete-lifecycle-action
命令可以主动完成事件,使生命周期继续(CONTINUE
)或放弃(ABANDON
),根据你获得的信息,提供 instance-id
、lifecycle-action-token
任一即可:
下面这个命令的例子演示了“完成 ${ASG}
组的实例 ${INSTANCE_ID}
上的 ${HOOK}
事件,结果为继续”:
aws autoscaling complete-lifecycle-action \
--auto-scaling-group-name ${ASG} \
--lifecycle-hook-name ${HOOK} \
--instance-id ${INSTANCE_ID} \
--lifecycle-action-result CONTINUE
命令 aws autoscaling record-lifecycle-action-heartbeat
可以重置超时时间,使用方法类似于上文所述的 complete-lifecycle-action
。
注意,如果希望 EC2 之上的脚本来执行上述操作,需要给 EC2 的 IAM 角色允许 autoscaling:CompleteLifecycleAction
或 autoscaling:RecordLifecycleActionHeartbeat
权限。
EC2 实例如何获取自身的 instance-id 呢?
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
ASG=$(aws autoscaling describe-auto-scaling-instances --output text --query 'AutoScalingInstances[?InstanceId==`'${INSTANCE_ID}'`].AutoScalingGroupName')
具体请参考 检索实例元数据 和 控制 aws-cli 的命令输出。
钩子事件的传播
AWS 允许钩子触发后向外传播这一事件,有三种方式——通过 CloudWatch Events、SNS 或者 SQS。他们会携带足够的信息,具体的携带数据请参考 AutoScaling 事件。
具体配置如果有疑问,请参考 添加生命周期钩子 中《向 Auto Scaling 组添加生命周期挂钩》节的前两步。
事件被传播后,一般使用 Amazon Lambda 或者额外的服务器来处理。同样注意,如果希望 Lambda 报告事件完成或延期,请给 Lambda 的 IAM 角色允许相应权限。
很多时候我们需要在 EC2 实例上执行脚本,这方面的内容请参见这篇博客 AWS 使用 Simple System Manager 向 EC2 发送命令远程执行脚本。
事件处理的个人经验
在实例启动时,我们一般不把事件传播出去交由 Lambda 处理,而是由用户数据里的启动脚本在执行结束后自行报告事件完成(继续),或者通知 SQS、SNS、Lambda 等来做。
因为在实例启动时我们需要手动安装 SSM Agent,此时该服务对外界而言不可用。所以我们不能用这种方式来与 EC2 通信。
在实例终止时,我们一般用 Lambda 处理,并用 SSM 通知 EC2 执行回收脚本,回收脚本自行报告事件完成(继续),如果 SSM 失败,再由 Lambda 报告事件完成(放弃)。
扩展 ASG 的大小
AWS 提供了多种 扩展 ASG 的大小 的策略。
这里我们提供两个简单的手工策略来扩展 ASG 的大小:
aws autoscaling put-scaling-policy \
--auto-scaling-group-name ${ASG} \
--policy-name "inc" \
--policy-type "SimpleScaling" \
--adjustment-type "ChangeInCapacity" \
--scaling-adjustment 1
aws autoscaling put-scaling-policy \
--auto-scaling-group-name ${ASG} \
--policy-name "dec" \
--policy-type "SimpleScaling" \
--adjustment-type "ChangeInCapacity" \
--scaling-adjustment -1
现在我们获得了高可用的弹性计算服务,配置过程很复杂,最好能够采用 CloudFormation 来配置,或者使用 aws-cli 编写脚本来完成。
祝大家使用愉快。