在AWS使用Lambda@Edge处理图片的最佳实践

Github

提供了docker环境和简单的图片处理代码;欢迎star、fork或提交pr以改善该实践。仓库:点我

这是什么

类似阿里云OSS的图片处理服务、腾讯云的数据万象。

因为AWS并没有单独将图片处理单独发布服务,只提供了lambda配合CloudFront用于处理这类需求。因此我设计了一套方案用于实现图片处理的需求,本文档包含使用说明、设计方案。

有了本方案,前端可以在原有的cdn图片链接后面拼接参数,获取对应的缩略图、加水印等图片处理需求。

本方案涉及到的几个角色

CloudFront

Amazon的CDN服务,在本方案用于缓存缩略图。下面简称CF

Lambda@edge

Lambda是Amazon的云函数,就是serverless那一套。

Lambda@edge则是可以运行在CloudFront边缘的函数,比Lambda支持的能力要弱一些。例如runtime只支持Node.js和Python;不支持Layer,导致使用的图片库无法部署在layer,只能全部打包到源码。

对图片的处理逻辑在这一层运行,是本方案的重点对象。下面简称Lambda

S3

Amazon的对象存储服务,类似阿里的OSS。在本方案扮演 存储原图、缩略图;cdn回源的角色

工作流程

Image Generate WOrkflow

注意:只有在CF行为规则配置了规则(例如 .jpg@ ),才会触发lambda。直接访问原图的不经过lambda,不必担心过度计费问题。

请参考上图,流程为:

  • 步骤0:用户请求图片链接:链接形如:http://cdn.com/image/abc.jpg@250w_60q.webp。其中 http://cdn.com/image/abc.jpg 是原图链接,@后面是处理参数,具体含义请参看使用手册
  • 步骤1:请求先到达CF,检查CF有缓存则直接返回给用户,并且etag与客户端一致时响应304 Not Modified
  • 步骤2:若CF未命中,则回源到S3获取图片。
  • 步骤3:S3响应后,CF事件会触发给lambda,函数逻辑会对response进行判断:
    • 如果http code为正常值,说明缩略图在S3已存在,那么不进行其它处理,直接将response返回
    • 步骤5:如果http code为异常值,说明S3没有该图,我们需要做以下处理:
      • 将URI从@开始剥离,前面的为原图KEY,后面的为处理参数。我们使用原图KEY从S3桶(CF事件的request链接可以提取到桶名)获取原图,然后使用处理参数对图片进行处理。这里使用sharp图片处理库(底层使用libvips,比ImageMagic性能要好)。
      • 将处理好的图片存储到S3,以便下次CF失效时,直接回源到S3获取。这里存储到S3时,给图片打了标签(createByLambda:1),方便在S3根据标签制定淘汰规则,避免过多缩略图长期存在S3造成浪费。
      • 将图片以base64编码返回到CF,content-encoding为base64。这里之所以使用base64是因为lambda只支持接收json
  • 步骤4:CF将源头的图片存储起来并响应给用户

前端使用说明

url构成:https://cdn域名/文件名@<参数值1><参数名1>_<参数值2><参数名2>.期望转换的文件格式

例子:https://d1xxxxxxxx.cloudfront.net/foamzou/image/4951f0e35a37e5190e78798dcfcad984.jpg@1020w_160h_0e_1l_70q.webp

参数

w: 指定目标缩略图的宽度。1-4096

h: 指定目标缩略图的高度。1-4096

e: 缩放优先边。0:长边优先,1:短边优先,2:强制按指定宽高缩放。默认为0

l: 目标缩略图大于原图是否处理。如果值是1, 即不处理;是0,表示处理。默认为0

p: 比例百分比。 小于100,即是缩小,大于100即是放大。1-1000。如果参数p跟w、h合用时,p将直接作用于w、h (乘以p%)得到新的w、h,例如100w_100h_200p的作用跟200w_200h的效果是一样的。默认为100

q: 质量百分比。1-100。默认为80

r: 是否使用渐进式jpeg。0:不使用,1使用。默认为0。只有在输出格式为jpg时,该参数才会生效。注意:1. 小尺寸图片不建议使用,因为渐进式jpeg会比原图大;2. 能够使用webp尽量使用webp;3. 渐进式展示图片的最佳方案应当是前端同时渲染小图和大图,动画过度到大图

.format 格式转换。目前支持jpg, png, webp。若保留原文件格式,那么  .format 可以不添加

注意

当总面积超过4096px * 4096px,或者单边长度超过4096px * 4,那么不会对图片进行缩放处理

实践

在S3创建一个公有桶

Block权限只勾选前两项

存储桶策略

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::你的桶名/*"
        }
    ]
}

CORS配置

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>http://*.your-domain.com</AllowedOrigin>
    <AllowedOrigin>https://*.your-domain.com</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <ExposeHeader>ETag</ExposeHeader>
    <ExposeHeader>x-amz-meta-custom-header</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

在全新的AWS账号接入这套方案(部署)

注意:必须统一在 us-east-1 地区创建函数

准备工作

  • 拉取最新的lambda代码。仓库:跳转到Github,按照readme指引部署好docker,从docker进入lambda目录,npm install 安装好依赖,将lambda整个目录压缩为zip文件,上传到S3(为了代码安全,建议上传到私有桶), 记好s3文件链接,下面部署要用到
  • 创建一个角色用于运行lambda。该角色需要有以下权限(权限→ 附加策略)
    • AmazonS3FullAccess
    • AWSLambdaReadOnlyAccess
    • AWSLambdaBasicExecutionRole
  • 需要给角色添加信任关系(信任关系→ 编辑信任关系)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "edgelambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

创建Lambda

  1. 打开控制台 → Lambda → 函数 → 创建函数,选择从头开始创作;
  2. 填写信息:基本信息:函数名称填:cdnEdgeImageHandler ;运行时选择 Node.js 12.x,选择角色→ 使用现有角色→ 选择前面创建好的角色;点击【创建函数】按钮
  3. 上传代码:正常情况下会跳转到函数详情页面。“函数代码”这行有个按钮“操作”→ “从AmazonS3上传”,然后粘贴前边上传后返回的zip链接(即使是私有桶链接,也不需要加签名,只要桶属于本账号,Lambda就能够读取)
  4. 设置运行时参数:基本设置→ 编辑,处理程序保持为“index.handler”,内存调到“384MB”,超时时间调为15秒。保存。
  5. 发布新版本:函数详情页顶部“操作”按钮 → 发布新版本 → 描述可选填 → 发布。此时页面刷新后会看到顶部ARN有一串字符串,后面部署需要用到。形如“arn:aws:lambda:us-east-1:016655625412:function:cdnEdgeImageHandler:1”

接入新的CDN业务

  1. 创建CloudFront(在已有的CF上接入请跳过这一步):选择分发方式,选择Web,下一步→ 源域名:选择回源的域名;源路径:没有特别设置起始目录就留空;→ 点击创建按钮
  2. 添加jpg行为:点击到对应CF的详情页→ 行为tab→ 创建行为→ 路径模式:填 /*.jpg@*   ;Lambda函数关联:CF事件选择“源响应”,Lambda函数ARN填写函数详情页顶部的那串字符串(注意字符串最后需要带有 “:版本id”)。 → 点击创建按钮
  3. 如果还有其他图片类型,也需要像步骤2一样添加。或者希望对某一个路径下边的图片统一处理,路径模式填目录名也可以,例如 /images/*
  4. 给对应S3实例添加生命周期规则(使用者可自行选择是否制定该规则):本方案会将生成的缩略图上传到S3,为了不浪费空间,可以定义规则将老文件删除。步骤:打开对应的S3实例配置页→ “管理”tab → 添加生命周期规则 → 填写规则名称“Delete cdn cache” → 规则范围:限制在特定前缀或标签 → 输入框输入“createByLambda”,选择“标签“,输入标签值”1” 。此时会看到下方多出一个元素:标签 createByLambda | 1。→ 下一步:转换不用配置 → 下一步:配置过期:勾选当前版本和先前版本,下面两个天数都修改为10天(可按实际情况自行修改)→ 下一步,保存。

如何更新Lambda

  1. 参考上边“创建Lambda”第3和第5步,发布新版本。
  2. 函数详情页→ 顶部“操作”按钮→ 部署到Lambda@edge → 选择 对此函数使用现有的CloudFront触发器 → 选择触发器(注意这里要选中对应CF实例的事件)→ 点击部署按钮
  3. 过一会检测效果。因为CF部署到全球节点需要点时间。PS:每次发布新版本到生产环境的cdn,建议先部署到测试CDN进行测试。

调试技巧

  1. 先在开发环境调试好。代码仓库里有一份event.json,可用于模拟CF的请求。
  2. 在部署到edge之前,先用lambda控制台里的“测试事件”先进行测试,建议把event.json里的内容粘贴过去作为测试事件。
  3. 利用好console.log,日志会输出到CloudWatch。但是你需要切换到特定的区域才能看到对应请求的日志。(在图片响应头的x-amz-cf-pop可以看到区域标识,可用该标识定位区域。ps. 香港节点比较坑,日志有时会跑到它临近的首尔、新加坡节点去)

相关资料

官方lambda入门demo

官方缩略图实践指南

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇