vlambda博客
学习文章列表

读书笔记《cloud-native-applications-in-java》平台部署-AWS

Chapter 8. Platform Deployment – AWS

在本章中,我们将介绍 Amazon AWS 平台中可用的一些部署选项。 AWS 平台是最古老、最成熟的云服务提供商之一。它于 2002 年推出,从那时起一直是该领域的领导者。 AWS 也一直在不断创新,并推出了几项新服务,这些服务已在从单人初创企业到企业的各种客户中广泛采用。

在本章中,我们将介绍以下主题:

  • The AWS platform
  • AWS platform deployment options

AWS platform


Amazon AWS 是云计算 的先驱,此后一直在扩展其云产品以保持其领先地位。下图给出了 AWS 平台为应用程序开发人员提供的服务的指示性列表:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

这只是一个指示性清单,绝不是详尽清单;有关完整列表,请参阅 Amazon AWS 门户。

类别如下:

  • Infrastructure: This is probably the core of the AWS platform that enables it to provide a plethora of other services. These can be further classified into:
    • Compute: Services such as EC2, Lambda, ECS, and ELB. We will be demonstrating the deployment of our sample application using primarily compute services, but it is relatively easy to tie them up with the other services offered by AWS.
    • Storage: Services such as S3, EBS, and CloudFront.
    • Networking: Services such as VPC, Route53, and DirectConnect.
  • Application: These services can be used as components to build and support applications.
  • Database: These services target databases, providing access to different relational database management system (RDBMS) and NoSQL data stores.
  • DevOps: These services provide the ability to construct build pipelines and enable continuous delivery. These include source code hosting, continuous integration tools, and cloud and software provisioning tools.
  • Security: These services provide role-based access control (RBAC) to the various services offered by AWS, and provide a mechanism to specify quotas and enforce them, key management, and secret storage.
  • Mobile: These services are targeted at providing backends for mobile applications and services such as notification.
  • Analytics: These services include batch systems such as MapReduce, and stream processing systems such as Spark are available for building analytics platforms.

AWS platform deployment options


在 AWS 平台提供的各种服务中,我们将在本章重点介绍一些专门针对此类的部署选项以我们已经为例的Web APIs。因此,我们将部署覆盖到:

  • AWS Elastic Beanstalk
  • AWS Elastic Container Service
  • AWS Lambda

由于我们将在云环境中运行我们的应用程序,我们不需要直接管理基础架构,也就是说,我们不会启动虚拟机并在其中安装应用程序,我们不需要服务发现,因为弹性负载均衡器将自动路由到所有启动的应用程序实例。因此,我们将使用不使用 Eureka 发现客户端的 product API 版本:

package com.mycompany.product;

 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;

 @SpringBootApplication
 public class ProductSpringApp {

    public static void main(String[] args) throws Exception {
       SpringApplication.run(ProductSpringApp.class, args);
    }

 }

Deploying Spring Boot API to Beanstalk

AWS Elastic Beanstalk (AEB) 是一项服务 由 AWS 提供,用于在 AWS 上托管 Web 应用程序,而无需直接预置或管理 IaaS 层。 AEB 支持 Java、.NET、Python、Ruby、Go 和 PHP 等流行语言。最近,它还提供了对运行 Docker 容器的支持。我们将采用我们迄今为止在旅程中构建的 product 服务的简化版本,并将其作为可运行的 JAR 和 Docker 容器部署在 AEB 中。

Deploying a runnable JAR

登录AWS控制台,选择Compute下的Elastic Beanstalk服务 类别,然后点击 开始 按钮:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

在下一个屏幕中填写应用程序详细信息:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

target 文件夹上传 product.jar 并点击 Configure more选项按钮。您将看到不同的类别,您可以通过选择 Software, 并在其下的 < code>环境属性,添加一个名为SERVER_PORT的新环境变量,并将值设置为5000 .这是必要的,因为默认情况下,由 AEB 环境创建的 NGINX 服务器会将所有请求代理到该端口,并且通过设置变量,我们确保我们的 Spring Boot 应用程序将在端口 5000:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

现在,AWS 将预置一个运行我们的应用程序的新环境:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

配置环境后,AEB 将为应用程序生成 URL

读书笔记《cloud-native-applications-in-java》平台部署-AWS

我们可以使用这个 URL 来访问 API 端点:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

Deploying Docker containers

现在我们已经了解了如何将可运行的 JAR 部署到 Elastic Beanstalk 服务,让我们也看看相同 where 的变体我们将部署一个运行相同应用程序的 Docker 容器。使用 Docker 容器的优势在于,我们可以使用 AWS Elastic Beanstalk 服务尚未支持的语言和平台,并且仍然将它们部署在云中,从而获得服务提供的好处。

对于此部署,我们将使用 弹性容器服务 (ECS< /strong>) 提供存储我们从应用程序构建的 Docker 容器。当我们部署到 ECS 时,我们将介绍如何将本地 Docker 容器推送到 ECS 存储库。现在,假设我们要部署的 Docker 容器在名为 <aws-account-id>.dkr.ecr.us-west-2.amazonaws.com 的存储库中可用/product-api。由于我们需要访问这个存储库,我们需要将 AmazonEC2ContainerRegistryReadOnly 策略添加到默认的 Elastic Beanstalk 角色, aws-elasticbeanstalk-ec2-role

这可以从 Roles 部分下的 IAM 控制台完成:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

创建一个名为 Dockerfile.aws.json 的文件,其内容如下:

{ 
  "AWSEBDockerrunVersion": "1", 
  "Image": { 
    "Name": "<aws-account-id>.dkr.ecr.us-west-2.amazonaws.com/product-api", 
    "Update": "true" 
  }, 
  "Ports": [ 
    { 
      "ContainerPort": "8080" 
    } 
  ] 
} 

现在我们准备好部署我们的 Docker 容器了。在 Elastic Beanstalk 控制台中,我们将选择单个 DockerJava > 容器并创建一个新应用程序:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

选择并上传 Dockerfile.aws.json 以创建环境:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

我们可以测试我们的 API 端点来验证我们的 Docker 容器是否正常运行。我们还可以将容器配置为使用 Amazon CloudWatch 日志记录和监控来更好地监控我们的应用程序:

读书笔记《cloud-native-applications-in-java》平台部署-AWS

Deploying Spring Boot App to the Elastic Container Service

AWS Elastic Container Service (ECS) 是一项 用户使用托管Docker 实例部署应用程序。在这里,AWS ECS 服务负责 用于预置虚拟机和 Docker 安装。我们可以通过执行以下操作来部署我们的应用程序:

  1. Start ECS, click on Continue:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Create the ECS repository with name product-api and click on Next step:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Build and push a Docker container to the repository, following the instructions given on the screen:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. The Docker login command generated by the GUI has an extra http:// which should be excluded for:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We can now build and push the Docker container to the created repository:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We will use this container repository when configuring our task definition:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. In the advanced options, we can configure AWS CloudWatch logging to capture the logs from the Docker container, under the STORAGE AND LOGGING section:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We need to create a corresponding log group in the CloudWatch console to capture the logs created from our application:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We can create a service mapping to the port exposed from the container, which is 8080:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Optionally, we can picture the EC2 instance type and configure a Key pair so that we will be able to log in to the EC2 instance that ECS will create for our application:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Once we review the configurations and submit, ECS will begin creating the EC2 instance and deploying our application into it:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We can click on Auto Scaling group and find the instance that was launched:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Find the instance:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Find the instance hostname:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Access the application through the instance hostname:
读书笔记《cloud-native-applications-in-java》平台部署-AWS

但是通过主机名单独访问应用程序是不可行的,因此,我们将创建一个弹性负载均衡器,它将请求路由到实例,从而允许我们在扩展或缩减时拥有稳定的端点:

  1. We will go to the EC2 console and select Create under Application Load Balancer:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Configure the Load Balancer Port:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Configure the Target group and the Health checks endpoint:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Register the target instances to the instance that was created by our cluster definition:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Find the DNS record for the Load balancer:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Connect to the load balancer endpoint and verify the application is working:
读书笔记《cloud-native-applications-in-java》平台部署-AWS

Deploying to AWS Lambda

AWS Lambda 服务允许在事件触发器上调用简单的函数。这些事件触发器可以分为四种类型,即:

  • Data Store (for example, AWS DyanmoDB)
  • Queues and Streams (for example, AWS Kinesis)
  • Blob Store (for example, AWS S3)
  • API Dateways:
读书笔记《cloud-native-applications-in-java》平台部署-AWS

AWS Lamda 支持的事件源的完整列表可以在 https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#api-gateway-with-lambda。

与前面讨论的其他部署选项不同,AWS Lambda 提供了最透明的扩展选项,AWS 平台可以根据需求扩展所需的实例。我们从配置实例、负载均衡器等中解放出来,而可以专注于应用程序逻辑。

我们现在将构建一个简单的 AWS Lambda 函数并将其绑定到 API 端点以调用它。

我们将首先创建一个具有以下依赖项的新 Spring Boot 应用程序。我们还将使用 maven-shade-plugin 创建一个可运行的 JAR:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.mycompany</groupId>
   <artifactId>hello-lambda</artifactId>
   <version>0.0.1-SNAPSHOT</version>

   <dependencies>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.12</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.amazonaws</groupId>
       <artifactId>aws-lambda-java-core</artifactId>
       <version>1.1.0</version>
     </dependency>
     <dependency>
       <groupId>com.amazonaws</groupId>
       <artifactId>aws-lambda-java-events</artifactId>
       <version>2.0.1</version>
     </dependency>
     <dependency>
       <groupId>com.amazonaws</groupId>
       <artifactId>aws-lambda-java-log4j2</artifactId>
       <version>1.0.0</version>
     </dependency>
   </dependencies>

   <build>
     <finalName>hello-lambda</finalName>
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
           <source>1.8</source>
           <target>1.8</target>
         </configuration>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
         <version>3.0.0</version>
         <configuration>
           <createDependencyReducedPom>false</createDependencyReducedPom>
         </configuration>
         <executions>
           <execution>
             <phase>package</phase>
             <goals>
               <goal>shade</goal>
             </goals>
           </execution>
         </executions>
       </plugin>
     </plugins>
   </build>

 </project>

现在创建 HelloHandler.java,内容如下:

package com.mycompany;

 import com.amazonaws.services.lambda.runtime.Context;
 import com.amazonaws.services.lambda.runtime.RequestHandler;
 import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
 import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

 import java.net.HttpURLConnection;

 public class HelloHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

   @Override
   public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request, Context context) {
     String who = "World";
     if ( request.getPathParameters() != null ) {
       String name  = request.getPathParameters().get("name");
       if ( name != null && !"".equals(name.trim()) ) {
         who = name;
       }
     }
     return new APIGatewayProxyResponseEvent().withStatusCode(HttpURLConnection.HTTP_OK).withBody(String.format("Hello %s!", who));
   }

 }

由于 lambda functions 是简单的函数,我们只需使用函数的输入和输出就可以很容易地测试它们。例如,一个示例测试用例可以是:

package com.mycompany;

 import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
 import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.BlockJUnit4ClassRunner;

 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;

 import static org.junit.Assert.*;

 @RunWith(BlockJUnit4ClassRunner.class)
 public class HelloHandlerTest {

   HelloHandler handler;
   APIGatewayProxyRequestEvent input;
   @Before
   public void setUp() throws Exception {
     handler = new HelloHandler();
     Map<String, String> pathParams = new HashMap<>();
     pathParams.put("name", "Universe");
     input = new APIGatewayProxyRequestEvent().withPath("/hello").withPathParamters(pathParams);
   }

   @Test
   public void handleRequest() {
     APIGatewayProxyResponseEvent res = handler.handleRequest(input, null);
     assertNotNull(res);
     assertEquals("Hello Universe!", res.getBody());
   }
   @Test
   public void handleEmptyRequest() {
     input.withPathParamters(Collections.emptyMap());
     APIGatewayProxyResponseEvent res = handler.handleRequest(input, null);
     assertNotNull(res);
     assertEquals("Hello World!", res.getBody());
   }
 }

现在我们可以使用 Maven 构建 lambda 函数:

$ mvn clean package 
[INFO] Scanning for projects... 
[WARNING] 
[WARNING] Some problems were encountered while building the effective model for com.mycompany:hello-lambda:jar:0.0.1-SNAPSHOT 
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-compiler-plugin is missing. @ line 35, column 15 
[WARNING] 
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. 
[WARNING] 
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects. 
[WARNING] 
[INFO] 
[INFO] ------------------------------------------------------------------------ 
[INFO] Building hello-lambda 0.0.1-SNAPSHOT 
[INFO] ------------------------------------------------------------------------ 
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello-lambda --- 
[INFO] Deleting /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target 
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-lambda --- 
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! 
[INFO] skip non existing resourceDirectory /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/src/main/resources 
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-lambda --- 
[INFO] Changes detected - recompiling the module! 
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! 
[INFO] Compiling 1 source file to /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target/classes 
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-lambda --- 
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! 
[INFO] skip non existing resourceDirectory /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/src/test/resources 
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-lambda --- 
[INFO] Changes detected - recompiling the module! 
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! 
[INFO] Compiling 1 source file to /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target/test-classes 
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-lambda --- 
[INFO] Surefire report directory: /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target/surefire-reports 
 
------------------------------------------------------- 
 T E S T S 
------------------------------------------------------- 
Running com.mycompany.HelloHandlerTest 
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.055 sec 
 
Results : 
 
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 
 
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-lambda --- 
[INFO] Building jar: /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target/hello-lambda.jar 
[INFO] 
[INFO] --- maven-shade-plugin:3.0.0:shade (default) @ hello-lambda --- 
[INFO] Including com.amazonaws:aws-lambda-java-core:jar:1.1.0 in the shaded jar. 
[INFO] Including com.amazonaws:aws-lambda-java-events:jar:2.0.1 in the shaded jar. 
[INFO] Including joda-time:joda-time:jar:2.6 in the shaded jar. 
[INFO] Including com.amazonaws:aws-lambda-java-log4j2:jar:1.0.0 in the shaded jar. 
[INFO] Including org.apache.logging.log4j:log4j-core:jar:2.8.2 in the shaded jar. 
[INFO] Including org.apache.logging.log4j:log4j-api:jar:2.8.2 in the shaded jar. 
[INFO] Replacing original artifact with shaded artifact. 
[INFO] Replacing /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target/hello-lambda.jar with /Users/shyam/workspaces/msa-wsp/CloudNativeJava/chapter-09/hello-lambda/target/hello-lambda-0.0.1-SNAPSHOT-shaded.jar 
[INFO] ------------------------------------------------------------------------ 
[INFO] BUILD SUCCESS 
[INFO] ------------------------------------------------------------------------ 
[INFO] Total time: 2.549 s 
[INFO] Finished at: 2018-02-12T13:52:14+05:30 
[INFO] Final Memory: 25M/300M 
[INFO] ------------------------------------------------------------------------

我们现在已经构建了 hello-lambda.jar,我们将把它上传到我们将从 AWS 控制台创建的 AWS Lambda 函数。

  1. We will begin by going to the API Gateway console, which appears in the Network and Content Delivery category of the AWS console, and create a new API:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We will add a new resource called hello for the path /hello:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We will also create a child resource with a path parameter:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Now, we will attach the HTTP GET method:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Create a lambda function with the details shown as follows:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Upload the runnable JAR and set the handler method:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Now add this lambda function to the API method:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Make sure you select Use Lambda Proxy Integration so that we can use the specific RequestHandler interface, instead of using the generic RequestStreamHandler. This will also give API Gateway permission to the lambda function:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Complete the API definition with the lambda function invocation:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. We can test the API endpoint from the console:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Now we can deploy the API:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Successful deployment of the API will result in the API endpoint:
读书笔记《cloud-native-applications-in-java》平台部署-AWS
  1. Now we can use this API endpoint, generated for this deployment environment, to access the application:
读书笔记《cloud-native-applications-in-java》平台部署-AWS

Summary


在本章中,我们介绍了 AWS 平台提供的一些选项,以及如何从用于 Web 应用程序的 Elastic Beanstalk 部署我们的应用程序。我们部署到 ECS,用于部署容器化工作负载,不限于 Web 应用工作负载。然后我们部署了一个 AWS Lambda 函数,无需配置底层硬件。我们将在下一章中使用 Azure 进行部署,以了解它为部署云原生应用程序提供的一些服务。