我最近一直在(在旁边)重新设计我构建的第一个 SaaS,一个用于罗马尼亚市场的手工市场。在撰写本文时,市场上有近 40,000 个在售的活跃产品,不包括草稿、待批准的产品或已售出的产品。

新设计具有更大的图像,它将显示不同比例的缩略图,并且还利用“srcset”属性来显示响应式图像。

旧的逻辑是这样工作的:用户上传产品照片 > 应用服务器使用 GD 库创建预定义的图像大小 > 上传到 S3 存储桶。它完成了这项工作,但浪费了服务器资源。

大约有 300k 图像需要调整大小,运行批处理将所有图像调整为新尺寸将非常耗时且成本高昂。

为什么要处理和存储用户甚至可能无法访问的资产?我将动态处理它们(懒惰地),只有在用户请求特定大小时才会创建调整大小的图像。

架构概述

aws-resizer-diagram.png

1)用户通过 Cloudfront 请求调整大小的图像(如果资产存在,它将被返回,一切都停在这里)

  1. Cloudfront 从 S3 存储桶请求资产(使用网站端点)

  2. 由于资产不存在,浏览器将被临时重定向 (307) 到 API Gateway 端点

  3. API Gateway 触发 Lambda 函数

  4. Lambda 函数从 S3 存储桶下载原始图像,调整其大小,然后将调整大小的图像上传回具有请求密钥的存储桶

  5. API 网关返回一个永久重定向 (301) 到新创建的资产 Cloudfront URL

设置

  • IAM

  • 拉姆达

  • API 网关

  • S3

  • CloudFront

IAM用户

我们将首先创建一个 IAM 用户,该用户将允许我们的 Lambda 函数将调整大小的图像放在 S3 上。

1.登录AWS账户,进入Identity & Access Management (IAM)

  1. 转到用户并单击“添加用户”> 选择一个更容易记住的用户名 > 选中“程序访问”截图 2021-08-10 at 11.07.40.png

3.点击“下一步”进入权限页面,直接点击“附加现有策略”,选择AmazonS3FullAccess*截图 2021-08-10 at 11.07.58.png

  1. 查看 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 函数

  1. 进入Lambda 函数并点击“创建函数”

2)选择“Author from Scratch”,输入函数名并选择Node runtime(14或更新),创建函数。

3)从 GitHub 下载的 zip,它包含 Lambda 函数

  1. 在函数的代码选项卡中,点击右上角的“从 zip 上传”并上传函数 zip。请注意,Sharp 模块非常大,因此您将无法在 AWS 控制台中编辑函数,但您可以将 Sharp 添加为Lambda 层并保持从 AWS 控制台编辑器更新函数的能力。

截图 2021-08-10 at 11.51.56.png

  1. 在函数的配置选项卡中,我们将添加环境变量
  • AWS_KEY(IAM用户访问密钥)

  • AWS_SECRET(IAM 用户访问密钥)

  • BUCKET(包含图像的 S3 存储桶)

  • CDN_URL (CloudFront URL,我们这一步没有,稍后设置)

截图 2021-08-10 at 12.06.24.png

  1. 在函数的配置中,在常规配置选项卡中将内存设置为 1024MB 并将超时设置为至少 15 秒,以便为 Lambda 分配足够的资源和时间来处理更大的图像。默认设置(128MB/3s 超时)可能会失败。

截图 2021-08-10 at 12.21.02.png

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”

  1. 对于集成,选择 Lambda 和我们刚刚创建的函数的名称,然后为新 API 命名并单击“下一步”截图 2021-08-10 at 11.57.04.png

  2. 对于路由,我们将使用 GET 方法和路由“/resize”路径到我们的 Lambda 集成,单击“下一步”截图 2021-08-10 at 11.57.51.png

5)我在下面的示例中使用“prod”作为阶段,您可以随意命名阶段截图 2021-08-10 at 11.59.49.png

  1. 创建 API 并将调用 URL 复制到某处,我们将需要它用于 S3 截图 2021-08-10 at 12.02.40.png

lambda 函数现在将由 Invoke URL + 路由触发,即https://bm8w4yiv1b.execute-api.us-east-1.amazonaws.com/prod/resize

S3

1) 启用“静态网站托管”

打开 AWS 控制台,转到 S3 > 选择托管图像的存储桶 > 属性 > 静态网站托管。

将存储桶网站端点复制到某处,我们将需要它来进行 CloudFront 设置。

截图 2021-08-10 at 10.03.58.png

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。

截图 2021-08-10 at 10.48.05.png

Lambda 配置

部署 CloudFront 分配后,

  • 如果您使用 AWS 控制台创建了 Lambda,则必须返回 Lambda 配置并更新 CDN_URL 环境变量

  • 如果你使用 Serverless 更新 CDN_URL 变量并再次部署函数sls deploy --f resizer

就是这样,它似乎比实际情况要复杂一些。

我写这篇文章是为了说明这种模式并解释动态图像处理的机制。 AWS 提供了一个无服务器图像处理程序解决方案,可以通过 AWS CloudFormation 模板进行部署。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐