部署与容错

将 Vert.x 原生函数部署到 AWS Lambda

本操作指南将向您展示如何部署由 AWS Lambda 提供服务的基于 Vert.x 的原生镜像函数

您将构建什么

  • 您将编写一个函数,该函数接收并返回每日名言(QOTD,类似于 UNIX 工具)。

  • 此函数将用 Java 编写。

  • 该代码将借助 GraalVM 编译为原生可执行文件。

  • 将使用 AWS 命令行界面 创建一个函数。

  • 该函数将部署到 AWS Lambda

您需要什么

  • 文本编辑器或 IDE

  • GraalVM (>= 1.0.0-rc13)

  • Maven

  • AWS CLI 工具

  • 用于部署函数的 AWS 账户。

什么是 AWS Lambda 函数,以及为什么 Vert.x 是一个很好的选择?

AWS Lambda 是一项服务,它负责执行您的代码,而无需您了解服务器环境。它被称为无服务器计算。代码的执行基于 AWS 服务中的事件响应,例如在 S3 存储桶中添加/删除文件、更新 Amazon DynamoDB 表、来自 Amazon API Gateway 的 HTTP 请求等。

可以使用 Amazon SDK 用 Java 编写 AWS Lambda 代码。然而,由于 JVM 的特性以及典型的 Java 框架启动时间长,这可能会成为函数性能的巨大瓶颈。为了解决这个问题,Java 函数通常会在 Java 服务器容器中运行,等待请求(这违背了无服务器的整个理念)。

Vert.x 和 GraalVM 应运而生。Vert.x 非常适合在 GraalVM 原生镜像上编写函数,因为

  1. Vert.x 应用程序启动非常快,因为在运行时没有“魔法”发生,

  2. GraalVM 用于编译以进一步减少启动时间和内存占用,

  3. Vert.x 应用程序资源效率高,即使在高负载下也能保持响应,

  4. Vert.x 提供了庞大的响应式客户端生态系统,用于与其他中间件(数据库、消息传递等)交互,

  5. 单个函数式接口实现就足以引导 Vert.x 应用程序!

创建项目

首先克隆以下 GitHub 项目:https://github.com/pmlopes/aws-lambda-native-vertx

接下来,我们来了解项目的重要部分。以下是您应该使用的 pom.xml 文件的内容

1 我们需要 svm-driver 来处理与 SubstrateVM 的代码不兼容性。
2 我们需要 vertx-webclient 来与 Lambda 环境交互。
3 GraalVM 配置,用于生成镜像。

编写函数

函数接收传入的 HTTP 头和 HTTP 正文。随机选择一个每日名言。对于每个请求,发送随机名言。

/*
 * Copyright 2019 Paulo Lopes.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  https://open-source.org.cn/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */
package lambda;

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import vertx.lambda.Lambda;

import java.util.Random;

/**
 * A simple QOTD Lambda
 */
public class QOTDLambda implements Lambda {

  private static final Random RANDOM = new Random();

  private static final String[] QUOTES = {    (1)
    "If you find a random command on the internet, try running it as root. What's the worse that could happen?",
    "Can root create a process that even root can't kill?",
    "There's no place like ~",
    "Thou shalt not kill -9.",
    "The 'n' in unmount is missing, leaving it as umount! If you find it, please write to Bell Labs, 600 Mountain Avenue Murray Hill, NJ.",
    "echo \"Just another useless use of cat.\" | cat",
    "`echo \"Just another useless use of backticks.\"`",
    "We've just created a special character device for handling complaints, conveniently located at /dev/null.",
    "false - do nothing, unsuccessfully"
  };

  @Override
  public
  Future<Buffer>  (2)
  call(Vertx vertx, MultiMap headers, Buffer body) {    (3)
    // return a random qotd
    return Future.succeededFuture(Buffer.buffer(QUOTES[RANDOM.nextInt(QUOTES.length)]));  (4)
  }
}
1 我们声明一个名言数组。
2 实现 Lambda 接口,该接口返回一个 Future 以允许异步处理。
3 该实现接收当前的 Vertx 实例以及请求头和请求正文。
4 对于每个请求,我们返回一个包含随机名言的已解析 Future。

测试函数

/*
 * Copyright 2019 Paulo Lopes.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  https://open-source.org.cn/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */
package lambda;

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.RunTestOnContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(VertxUnitRunner.class)
public class QOTDLambdaTest {

  private final QOTDLambda fn = new QOTDLambda();

  @Rule
  public RunTestOnContext rule = new RunTestOnContext();

  @Test
  public void shouldGetAQOTD(TestContext should) {
    final Async test = should.async();

    Future<Buffer> fut = fn.call(rule.vertx(), MultiMap.caseInsensitiveMultiMap(), null);   (1)

    fut.setHandler(call -> {
      if (call.failed()) {
        should.fail(call.cause());  (2)
      } else {
        should.assertNotNull(call.result());  (3)
        should.assertTrue(call.result().length() > 0);
        test.complete();  (4)
      }
    });
  }

}
1 使用空的头和正文调用函数。
2 如果调用失败,则测试失败。
3 验证是否存在名言。
4 终止测试。

我们可以轻松测试函数是否正常工作:

  1. 在您的 IDE 中运行 junit 测试,或者

  2. 使用 Maven:mvn test

准备您的 AWS 开发环境

创建 Lambda 角色

在部署到 AWS 之前,您需要一个能够访问该服务的角色。为此,请创建一个临时 JSON 文件 (/tmp/trust-policy.json),内容如下:

并使用以下命令部署:

$ aws iam create-role \
    --role-name lambda-role \
    --path "/service-role/" \
    --assume-role-policy-document file:///tmp/trust-policy.json

构建函数

构建函数即运行常规的 Maven 命令:

$ mvn clean package
$ zip -r function.zip bootstrap target/lambda

最终的 zip 文件是函数使用的基础层。

部署运行时层

此层将安装在 Amazon Linux 的 /opt 目录下。

$ aws lambda publish-layer-version \
    --layer-name vertx-native-example \
    --zip-file fileb://function.zip

这将安装 Lambda 入口点 /opt/bootstrap 以及包含所有函数的原生镜像 ELF。

部署函数

您需要提供一个函数 zip 文件,在我们的例子中,这并没有什么用处,因为所有代码都位于基础层中,因此我们可以再次上传相同的 zip 文件或上传一个空文件。

$ aws lambda create-function --function-name vertxNativeTester \
    --zip-file fileb://function.zip --handler lambda.EchoLambda --runtime provided \
    --role arn:aws:iam::<YOUR-ARN>:role/service-role/lambda-role

如果您的函数需要文件资源,这可能会很有用。在这种情况下,您可以将每个函数的资源打包到单独的 zip 文件中。

由于部署是使用自定义运行时完成的,我们需要将两者链接起来。

$ aws lambda update-function-configuration --function-name vertxNativeTester \
    --layers arn:aws:lambda:eu-central-1:<YOUR-ARN>:layer:vertx-native-example:1

测试函数

恭喜您,您刚刚发布了您的第一个原生函数。为了快速测试它,请使用工具包:

$ aws lambda invoke --function-name vertxNativeTester \
    --payload '{"message":"Hello World"}' \
    --log-type Tail response.txt | grep "LogResult" | awk -F'"' '{print $4}' | base64 --decode

总结

  • 我们编写了一个使用 Vert.x 的原生函数,它返回一个每日名言(QOTD)。

  • 我们构建了一个 AWS Lambda 包。

  • 我们使用 AWS CLI 部署了这个函数。