亚马逊AWS官方博客

构建自动化的 EBS 快照生命周期管理

一 、引言

数据是企业的核心财产之一,数据备份一直是运维人员关注的工作,传统IDC通过专业备份软件与备份设备完成数据的备份与存储,当企业进行数字化转型拥抱云计算平台,如何在云端进行数据备份是新的热门话题,AWS 提供 EBS(Elastic Block Storage) 卷快照的功能,并开放相应的 API (快照创建、删除等),本文将介绍如何在 AWS 北京区域和宁夏区域构建一个自动化的 EBS 存储卷快照生命周期管理方案。
(AWS 海外区域可以直接使用 EBS 快照生命周期管理功能,本文方案建议用于AWS 北京区域和宁夏区域。文中代码仅用于指导 EBS 快照生命周期管理的创建,请在应用到生产环境前进行仔细评估、测试与修改)

二、方案说明

本方案将涉及 Amazon CloudWatch Event,AWS Lambda,Amazon DynamoDB,Amazon EBS 快照等服务,简要说明如下。
Amazon CloudWatch Events 提供近乎实时的系统事件流,该流描述 Amazon Web Services (AWS) 资源的变化。通过使用可快速设置的简单规则,您可以匹配事件并将事件路由到一个或多个目标函数或流。您还可以使用 CloudWatch Events 来计划使用 Cron 或 rate 表达式在某些时间自行触发的自动化操作。本方案中将使用Cron表达式在特定时间触发 Lambda进行相应的动作。
AWS Lambda 是一项计算服务,可使您无需预配置或管理服务器即可运行代码。AWS Lambda 只在需要时执行您的代码并自动缩放,从每天几个请求到每秒数千个请求。您只需按消耗的计算时间付费 – 代码未运行时不产生费用。您只需要以 AWS Lambda 支持的一种语言 (目前为 Node.js、Java、C#、Go 和 Python) 提供您的代码。本方案中我们将使用Python通过Lambda完成EBS卷的创建、删除等操作。
Amazon DynamoDB 是一种完全托管的 NoSQL 数据库服务,提供快速而可预测的性能,能够实现无缝扩展。使用 DynamoDB,您可以免除操作和扩展分布式数据库的管理工作负担,因而无需担心硬件预置、设置和配置、复制、软件修补或集群扩展等问题。DynamoDB 可以从表中自动删除过期的项,从而帮助您降低存储用量,减少用于存储不相关数据的成本。
DynamoDB 流(DynamoDB Stream) 是一种有关 Amazon DynamoDB 表中的项目更改的有序信息流。当您对表启用流时,DynamoDB 将捕获有关对表中的数据项目进行的每项修改的信息。
DynamoDB 的生存时间 (DynamoDB TTL) 可以定义表中项目的过期时间,以便它们能够从数据库中自动删除。在表中启用 TTL 后,您可以为每个项目设置删除时间戳,使得只有相关的记录才能占用存储。DynamoDB 通常在过期 48 小时内删除已过期的项目。项目在过期后真正被删除的确切时间取决于工作负载的性质和表的大小。
本方案中将应用 DynamoDB TTL 功能以及 DynamoDB 流以自动删除过期的项目(DynamoDB 表中“一条”数据成为一个项目,即一个 item )并通过流触发 Lambda 完成快照删除的动作。
Amazon EBS 快照 是基于时间点的快照,快照属于增量 备份,这意味着仅保存设备上在最新快照之后更改的数据块。由于无需复制数据,这将最大限度缩短创建快照所需的时间和增加存储成本节省。删除快照时,只有该快照特有的数据会被删除。每个快照都包含将数据 (拍摄快照时存在的数据) 还原到新 EBS 卷所需的所有信息。
标签(Tag) 是一些充当元数据的词和短语,用于标识和组织 AWS 资源。标签限制随资源而有所不同,但大多数最多可以有 50 个标签。每个标签都包含一个键(Key) 和一个值(Value)。通过使用标签标记 AWS 资源,可以方便用户识别与管理资源,比如 EC2 实例可以使用 键为 Name 标签,以区分使用实例的项目或应用系统,又或是使用键为 Env 的标签,以区分是生产环境还是测试开发环境。

本方案中简单架构如下:

方案说明:

1、快照创建自动化:
(1)通过 Cloud Watch Event 计划任务设定事件触发时间,如每天24点( UTC +8)触发创建快照的 Lambda,我们将其命名为 ebs-kuaizhao。
(2)该 Lambda 将完成三个事情:首先获取需要对哪些卷进行快照,在此我们通过获取 EBS 卷是否带有一个键(Key)为 Snapshot 的标签(tag)的方式来决定是否对一个卷做快照。
(3)同时该 Lambda 对标记需要做快照的卷进行快照,并将快照的元数据信息(如快照 id,快照创建时间等信息)写入到一张 DynamoDB 的表中。该表将启用 TTL 功能及 DynamoDB 流功能。
本方案中表中一个项目通常会有4个字段,分别为 snapshot_id (作为主键),create_time,description,timestamp,ttl(该字段为 TTL 属性)。

2、快照删除自动化:
(1)如1中所说明,DynamoDB 的表启用了 TTL (生存时间)功能,生存时间可以设定为快照需要保留的时间,如7天,则该项目写入到表7天后将自动被删除,当表中的数据发生变化的时候,会自动向流中写入一条记录。
(2)DynamoDB 流触发负责删除快照的 Lambda,该 Lambda 读取流中记录中的信息,如果发现是一条删除表中项目的记录,则获取待删除的快照 id,然后删除对应的快照。

通过该方案,您只需要对需要打快照的 EBS 卷添加一个标签,创建快照的 Lambda 将自动识别并对其进行打快照,新增或者删除的卷也将被自动识别出。另外通过使用 DynamoDB 流及生存时间,简化了快照生命周期管理方式,如果不用该方式,通常需要定期检查存储快照信息的 DynamoDB 的表,比较当前时间与快照创建时间,需要遍历或者条件查询该表,若发现超过快照保存时间,则删除该快照并删除表中的该条记录,增加了代码复杂度。

三、如何构建

了解了方案的思路后,我们就可以动手构建了,同样我们将分为两个部分。
前提:
(1)对需要做快照的 EBS 卷打上标签,标签至少要有两组;

Key Value 说明
Name 用户自定义,且为英文字母 必须项,且 Key 为 Name,Value 内容为英文或者拼音,不能为中文字符
Snapshot Snapshot 必须项,且 Key 为 Snapshot

 

1、快照创建自动化
创建 DynamoDB  表,登录 AWS Console,选择 DynamoDB,创建表。

本示例中表名为 EBS-Snapshot-Lifecycle,您可以按需要自定义表名,主键为 snapshot_id,您可以自定义主键键名,本例中使用 snapshot_id,若改为其他,请注意后面的示例代码中修改对应的调用。
启用 TTL 功能。

TTL 属性填 ttl,当然您可以自定义该字段名字,本例中后面代码中使用了 ttl 字段,并勾选 DynamoDB 流,选择继续。在该部分中,关于 DynamoDB 表的吞吐能力选择了默认,即读取容量为5,写入容量为5,关于容量配置参考文件[5]。

在概述部分,可以得到该 DynamoDB 表的 arn 以及流的 arn 信息,该部分会在后面创建 Lambda 策略时用到,先复制到文本文件中,便于后面使用。

创建Lambda

本方案中使用 Python 3.6。

选择创建自定义角色,跳转到新界面,IAM

选择确定就可以编辑策略。

将如下内容贴到编辑框内,并将 Resource 中加粗部分(arn:aws-cn:dynamodb:cn-north-1:xxaccount-id-xxxx:table/EBS-Snapshot-Lifecycle)替换为刚刚创建的 DynamoDB 的表的 arn:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "dynamodb:PutItem",
                "logs:PutLogEvents"
            ],
            "Resource": [
              "arn:aws-cn:dynamodb:cn-north-1:xxaccount-id-xxxx:table/EBS-Snapshot-Lifecycle",
                "arn:aws-cn:logs:*:*:*"
            ]
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeVolumes",
                "ec2:CreateSnapshot"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws-cn:logs:*:*:*"
        }
    ]
}

 

右下角 选择  允许;
跳回刚才 Lambda 界面,看到角色已经配置好了。

选择“创建函数”。
添加 CloudWatch Events 触发器:

配置 CloudWatch Events 触发器。

其中计划表达式为:cron(0 16 ? * MON-SUN *)
表示每天北京时间24点触发,关于计划表达式的写法请参考文献[1].

 

接下来编辑 Lambda 代码,点击中间的 Lambda,下方将出现代码编辑器。

将原有代码删掉,将如下代码复制进去,标红部分改为自己的 DynamoDB 表名。(代码文件可在文后下载)

import boto3
import os,time
from botocore.exceptions import ClientError
from datetime import datetime, timedelta, timezone

client = boto3.client('ec2')
ec2 = boto3.resource('ec2')
ddb = boto3.resource('dynamodb')

# 'EBS-Snapshot-Lifecycle' is the table you store snapshot metadata, modify it to your own table name
table = ddb.Table('EBS-Snapshot-Lifecycle')

# set days to keep 快照保留周期
DAYS_TO_RETAIN = 7

def lambda_handler(event, context):

    os.environ['TZ'] = 'Asia/Shanghai'
    time.tzset()
    i=time.strftime('%X %x %Z')
    
    # set volume id, get volume who has a tag-key is 'Snapshot'
    describe_volumes=client.describe_volumes(
        Filters=[
            {
                'Name': 'tag-key',
                'Values': ['Snapshot',
                ]
                
            }
        ]
    )
    volume_id_list = []
    for vol in describe_volumes['Volumes']:
        volume_id_list.append(vol.get('VolumeId'))
    
    # set snapshot
    for volume_id in volume_id_list:
        volume = ec2.Volume(volume_id)
    
        for tags in volume.tags:
            if(tags.get('Key') == 'Name'):
                volume_name = tags.get('Value')
        description = volume_name + ' volume snapshot is created at ' + i
        
        try:
            response = client.create_snapshot(
                Description=description,
                VolumeId=volume_id)
        except:
            print('Create Snapshot occured error, Volume id is ' + volume_id)
        else:
            print('Snapshot is created succeed, Snapshot id is ' + response.get('SnapshotId'))
        
        # write into DynanoDB table
        snapshot_id = response.get('SnapshotId')
        start_time = response.get('StartTime')
        ttl_time = int(start_time.timestamp()) + 86400 * DAYS_TO_RETAIN
    
        try:
            table.put_item(
                Item={
                    'snapshot_id': snapshot_id,
                    'create_time': start_time.astimezone(timezone(timedelta(hours=8))).strftime('%X %x %Z'),
                    'description': description,
                    'timestamp': int(start_time.timestamp()),
                    'ttl': ttl_time,
                }
            )
        except:
            print('Write to DynanoDB Table occured error, snapshot id is ' + snapshot_id)
        else:
            print('Write to DynanoDB Table succeed, snapshot id is ' + snapshot_id)

 

说明:
(1)将11行中的 table = ddb.Table(‘EBS-Snapshot-Lifecycle’) 中 EBS-Snapshot-Lifecycle 替换为前面创建的 DynamoDB 的表名;
(2)14行中的 DAYS_TO_RETAIN = 7 表示快照保留的周期,上例中表示保存7天,您可以根据需要修改该值。
(3)27行的 Snapshot 是标签的键的内容,即我们设定凡是有一个键名为 Snapshot 的标签的卷,我们就对其做快照。

在 Lambda 基本设置中,可以将内存设为128 MB,超时设为10 s (通常情况下,1 s  内可执行完该 Lambda 代码,关于这部分的配置请参考文献[4])。

最后选择右上方“保存”。

至此,完成快照创建自动化。

2、快照删除自动化
创建一个新的 Lambda,方法如上,本例中命名为 EBS_Snapshot_Delete,同样创建时选择“创建自定义角色”,并将如下策略替换进去,其中 Resource 中内容(arn:aws-cn:dynamodb:cn-north-1:xxxx–your-account-id-xxxxx:table/EBS-Snapshot-Lifecycle/stream/yyyy-mm-ddThh:mm:ss.uuu)全部替换为前面创建的 DynamoDB 表的流的 arn 信息(注意是流的 arn 不是表的 arn)。

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "ec2:DeleteSnapshot",
                "logs:CreateLogGroup",
                "logs:PutLogEvents",
                "dynamodb:ListStreams"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetShardIterator",
                "dynamodb:DescribeStream",
                "dynamodb:GetRecords"
            ],
            "Resource": "arn:aws-cn:dynamodb:cn-north-1:xxxx--your-account-id-xxxxx:table/EBS-Snapshot-Lifecycle/stream/yyyy-mm-ddThh:mm:ss.uuu"
        }
    ]
}

选择 DynamoDB 作为触发器。

选择前面创建的 DynamoDB 表,选择添加。

在 Lambda 代码部分,使用如下代码,代码文件可在文后下载。该部分代码主要读取流中的记录,对于表中项目删除事件( REMOVE)获取快照 id 信息,然后调用快照删除 API,删除对应的快照。

import boto3
from botocore.exceptions import ClientError

client = boto3.client('ec2')

def lambda_handler(event, context):
    #将event打印出,主要为了用于调试与排错
    print(event)
    
    snapshot_to_delete = []

    for record in event['Records']:
        if(record['eventName'] == 'REMOVE'):
            snapshot_to_delete.append(record['dynamodb']['Keys']['snapshot_id']['S'])
            
    for snapshot_id in snapshot_to_delete:
        try:
            response = client.delete_snapshot(
            SnapshotId=snapshot_id,
            )
        except ClientError as e:
            if(e.response['Error']['Code'] == 'InvalidSnapshot.NotFound'):
                print(snapshot_id + ' is not founded')
        except:
            print('Delete snapshot occured error, snapshot id is ' + snapshot_id)
        else:
            print('Delete snapshot succeed, snapshot id is '+ snapshot_id)

同样 Lambda 的配置可以选择128 MB,超时设为10 s (通常1 s 内能够执行完)。

选择右上角“保存”。
至此完成快照自动删除功能的配置。

当自动创建快照触发时,会在 DynamoDB 的表中看到有项目出现。

以上方案是一个简单的快照自动创建与定期删除方案,可以用于 AWS EBS 卷的快照的生命周期管理(自动创建、删除),快照是针对存储卷在该时刻点的快照,在做快照的时候我们通常需要考虑到先将内存数据写入到磁盘中,或者在快照前将磁盘写 IO 暂停,以免出现数据不一致的问题。在应用该方案到生产环境之前建议做好测试与评估,并对示例代码做必要的修改。

四、总结

本方案通过使用 Amazon CloudWatch Events、AWS Lambda、 Amazon DynamoDB 等服务实现了一个无服务器架构的 EBS 卷生命周期管理,希望通过本文读者对以上服务有进一步直观的认识与了解。AWS 提供丰富的无服务器服务以及 API 接口,用户可以很容易的根据业务需求构建适合自己业务场景的解决方案。

另附Lambda 代码下载:
ebs-snapshot-create.py
ebs-snapshot-delete.py

五、参考资料

[1] Amazon CloudWatch Events 规则的计划表达式: https://docs.aws.amazon.com/zh_cn/AmazonCloudWatch/latest/events/ScheduledEvents.html

[2] 配合使用 AWS Lambda 和 Amazon DynamoDB:https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/with-ddb.html

[3] 使用 DynamoDB 流 捕获表活动:https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Streams.html

[4] 配置 Lambda 函数:https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/resource-model.html

[5] DynamoDB 读取和写入吞吐容量: https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html

[6] Python Boto3:https://boto3.amazonaws.com/v1/documentation/api/latest/index.html

[7] DynamoDB 生成时间: https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/TTL.html

本篇作者

王世帅

AWS解决方案架构师,负责基于 AWS 的云计算方案的咨询与架构设计,同时致力于 AWS 云服务在教育、医疗、媒体等行业的应用和推广。在互联网直播,OTT,企业数字化转型等领域有着广泛的设计与实践经验,对于人工智能,深度学习框架和应用等有浓厚的兴趣和热情。在加入AWS 之前曾在国航担任系统工程师,负责存储方案的架构设计,在企业混合 IT 等方面有丰富经验。