使用 AWS Lambda、S3 和 CloudFront 实时调整图像大小
我最近一直在(在旁边)重新设计我构建的第一个 SaaS,一个用于罗马尼亚市场的手工市场。在撰写本文时,市场上有近 40,000 个在售的活跃产品,不包括草稿、待批准的产品或已售出的产品。 新设计具有更大的图像,它将显示不同比例的缩略图,并且还利用“srcset”属性来显示响应式图像。 旧的逻辑是这样工作的:用户上传产品照片 > 应用服务器使用 GD 库创建预定义的图像大小 > 上传到 S3 存储桶
我最近一直在(在旁边)重新设计我构建的第一个 SaaS,一个用于罗马尼亚市场的手工市场。在撰写本文时,市场上有近 40,000 个在售的活跃产品,不包括草稿、待批准的产品或已售出的产品。
新设计具有更大的图像,它将显示不同比例的缩略图,并且还利用“srcset”属性来显示响应式图像。
旧的逻辑是这样工作的:用户上传产品照片 > 应用服务器使用 GD 库创建预定义的图像大小 > 上传到 S3 存储桶。它完成了这项工作,但浪费了服务器资源。
大约有 300k 图像需要调整大小,运行批处理将所有图像调整为新尺寸将非常耗时且成本高昂。
为什么要处理和存储用户甚至可能无法访问的资产?我将动态处理它们(懒惰地),只有在用户请求特定大小时才会创建调整大小的图像。
架构概述
1)用户通过 Cloudfront 请求调整大小的图像(如果资产存在,它将被返回,一切都停在这里)
-
Cloudfront 从 S3 存储桶请求资产(使用网站端点)
-
由于资产不存在,浏览器将被临时重定向 (307) 到 API Gateway 端点
-
API Gateway 触发 Lambda 函数
-
Lambda 函数从 S3 存储桶下载原始图像,调整其大小,然后将调整大小的图像上传回具有请求密钥的存储桶
-
API 网关返回一个永久重定向 (301) 到新创建的资产 Cloudfront URL
设置
-
IAM
-
拉姆达
-
API 网关
-
S3
-
CloudFront
IAM用户
我们将首先创建一个 IAM 用户,该用户将允许我们的 Lambda 函数将调整大小的图像放在 S3 上。
1.登录AWS账户,进入Identity & Access Management (IAM)
- 转到用户并单击“添加用户”> 选择一个更容易记住的用户名 > 选中“程序访问”
3.点击“下一步”进入权限页面,直接点击“附加现有策略”,选择AmazonS3FullAccess*
- 查看 API Key & Secret 并将其复制到一个临时位置,我们将需要它们用于 Lambda 配置。
仅使用 AmazonS3FullAccess 策略进行快速演示设置。在生产环境中,我建议遵循最小权限原则并设置更多限制访问。 Lambda 函数只需要我们特定 S3 存储桶的 getObject 和 putObject 权限。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::crafty-products/*"
}
]
}
Lambda 和 API 网关
使用无服务器框架
我正在使用无服务器框架来构建和部署我的服务,但也可以直接从 AWS 控制台完成。
如果您使用的是无服务器,这是 serverless.yml 文件中的 Lambda 函数配置:
# ...
functions:
# on the fly image resizer function
resizer:
handler: src/resizer.handler
memorySize: 1024
timeout: 15
environment:
AWS_KEY: ${self:custom.environment.AWS_KEY}
AWS_SECRET: ${self:custom.environment.AWS_SECRET}
BUCKET: ${self:custom.environment.PRODUCTS_BUCKET}
CDN_URL: ${self:custom.environment.CDN_URL}
events:
- http:
path: /resizer
method: GET
# ...
当服务使用无服务器 (sls deploy
) 部署时,它将创建一个 Lambda 函数和 API 网关,该网关将侦听 GET 请求并触发 Lambda 以调整图像大小。
我在另一篇文章中详细介绍了无服务器配置和部署:如何使用 GitHub Actions在 AWS 上自动部署无服务器应用程序。
使用 AWS 控制台
如果不使用无服务器,一切都可以从 AWS 控制台完成,虽然很麻烦,但步骤如下:
创建 Lambda 函数
- 进入Lambda 函数并点击“创建函数”
2)选择“Author from Scratch”,输入函数名并选择Node runtime(14或更新),创建函数。
3)从 GitHub 下载的 zip,它包含 Lambda 函数
- 在函数的代码选项卡中,点击右上角的“从 zip 上传”并上传函数 zip。请注意,Sharp 模块非常大,因此您将无法在 AWS 控制台中编辑函数,但您可以将 Sharp 添加为Lambda 层并保持从 AWS 控制台编辑器更新函数的能力。
- 在函数的配置选项卡中,我们将添加环境变量
-
AWS_KEY(IAM用户访问密钥)
-
AWS_SECRET(IAM 用户访问密钥)
-
BUCKET(包含图像的 S3 存储桶)
-
CDN_URL (CloudFront URL,我们这一步没有,稍后设置)
- 在函数的配置中,在常规配置选项卡中将内存设置为 1024MB 并将超时设置为至少 15 秒,以便为 Lambda 分配足够的资源和时间来处理更大的图像。默认设置(128MB/3s 超时)可能会失败。
Lambda函数代码
您可以在GitHub 上找到 Lambda 函数代码
它从 S3 存储桶中读取源图像作为流,将其通过管道传输到 Sharp,然后将调整大小的版本作为流写回 S3 存储桶。它在后台使用 Node.js 流来防止在 Lambda 函数的内存中加载大量数据和 Sharp,一个用于图像处理的高性能 Node.js 模块 (sharp.pixelplumbing.com)
该功能具有一组预定义的图像大小,您必须根据需要进行调整或完全删除。目标是通过生成永远不会使用的图像来防止恶意用户增加 AWS 账单。
//
const s3 = new AWS.S3(),
sizes =[
"360x270", // larger thumb restricted
"480h", // height restricted
"640w",
"1280w", // largest web image
];
//
Lambda 函数将通过这样的 GET 请求触发:... ?key=produs_227613/360x270/img.AZ70KuRzw9.jpg
key 查询字符串参数包含 S3 路径、所需的输出大小和原始资产文件名。
尺寸有 3 种类型:
-
宽度 x 高度 - 即 360x270 将生成宽度和高度受限的缩略图 360 像素宽和 270 像素高
-
仅宽度 - 即 640w 将生成 640px 宽的图像,高度将自动调整以保持比例
-
仅高度 - 即 640h 将生成 640px 高的图像,宽度将自动调整以保持比例
创建API网关
1)进入API网关点击“创建API”
2)我们需要一个HTTP API,所以点击“Build”
-
对于集成,选择 Lambda 和我们刚刚创建的函数的名称,然后为新 API 命名并单击“下一步”
-
对于路由,我们将使用 GET 方法和路由“/resize”路径到我们的 Lambda 集成,单击“下一步”
5)我在下面的示例中使用“prod”作为阶段,您可以随意命名阶段
- 创建 API 并将调用 URL 复制到某处,我们将需要它用于 S3
lambda 函数现在将由 Invoke URL + 路由触发,即https://bm8w4yiv1b.execute-api.us-east-1.amazonaws.com/prod/resize
S3
1) 启用“静态网站托管”
打开 AWS 控制台,转到 S3 > 选择托管图像的存储桶 > 属性 > 静态网站托管。
将存储桶网站端点复制到某处,我们将需要它来进行 CloudFront 设置。
2) 重定向规则
这将临时将任何不存在的 S3 对象重定向到将 S3 密钥作为查询字符串传递的 API Gateway URL。确保使用您的更新主机名。
[
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404",
"KeyPrefixEquals": ""
},
"Redirect": {
"HostName": "k28ihy1li5.execute-api.us-east-1.amazonaws.com",
"HttpRedirectCode": "307",
"Protocol": "https",
"ReplaceKeyPrefixWith": "prod/resizer?key="
}
}
]
换句话说,当访问者尝试使用此 URL 加载产品缩略图时:https://d1p41knyaaw8le.cloudfront.net/produs_227613/360x270/img.AZ70KuRzw9.jpg
如果 S3 中不存在缩略图,浏览器将被重定向到 API 网关 URL:https://k28ihy1li5.execute-api.us-east-1.amazonaws.com/prod/resizer?key=produs_227613/360x270/img.AZ70KuRzw9.jpg
3) 桶策略
打开存储桶“权限”选项卡并允许对存储桶中的任何资产执行 GetObject 操作(相应地更新资源 ARN)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::crafty-products/*"
}
]
}
CloudFront
使用 CDN 提供静态资产是一件轻而易举的事,因此我们将创建一个新的 CloudFront 分配(如果我们还没有的话)。将 CloudFront 设置为惰性图像处理时,需要注意 2 个主要事项。
源域
输入 S3 bucket website endpoint URL,不要从下拉列表中选择建议的 S3 选项,应该如下所示:
crafty-products.s3-website-eu-west-1.amazonaws.com
行为
当请求在 S3 中未找到的图像时,将 CloudFront 放在 S3 前面会带来一个额外的挑战:未找到的响应被缓存,用户最终将进入重定向循环。
我们需要为所有将被调整大小的图像类型定义一个行为,在本例中为 jpg、jpeg 和 png。
Lambda 配置
部署 CloudFront 分配后,
-
如果您使用 AWS 控制台创建了 Lambda,则必须返回 Lambda 配置并更新 CDN_URL 环境变量
-
如果你使用 Serverless 更新 CDN_URL 变量并再次部署函数
sls deploy --f resizer
就是这样,它似乎比实际情况要复杂一些。
我写这篇文章是为了说明这种模式并解释动态图像处理的机制。 AWS 提供了一个无服务器图像处理程序解决方案,可以通过 AWS CloudFormation 模板进行部署。
更多推荐
所有评论(0)