一、背景
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.jpg
的Delete 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://docs.aws.amazon.com/AmazonS3/latest/userguide/DeletingObjectVersions.html