无服务器图像处理流水线:使用AWS ECS和Lambda
欢迎来到开发和自动化的世界!今天我们将深入探讨一个令人兴奋的项目:使用AWS服务构建无服务器图像处理流水线。
项目从创建S3存储桶开始,用于存储上传的图像和处理后的缩略图,最终使用Lambda、API Gateway(用于触发Lambda函数)、DynamoDB(存储图像元数据)等多种服务,最后通过创建项目的Docker镜像在ECS集群中运行该程序。
这个项目包含了丰富的云服务和开发技术栈,如Next.js,实践这个项目将进一步提升您对云服务及其相互交互方式的理解。事不宜迟,让我们开始吧!
注意:本文中的代码和说明仅用于演示和学习目的。生产环境需要对配置和安全性进行更严格的控制。
先决条件
在开始项目之前,我们需要确保系统满足以下要求:
- AWS账户:由于我们使用AWS服务,需要一个AWS账户。建议配置具有所需服务访问权限的IAM用户
- AWS服务基础理解:需要了解S3(用于存储)、API Gateway(触发Lambda函数)等服务
- Node.js安装:前端使用Next.js构建,因此需要安装Node.js
代码参考请查看GitHub仓库。
AWS服务设置
我们将从设置AWS服务开始项目。首先创建两个S3存储桶:sample-image-uploads-bucket
和sample-thumbnails-bucket
。名称较长的原因是存储桶名称必须在整个AWS工作空间中唯一。
创建存储桶:转到S3仪表板,点击"创建存储桶",选择"通用目的",命名(sample-image-uploads-bucket
),其余配置保持默认。
同样创建另一个名为sample-thumbnails-bucket
的存储桶,但在此存储桶中确保取消选中"阻止公共访问",因为ECS集群需要此权限。
我们需要确保sample-thumbnails-bucket
具有公共读取权限,以便ECS前端可以显示图像。为此,我们将以下策略附加到该存储桶:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::sample-thumbnails-bucket/*"
}
]
}
|
创建存储桶后,让我们转到数据库以存储图像元数据。我们将创建一个DynamoDB表。转到DynamoDB控制台,点击"创建表",命名(image_metadata
),在主键中选择字符串,命名为image_id
。
AWS服务需要相互通信,因此需要具有适当权限的角色。要创建角色,请转到IAM仪表板,选择"角色",点击"创建角色"。在信任身份类型下选择"AWS服务",在使用案例中选择"Lambda"。附加以下策略:
- AmazonS3FullAccess
- AmazonDynamoDBFullAccess
- CloudWatchLogsFullAccess
为此角色命名(Lambda-Image-Processor-Role
)并保存。
创建Lambda函数
我们已经准备好了Lambda角色、存储桶和DynamoDB表,现在让我们创建Lambda函数来处理图像并生成缩略图。由于我们使用Pillow库处理图像,而Lambda默认不提供该库。要解决此问题,我们将在Lambda函数中添加一个层。
转到Lambda仪表板,点击"创建函数"。选择"从头开始创作",选择Python 3.9作为运行时语言,命名:image-processor
。在代码选项卡中,选择"从选项上传",选择zip文件,上传image-processor的zip文件。
转到配置,在权限列下,通过将现有角色更改为我们创建的Lambda-Image-Processor-Role
来编辑配置。
现在转到S3存储桶(sample-image-uploads-bucket
),转到其属性部分,向下滚动到"事件通知",点击"创建事件通知",命名(trigger-image-processor
),在事件类型中选择"PUT",选择我们创建的Lambda函数(image-processor
)。
由于Pillow不随Lambda库内置,我们将执行以下步骤来解决此问题:
转到Lambda函数(image-processor
),向下滚动到"层"部分,点击"添加层"。
在添加层部分,选择"指定ARN"并提供此ARN:arn:aws:lambda:us-east-1:770693421928:layer:Klayers-p39-pillow:1
。根据情况更改区域;我使用us-east-1。添加层。
现在在Lambda函数的代码选项卡中,您将拥有一个lambda_function.py
文件,将以下内容放入其中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
import boto3
import uuid
import os
from PIL import Image
from io import BytesIO
import datetime
s3 = boto3.client('s3')
dynamodb = boto3.client('dynamodb')
UPLOAD_BUCKET = '<YOUR_BUCKET_NAME>' # 确保更改此值
THUMBNAIL_BUCKET = '<YOUR_BUCKET_NAME>' # 确保更改此值
DDB_TABLE = 'image_metadata'
def lambda_handler(event, context):
# 获取上传的图像详情
record = event['Records'][0]
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
# 下载图像
response = s3.get_object(Bucket=bucket, Key=key)
image = Image.open(BytesIO(response['Body'].read()))
# 生成缩略图
image.thumbnail((200, 200))
# 在内存中保存缩略图
thumbnail_buffer = BytesIO()
image.save(thumbnail_buffer, 'JPEG')
thumbnail_buffer.seek(0)
# 上传缩略图到S3
thumbnail_key = f"thumb_{key}"
s3.put_object(
Bucket=THUMBNAIL_BUCKET,
Key=thumbnail_key,
Body=thumbnail_buffer,
ContentType='image/jpeg'
)
# 在DynamoDB中存储元数据
image_id = str(uuid.uuid4())
original_url = f"https://{UPLOAD_BUCKET}.s3.amazonaws.com/{key}"
thumbnail_url = f"https://{THUMBNAIL_BUCKET}.s3.amazonaws.com/{thumbnail_key}"
uploaded_at = datetime.datetime.now().isoformat()
dynamodb.put_item(
TableName=DDB_TABLE,
Item={
'image_id': {'S': image_id},
'original_url': {'S': original_url},
'thumbnail_url': {'S': thumbnail_url},
'uploaded_at': {'S': uploaded_at}
}
)
return {
'statusCode': 200,
'body': f"缩略图已创建: {thumbnail_url}"
}
|
现在,我们需要另一个用于API Gateway的Lambda函数,因为这将作为我们前端ECS应用程序从DynamoDB获取图像数据的入口点。
要创建Lambda函数,请转到Lambda仪表板,点击"创建函数",选择"从头开始创作"和python 3.9作为运行时,命名:get-image-metadata
。在配置中,选择我们分配给其他Lambda函数的相同角色(Lambda-Image-Processor-Role
)。
现在,在函数的代码部分,放入以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import boto3
import json
dynamodb = boto3.client('dynamodb')
TABLE_NAME = 'image_metadata'
def lambda_handler(event, context):
try:
# 扫描整个表(为简单起见,无分页)
response = dynamodb.scan(TableName=TABLE_NAME)
# 将DynamoDB项转换为JSON格式
images = []
for item in response['Items']:
images.append({
'image_id': item['image_id']['S'],
'original_url': item['original_url']['S'],
'thumbnail_url': item['thumbnail_url']['S'],
'uploaded_at': item['uploaded_at']['S']
})
return {
'statusCode': 200,
'headers': {
"Content-Type": "application/json"
},
'body': json.dumps(images)
}
except Exception as e:
return {
'statusCode': 500,
'body': f"错误: {str(e)}"
}
|
创建API Gateway
API Gateway将作为ECS前端应用程序从DynamoDB获取图像数据的入口点。它将连接到查询DynamoDB并返回图像元数据的Lambda函数。网关的URL在我们的前端应用程序中用于显示图像。
要创建API Gateway,请执行以下步骤:
转到AWS管理控制台 → 搜索"API Gateway" → 点击"创建API"。
选择"HTTP API"。
点击"构建"。
API名称:image-gallery-api
添加集成:选择"Lambda"并选择get_image_metadata
函数
选择方法:GET和路径:/images
端点类型:区域
点击"下一步"并创建API Gateway URL。
创建前端应用
为了简单起见,我们将使用Next.js创建一个最小化的简单图库前端,将其Docker化,并部署在ECS上。
初始化
1
2
3
4
|
npx create-next-app@latest image-gallery
cd image-gallery
npm install
npm install axios
|
创建图库组件
创建新文件components/Gallery.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
'use client';
import { useState, useEffect } from 'react';
import axios from 'axios';
import styles from './Gallery.module.css';
const Gallery = () => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchImages = async () => {
try {
const response = await axios.get('https://<YOUR_API_GATEWAY_INVOKE_URL>/images');
setImages(response.data);
setLoading(false);
} catch (error) {
console.error('获取图像时出错:', error);
setLoading(false);
}
};
fetchImages();
}, []);
if (loading) {
return <div className={styles.loading}>加载中...</div>;
}
return (
<div className={styles.gallery}>
{images.map((image) => (
<div key={image.image_id} className={styles.imageCard}>
<img
src={image.thumbnail_url}
alt="图库缩略图"
width={200}
height={150}
className={styles.thumbnail}
/>
<p className={styles.date}>
{new Date(image.uploaded_at).toLocaleDateString()}
</p>
</div>
))}
</div>
);
};
export default Gallery;
|
确保将Gateway-URL更改为您的API_GATEWAY_URL
添加CSS模块
创建components/Gallery.module.css
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.imageCard {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.2s;
}
.imageCard:hover {
transform: scale(1.05);
}
.thumbnail {
width: 100%;
height: 150px;
object-fit: cover;
}
.date {
text-align: center;
padding: 10px;
margin: 0;
font-size: 0.9em;
color: #666;
}
.loading {
text-align: center;
padding: 50px;
font-size: 1.2em;
}
|
更新主页
修改app/page.js
:
1
2
3
4
5
6
7
8
9
10
|
import Gallery from '../components/Gallery';
export default function Home() {
return (
<main>
<h1 style={{ textAlign: 'center', padding: '20px' }}>图像图库</h1>
<Gallery />
</main>
);
}
|
使用Next.js内置的Image组件
要使用Next.js内置的Image组件进行更好的优化,更新next.config.mjs
:
1
2
3
4
5
6
7
8
|
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['sample-thumbnails-bucket.s3.amazonaws.com'],
},
};
export default nextConfig;
|
运行应用程序
在浏览器中访问http://localhost:3000,您将看到应用程序运行并显示所有上传的缩略图。
容器化和创建ECS集群
现在我们几乎完成了项目,我们将继续创建项目的Dockerfile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# 使用官方Node.js镜像作为基础
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制包文件并安装依赖项
COPY package.json package-lock.json ./
RUN npm install
# 复制其余应用程序代码
COPY . .
# 构建Next.js应用
RUN npm run build
# 为生产使用轻量级Node.js镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 从构建阶段复制构建的文件
COPY --from=builder /app ./
# 暴露端口
EXPOSE 3000
# 运行应用程序
CMD ["npm", "start"]
|
现在我们将使用以下命令构建Docker镜像:
1
|
docker build -t sample-nextjs-app .
|
现在我们有了Docker镜像,我们将将其推送到AWS ECR仓库:
步骤1:将Docker镜像推送到Amazon ECR
转到AWS管理控制台 → 搜索"ECR"(弹性容器注册表)→ 打开ECR。
创建新仓库:
- 点击"创建仓库"
- 设置仓库名称(例如
sample-nextjs-app
)
- 选择私有(或根据需要选择公共)
- 点击"创建仓库"
将Docker镜像推送到ECR:
- 在新创建的仓库中,点击"查看推送命令"
- 按照命令:
- 使用ECR验证Docker
- 构建、标记和推送镜像
此步骤需要配置AWS CLI
步骤2:创建ECS集群
1
|
aws ecs create-cluster --cluster-name sample-ecs-cluster
|
步骤3:创建任务定义
在ECS控制台中,转到"任务定义"。
点击"创建新任务定义"。
选择"Fargate" → 点击"下一步"。
设置任务定义详情:
- 名称:
sample-nextjs-task
- 任务角色:
ecsTaskExecutionRole
(如果缺少则创建一个)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "arn:aws:ecr:us-east-1:624448302051:repository/sample-nextjs-app"
}
]
}
|
任务内存和CPU:选择适当的值(例如512MB和256 CPU)。
定义容器:
- 点击"添加容器"
- 容器名称:
sample-nextjs-container
- 镜像URL:粘贴来自步骤1的ECR镜像URI
- 端口映射:为容器和主机端口都设置3000
- 点击"添加"
点击"创建"。
步骤4:创建ECS服务
转到"ECS" → 点击"集群" → 选择您的集群(sample-ecs-cluster
)。
点击"创建服务"。
选择"Fargate" → 点击"下一步"。
设置服务:
- 任务定义:选择
sample-nextjs-task
- 集群:
sample-ecs-cluster
- 服务名称:
sample-nextjs-service
- 任务数量:1(以后可以扩展)
网络设置:
- 选择现有VPC
- 选择公共子网
- 启用自动分配公共IP
点击"下一步" → “创建服务”。
步骤5:访问应用程序
转到ECS > 集群 > sample-ecs-cluster
。
点击"任务"选项卡。
点击运行中的任务。
在网络下找到公共IP。
在浏览器中打开:http://<TASK_PUBLIC_IP>:3000
您的Next.js应用应该已经上线!🚀
结论
这标志着博客的结束。今天,我们深入探讨了许多AWS服务:S3、IAM、ECR、Lambda函数、ECS、Fargate和API Gateway。我们从创建S3存储桶开始项目,最终在ECS集群中部署了我们的应用程序。
在整个指南中,我们涵盖了容器化Next.js应用、将其推送到ECR、配置ECS任务定义以及通过AWS控制台进行部署。这种设置允许自动扩展、轻松更新和安全API访问——这些都是云原生部署的关键优势。
潜在的生产配置可能包括以下更改:
- 实施更严格的IAM权限,改进对S3存储桶公共访问的控制(使用CloudFront、预签名URL或后端代理,而不是使sample-thumbnails-bucket公开)
- 添加错误处理和分页(特别是对于DynamoDB查询)
- 为ECS使用安全的VPC/网络配置(如使用应用程序负载均衡器和私有子网而不是直接公共IP)
- 通过将元数据获取Lambda中的DynamoDB.scan操作替换为DynamoDB.query来解决扩展问题
- 在Next.js代码中使用环境变量而不是硬编码的API网关URL