使用无服务器应用程序模型 (SAM) 部署到 AWS Lambda
本指南说明了如何使用 AWS 无服务器应用程序模型 (SAM) 工具包在 AWS 上部署服务器端 Swift 工作负载。该工作负载是一个用于跟踪待办事项列表的 REST API。它使用 Amazon API Gateway 部署 API。API 方法使用 AWS Lambda 函数在 Amazon DynamoDB 数据库中存储和检索数据。
架构
- Amazon API Gateway 接收 API 请求
- API Gateway 调用 Lambda 函数来处理 PUT 和 GET 事件
- Lambda 函数使用 AWS SDK for Swift 和 Swift AWS Lambda Runtime 来检索和保存项目到数据库
先决条件
要构建此示例应用程序,您需要
- AWS 账户
- AWS 命令行界面 (AWS CLI) - 安装 CLI 并使用您的 AWS 账户凭证进行配置
- AWS SAM CLI - 一种用于在 AWS 上创建无服务器工作负载的命令行工具
- Docker Desktop - 用于将您的 Swift 代码编译成 Docker 镜像
步骤 1:创建一个新的 SAM 项目
SAM 项目在您的 AWS 账户中创建资源(Lambda 函数、API Gateway 和 DynamoDB 表)。您在 YAML 模板中定义资源。
为您的项目创建一个文件夹,并新建一个 template.yml 文件。
mkdir swift-lambda-api && cd swift-lambda-api
touch template.yml
打开 template.yml 文件并添加以下代码。查看代码中的注释以确定每个部分创建的内容。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
# DynamoDB table to store your data
SwiftAPITable:
Type: AWS::Serverless::SimpleTable
Properties:
PrimaryKey:
Name: id
Type: String
# Lambda function to put items to the database
PutItemFunction:
Type: AWS::Serverless::Function
Properties:
# package the function as a Docker image
PackageType: Image
Policies:
# allow function to read and write to database table
- DynamoDBCrudPolicy:
TableName: !Ref SwiftAPITable
Environment:
# store database table name as an environment variable
Variables:
TABLE_NAME: !Ref SwiftAPITable
Events:
# handles the POST /item method of the REST API
Api:
Type: HttpApi
Properties:
Method: post
Path: /item
Metadata:
# location of the code and Docker file for function
DockerContext: ./src/put-item
Dockerfile: Dockerfile
DockerBuildArgs:
TARGET_NAME: put-item
# Lambda function to retrieve items from database
GetItemsFunction:
Type: AWS::Serverless::Function
Properties:
# package the function as a Docker image
PackageType: Image
Policies:
# allow function to read and write to database table
- DynamoDBCrudPolicy:
TableName: !Ref SwiftAPITable
Environment:
# store database table name as an environment variable
Variables:
TABLE_NAME: !Ref SwiftAPITable
Events:
# handles the GET /items method of the REST API
Api:
Type: HttpApi
Properties:
Method: get
Path: /items
Metadata:
# location of the code and Docker file for function
DockerContext: ./src/get-items
Dockerfile: Dockerfile
DockerBuildArgs:
TARGET_NAME: get-items
# print API endpoint and name of database table
Outputs:
SwiftAPIEndpoint:
Description: "API Gateway endpoint URL for your application"
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"
SwiftAPITable:
Description: "DynamoDB Table Name"
Value: !Ref SwiftAPITable
步骤 2:使用 SwiftPM 初始化 Lambda 函数
Lambda 函数用 Swift 编写,用于处理 API 事件。PutItem 函数处理 POST 请求以将项目添加到数据库。GetItems 函数处理 GET 请求以从数据库检索项目。
使用 Swift Package Manager 为每个函数初始化一个项目。您还需要向每个文件夹添加一个 Dockerfile。
mkdir -p src/put-item
cd src/put-item
swift package init --type executable
touch Dockerfile
cd ../..
mkdir -p src/get-items
cd src/get-items
swift package init --type executable
touch Dockerfile
步骤 3:更新 Dockerfile
Docker 用于编译您的 Swift 代码并将镜像部署到 Lambda。将以下代码复制到您在每个函数的文件夹中创建的 Dockerfile 中。
# image used to compile your Swift code
FROM --platform=linux/amd64 public.ecr.aws/docker/library/swift:5.7.2-amazonlinux2 as builder
ARG TARGET_NAME
RUN yum -y install git jq tar zip openssl-devel
WORKDIR /build-lambda
RUN mkdir -p /Sources/$TARGET_NAME/
RUN mkdir -p /Tests/$TARGET_NAME/
ADD /Sources/ ./Sources/
ADD /Tests/ ./Tests/
COPY Package.swift .
RUN cd /build-lambda && swift package clean && swift build --static-swift-stdlib -c release
# image deplpoyed to AWS Lambda with your compiled executable
FROM public.ecr.aws/lambda/provided:al2-x86_64
ARG TARGET_NAME
RUN mkdir -p /var/task/
RUN mkdir -p /var/runtime/
COPY --from=builder /build-lambda/.build/release/$TARGET_NAME /var/task/lambdaExec
RUN chmod 755 /var/task/lambdaExec
RUN ln -s /var/task/lambdaExec /var/runtime/bootstrap
RUN chmod 755 /var/runtime/bootstrap
WORKDIR /var/task
CMD ["/var/task/lambdaExec"]
步骤 4:更新 Swift 依赖项
您的项目需要 3 个库。
- swift-aws-lambda-runtime
- swift-aws-lambda-events
- aws-sdk-swift
您在 Package.swift 文件中定义这些库。将每个函数文件夹中的 Package.swift 文件的内容替换为以下代码。
src/put-item/Sources/put-item/Package.swift
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "put-item",
platforms: [.macOS(.v12)],
dependencies: [
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime", branch: "main"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events", branch: "main"),
.package(url: "https://github.com/awslabs/aws-sdk-swift", from: "0.9.1")
],
targets: [
.executableTarget(
name: "put-item",
dependencies: [
.product(name: "AWSLambdaRuntime",package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "AWSDynamoDB", package: "aws-sdk-swift")
]),
.testTarget(
name: "put-itemTests",
dependencies: ["put-item"]),
]
)
src/get-items/Sources/get-items/Package.swift
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "get-items",
platforms: [.macOS(.v12)],
dependencies: [
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime", branch: "main"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events", branch: "main"),
.package(url: "https://github.com/awslabs/aws-sdk-swift", from: "0.9.1")
],
targets: [
.executableTarget(
name: "get-items",
dependencies: [
.product(name: "AWSLambdaRuntime",package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "AWSDynamoDB", package: "aws-sdk-swift")
]),
.testTarget(
name: "get-itemsTests",
dependencies: ["get-items"]),
]
)
步骤 5:更新 Lambda 函数源代码
将每个 Swift 项目的主代码文件的内容替换为以下代码。
src/put-item/Sources/put-item/put_item.swift
// import the packages required by our function
import Foundation
import AWSLambdaRuntime
import AWSLambdaEvents
import AWSDynamoDB
// define Codable struct for function response
struct Item : Codable {
var id: String?
let itemName: String
}
enum FunctionError: Error {
case envError
}
@main
struct PutItemFunction: SimpleLambdaHandler {
// Lambda Function handler
func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws -> Item {
print("event received:\(event)")
// create a client to interact with DynamoDB
let client = try await DynamoDBClient()
// obtain DynamoDB table name from function's environment variables
guard let tableName = ProcessInfo.processInfo.environment["TABLE_NAME"] else {
throw FunctionError.envError
}
// decode data from APIGateway POST into a codable struct
var item = try JSONDecoder().decode(
Item.self,
from: event.body!.data(using: .utf8)!
)
// generate a unique id for the key of the item
item.id = UUID().uuidString
// use SDK to put the item into the database and return the item with key value
let input = PutItemInput(item: ["id": .s(item.id!), "itemName": .s(item.itemName)], tableName: tableName)
_ = try await client.putItem(input: input)
return item
}
}
src/get-items/Sources/get_items/get_items.swift
// import the packages required by our function
import Foundation
import AWSLambdaRuntime
import AWSLambdaEvents
import AWSDynamoDB
// define Codable struct for function response
struct Item : Codable {
var id: String = ""
var itemName: String = ""
}
enum FunctionError: Error {
case envError
}
@main
struct GetItemsFunction: SimpleLambdaHandler {
// Lambda Function handler
func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws -> [Item] {
print("event received:\(event)")
// create a client to interact with DynamoDB
let client = try await DynamoDBClient()
// obtain DynamoDB table name from function's environment variables
guard let tableName = ProcessInfo.processInfo.environment["TABLE_NAME"] else {
throw FunctionError.envError
}
// use SDK to retrieve items from table
let input = ScanInput(tableName: tableName)
let response = try await client.scan(input: input)
// return items in an array
return response.items!.map() {i in
var item = Item()
if case .s(let value) = i["id"] {
item.id = value
}
if case .s(let value) = i["itemName"] {
item.itemName = value
}
return item
}
}
}
步骤 6:构建 SAM 项目
构建您的 SAM 项目使用您机器上的 Docker 将您的 Swift 代码编译成 Docker 镜像。从您的项目根文件夹 (swift-lambda-api) 运行以下命令。
sam build
步骤 7:部署 SAM 项目
部署您的 SAM 项目会在您的 AWS 账户中创建 Lambda 函数、API Gateway 和 DynamoDB 数据库。
sam deploy --guided
接受对每个提示的默认响应,以下两个提示除外
PutItemFunction may not have authorization defined, Is this okay? [y/N]: y
GetItemsFunction may not have authorization defined, Is this okay? [y/N]: y
该项目创建了一个公开访问的 API 端点。这些警告是为了通知您 API 没有授权。如果您有兴趣向 API 添加授权,请参阅 SAM 文档。
步骤 8:使用您的 API
在部署结束时,SAM 会显示您的 API Gateway 的端点
Outputs
----------------------------------------------------------------------------------------
Key SwiftAPIEndpoint
Description API Gateway endpoint URL for your application
Value https://[your-api-id].execute-api.[your-aws-region].amazonaws.com
----------------------------------------------------------------------------------------
使用 cURL 或 Postman 等工具与您的 API 交互。将 [your-api-endpoint] 替换为部署输出中的 SwiftAPIEndpoint 值。
添加一个待办事项列表项
curl --request POST 'https://[your-api-endpoint]/item' --header 'Content-Type: application/json' --data-raw '{"itemName": "my todo item"}'
检索待办事项列表项
curl https://[your-api-endpoint]/items
清理
当完成您的应用程序后,使用 SAM 将其从您的 AWS 账户中删除。对所有提示回答 是 (y)。
sam delete