AWS系列:S3不仅仅是存储
![](https://res.infoq.com/articles/aws-s3-dive-in/zh/smallimage/s3100.jpg)
编者按:本文系InfoQ中文站向陈天的约稿,这是AWS系列文章的第二篇。以后会有更多文章刊出,但并无前后依赖的关系,每篇都自成一体。读者若要跟随文章来学习AWS,应该至少注册了一个AWS账号,事先阅读过当期所介绍服务的简介,并在AWS management console中尝试使用过该服务。否则,阅读的效果不会太好。在这篇文章里,介绍了尝试用 S3 创建公司内部的文件服务器,保存员工私人/共享文件,并以类似Dropbox的方式双向同步。
S3 介绍
S3是 AWS 最早发布的诸多服务之一,用作可信存储。所谓可信,AWS给出的概念是:「在指定年度内为对象提供 99.999999999% 的持久性和高达 99.99% 的可用性」,换句话说就是任何存储于S3的数据基本不可能丢失,在一个年度内,不超过1小时(3153.6s)的宕机时间。除此之外,S3还提供如下特性:
- 跨区域复制:只需要简单的配置,存储于S3中的数据会自动复制到选定的不同区域中。当你的数据对象的收集分散在不同的区域,而处理集中在某些区域时非常有用。
- 事件通知:当数据对象上传到 Amazon S3 中或从中删除的时候会发送事件通知。事件通知可使用 SQS 或 SNS 进行传送,也可以直接发送到 AWS Lambda 进行处理。比如说,上传到S3的图片的resize。
- 版本控制:数据对象可以启用版本控制,这样你就可以很方便地进行回滚。对于应用开发者来说,这是个特别有用的特性。
- 加密:S3的访问本身是支持 SSL(HTTPS)的,保证传输的安全,对于数据本身,你可以通过Server side encryption(AES256)来加密存储在S3的数据。
- 访问管理:通过 IAM/VPC 可以控制S3的访问粒度,你甚至可以控制一个bucket(S3对数据的管理单元,一个bucket类似于一组数据的根目录)里面的每个folder,甚至每个文件的访问权限。
- 可编程:可以使用 AWS SDK 进行客户端或者服务端的开发。
- 成本监控和控制:S3 有几项管理和控制成本的功能,包括管理成本分配的添加存储桶标签和接收账单警报的 Amazon Cloud Watch 集成。
- 灵活的存储选项:除了 S3 Standard,还有低成本的 Standard – Infrequent Access 选项可用于非频繁访问数据,存储的价格大概是 Standard 的 2/5。至于那些访问不了多少次的冷数据(如1年前的Log),可以存储在Glacier中,价格在 Standard 的 1/4(1T $7/月),缺点是需要几个小时来恢复数据(估计是存放于离线的磁带中)。
基本用法
S3 的用户可以使用 AWS management console 来创建 bucket(类比文件系统的根目录),以及 bucket 内部的目录树,并上传文件,但这不是使用 S3 的最佳方式。日常的主要操作应该使用 AWS CLI 和 AWS SDK 完成。
AWS CLI
安装 AWS CLI 可以使用 pip / brew 等安装工具,不再详述。AWS CLI 是 AWS 官方提供的 CLI 工具,简单好用,我会另行撰文深度介绍 AWS CLI。AWS CLI 目前不支持命令和参数的自动补全,从 AWS re:invent 2015 透露出来的信息,其团队在做一些自动补全的尝试,未来会变得更加人性化。如果你想现在就用得更舒服一些,可以使用 sAws。
使用 AWS CLI 操作 S3 非常简单,创建/删除 bucket 可以使用 aws s3api
:
$ aws s3api create-bucket --bucket <name>
$ aws s3api delete-bucket --bucket <name>
如果要像一般的文件系统一样操作 S3,可以使用 aws s3
命令:
$ aws s3 ls
$ aws s3 cp
$ aws s3 rm
此外,aws s3
还提供了 sync
,方便本地文件和 S3 上的文件互相 sync,比如我本地用 pandoc 编译出了 markdown 撰写的 reveal.js 的 slides,可以这样同步到 S3:
$ aws s3 sync ./output s3://eng-assets/slides
AWS SDK
AWS SDK 提供了对几乎所有主流语言的支持,在程序里使用 S3,一般的流程是:
- 创建 AWS connection(这一步需要用到你的 access key)。
- 使用 connection 创建 S3 对象。
- 使用 S3 API 进行各种 API 操作,比如创建 bucket,上传文件等。
这里列一个 JavaScript 的例子:
const aws = require('aws-sdk');
const Promise = require("bluebird");
const s3 = Promise.promisifyAll(new aws.S3());
s3.createBucketAsync({Bucket: 'test-myBucket'}).then(function() {
var params = {Bucket: 'test-myBucket', Key: 'myKey', Body: 'Hello!'};
s3.putObjectAsync(params).then(function(data) {
console.log('successfully uploaded data');
}).error(function(err) {
console.log(err);
})
});
使用 S3 的典型场景
S3的一些典型使用场景如下:
- 存储用户上传的文件,如头像,照片,视频等静态内容。
- 当作一个的key value store,承担简单的数据库服务功能。
- 数据备份。
- 静态网站的托管:你可以对一个 bucket 使能 Web Hosting。
我们简单介绍一下 S3 实现静态网站托管,然后以一个例子讲述如何使用 S3 实现一个能最大程度保证数据安全同时又价格低廉的团队内部的文件服务器。
使用 S3 实现安全的静态网站托管
经常使用 GitHub 的朋友对 GitHub pages 服务一定不会陌生,你只要把各种静态网站生成工具的生成的目标放入 gh-
pages 的 branch,GitHub pages 就会帮你做静态网站的托管。得益于如今越来越强大的 JavaScript 和各种 API,静态网站其实早已脱离了展示 HTML 的基本范畴。
GitHub pages 有一个缺点就是,只要你使用,它就是开放的,无法变成一个私有网站,存放公司内部的私密文件。公司内部的一些私有内容,比如:
- 使用 reveal.js 生成的 slides。
- 使用 new relic 生成的各种嵌入式报表和图表。
- 使用 JavaScript + AWS SDK 做的各种内部工具(由于 AWS SDK 提供了 JavaScript SDK,所以你可以用静态网站的方式访问数据库等服务,实现 server less 的效果)。
你无论如何都不会想将其暴露给外界。这个时候,GitHub pages 就不适用,我们可以使用 S3 Web Hosting + IAM policy 来完成。
使能 S3 Web Hosting 是件很简单的事情,只需在 AWS console 中,为对应的 bucket 打开这个选项即可,然后添加如下 IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:AWS:s3:::team-assets/*"
}
]
}
S3 Web Hosting 会告诉你一个用于访问的域名,你也可将你自己的私有域名指定一个 CNAME
指向该域。这样配置下来,只要域名和要访问的文件夹没有暴露,文件内容就是安全的。适用于安全等级不高的内容。
如果需要更高的安全级别,可以配合 VPC + IAM policy。一般而言,使用 VPC 的用户,都会将 VPC 设置成私有网络(比如 10.0.0.x 的网络),然后在网络边界配置一台 VPN 服务器,用于内外网的交互。任何用户要访问内网,必须先接入 VPN。我们可以设置用于 Web Hosting 的 S3 的 bucket 的 IAM 仅允许 VPN 服务器的 IP 访问,如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:AWS:s3:::team-assets/*",
"Condition" : {
"IpAddress" : {
"aws:SourceIp" : ["5.5.5.5/32"]
}
}
}
]
}
那么,只用当用户接入 VPN 之后,才能访问 Web Hosting 的域名下的文件,进一步提高了安全性。当然,由于不是在路由层面控制访问,所以没办法防止 ip spoofing,还是有一些潜在风险的,不过风险不大(攻击者需要知道要访问的文件所在的域名和路径,并且知道仅允许哪个源IP访问,进而进行 IP spoofing,而公网上 IP spoofing 的难度很大,基本上所有的路由器都会做 reverse route check)。
使用 S3 实现文件服务器
很多公司都会为员工提供私人和共享的文件存储。比如作为一个用户,我可以把我的私人文件存放在:fileserver://home/tyrchen/*
下,把一些共享文件存放在 fileserver://public/tyrchen/*
下。为了能够安全的存储这些文件,公司的 IT 部门一般会使用昂贵的 SAN(Storage Area Network)来保证一定程度的 SLA(Service Level Agreement),同时,还要做各种各样的备份(和恢复)。如果我们使用 S3 来实现类似的文件服务器,其代价和未来的维护成本会小得多。此外,我们还可以做一些额外的开发,使得文件服务器的使用体验类似于 Dropbox。
大致的想法是这样的:
- 新员工入职后会为其在S3上建立 home folder,用来保存重要的私人文件和共享文件。
- 员工电脑的本地文件中会有一个目录 corp-fs-box,里面包含三个子目录:
- private:存放任意文件,私有,会自动sync到私人目录,别人无法访问。
- photos:存放各种媒体文件,公开,会自动sync到共享目录,并生成合适的尺寸放在供Web访问的S3 bucket中。
- 员工只要在本地目录中存放文件,就会按照上述规则自动同步,类似Dropbox。
解决思路:
- 创建两个 S3 bucket:corp-fs-team 和 corp-fs-web。corp-fs-web打开 Web Hosting 功能。
- 使用IAM policy来设置 home folder 的权限。
- 使用
aws s3
sync 来同步文件夹:
- 对本地
corp-fs-box/private
里的文件,同步到S3://corp-fs-team/home/{AWS:username}/
中。这个目录只有当前用户可以访问,其他用户不能访问。- 对本地
corp-fs-box/pub/photos
里的文件,同步到S3:web//corp-fs-team/pub/photos
中。这个目录任何用户都可以访问并修改。- S3 配置 Events,使得对于
S3://corp-fs-team/pub/photos/{AWS:username}/
的任何更新行为(添加/删除)都会触发 lambda 函数。- lambda 函数扫描上传的文件,如果是
*.jpg
或者*.mp4 / *.mov
,则将其进行 resize / transcoding 等处理,并将编译的结果放在S3://corp-fs-web/pub/photos/{AWS:username}/*
下,供内网的用户浏览。
涉及的 IAM policy 如下:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGroupToSeeBucketList",
"Action": ["s3:ListAllMyBuckets", "s3:GetBucketLocation"],
"Effect": "Allow",
"Resource": ["arn:AWS:s3:::*"]
}, {
"Sid": "AllowRootLevelList",
"Action": ["s3:ListBucket"],
"Effect": "Allow",
"Resource": ["arn:AWS:s3:::corp-fs-team"],
"Condition": {
"StringEquals": {
"s3:prefix": ["", "home/"],
"s3:delimiter": ["/"]
}
}
}, {
"Sid": "AllowListForUserPrefix",
"Action": ["s3:ListBucket"],
"Effect": "Allow",
"Resource": ["arn:AWS:s3:::corp-fs-team"],
"Condition": {
"StringLike": {
"s3:prefix": ["home/${AWS:username}/*"]
}
}
}, {
"Sid": "AllowUserFullAccessToUserPrefix",
"Action": ["s3:*"],
"Effect": "Allow",
"Resource": [
"arn:AWS:s3:::corp-fs-team/home/${AWS:username}",
"arn:AWS:s3:::corp-fs-team/home/${AWS:username}/*"
]
}
]
}
以及访问 pub 目录的 IAM policy:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowPublicLevelList",
"Action": ["s3:ListBucket"],
"Effect": "Allow",
"Resource": ["arn:AWS:s3:::corp-fs-team-bucket"],
"Condition": {
"StringLike": {
"s3:prefix": ["pub/*"]
}
}
}, {
"Sid": "AllowUserFullAccessToPublicPrefix",
"Action": ["s3:*"],
"Effect": "Allow",
"Resource": [
"arn:AWS:s3:::corp-fs-team-bucket/pub",
"arn:AWS:s3:::corp-fs-team-bucket/pub/*"
]
}
]
}
具体的 lambda 函数不在本文讨论的范围之内。
除此之外,我们还需要一个类似于 Dropbox 的客户端软件来监控本地目录(S3目录)的更改,以便在合适的时候进行同步。思路如下:
- 客户端软件做成一个开机启动的 daemon。
- 随时监控本地目录
corp-fs-box/*
和 S3 bucket 的修改,并按上述规则同步。
由于涉及的目录都是个人目录,不太会产生冲突(除非同一用户在多个 device 下载没有 sync 的前提下修改同一文件。所以在这里,为简单起见,我们可以不涉及到 diff / merge,简单遵循 last writer wins 进行处理就可以了。另外 S3 自带 versioning,也可以使能这一功能,保存历史版本,在冲突发生的时候,让用户选择。
小结
S3 是一个非常强大的文件服务,如果使用得当,可以带来非常大的收益,建议大家多多深入研究。AWS 的很多服务,如 Elastic Beanstalk,Elastic Transcoder,CloudFormation 实际上都在使用 S3 作为服务的关键一环。