如何使用 AWS SAM 在 Java 中為 CloudFormation 建立 Lambda 支援的自訂資源?

4 分的閱讀內容
0

我要使用 Java 建置 AWS Lambda 支援的自訂支援,以在 AWS CloudFormation 中實施。

簡短說明

在 Lambda 為 CloudFormation 建立 Java 自訂資源的程序複雜,手動操作時更是如此。若要手動設定環境,您必須設定 Java 開發套件和執行階段才能建立資源。然後,您必須將程式碼封裝並上傳至 Amazon Simple Storage Service (Amazon S3),才能建立 Lambda 函數。

若要簡化此程序,您可以使用 AWS Serverless Application Model (AWS SAM)。AWS SAM 是開放原始碼架構,可供您用於在 AWS 上建立無伺服器應用程式。使用此服務協助建立 Java 自訂資源、上傳您的程式碼,以及部署自訂資源和 Lambda 函數。

解決方法

建立用於開發的執行個體

1.    建立 Amazon Elastic Compute Cloud (Amazon EC2) 執行個體以開發您的資源。您可以為執行個體使用任何環境,但最佳實務是為此使用案例在 Amazon Linux 2 中建立執行個體。

2.    建立 Amazon EC2 SSH 金鑰組並指派至您的 EC2 執行個體

3.    設定具有堆疊和資源部署權限的執行個體設定檔。具體來說,授予權限以執行下列動作:

  • 建立 Lambda 函數
  • 更新函數程式碼
  • 調用函數
  • 建立儲存 Lambda 日誌的日誌群組

4.    啟動執行個體後,請使用 SSH 登入。請將此執行個體作為下列區段中的開發環境使用。

設定您的開發環境

**重要事項:**在您開始前,請安裝設定 AWS Command Line Interface (AWS CLI)。如果您在執行 AWS CLI 命令時收到錯誤訊息,請確定您使用的是最新版本的 AWS CLI

安裝 java-corretto11

若要安裝 corretto11,請執行下列命令: command:

sudo rpm --import https://yum.corretto.aws/corretto.key   
sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo  
sudo yum install -y java-11-amazon-corretto-devel

使用下列命令驗證安裝:

java -version

如需詳細資訊,請參閱在 RPM Linux 上安裝 Amazon Corretto 11

安裝 AWS SAM

若要安裝 AWS SAM,請執行下列命令:

wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install
sam --version

如需詳細資訊,請參閱安裝 AWS SAM CLI

安裝 Gradle

若要安裝 Gradle,請執行下列命令:

wget https://services.gradle.org/distributions/gradle-7.6-bin.zip
sudo mkdir /opt/gradle
sudo unzip -d /opt/gradle gradle-7.6-bin.zip
export PATH=$PATH:/opt/gradle/gradle-7.6/bin
gradle -v

如需詳細資訊,請參閱在 Gradle 網站上與封裝管理員一同安裝

**注意:**重新開機後,您可能需要再次匯入 PATH。若要避免此步驟,請將下一行附加至 ~/.bashrc 檔案:

export PATH=$PATH:/opt/gradle/gradle-7.6/bin

建立專案和檔案

若要為 SAM 專案建立根資料夾,請執行下列命令:

mkdir javaBasedCustomResource
cd javaBasedCustomResource/

若要為您的專案建立必要資料夾,請執行下列命令:

mkdir -p src/Function/src/main/java/

透過 Vim 文字編輯器建立專案檔案。如需詳細資訊,請參閱 Vim 網站。複製下列內容以運行於 Vim:

vim template.yaml
vim ./src/Function/build.gradle
vim ./src/Function/src/main/java/Handler.java

您的專案結構類似下列結構樹:

.
└── javaBasedCustomResource
    ├── src
    │   └── Function
    │       ├── build.gradle
    │       └── src
    │           └── main
    │               └── java
    │                   └── Handler.java
    └── template.yaml

請參閱下列區段,了解建立專案的檔案範本。

template.yaml

使用下列範本進行堆疊。這定義了 Lambda 函數、日誌群組及 CustomResource

Transform: AWS::Serverless-2016-10-31
Resources:
  CustomResourceJavaBased:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !Join ["", [ !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:", !Ref CustomResourceLambdaInJava]]
      DummyKey: DummyValue

  CustomResourceLambdaInJava:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: CustomResourceLambdaInJava
      CodeUri: src/Function
      Handler: Handler::handleRequest
      Runtime: java11
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
  CustomResourceLambdaInJavaLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${CustomResourceLambdaInJava}

build.gradle

使用下列範本協助組建您的 Java:

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.amazonaws:aws-lambda-java-core:1.2.2'
    implementation 'com.amazonaws:aws-lambda-java-events:3.11.0'
    implementation 'com.google.code.gson:gson:2.10'

}

task buildZip(type: Zip) {
    from compileJava
    from processResources
    into('lib') {
        from configurations.compileClasspath
    }
}

build.dependsOn buildZip

Handler.java

Lambda 會執行下列程式碼。這會將輸入事件設為對應,讓您能夠取得將回應傳回的必要端點 URL。將所有必須的參數放入 JSON 字串,然後將 HTTP 要求傳送給端點:

import com.google.gson.Gson;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import java.util.*;
import java.io.*;

public class Handler {
    public String handleRequest(Object event) {
    /* Customer workload */
    System.out.println("Dummy CustomResource Job");

    /* sending signal back to CFN stack */
    try {
        sendResponse(event);
    }
    catch (Exception e){
        System.out.println("Got Exception!!");
        e.printStackTrace(System.out);
    }

        return "JobFinished";
    }


    public void sendResponse(Object event) throws IOException, InterruptedException {
    System.out.println("start sending signal");

    Gson gson = new Gson();
    String eventJson = gson.toJson(event);
    Map map = gson.fromJson(eventJson, Map.class);

    System.out.println("Request event: " + eventJson);

    /* Generate response  parameters */
    String putEndpoint = (String)map.get("ResponseURL");

    String Status = "SUCCESS"; // "SUCCESS" or "FAILED"

    String Reason = "Dummy reason for Java based Custom Resource";

    String PhysicalResourceId = "CustomResourcePhysicalID";

    String StackId = (String)map.get("StackId");

    String RequestId = (String)map.get("RequestId");

    String LogicalResourceId = (String)map.get("LogicalResourceId");

    /* Building response */
        String responseJson = "{\"Status\":\"" + Status + "\",\"Reason\":\"" + Reason + "\",\"PhysicalResourceId\":\"" + PhysicalResourceId + "\",\"StackId\":\"" + StackId + "\",\"RequestId\":\"" + RequestId + "\",\"LogicalResourceId\":\"" + LogicalResourceId + "\",\"NoEcho\":false,\"Data\":{\"Key\":\"Value\"}}";

    System.out.println("Response event: " + responseJson);

    var request = HttpRequest.newBuilder()
            .uri(URI.create(putEndpoint))
            .header("Content-Type", "application/json")
            .PUT(HttpRequest.BodyPublishers.ofString(responseJson))
            .build();

        var client = HttpClient.newHttpClient();

    /* Sending Response */
    System.out.println("Sending Response to stack, response code: ");

        var response = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.statusCode());
        System.out.println(response.body());

    System.out.println("Finish sending signal");
    }
}

部署專案

1.    建立 AWS SAM 專案後,請在根專案資料夾 javaBasedCustomResource 下方執行下列命令:

sam build

2.    若要部署專案,請執行下列命令。這會啟動提示您指定堆疊名稱,以及建立資源所在 AWS 區域的指南:

sam deploy --guided

這會使用您指定的名稱在帳戶中部署 CloudFormation 堆疊。該堆疊包含執行先前步驟中程式碼的 Lambda 函數。CloudFormation 也會在相同堆疊中建立自訂資源類型 AWS::CloudFormation::CustomResource

當 CloudFormation 建立 AWS::CloudFormation::CustomResource 時,CloudFormation 也會調用上方 Lambda 函數。在回應中,Lambda 函數會將 SUCCESS 訊號傳回 CloudFormation。這個 SUCCESS 訊號會將資源傳送至 CreateComplete

當您在堆疊中成功看到自訂資源,這表示 Lambda 也已成功執行。Lambda 會將訊號傳回到堆疊,而且會成功啟動 Java 自訂資源。

您可以檢查 Lambda 日誌,了解更多關於要求事件和回應事件等指標的詳細資料。您可以修改函數程式碼,以指定自己的任務或建立自己的資源。

AWS 官方
AWS 官方已更新 1 年前