S3版本管理101: 使用版本管理用于文件恢复和反删除

一、背景

S3服务具有文件版本管理功能,这允许您不改变对象目标的文件名而保留多个历史版本。例如在上传时候看起来文件名相同是覆盖了,其实是将新上传的文件保存为当前最新版本(Latest版本),原来的文件变成历史版本。被删除的对象,如果是在一个打开了历史版本的存储桶,那么是可以反删除。当然如果所有历史版本都被删除了,那么这个文件无法找回了。

因此正确使用版本管理功能,可解决许多业务场景问题,通过反删除也可以增加业务上的保护,而了解版本管理的机制,可实现彻底永久删除,实现更好的数据隐私保护。

二、开启版本管理功能

1、创建S3存储桶时候打开版本管理功能

在创建新的存储桶的时候,输入名称之后,将页面向下滚动,即可看到名为Bucket Versioning的选项。这个选项默认是Disable禁用状态的。对于新创建的存储桶,要在创建时候,手工设置为打开。然后完成创建。如下截图。

2、修改已经存在的存储桶打开版本管理功能

对于一个已经存在的存储桶、且里边已经有数据,需要注意的是,在版本控制功能开启之前写入的所有文件,都只有单一版本也就是当前最新状态。只有当版本控制打开后,新写入的文件都具有历史版本,且针对已经存在的文件再执行覆盖,也会有历史版本功能。

进入要修改的存储桶,找到第二个标签页Properties属性,查看下方Bucket Versionning选项,点击编辑按钮,即可完成配置。如下截图。

三、获取历史版本清单和特定文件

1、从AWS控制台S3服务界面查看历史版本并获取文件

在确认文件版本管理功能被启用之后,首先我们上传一个测试文件demo1.jpg。然后,再次上传另一个文件,内容不同、大小不同,但是文件名继续使用demo1.jpg这个文件名。上传过程不管是通过AWS控制台还是API上传,都不会有报错和提示文件已经存在,都是直接上传成功。

进入S3控制台,查看存储桶,点击页面中部的Show versions开关,将其打开,随后即可在文件清单中看到历史版本。如下截图。

由于图形控制台能显示历史版本,因此需要下载的话,只要选择历史文件,即可通过S3控制台直接下载。

2、查看特定文件的历史版本清单

(1) 使用AWSCLI操作

使用AWSCLI,在配置了正确的AKSK后执行如下命令:

aws s3api list-object-versions --bucket s3-ver-test-lxy --prefix demo1.jpg

返回结果如下:

从以上截图中,可看到返回的历史版本号。

(2) 使用API查看特定文件的历史版本清单

以Python3为例,AWS SDK名叫Boto3,可构建如下代码:

import boto3

buketname = 's3-ver-test-lxy'
filename = 'demo1.jpg'

client = boto3.client('s3')

response = client.list_object_versions(
    Bucket=buketname,
    Prefix=filename
)

versionid = response['Versions']

print(versionid)

返回信息如下。可通过其中的IsLatest=True来判断此文件是否是所有历史版本中最新的。

[
    {
        'ETag': '"7a6e529c1612ed7cd2a3176c38da2af6"', 
        'Size': 2367629, 
        'StorageClass': 'STANDARD',
         'Key': 'demo1.jpg', 
         'VersionId': 'Nmludlvw0AX0i_JHWcXWjHsAT8dGDkJO', 
         'IsLatest': True, 
         'LastModified': datetime.datetime(2023, 10, 2, 5, 40, 16, tzinfo=tzutc()), 
         'Owner': {
            'ID': 'fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298'
        }
    }, 
    {
        'ETag': '"68e69b4b61a0971140d61d69d0b9c1b8"', 
        'Size': 7558500, 
        'StorageClass': 'STANDARD', 
        'Key': 'demo1.jpg', 
        'VersionId': '...pm.I55IzATHkGt.VU3n_s_AP8LuWX', 
        'IsLatest': False, 
        'LastModified': datetime.datetime(2023, 10, 2, 5, 39, 36, tzinfo=tzutc()), 
        'Owner': {
            'ID': 'fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298'
        }
    }
]

注意,以上返回结果默认只返回1000条,也就是说如果本文件历史版本超过了1000个,需要额外查询。请参考本文末尾的Boto3 SDK文档。

3、通过版本ID获取某个特定版本

在上一个章节的操作中,我们已经获得了指定文件名的所有历史版本,那么要读取其中一个特定版本,方法如下。

(1) 使用AWSCLI操作

aws s3api get-object \
    --bucket s3-ver-test-lxy \
    --key demo1.jpg \
    --version-id "...pm.I55IzATHkGt.VU3n_s_AP8LuWX" \
    download-froms-s3-version.jpg

以上这条命令,会下载S3存储桶内文件名是demo1.jpg的指定版本,并且重命名为download-froms-s3-version.jpg,保存在当前目录下。

返回结果如下:

{
    "AcceptRanges": "bytes",
    "LastModified": "2023-10-02T05:39:36+00:00",
    "ContentLength": 7558500,
    "ETag": "\"68e69b4b61a0971140d61d69d0b9c1b8\"",
    "VersionId": "...pm.I55IzATHkGt.VU3n_s_AP8LuWX",
    "ContentType": "image/jpeg",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

然后可查看当前目录下,下载到的jpg文件,即可确认返回的是需要的指定版本。

(2) 使用API操作(AWS SDK)

以Python3为例,AWS SDK名叫Boto3,可构建如下代码:

import boto3

buketname = 's3-ver-test-lxy'
filename = 'demo1.jpg'
versionid = '...pm.I55IzATHkGt.VU3n_s_AP8LuWX'

client = boto3.client('s3')

response = client.get_object(
    Bucket=buketname,
    Key=filename,
    VersionId=versionid,
)

print(response)

返回结果如下:

{'ResponseMetadata': {'RequestId': 'CD1N1C53DM3X5WAX', 'HostId': 'jZ32AqS9KaNoNdrnNky4axKcOGlREjgwAr7AoC3LG6c4/fSYnsmKuHMbVkiD8ovW3r/XAX6T1a4=', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': 'jZ32AqS9KaNoNdrnNky4axKcOGlREjgwAr7AoC3LG6c4/fSYnsmKuHMbVkiD8ovW3r/XAX6T1a4=', 'x-amz-request-id': 'CD1N1C53DM3X5WAX', 'date': 'Mon, 02 Oct 2023 15:06:51 GMT', 'last-modified': 'Mon, 02 Oct 2023 05:39:36 GMT', 'etag': '"68e69b4b61a0971140d61d69d0b9c1b8"', 'x-amz-server-side-encryption': 'AES256', 'x-amz-version-id': '...pm.I55IzATHkGt.VU3n_s_AP8LuWX', 'accept-ranges': 'bytes', 'content-type': 'image/jpeg', 'server': 'AmazonS3', 'content-length': '7558500'}, 'RetryAttempts': 0}, 'AcceptRanges': 'bytes', 'LastModified': datetime.datetime(2023, 10, 2, 5, 39, 36, tzinfo=tzutc()), 'ContentLength': 7558500, 'ETag': '"68e69b4b61a0971140d61d69d0b9c1b8"', 'VersionId': '...pm.I55IzATHkGt.VU3n_s_AP8LuWX', 'ContentType': 'image/jpeg', 'ServerSideEncryption': 'AES256', 'Metadata': {}, 'Body': <botocore.response.StreamingBody object at 0x1077589a0>}

以上返回结果中,图片是在Body部分以Stream的形式返回,如果需要进一步处理,请使用其他Python库来处理图片。

四、打开版本管理后的删除和反删除

注意:如果本文件是被彻底删除的,包括文件本身、历史版本都被彻底删除,那么文件是无法恢复的。因此执行本文测试的时候,请用测试存储桶和测试文件做测试,不要在生产用的存储桶内发起测试,以免误删生产文件。

1、删除文件的原理

(1) 当前最新版本的文件被删除了会怎么样

当一个存储桶开启了版本管理后,在存储桶中执行的删除文件的操作(且不指定文件版本ID),会将这个文件的最新版本标记为Delete marker的删除标记符,这个标识符的体积是0。当然,这个文件的历史版本还继续存在,并且也拥有自己的版本ID。当这个文件的最新版本是Delete marker的删除标记符时候,这个文件默认不会被显示,除非以下两种情况才会显示已经删除的文件:

  • 在AWS控制台上S3界面中,打开了Show versions按钮,将显示所有历史版本(包括被删除)列表
  • 在AWSCLI/API/SDK的查询中,使用list-object-versions等命令,要求返回结果显式的列出所有版本

如果不是以上两种情况,那么查询存储桶时候,是看不到被删除掉的文件的。

(2) 如何反删除

如果需要反删除,将文件恢复回来,那么只要删除掉当前作为最新版本的Delete marker,这个文件就被恢复回来了。

(3) 如何恢复到N个历史版本中的某一个特定版本

方法是查询本文件所有历史版本,然后调用其特定版本ID,即可读取文件。

确认这就是要恢复的版本后,将比这个日期更新一些的历史版本都删除,于是这个版本就成了默认的最新版本。

(3) 文件的历史版本能否反删除

如果删除目标是针对本文件的某个历史版本,也就是传入了VersionID参数,那么这个删除是不可逆的。删除历史版本时候不会被标记Delete marker,而是被永久删除。

(4) 如何彻底删除一个文件

如果将隶属于这个文件的所有历史版本都删除,那么这个文件就彻底被删除了。

2、在AWS控制台上操作

在AWS控制台上操作需要注意,界面上是否打开Show versions这个按钮,执行的命令是不一样的。两种场景如下:

  • 当没有打开Show versions按钮时候,执行的删除是逻辑删除,图形界面的删除提示是输入删除字符(Delete)确认删除,然后这个文件会被打上Delete marker
  • 当开启了Show versions按钮时候,执行文件是真的删除,图形界面的删除提示是输入永久删除字符(Permanently delete)确认永久删除,不生成任何历史记录。

由此,二者需要区别和谨慎操作。

(1) 在没有打开Show versions选项时候逻辑删除

确认本存储桶开启了版本管理后,对某个文件执行删除操作。注意当前页面上,Show versions选项没有打开。如下截图。

出现了提示信息,可看到界面上提示,这里是逻辑删除,删除后会生成Delete marker,并可以反删除。如下截图。

删除成功。为了反删除,需要开启Show versions选项,就可以看到Delete marker的标记了。如下截图。

如果需要反删除,则只需要删除掉Delete marker的删除标记符即可。

在删除Delete marker标记符的时候,界面会提示确认是否要永久删除。如下截图。

当开启了Show versions按钮时候,执行文件是真的删除,图形界面的删除提示是输入永久删除字符确认永久删除,不生成任何历史记录。

因此这里仔细阅读提示信息后,确认操作的没错,就可以输入确认永久删除的字样,然后按下删除按钮。删除Delete marker之后,文件将被恢复,即使Show versions按钮是关闭的,也能看找到这个文件被正常显示在列表中。如下截图。

(2) 打开Show versions选项时候的永久删除

如前文所说,打开Show versions选项时候的永久删除。在控制台上发起对某个文件的彻底删除,如下截图。

在彻底删除的时候,弹出的提示信息与之前的不同,提示信息是永久删除,且不可恢复。要输入的确认文字也是永久删除。如下截图。

删除完成后,回到文件列表。即使打开Show versions选项,也看不到这个文件的历史版本,也就是说这个文件被彻底删除了,此过程不可逆。如下截图。

3、在AWSCLI上删除和反删除

(1) 删除单个文件(生成删除标识符)

以刚才的存储桶为例,执行如下命令列出存储桶内所有文件的最新版本:

aws s3 ls s3://s3-ver-test-lxy/

返回结果如下。

2023-10-02 14:08:46     542065 153713sf34m34ra3ar4jf3.jpg
2023-10-02 13:40:16    2367629 demo1.jpg

接下来要删除demo1.jpg文件。执行如下命令:

aws s3 rm s3://s3-ver-test-lxy/demo1.jpg

删除后再次查询,已经看不到这个文件了。结果如下:

2023-10-02 14:08:46     542065 153713sf34m34ra3ar4jf3.jpg

(2) 列出存储桶内所有文件的所有历史版本(含删除标识符)

我们此时使用底层API查看存储桶内所有文件的所有版本,可以使用如下命令:

aws s3api list-object-versions --bucket s3-ver-test-lxy

这时候返回结果就会包含所有文件的所有版本,且会列出标记为已经删除的Delete marker的版本。而且在文件各版本返回信息中,会把Delete marker单独列在最下方。结果如下:

{
    "Versions": [
        {
            "ETag": "\"79f19d1fc69fe3d2f29805e632acd42f\"",
            "Size": 542065,
            "StorageClass": "STANDARD",
            "Key": "153713sf34m34ra3ar4jf3.jpg",
            "VersionId": "G1yKjlUz9YDWMvwt40Uqjsc.z.vc_xZ7",
            "IsLatest": true,
            "LastModified": "2023-10-02T06:08:46+00:00",
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            }
        },
        {
            "ETag": "\"2cc4191781c9b65d1893a286214767c0\"",
            "Size": 712725,
            "StorageClass": "STANDARD",
            "Key": "demo1.jpg",
            "VersionId": ".H8p75R9kOuukaD.aq49wrKSTTkcOGlc",
            "IsLatest": false,
            "LastModified": "2023-10-03T05:18:54+00:00",
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            }
        },
        {
            "ETag": "\"7a6e529c1612ed7cd2a3176c38da2af6\"",
            "Size": 2367629,
            "StorageClass": "STANDARD",
            "Key": "demo1.jpg",
            "VersionId": "oJQG7HZV1ED70065M1RSTz3yrOABPI5l",
            "IsLatest": false,
            "LastModified": "2023-10-03T05:00:38+00:00",
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            }
        },
        {
            "ETag": "\"68e69b4b61a0971140d61d69d0b9c1b8\"",
            "Size": 7558500,
            "StorageClass": "STANDARD",
            "Key": "demo1.jpg",
            "VersionId": "UfxnSHcrfE6P1BkjaEKlsv43bmQma.A2",
            "IsLatest": false,
            "LastModified": "2023-10-03T04:51:43+00:00",
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            }
        }
    ],
    "DeleteMarkers": [
        {
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            },
            "Key": "demo1.jpg",
            "VersionId": "pNnhw0r6gg5DGrURAVFkBvOn7DAZp1fI",
            "IsLatest": true,
            "LastModified": "2023-10-03T05:20:13+00:00"
        }
    ]
}

以上命令是列出存储桶内所有文件的历史版本的。

(3) 列出当个文件的所有历史版本(含删除标识符)

如果要单独查询文件demo1.jpg的所有历史版本(含删除标识符),可使用如下命令:

aws s3api list-object-versions --bucket s3-ver-test-lxy --prefix demo1.jpg

从以上命令返回结果中,即可看到demo1.jpg这个文件的Delete marker对应的VersionID的值是pNnhw0r6gg5DGrURAVFkBvOn7DAZp1fI

(4) 对文件反删除恢复到上一个版本

现在要进行反删除,也就是删除掉Delete marker这个标识符。执行如下命令:

aws s3api delete-object --bucket s3-ver-test-lxy --key demo1.jpg --version-id pNnhw0r6gg5DGrURAVFkBvOn7DAZp1fI

返回结果如下:

{
    "DeleteMarker": true,
    "VersionId": "pNnhw0r6gg5DGrURAVFkBvOn7DAZp1fI"
}

Delete marker被删除后,这个文件的上一个版本就称为了Latest当前最新版本,这时候文件也就处于有效可见的状态了。

在AWSCLI上执行列出存储桶文件确认下:

aws s3 ls s3://s3-ver-test-lxy/

可发现文件名是demo1.jpg的这个之前被删除掉的已经显示出来了。

2023-10-02 14:08:46     542065 153713sf34m34ra3ar4jf3.jpg
2023-10-03 13:18:54     712725 demo1.jpg

(5) 删除某特定历史版本

在上文查询文件demo1.jpg的所有历史版本(含删除标识符)时候,可以看到返回结果除了删除标识符,还返回了三个历史版本,分别是:

  • 大小712725,VersionID是.H8p75R9kOuukaD.aq49wrKSTTkcOGlc
  • 大小2367629,VersionID是oJQG7HZV1ED70065M1RSTz3yrOABPI5l
  • 大小7558500,VersionID是UfxnSHcrfE6P1BkjaEKlsv43bmQma.A2

接下来要删除中间这个版本。在上文介绍AWS图形控制台上操作的时候,我们提到过中间版本的删除是永久删除,不生成Delete marker的。现在来做下验证。

执行如下命令删除中间版本。

aws s3api delete-object --bucket s3-ver-test-lxy --key demo1.jpg --version-id oJQG7HZV1ED70065M1RSTz3yrOABPI5l

删除成功返回信息如下:

{
    "VersionId": "oJQG7HZV1ED70065M1RSTz3yrOABPI5l"
}

再次执行命令列出文件demo1.jpg的所有历史版本(含删除标识符),可使用如下命令:

aws s3api list-object-versions --bucket s3-ver-test-lxy --prefix demo1.jpg

返回结果只有两个版本,中间版本已经删除。结果如下:

{
    "Versions": [
        {
            "ETag": "\"2cc4191781c9b65d1893a286214767c0\"",
            "Size": 712725,
            "StorageClass": "STANDARD",
            "Key": "demo1.jpg",
            "VersionId": ".H8p75R9kOuukaD.aq49wrKSTTkcOGlc",
            "IsLatest": true,
            "LastModified": "2023-10-03T05:18:54+00:00",
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            }
        },
        {
            "ETag": "\"68e69b4b61a0971140d61d69d0b9c1b8\"",
            "Size": 7558500,
            "StorageClass": "STANDARD",
            "Key": "demo1.jpg",
            "VersionId": "UfxnSHcrfE6P1BkjaEKlsv43bmQma.A2",
            "IsLatest": false,
            "LastModified": "2023-10-03T04:51:43+00:00",
            "Owner": {
                "ID": "fb95c5537d2d08563538fe36fd03bf6d2bbb7afc02315fd34f209c45e143b298"
            }
        }
    ]
}

这也验证了对中间版本的删除,不会留下Delete marker,只有删除最新版本Latest时候才会留下删除标识符的。以上过程,也可以通过AWS Console图形控制台复现。

4、在API上删除和反删除

API的操作,本质上是借助各编程语言的AWS SDK对API借口的再封装。其操作原理和上文的AWSCLI是一致的,操作方法和命令都几乎一样,只有所谓语法的不同。本文就不再重复讲解了,只是以Python3语言调用Boto3 SDK为例,给出代码样例。

(1) 列出某文件所有历史版本

本文章节三的标题2的(2)部分已经讲解。

(2) 删除文件最新版本生成Delete marker标识符

import boto3

buketname = 's3-ver-test-lxy'
filename = 'demo1.jpg'

client = boto3.client('s3')

response = client.delete_object(
    Bucket=buketname,
    Key=filename,
)

print(response)

执行后返回结果:

{'ResponseMetadata': {'RequestId': 'T84REPC8MCDB94E3', 'HostId': 'qeWiHa/Wy4mMyj0m4RRSxxDfWDB3/GZFMLt9MNIAM7srDyAoWNfC08Lar9mRvYlYybQjB62XzcA=', 'HTTPStatusCode': 204, 'HTTPHeaders': {'x-amz-id-2': 'qeWiHa/Wy4mMyj0m4RRSxxDfWDB3/GZFMLt9MNIAM7srDyAoWNfC08Lar9mRvYlYybQjB62XzcA=', 'x-amz-request-id': 'T84REPC8MCDB94E3', 'date': 'Tue, 03 Oct 2023 07:04:51 GMT', 'x-amz-version-id': 'GkuHxVlGZvyDh7lByPrtyWOiFQM5Z21E', 'x-amz-delete-marker': 'true', 'server': 'AmazonS3'}, 'RetryAttempts': 0}, 'DeleteMarker': True, 'VersionId': 'GkuHxVlGZvyDh7lByPrtyWOiFQM5Z21E'}

这样文件就被打上Delete marker标识符了。其效果可以通过AWS控制台、AWSCLI等方式进行确认。

(3) 反删除/删除文件特定版本

反删除操作时候,可以是删除Delete marker标识符,也可以是中间版本,只取决于传入的VersionID对应的是哪一个。

如下代码,首先查询demo1.jpgDelete marker标识符并获取对应VersionID,然后再删除Delete marker标识符对应的VersionID,即可实现反删除。

import boto3

buketname = 's3-ver-test-lxy'
filename = 'demo1.jpg'

client = boto3.client('s3')

# get DeleteMarkers's VersionID
response = client.list_object_versions(
    Bucket=buketname,
    Prefix=filename
)
versionid = response['DeleteMarkers'][0]['VersionId']
print(versionid)

# delete DeleteMarker with the VersionID
response1 = client.delete_object(
    Bucket=buketname,
    Key=filename,
    VersionId=versionid
)
print(response1)

执行后返回结果:

GkuHxVlGZvyDh7lByPrtyWOiFQM5Z21E
{'ResponseMetadata': {'RequestId': 'EGPSWVSJAA5G9ENG', 'HostId': 'pigrBT5wWVwOxlENNI+P/mpEnnd77qOql0WeNzavA6+3p9ZVL68NVhJFs7iCjeGGM8vEPkI3GL0ilhTJvTpZOg==', 'HTTPStatusCode': 204, 'HTTPHeaders': {'x-amz-id-2': 'pigrBT5wWVwOxlENNI+P/mpEnnd77qOql0WeNzavA6+3p9ZVL68NVhJFs7iCjeGGM8vEPkI3GL0ilhTJvTpZOg==', 'x-amz-request-id': 'EGPSWVSJAA5G9ENG', 'date': 'Tue, 03 Oct 2023 14:59:18 GMT', 'x-amz-version-id': 'GkuHxVlGZvyDh7lByPrtyWOiFQM5Z21E', 'x-amz-delete-marker': 'true', 'server': 'AmazonS3'}, 'RetryAttempts': 0}, 'DeleteMarker': True, 'VersionId': 'GkuHxVlGZvyDh7lByPrtyWOiFQM5Z21E'}

5、在开启历史版本的存储桶删除所有历史版本

要删除一个文件的所有历史版本,这需要遍历文件demo1.jgp对应的所有VersionID,包括普通历史版本,和DeleteMarker的版本所对应的VersionID。将他们依次删除,这个文件就彻底删除了。

代码样例如下:

import boto3

buketname = 's3-ver-test-lxy'
filename = 'demo1.jpg'

client = boto3.client('s3')

# delete version
def deleteObjectVersion(version_id):
    response = client.delete_object(
        Bucket=buketname,
        Key=filename,
        VersionId=version_id
    )

# get DeleteMarkers's ID to delete
response = client.list_object_versions(
    Bucket=buketname,
    Prefix=filename
)

# delete DeleteMarkers's VersionID
for i in response['DeleteMarkers']:
    print(i['VersionId'])
    deleteObjectVersion(i['VersionId'])

# delete Object's VersionID
for i in response['Versions']:
    print(i['VersionId'])
    deleteObjectVersion(i['VersionId'])
    

五、参考文档

在 S3 桶中使用版本控制

https://docs.amazonaws.cn/AmazonS3/latest/userguide/Versioning.html

列出启用版本控制的桶中的对象

https://docs.amazonaws.cn/AmazonS3/latest/userguide/list-obj-version-enabled-bucket.html

Python boto3 SDK: listobjectversions

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/listobjectversions.html

从启用了版本控制的桶中删除对象版本

https://docs.aws.amazon.com/AmazonS3/latest/userguide/DeletingObjectVersions.html