242 lines
7.2 KiB
Markdown
242 lines
7.2 KiB
Markdown
---
|
|
name: aws-serverless
|
|
description: "Kỹ năng chuyên biệt để xây dựng các ứng dụng serverless sẵn sàng cho production trên AWS. Bao gồm Lambda functions, API Gateway, DynamoDB, các mẫu hướng sự kiện SQS/SNS, triển khai SAM/CDK, và tối ưu hóa cold start."
|
|
source: vibeship-spawner-skills (Apache 2.0)
|
|
---
|
|
|
|
# AWS Serverless
|
|
|
|
## Các Mẫu (Patterns)
|
|
|
|
### Mẫu Lambda Handler
|
|
|
|
Cấu trúc Lambda function đúng chuẩn với xử lý lỗi.
|
|
|
|
**Khi nào dùng**: ['Bất kỳ triển khai Lambda function nào', 'API handlers, bộ xử lý sự kiện, tác vụ định kỳ']
|
|
|
|
```javascript
|
|
// Node.js Lambda Handler
|
|
// handler.js
|
|
|
|
// Khởi tạo bên ngoài handler (tái sử dụng qua các lần gọi)
|
|
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
|
|
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');
|
|
|
|
const client = new DynamoDBClient({});
|
|
const docClient = DynamoDBDocumentClient.from(client);
|
|
|
|
// Hàm Handler
|
|
exports.handler = async (event, context) => {
|
|
// Tùy chọn: Không đợi event loop trống (Node.js) có thể giúp function kết thúc sớm hơn
|
|
context.callbackWaitsForEmptyEventLoop = false;
|
|
|
|
try {
|
|
// Parse input dựa trên nguồn sự kiện
|
|
const body = typeof event.body === 'string'
|
|
? JSON.parse(event.body)
|
|
: event.body;
|
|
|
|
// Logic nghiệp vụ
|
|
const result = await processRequest(body);
|
|
|
|
// Trả về phản hồi tương thích API Gateway
|
|
return {
|
|
statusCode: 200,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': '*'
|
|
},
|
|
body: JSON.stringify(result)
|
|
};
|
|
} catch (error) {
|
|
console.error('Error:', JSON.stringify({
|
|
error: error.message,
|
|
stack: error.stack,
|
|
requestId: context.awsRequestId
|
|
}));
|
|
|
|
return {
|
|
statusCode: error.statusCode || 500,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
error: error.message || 'Internal server error'
|
|
})
|
|
};
|
|
}
|
|
};
|
|
|
|
async function processRequest(data) {
|
|
// Logic nghiệp vụ của bạn ở đây
|
|
const result = await docClient.send(new GetCommand({
|
|
TableName: process.env.TABLE_NAME,
|
|
Key: { id: data.id }
|
|
}));
|
|
return result.Item;
|
|
}
|
|
```
|
|
|
|
### Mẫu Tích hợp API Gateway
|
|
|
|
Tích hợp REST API và HTTP API với Lambda.
|
|
|
|
**Khi nào dùng**: ['Xây dựng REST APIs bằng Lambda', 'Cần các HTTP endpoints cho functions']
|
|
|
|
```yaml
|
|
# template.yaml (SAM)
|
|
AWSTemplateFormatVersion: '2010-09-09'
|
|
Transform: AWS::Serverless-2016-10-31
|
|
|
|
Globals:
|
|
Function:
|
|
Runtime: nodejs20.x
|
|
Timeout: 30
|
|
MemorySize: 256
|
|
Environment:
|
|
Variables:
|
|
TABLE_NAME: !Ref ItemsTable
|
|
|
|
Resources:
|
|
# HTTP API (khuyên dùng cho các trường hợp đơn giản, hiệu năng cao, rẻ hơn)
|
|
HttpApi:
|
|
Type: AWS::Serverless::HttpApi
|
|
Properties:
|
|
StageName: prod
|
|
CorsConfiguration:
|
|
AllowOrigins:
|
|
- "*"
|
|
AllowMethods:
|
|
- GET
|
|
- POST
|
|
- DELETE
|
|
AllowHeaders:
|
|
- "*"
|
|
|
|
# Lambda Functions
|
|
GetItemFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
Handler: src/handlers/get.handler
|
|
Events:
|
|
GetItem:
|
|
Type: HttpApi
|
|
Properties:
|
|
ApiId: !Ref HttpApi
|
|
Path: /items/{id}
|
|
Method: GET
|
|
Policies:
|
|
- DynamoDBReadPolicy:
|
|
TableName: !Ref ItemsTable
|
|
|
|
CreateItemFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
Handler: src/handlers/create.handler
|
|
Events:
|
|
CreateItem:
|
|
Type: HttpApi
|
|
Properties:
|
|
ApiId: !Ref HttpApi
|
|
Path: /items
|
|
Method: POST
|
|
Policies:
|
|
- DynamoDBCrudPolicy:
|
|
TableName: !Ref ItemsTable
|
|
|
|
# DynamoDB Table
|
|
ItemsTable:
|
|
Type: AWS::DynamoDB::Table
|
|
Properties:
|
|
AttributeDefinitions:
|
|
- AttributeName: id
|
|
AttributeType: S
|
|
KeySchema:
|
|
- AttributeName: id
|
|
KeyType: HASH
|
|
BillingMode: PAY_PER_REQUEST
|
|
|
|
Outputs:
|
|
ApiUrl:
|
|
Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com/prod"
|
|
```
|
|
|
|
### Mẫu SQS Hướng Sự Kiện (Event-Driven SQS Pattern)
|
|
|
|
Lambda được kích hoạt bởi SQS để xử lý không đồng bộ tin cậy.
|
|
|
|
**Khi nào dùng**: ['Xử lý không đồng bộ, tách biệt (decoupled)', 'Cần logic thử lại và Dead Letter Queue (DLQ)', 'Xử lý tin nhắn theo lô (batches)']
|
|
|
|
```yaml
|
|
# template.yaml
|
|
Resources:
|
|
ProcessorFunction:
|
|
Type: AWS::Serverless::Function
|
|
Properties:
|
|
Handler: src/handlers/processor.handler
|
|
Events:
|
|
SQSEvent:
|
|
Type: SQS
|
|
Properties:
|
|
Queue: !GetAtt ProcessingQueue.Arn
|
|
BatchSize: 10
|
|
FunctionResponseTypes:
|
|
- ReportBatchItemFailures # Xử lý thất bại từng phần trong lô
|
|
```
|
|
|
|
```javascript
|
|
// src/handlers/processor.js
|
|
exports.handler = async (event) => {
|
|
const batchItemFailures = [];
|
|
|
|
for (const record of event.Records) {
|
|
try {
|
|
const body = JSON.parse(record.body);
|
|
await processMessage(body);
|
|
} catch (error) {
|
|
console.error(`Failed to process message ${record.messageId}:`, error);
|
|
// Báo cáo item này bị lỗi (để SQS thử lại chỉ item này)
|
|
batchItemFailures.push({
|
|
itemIdentifier: record.messageId
|
|
});
|
|
}
|
|
}
|
|
|
|
// Trả về các item thất bại để thử lại
|
|
return { batchItemFailures };
|
|
};
|
|
```
|
|
|
|
## Anti-Patterns (Nên tránh)
|
|
|
|
### ❌ Monolithic Lambda
|
|
|
|
**Tại sao tệ**: Gói triển khai lớn gây cold start chậm.
|
|
Khó mở rộng các hoạt động riêng lẻ.
|
|
Cập nhật ảnh hưởng đến toàn bộ hệ thống.
|
|
|
|
**Thay vào đó**: Chia nhỏ function theo trách nhiệm (Single Reponsibility).
|
|
|
|
### ❌ Phụ thuộc Lớn (Large Dependencies)
|
|
|
|
**Tại sao tệ**: Tăng kích thước gói triển khai.
|
|
Làm chậm cold start đáng kể.
|
|
Hầu hết SDK/thư viện có thể không được sử dụng.
|
|
|
|
**Thay vào đó**: Dùng esbuild/webpack để tree-shaking, chỉ import client cần thiết từ AWS SDK v3.
|
|
|
|
### ❌ Gọi Đồng bộ trong VPC
|
|
|
|
**Tại sao tệ**: Lambdas gắn VPC có overhead thiết lập ENI (dù đã được cải thiện).
|
|
Tra cứu DNS hoặc kết nối bị chặn làm tồi tệ thêm cold start.
|
|
|
|
## ⚠️ Các Cạnh Sắc (Rủi ro)
|
|
|
|
| Vấn đề | Mức độ nghiêm trọng | Giải pháp |
|
|
|-------|----------|----------|
|
|
| Cold start quá lâu | cao | ## Đo lường pha INIT, sử dụng Provisioned Concurrency nếu cần |
|
|
| Function bị timeout | cao | ## Đặt timeout phù hợp (mặc định 3s là quá ngắn cho nhiều tác vụ) |
|
|
| Hết bộ nhớ (OOM) | cao | ## Tăng dung lượng bộ nhớ (cũng tăng CPU) |
|
|
| Lỗi kết nối mạng trong VPC | trung bình | ## Xác minh cấu hình VPC, Subnet và Security Group |
|
|
| Connection pool bị cạn kiệt | trung bình | ## Sử dụng `callbackWaitsForEmptyEventLoop = false` và tái sử dụng kết nối |
|
|
| Lỗi upload file lớn qua API Gateway | trung bình | ## Sử dụng S3 Presigned URLs cho upload trực tiếp |
|
|
| Đệ quy vô hạn (Infinite Loop) | cao | ## Sử dụng bucket/prefix khác nhau cho trigger S3 |
|