2023年InfoQ 主办的最后一场会议——QCon 全球软件开发大会·上海站,正在热卖中! 了解详情
写点什么

基于 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:29486
用户头像

发布了 1742 篇内容, 共 81.9 次阅读, 收获喜欢 72 次。

关注

评论

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

首发!阿里P8顶级架构师总结的这份全网最全 JVM 知识宝典,帮你查漏补缺

了不起的程序猿

Java JVM 虚拟机 java编程 Java程序猿

超全面!字节最新发布22年秋招200道Java面试题(含答案)

Java面试那些事儿

Java 编程 面试 后端 架构师

StarRocks 技术内幕:向量化编程精髓

StarRocks

数据透视表上线!如何在纯前端实现这个强大的数据分析功能?

葡萄城技术团队

前端

高性能实战Alibaba Sentinel笔记,深度还原阿里微服务高并发方案

小柴说Java

Java 编程 架构 面试 后端

这么好用的接口工具,请允许我油腻一次!

Liam

Java 开发 Postman API 开放api

一文看懂流程挖掘是如何工作的

望繁信科技

延时任务-基于netty时间轮算法实现

字母哥哥

Java 架构 后端 Netty

听潮汐,筑灯塔,聚千帆:智慧港口全球创新实验室启航时

脑极体

证照之星XE重磅发布 制作证件照从未如此简单

懒得勤快

重磅揭秘!10分钟10TB数据跨云、跨地域传输的技术实践

星汉未来

云原生 数据迁移 东数西算 星汉未来

低/无代码的发展将显著改变银行开发生态

易观分析

代码 银行

toB行业知识管理的重要性

Baklib

你可能不知道,自动化元数据管理的“七宗最”?

雨果

元数据

爆肝!阿里大佬熬夜38天整合的这份Spring Security源码手册我粉了

Java全栈架构师

Java 程序员 面试 程序人生 springsecurity

前端培训就业后的程序员就业方向有哪些?

小谷哥

新手指南|帮助中心应该包含哪些内容?

Geek_da0866

完整实现-通过DelayQueue实现延时任务

字母哥哥

Java 架构 并发编程 后端

手把手地教你如何建立最好的知识管理体系

Baklib

[极致用户体验] 2行代码,让你的UI适配移动端、PC端,快来收藏

HullQin

CSS JavaScript html 前端 8月月更

前端培训机构毕业后在一线城市该注意什么

小谷哥

一对一直播系统源码——如何只需三步搭建

开源直播系统源码

软件开发 一对一直播源码 直播系统源码 一对一直播系统

开源流式湖仓服务 Arctic 详解:并非另一套 Table Format

网易数帆

大数据 iceberg Hudi Arctic

题目新颖,内容全面!阿里巴巴又一Java面试神册开源!

Java永远的神

Java spring 程序员 面试 JVM

云堡垒机和软件堡垒机哪个好?区别是什么?

行云管家

网络安全 数据安全 堡垒机 云堡垒机

学术加油站|面向HTAP数据库的基准评测工具研究进展

OceanBase 数据库

怎样设计一个协助中心来帮助你的顾客?

Baklib

简单WiFi控制小车系统(树莓派+python+web控制界面)

Five

树莓派 8月月更

非科班出身,开发五年之后我对编程有了新的领悟

Java永远的神

Java 编程 程序员 程序人生 计算机

大数据软件开发培训中心有哪些?

小谷哥

堡垒机可以管理哪些网络资产?咨询电话多少?

行云管家

网络安全 数据安全 堡垒机

  • 扫码添加小助手
    领取最新资料包
基于 Tag 驱动的 EBS 类型优化 CloudFormation 模板_语言 & 开发_亚马逊云科技 (Amazon Web Services)_InfoQ精选文章