背景
本文介绍如何解决EMR集群实例组模式下竞价实例请求失败后无法切换到按需实例问题,主要思路是通过CloudWatch+Lambda实现在EMR集群竞价实例请求失败情况下补充制定数量的按需实例到EMR集群。
目前较多公司使用EMR构建大数据平台,使用按需实例+竞价实例混合配置完成计算任务所需资源,可最大程度节省成本并满足平台高峰期计算能力的需求。但竞价实例有可能会出现容量不足无法正常请求的情况,如何在竞价实例请求失败的情况下补充按需实例到集群,是使用该架构用户需要注意的问题。
架构
主题逻辑为:通过自动化的脚本监控竞价实例组状态,如果指定时间内实例组处于扩展状态但添加的实例为0的情况下触发Lambda程序,由Lambda程序扩展按需实例并添加到集群中。
配置
1. 定时任务监控竞价实例组
前提:需配置相应的权限才可完成实例组状态的监控,建将程序配置到EMR Master节点来监控竞价实例组配置,可节省相应权限配置。
配置定时任务监控竞价实例组状态:
1. 运行crontab -e命令
2. 写入如下定时任务(可根据实际情况配置)
*/5 * * * * sh /backup/scripts/linkyoyo-emr-instance-group-status.sh
3. 编写定时任务脚本,脚本内容包括两个部分,定时任务脚本主体内容如下:
#!/bin/bash
Instance=$(hostname -I | awk '{print $1}')
#通过脚本 获取该实例目前扩展状态
task=$(/usr/bin/python /backup/scripts/spot-task.py)
#实例组扩充状态指标上传
aws cloudwatch put-metric-data --namespace Mynamespace --metric-name Mymetricname --dimensions Instance=$Instance --unit Count --value $task
4. 编写python脚本定时任务脚本,python程序如下:
import os
import json
#获取对应EMR集群相关信息,并读取到tasks缓存变量
tasks = os.popen("aws emr describe-cluster --cluster-id j- xxxxxxxxx").read()
tasks=json.loads(tasks)
#设置标识位
flag=1
#循环读取集群中不同实例组的相关信息并进行判断
for i in range(4):
#对集群信息中的实例组的id进行读取
task_id=tasks["Cluster"]["InstanceGroups"][i]["Id"]
#读取该实例组的状态信息
task_status=tasks["Cluster"]["InstanceGroups"][i]["Status"]["State"]
#读取该实例组的运行实例数量
task_num=tasks["Cluster"]["InstanceGroups"][i]["RunningInstanceCount"]
#判断是否为目标监控实例组,是则判断状态和数量,如果状态不为running且数量为0,则置标识位为0
if task_id == "ig-xxxxxxxxx":
if task_status != "RUNNING":
if task_num == "0":
flag=0
#输出标识位
print (flag)
定时任务配置注意事项:
--namespace 命名空间(必填)
--metric-name 监控项名称(必填)
--dimensions 区别信息,,可以设为实例ID或者其他可以用来区分机器的标识,例如:Instance=i-xxxxxxx
--value 数值,监控项对应数值
5. 以上程序配置完成后可在Amazon CloudWatch控制台查看到配置的指标信息,
2. 配置Lambda程序
配置用于扩展按需实例组的Lambda程序
首先创建Lambda程序所需的IAM Role,创建步骤如下:
1. 打开 Amazon IAM控制台:
2. 在导航窗格上选择Roles
3. 选择Create role
4. Trusted entity type选择Amazon Web Services service
5. Common use cases选择Lambda,点击下一步
6. 选择Create policy
7. 请确认创建的policy 包含ModifyInstanceGroups权限,权限配置可参考:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "elasticmapreduce:ModifyInstanceGroups",
"Resource": "*"
}
]
}
8. 在新IAM Role中选择新创建的policy,完成IAM Role创建。
然后创建Lambda并部署程序代码
1. 打开 Amazon Lambda控制台:
2. 在导航窗格上选择Functions
3. 选择Create function
4. 选择Author from scratch选项,填写Function name名称
5. Runtime选择python3.9
6. Change default execution role选择Use an existing role并在列表中选择上一步已经创建的IAM Role。
7. 完成Lambda的创建
8. 在新创建的Lambda程序的Code选项中部署如下Python代码:
import boto3
import datetime
# 获取当前时间
today = datetime.datetime.now().strftime("%H:%M:%S")
class Emrclient(object):
def __init__(self):
# 修改emr所在区域
self.region_name = "us-east-1"
self.client = boto3.client("emr", region_name=self.region_name)
# EMR修改实例组的节点数
def modify_instance_groups(self, ClusterId, InstanceGroupId, InstanceCount):
response = self.client.modify_instance_groups(
ClusterId=ClusterId,
InstanceGroups=[
{
'InstanceGroupId': InstanceGroupId,
'InstanceCount': InstanceCount,
},
]
)
return response
def lambda_handler(event, context):
# 实例化
emr = Emrclient()
# 需传入EMR集群ID,实例组ID,自定义节点数量
response = emr.modify_instance_groups('j-xxxxxxx', 'ig-xxxxxxx', 节点数量)
3. 配置CloudWatch告警
创建CloudWatch告警,在达到阈值的时候扩展按需实例组数量。CloudWatch配置步骤如下:
配置SNS通知
1. 打开 Amazon SNS控制台:
2. 在导航窗格上选择Topics
3. 点击Create topic
4. 在Type中选择Standard
5. 填写Name后选择Create topic
6. 在导航窗格上选择Subscriptions
7. 点击Create subscription
8. 在Topic ARN选择步骤三创建的Topic
9. 在Protocol选择Amazon Lambda
10. 在Endpoint中选择已配置的Lambda程序
11. 点击Create subscription完成SNS配置
Amazon SNS 与 Amazon Lambda 结合使用:
https://docs.amazonaws.cn/lambda/latest/dg/with-sns.html
创建CloudWatch告警
1. 打开 Amazon CloudWatch控制台:
2. 在导航窗格上选择Functions
3. 选择In alarm
4. 点击Create alarm
5. 在Metric中选择定时任务程序输出的指标名称
6. 在Statistic选择Average,Period选择5minutes (可根据实际配置情况选择)
7. Threshold type选择Static
8. Whenever xxxx is中选择Lower<1,目前程序中1为正常状态0是异常状态。
9. 在Additional configuration中选择Datapoints to alarm的值,点击下一步。该值配置的是触法告警的次数,可根据实际情况配置,本文推荐配置6最大为6,这会在连续6个5分钟指标都处于告警的状态才会触发告警。
10. 配置Notification选项,选择已配置的SNS通知,点击下一步
11. 填写Alarm name和Alarm description后即可完成CloudWatch配置
更对cloudwatch alarm配置可查看:
https://docs.amazonaws.cn/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html