如何使用 AWS SAM 在 Java 中为 CloudFormation 构建由 Lambda 提供支持的自定义资源?

4 分钟阅读
0

我想使用 Java 构建由 AWS Lambda 提供支持的自定义资源,然后在 AWS CloudFormation 中实现。

简短描述

在 Lambda for 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.    为您的 EC2 实例创建并分配 Amazon EC2 SSH 密钥对

3.    设置一个有权部署堆栈和资源的实例配置文件。具体而言,就是授予权限执行以下操作:

  • 创建 Lambda 函数
  • 更新函数代码
  • 调用函数
  • 创建存储 Lambda 日志的日志组

4.    启动实例后,使用 SSH 登录。在以下部分,使用此实例作为您的开发环境。

设置您的开发环境

**重要事项:**在开始之前,请安装配置 AWS 命令行界面 (AWS CLI)。如果在运行 AWS CLI 命令时收到错误,请确保您使用的是最新版本的 AWS CLI

安装 java-corretto11

要安装 corretto11,请运行以下命令:

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 函数向 CloudFormation 发回一个 SUCCESS 信号作为响应。此 SUCCESS 信号将资源发送给 CreateComplete

当您看到堆栈中的自定义资源创建成功时,则表示 Lambda 也已成功运行。Lambda 将信号返回到堆栈,基于 Java 的自定义资源成功启动。

您可以查看 Lambda 日志,了解有关请求事件和响应事件等指标的更多详细信息。您可以修改函数代码,以指定自己的任务或创建自己的资源。

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