透過 AWS Lambda、API Gateway 和 DynamoDB,可以建構高效能且可擴充套件的無伺服器架構,實作貓圖上傳 API。API Gateway 作為前端入口,接收 HTTP 請求並觸發 Lambda 函式執行,Lambda 函式則負責處理上傳邏輯,並將貓圖資料儲存至 DynamoDB。此架構不僅簡化了後端管理,也提升了應用程式的彈性與可靠度。文章將逐步說明如何使用 AWS SAM 定義和佈署此架構,並提供 Rust 程式碼範例,演示如何處理 HTTP 請求、與 DynamoDB 互動,以及進行測試與佈署。

6.5 完整架構解析

在前一節中,我們建立了一個簡單的Lambda函式,但它還無法直接處理HTTP請求。要實作這一點,我們需要在其前端佈署一個API Gateway REST API。完整的架構如圖6-5所示。

簡單REST API架構圖示

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 簡單REST API架構圖示

rectangle "HTTP請求" as node1
rectangle "觸發Lambda" as node2
rectangle "存取資料函式庫" as node3
rectangle "靜態檔案" as node4
rectangle "自訂網域名稱" as node5

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5

@enduml

此圖示展示了使用者端如何透過API Gateway與Lambda函式互動,並存取DynamoDB資料函式庫。同時,靜態檔案可以儲存在S3儲存桶中,並可選用CloudFront CDN進行內容分發。

架構解析

REST API透過API Gateway提供服務。API Gateway負責處理HTTP連線並為每個請求觸發對應的Lambda函式。如果需要服務兩個API(例如GET /cats和POST /cat),可以為每個API組態一個獨立的Lambda函式。資料函式庫方面,我們將使用AWS DynamoDB。DynamoDB是一種高效能的NoSQL資料函式庫,可以直接透過AWS SDK for Rust從Lambda函式中存取。

此外,我們還有一些前端檔案:HTML、CSS和JavaScript。這些檔案可以儲存在S3儲存桶中並對外提供服務。S3儲存桶是一種物件儲存服務,不僅可以儲存檔案,還可以像靜態網站伺服器一樣透過HTTP提供檔案服務。

API Gateway和S3靜態檔案託管所產生的URL是由AWS自動生成的,因此無法直接自訂。不過,我們可以透過Route53(一個受管理的DNS服務)新增CloudFront CDN並設定自訂網域名稱,從而完全掌控API和靜態檔案所使用的網域名稱。但這部分內容超出了本文的範圍,且與Rust無直接關聯,因此在此不作探討。有興趣的讀者可以參考AWS官方檔案進行設定。

6.6 使用AWS Serverless Application Model (AWS SAM)

透過Web控制檯組態所有這些資源並非易事。很難追蹤實際佈署在生產環境中的內容。如果不小心銷毀了整個堆積疊,也很難從頭開始重建。基礎設施即程式碼(Infrastructure-as-code,簡稱IaC)是一種解決這個問題的方法。你可以透過程式碼定義基礎設施和組態,然後使用你選擇的IaC工具按照你的程式碼組態一切。如果你對定義進行了任何更改,可以透過快速佈署來反映這些更改。因此,你可以像管理程式碼一樣對基礎設施進行版本控制,修復或重建整個堆積疊只需簡單的佈署即可完成。

在本章中,你將使用AWS Serverless Application Model(簡稱AWS SAM)來定義你的基礎設施。AWS SAM不僅管理基礎設施(底層使用AWS CloudFormation的擴充套件),還能協助管理應用的整個生命週期,從測試、封裝Lambda程式碼到日誌記錄等。

設定AWS SAM CLI憑證

SAM CLI需要設定AWS憑證,以便代表你管理AWS資源。你可以按照逐步指示進行設定:https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/prerequisites.html

簡而言之,你需要執行以下步驟:

  1. 建立一個具有程式化存取權的新IAM使用者,並附加AdministratorAccess策略。
  2. 複製新建立的使用者的存取金鑰和秘密存取金鑰。
  3. 安裝AWS CLI V2(如果尚未安裝)以組態憑證。
  4. 使用aws configure命令設定存取金鑰和秘密存取金鑰。

設定指令範例

$ aws configure
AWS Access Key ID [None]: 你的存取金鑰
AWS Secret Access Key [None]: 你的秘密存取金鑰
Default region name [None]: 你預設的區網域名稱
Default output format [None]: 你預設的輸出格式

6.7 建立Catdex無伺服器專案

要建立一個新的專案,可以執行與之前相同的Cargo Lambda命令,但需指定新的專案名稱:catdex-serverless。這次你應該選擇建立一個HTTP函式,並指定將建立一個Amazon API Gateway REST API。

建立專案指令範例

$ cargo lambda new catdex-serverless
> Is this function an HTTP function? y
> Which service is this function receiving events from? Amazon Api Gateway REST Api

建立專案後的目錄結構

.
+-- template.yaml
+-- Cargo.toml
+-- src
| +-- main.rs

現在,你應該有一個包含範例HTTP Lambda函式處理器的專案。Cargo Lambda非常適合用於建立包含Lambda函式的基本專案,但它並不直接設定我們剛才描述的支援架構。

建立基礎設施的第一步是建立template.yaml檔案,即AWS SAM範本,這將是定義無伺服器架構各個組成部分的地方。AWS SAM範本是CloudFormation的擴充套件,CloudFormation是一種AWS服務,允許你透過將基礎設施視為程式碼來建模、提供和管理AWS及第三方資源。

template.yaml範例內容

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  catdex-serverless
  Sample SAM Template for catdex-serverless

內容解密:

  1. AWSTemplateFormatVersion: 指定範本的版本。
  2. Transform: 指定使用AWS SAM的轉換器。
  3. Description: 提供對範本的描述。

這個AWS SAM範本檔案使用AWS SAM規範定義了基礎設施。該範本是AWS CloudFormation範本的擴充套件,具有一些額外的元件,能夠在較低層級的CloudFormation元件上提供更高層級的抽象。

使用 AWS SAM 佈署 Serverless 應用程式

在現代雲端運算中,Serverless 架構因其靈活性、可擴充套件性和成本效益而越來越受到歡迎。AWS SAM(Serverless Application Model)是一種開源框架,簡化了在 AWS 上構建 Serverless 應用程式的過程。本篇文章將探討如何使用 AWS SAM 佈署一個簡單的 Serverless 應用程式,該程式使用 AWS Lambda、Amazon API Gateway 和 Amazon DynamoDB。

範本檔案結構

AWS SAM 使用 YAML 或 JSON 格式的範本檔案來定義 Serverless 應用程式的資源。在本例中,我們使用 template.yaml 檔案來定義我們的應用程式。

Globals:
  Function:
    Timeout: 3

Resources:
  CatTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: cat
        Type: String

  PostCatFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: target/lambda/catdex-serverless/
      Handler: bootstrap
      Runtime: provided.al2
      Architectures: ["arm64"]
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /cat
            Method: post
      Environment:
        Variables:
          TABLE_NAME: !Ref CatTable
      Policies:
        - DynamoDBWritePolicy:
            TableName: !Ref CatTable

Outputs:
  PostApi:
    Description: "API Gateway endpoint URL for Prod stage for Post function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/"
  PostCatFunction:
    Description: "Post Lambda Function ARN"
    Value: !GetAtt PostCatFunction.Arn
  CatTable:
    Description: "DynamoDB table name"
    Value: !GetAtt CatTable.Arn

內容解密:

  1. Globals 部分:定義了所有 Lambda 函式的預設超時時間為 3 秒。
  2. Resources 部分
    • CatTable:定義了一個 Amazon DynamoDB 表,使用 AWS::Serverless::SimpleTable 型別,指定了主鍵 cat 為字串型別。
    • PostCatFunction:定義了一個 AWS Lambda 函式,使用 AWS::Serverless::Function 型別。該函式的程式碼位於 target/lambda/catdex-serverless/,使用自定義執行環境 provided.al2,並且支援 arm64 架構。
      • Events 部分:定義了一個 API Gateway 事件,當收到 POST 請求到 /cat 路徑時觸發該 Lambda 函式。
      • Environment 部分:將 CatTable 的名稱作為環境變數 TABLE_NAME 傳遞給 Lambda 函式。
      • Policies 部分:授予 Lambda 函式寫入 CatTable 的許可權。
  3. Outputs 部分:定義了佈署完成後輸出的資訊,包括 API Gateway 的 URL、Lambda 函式的 ARN 和 DynamoDB 表的 ARN。

使用 AWS SAM CLI 佈署應用程式

  1. 初始化佈署:執行 sam deploy --guided 命令,按照提示填寫相關資訊,完成初始佈署組態。
  2. 生成組態檔案:完成初始佈署後,會生成一個 samconfig.toml 檔案,用於儲存佈署組態,方便後續直接執行 sam deploy 命令進行佈署更新。

samconfig.toml 示例

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "sam-app"
s3_bucket = "<your auto-generated S3 bucket name>"
s3_prefix = "sam-app"
region = "us-east-2"
capabilities = "CAPABILITY_IAM"
image_repositories = []

測試佈署的 Lambda 函式

使用 AWS CLI 測試佈署的 Lambda 函式,首先需要取得 Lambda 函式的 ARN,然後執行以下命令:

aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--payload '{"httpMethod": "POST"}' \
--function-name arn:aws:lambda:us-east-2:003164948199:function:sam-app-PostCatFunction-SpBjt2nLOlpa \
output.json

內容解密:

  1. aws lambda invoke 命令:用於呼叫指定的 Lambda 函式。
  2. --cli-binary-format raw-in-base64-out 引數:指定 CLI 的二進位制格式處理方式。
  3. --payload '{"httpMethod": "POST"}' 引數:指定傳遞給 Lambda 函式的事件資料,模擬一個 HTTP POST 請求。
  4. --function-name 引數:指定要呼叫的 Lambda 函式的 ARN。

使用AWS Lambda和DynamoDB建立無伺服器貓圖上傳API

概述

本章節將介紹如何使用AWS Lambda和DynamoDB建立一個無伺服器的貓圖上傳API。首先,我們會設定基本的基礎設施組態,並測試佈署是否成功。接著,我們將自定義Lambda函式,以允許上傳貓圖到DynamoDB。

基礎設施組態與測試

首先,CLI會呼叫Lambda函式,並傳送一個空的HTTP POST請求。由於我們的Hello World程式碼並未對請求進行任何處理,因此應該會成功並傳回一個回應。回應會被儲存到output.json檔案中。如果一切設定正確,您應該會看到類別似以下的回應:

{
  "statusCode": 200,
  "headers": {
    "content-type": "text/html"
  },
  "multiValueHeaders": {
    "content-type": ["text/html"]
  },
  "body": "Hello AWS Lambda HTTP request",
  "isBase64Encoded": false
}

此時,您已經完成了基本的基礎設施組態,並測試了佈署是否成功。現在可以探討Lambda函式的具體細節。

建立上傳API

接下來,我們將自定義Lambda函式,以允許上傳貓圖到DynamoDB。首先,將src/main.rs移到src/bin/lambda/post-cat.rs,以便支援多個Lambda函式。然後,在src資料夾中建立一個lib.rs檔案。

更新專案結構

更新後的專案結構如下:

.
+-- template.yaml
+-- Cargo.toml
+-- src
    |-- bin
    |   |-- lambda
    |       |-- post-cat.rs
    |-- lib.rs

同時,需要更新Cargo.toml以新增二進位制檔案到專案中:

[[bin]]
name = "post-cat"
path = "src/bin/lambda/post-cat.rs"

新增依賴項

執行以下命令以新增AWS SDK for DynamoDB和serde crate:

cargo add aws_sdk_dynamodb serde

確保使用的版本是0.24.0。同時,也需要新增aws-config crate:

cargo add aws-config

確保版本是0.54.1。

更新template.yaml

template.yaml中的PostCatFunctionCodeUritarget/lambda/catdex-serverless更新為target/lambda/post-cat

Lambda函式實作

post-cat.rs中,我們使用以下程式碼:

use aws_sdk_dynamodb as dynamodb;
use aws_sdk_dynamodb::model::AttributeValue;
use lambda_http::{http::StatusCode, run, service_fn, Body, Error, Request, RequestExt, Response};
use serde::Deserialize;

#[derive(Deserialize)]
struct RequestBody {
    name: String,
}

async fn function_handler(
    request: Request,
    client: &dynamodb::Client,
    table_name: &str,
) -> Result<Response<Body>, Error> {
    #### 內容解密:
    此函式處理Lambda函式的事件,包含以下步驟:
    1. 從請求中提取貓的名字。
    2. 建立一個PutItemRequest,將新的貓新增到資料函式庫中。
    3. 呼叫client.put_item()將專案新增到DynamoDB

    let body: RequestBody = match request.payload() {
        Ok(Some(body)) => body,
        _ => {
            return Ok(Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body("Invalid payload".into())
                .expect("Failed to render response"))
        }
    };

    let dynamo_request = client
        .put_item()
        .table_name(table_name)
        .item("cat", AttributeValue::S(body.name.clone()));
    dynamo_request.send().await?;

    let resp = Response::builder()
        .status(StatusCode::OK)
        .header("content-type", "text/html")
        .body(format!("Added cat {}", body.name).into())
        .map_err(Box::new)?;
    Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    #### 內容解密:
    此函式是Lambda函式的入口點,包含以下步驟:
    1. 初始化tracing_subscriber
    2. 從環境變數中載入AWS組態
    3. 建立一個DynamoDB客戶端
    4. 從環境變數中取得TABLE_NAME

    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .with_target(false)
        .without_time()
        .init();

    let config = aws_config::load_from_env().await;
    let client = dynamodb::Client::new(&config);
    let table_name = std::env::var("TABLE_NAME")?.to_string();

    run(service_fn(|request| {
        function_handler(request, &client, &table_name)
    }))
    .await
}

程式碼說明

此Lambda函式處理HTTP請求,並將貓的名字新增到DynamoDB中。首先,從請求中提取貓的名字,然後建立一個PutItemRequest,將新的貓新增到資料函式庫中。最後,傳回一個成功的HTTP回應。