写点什么

基于 Tag 驱动的 EBS 类型优化 CloudFormation 模板

  • 2019-10-12
  • 本文字数:5219 字

    阅读完需:约 17 分钟

基于 Tag 驱动的 EBS 类型优化 CloudFormation 模板

需求背景

企业中用于做报表的数据库服务器只是在每天的某个时间点需要大规模的磁盘 IO 操作,持续时间大约几个小时,如果按照高峰时期的磁盘 IO 需求来设定 EBS 卷的类型,需要 IOPS 为 10000 的 io1 卷。而按照平时大多数时候的磁盘 IO 水平来看,gp2 类型的磁盘已经可以满足要求。为了能够做到物尽其用,节省成本,在 IO 高峰时候使用 io1 卷,而平时使用 gp2 卷会是一个理想的安排。

方案概述

为了能够简化实际操作过程的复杂度,方案采用通过定义 EBS 卷的 Tag 来触发对应的 Lambda 功能从而实现定期更改 EBS 卷的类型。


首先,用户需要启动 CloudTrail, 并将 CloudTrail 与 CloudWatch 集成,本文假设 CloudWatch Log Group 的名字是 CloudTrail/DefaultLogGroup。


我们将通过 CloudTrail 来捕捉用户对 EBS 卷的操作,如果用户创建、更改或者删除名字为 ChangeEBSType 的 Tag,就会触发一个 Lambda 功能调用。判断触发条件是通过定义 Log Group 的 Filter 实现的,Filter 的定义如下:


  "SubscriptionFilter": {
"Type": "AWS::Logs::SubscriptionFilter",
"Properties": {
"LogGroupName": {
"Ref": "CloudTrailLogGroup"
},
"FilterPattern": "{($.requestParameters.tagSet.items[0].key = \"ChangeEBSType\") && ($.eventName = *Tags) && ($.requestParameters.tagSet.items[0].value!= \"\" ) && ($.requestParameters.resourcesSet.items[0].resourceId = \"vol*\")}",
"DestinationArn": {
"Fn::GetAtt": ["EBSChangeScheduler", "Arn"]
}
}
}
复制代码


当 Filter 的条件满足后,就会触发名字为 EBSChangeScheduler 的 Lambda 功能,这个 Lambda 程序将根据 Tag 的输入值,调用另一个 CloudFormation 模板以部署对应的 CloudWatch Event Rules(规定什么时间对 EBS 卷的类型进行修改)和 Lambda 功能(change-ebs-type 完成 EBS 卷的类型修改)。


整个方案的流程如下:



调用现有的 CloudFormation 模板并创建对应 Stack 的 Lambda(EBSChangeScheduler.py)功能的示例代码如下:


import json
import zlib
import boto3
import botocore
import base64
import string

TemplateURL = ""

def get_cloudtrail_event(event):
data = base64.b64decode(event['awslogs']['data'])
data = zlib.decompress(data, 16 + zlib.MAX_WBITS)
cloudtrail_event = json.loads(data)
return cloudtrail_event

def get_message_from_cloudtrail_event(log_event):
old_str = '\\"'
new_str = '"'
message = log_event['message']
message = message.replace(old_str, new_str)
return json.loads(message)

def create_cloudformation(stack_name, parameter1, parameter2, volume_id, client):
print ("Create cloudformation stack: %s" % stack_name)
try:
response = client.create_stack(StackName=stack_name, TemplateURL=TemplateURL, Parameters=[
{'ParameterKey': 'TargetEBSVolumeInfo', 'ParameterValue': parameter1}, {'ParameterKey': 'ScheduleExpression', 'ParameterValue': parameter2}, ], Capabilities=['CAPABILITY_IAM'])
except Exception as ex:
print ex.message

def update_cloudformation(stack_name, parameter1, parameter2, volume_id, client):
print ("Update cloudformation stack: %s" % stack_name)
try:
response = client.update_stack(StackName=stack_name, UsePreviousTemplate=True, Parameters=[
{'ParameterKey': 'TargetEBSVolumeInfo', 'ParameterValue': parameter1}, {'ParameterKey': 'ScheduleExpression', 'ParameterValue': parameter2}, ], Capabilities=['CAPABILITY_IAM'])
except botocore.exceptions.ClientError as ex:
error_message = ex.response['Error']['Message']
if error_message == 'No updates are to be performed.':
print("No changes")
else:
raise

def check_valid_stack(stack_name, client):
try:
response = client.describe_stacks()
except Exception as ex:
print ex.message
for stack in response['Stacks']:
if stack_name in stack['StackName']:
return True

def build_ebs_volume_change_schedule(stack_name, target_schedule, volume_id, client):
target_type = target_schedule.split(':')
parameter1 = volume_id + ":" + target_type[0] + ":" + target_type[1]
parameter2 = "cron" + target_type[2]
print ("CloudForamtion template parameters:{},{}".format(
parameter1, parameter2))
print ("Volume %s will be changed to %s, IOPS is %s" %
(volume_id, target_type[0], target_type[1]))
print ("This task will be executed based on %s" % target_type[2])
if check_valid_stack(stack_name, client):
try:
cloudformation = boto3.resource('cloudformation')
try:
stack = cloudformation.Stack(stack_name)
except Exception as ex:
print ex.message
stack_status = stack.stack_status
print ("Stack (%s) status: %s" % (stack_name, stack_status))
if stack_status == "ROLLBACK_COMPLETE" or stack_status == "ROLLBACK_FAILED" or stack_status == "DELETE_FAILED":
try:
response = client.delete_stack(StackName=stack_name)
waiter = client.get_waiter('stack_delete_complete')
waiter.wait(StackName=stack_name)
except Exception as ex:
print ex.message
if stack_status == "CREATE_IN_PROGRESS":
waiter = client.get_waiter('stack_create_complete')
waiter.wait(StackName=stack_name)
if stack_status == "DELETE_IN_PROGRESS":
waiter = client.get_waiter('stack_delete_complete')
waiter.wait(StackName=stack_name)
if stack_status == "UPDATE_IN_PROGRESS":
waiter = client.get_waiter('stack_update_complete')
waiter.wait(StackName=stack_name)
except Exception as ex:
print ex.message
if check_valid_stack(stack_name, client):
update_cloudformation(stack_name, parameter1, parameter2,
volume_id, client)
waiter = client.get_waiter('stack_update_complete')
else:
create_cloudformation(stack_name, parameter1, parameter2,
volume_id, client)
waiter = client.get_waiter('stack_create_complete')

def delete_ebs_volume_change_schedule(volume_id, client):
response = client.describe_stacks()
for stack in response['Stacks']:
if volume_id in stack['StackName']:
try:
print("Delete cloudformation stack: %s" %
stack['StackName'])
response = client.delete_stack(
StackName=stack['StackName'])
except Exception as ex:
print ex.message

def lambda_handler(event, context):
volume_id = []
global TemplateURL
export = {}
client = boto3.client('cloudformation')
print (event)
export = client.list_exports()
for item in export['Exports']:
if item['Name'] == 'CFUrl':
TemplateURL = item['Value']
print ("CF URL: %s" % TemplateURL)
cloudtrail_event = get_cloudtrail_event(event)
for log_event in cloudtrail_event['logEvents']:
trail_message = get_message_from_cloudtrail_event(log_event)
volume_id = trail_message['requestParameters']['resourcesSet']['items'][0]['resourceId']
if trail_message['eventName'] == "CreateTags":
for item in trail_message['requestParameters']['tagSet']['items']:
if item['key'] == 'ChangeEBSType':
cf_parameter = item['value']
break
if trail_message['eventName'] == "CreateTags":
start_stop = cf_parameter.split(',')
i = 0
for schedule in start_stop:
stack_name = "change-ebs-type-" + str(i) + "-" + volume_id
build_ebs_volume_change_schedule(
stack_name, schedule, volume_id, client)
i = i + 1
if trail_message['eventName'] == "DeleteTags":
delete_ebs_volume_change_schedule(volume_id, client)
复制代码


特殊处理: 由于中国区的 Lambda 尚不支持环境变量,修改 EBS 卷的 CloudFormation 模板 URL 无法传给 Python 程序,所以利用了 CloudFormation 的 Export 功能,通过将 URL 变成 Export 的变量,在 Python 里面读取这个变量完成参数传递。 CloudFormation: “Outputs”: { “CFPath”: { “Description”: “The URL of CF”, “Value”: { “Ref”: “CFUrl” }, “Export”: { “Name”: “CFUrl” } } }


对应的 Python 语句:


global TemplateURL
export = {}
client = boto3.client('cloudformation')
export = client.list_exports()
for item in export['Exports']:
if item['Name'] == 'CFUrl':
TemplateURL = item['Value']
print ("CF URL: %s" % TemplateURL)
复制代码


EBS 卷的 Tag (ChangeEBSType)的格式有如下约定:


卷类型:IOPS 值:变更起始计划, 卷类型:IOPS 值:变更起始计划


例如如下设置:


io1:30000:(0 11 * * ? *),gp2:100:(0 19 * * ? *)


可以解释为:每天 11 点(UTC 时间)将当前的卷变更为 IOPS 为 30000 的 io1 类型,同日 19 点(UTC 时间)将当前卷恢复成 gp2 类型。(注意 gp2 类型的 EBS 卷忽略 IOPS 的值,所有 IOPS 值可以随意写,但不能为空值)


因为 EBS 卷的变更最小间隔时间为 6 小时,所以要确保 6 个小时内仅有一次磁盘类型的变更。


CloudWatch Event Rule可以通过如下CloudFormation的JSON语句创建:
"MyEventsRule": {
"Type": "AWS::Events::Rule",
"Properties": {
"Description": "Events Rule Invoke Lambda",
"Name": {
"Fn::Sub": "${AWS::StackName}-ChangeEBSEvent"
},
"ScheduleExpression": {
"Ref": "ScheduleExpression"
},
"State": "ENABLED",
"Targets": [{
"Arn": {
"Fn::GetAtt": [
"ModifyEbs",
"Arn"
]
},
"Id": "ModifyEbs"
}]
}
}
复制代码

安装和运行

  1. 在浏览器中输入:https://github.com/shaneliuyx/ChangeEBS, 下载:


ebs_change_scheduler_v2.json


ebs_change_scheduler_v2.zip


change_ebs_type.json


2.将 json 上载到 S3 (上载 URL 假设为https://s3.cn-north-1.amazonaws.com.cn/shane/change_ebs_type.json


3.打开 AWS 控制台,并选择 CloudFormation,选择存储在本地的 json 文件 json,运行 CloudFormation 模板。


4.假设输入参数如下:


  1. 选择 Next,直至 AWS 资源开始创建

  2. 最终运行结果如下



7.选择 Volume ID 为 vol-0e3625c0f2f14e30d 的 EBS 卷,创建新的 tag,名称:ChangeEBSType,值:io1:30000:(0 12 * * ? *),gp2:100:(0 19 * * ? *)


8.我们可以在 CloudTrail 上查到如下记录:



9.几分钟后,我们可以在 CloudFormation 的控制台上看到 2 个新的 Stack 已经建立完成了:



10.同时检查 CloudWatch 控制台,选择 Events-Rules,发现建立了 2 个新的 Rules:



至此,我们就已经设置好了一个针对 EBS 卷的类型调度计划,此计划规定在 1 天中该 EBS 卷使用 io1 类型运行 7 个小时,使用 gp2 类型运行 17 个小时。在满足了服务器性能需求的同时,每天节省了 17 个小时的 io1 卷使用费用。


需要注意的是,由于磁盘类型转换的时间与磁盘的容量相关,在指定调度计划的时候一定要预估磁盘转换完成需要预留的时间,以免影响正常系统的使用效率。


作者介绍:


刘育新


AWS 专业服务部资深顾问,专注于企业客户的云迁移项目,长期从事 IT 基础设施的设计和实施工作。


本文转载自 AWS 技术博客。


原文链接:


https://amazonaws-china.com/cn/blogs/china/based-tag-drive-ebs-cloudformation-model/


2019-10-12 13:29745
用户头像

发布了 1960 篇内容, 共 167.9 次阅读, 收获喜欢 82 次。

关注

评论

发布
暂无评论
发现更多内容

最佳LED视频墙分辨率推荐

Dylan

LED显示屏 户外LED显示屏 led显示屏厂家 户内led显示屏 算力租赁

Flink exactly once sink 实现

Joseph295

MKVToolNix for Mac mkv视频编辑工具

晨光熹微

javax.security.auth.login.LoginException: null (68)

刘大猫

人工智能 数据挖掘 算法 数据分析 LoginException

淘宝商品详情API响应数据解析(一)

tbapi

淘宝商品详情接口 淘宝API 天猫商品详情接口 天猫API

Netflix 数据网关实践

俞凡

架构 大厂实践

漏洞披露的罗生门:安全行业的复杂博弈

qife122

漏洞披露 零日漏洞

完美黑客配置的秘诀?Docker化渗透测试工具集ReconBox详解

qife122

网络安全 渗透测试

从Web 2.0到Agentic Web:MCP对智能代理生态构建的推动作用

申公豹

Web

ZKTorch:通过并行证明累积将机器学习推理编译为零知识证明

qife122

零知识证明 密码学

开启你的专属智能时代:枫清科技个人智能体限时体验计划上线!

Fabarta

人工智能 智能体 agent

AI虫子种类识别数据集(近3000张图片已划分、已标注)|适用于YOLO系列深度学习分类检测任务【数据集分享】

申公豹

数据集 yolo

亚马逊AI编程软件Kiro:产品经理赶紧抱紧饭碗!

阿星AI工作室

产品 编程 AI 亚马逊 kiro

Recompress for Mac PDF文件压缩软件

晨光熹微

使用Svelte和D3实现交互式数据可视化

qife122

数据可视化 Svelte

淘宝商品评论API接口详解

tbapi

淘宝商品评论接口 天猫商品评论接口 淘宝评论API 天猫评论API 淘宝评论采集

n8n教程分享,从Github读取.md文档内容

溪抱鱼

人工智能 n8n

MCP协议中的不安全凭证存储漏洞分析与安全实践

qife122

MCP 凭证安全

使用AWS KMS和ML-DSA创建后量子签名指南

qife122

AWS KMS ML-DSA

基于YOLOv8的AI虫子种类识别项目|完整源码数据集+PyQt5界面+完整训练流程+开箱即用!

申公豹

yolov8

从 Berlinterop 看三大确定性趋势:以太坊升级与 Layer 2 生态新篇章

NFT Research

Ethereum blockchain web3、

大数据-45 Redis 从快照到日志:RDB 与 AOF 持久化机制

武子康

Java redis 大数据 缓存 分布式

高性能数据模型设计

俞凡

大数据

一篇说人话的文章,告诉你 Django、Flask、FastAPI 到底怎么选

程序员晚枫

django flask FastApi

摆脱偏见,Mac也要逆袭“游戏圈”

阿拉灯神丁

crossover MacBook M1 如何在Mac上运行win游戏 Mac游戏推荐 游戏本

告别 T+1!解密金融级实时数据平台的构建与实践

镜舟科技

数据分析 StarRocks 物化视图 主键模型 湖仓架构

低代码可视化工作流的系统设计与实现路径研究

JeeLowCode低代码平台

工作流 低代码 可视化 工作流编排 低代码选择

AI招聘机器人公司Paradox.ai因弱密码泄露数百万求职者数据

qife122

网络安全 密码安全

大数据-46 Redis RDB 持久化机制详解:原理、配置与优缺点解析

武子康

Java redis 大数据 缓存 分布式

基于 Tag 驱动的 EBS 类型优化 CloudFormation 模板_语言 & 开发_亚马逊云科技 (Amazon Web Services)_InfoQ精选文章