elastic pdf
编者注: AWS Elastic Beanstalk是Amazon Web Services提供的一项编排服务,用于部署基础架构,该基础架构编排了各种AWS服务,包括EC2,S3,简单通知服务(SNS),CloudWatch,自动扩展和Elastic Load Balancer。
Elastic Beanstalk在裸服务器和OS上提供了一个附加的抽象层。 用户会看到操作系统和平台的预构建组合。
部署需要定义许多组件:“应用程序”作为项目的逻辑容器,“版本”是应用程序可执行文件的可部署构建,“配置模板”包含两个Beanstalk环境的配置信息和产品。 (来源: * )
现在,我们提供了全面的指南,以便您可以开发自己的基于Amazon Elastic Beanstalk的应用程序。 我们涵盖了广泛的主题,从部署和配置到Java集成和命令行接口。 有了本指南,您将能够在最短的时间内启动并运行自己的项目。 请享用!
目录
1.简介
Amazon Elastic Beanstalk是一项服务,可让您使用一组现有的Amazon AWS服务托管您的应用程序。 与EC2等更通用的服务相比,您不必提供已部署到云中的计算机的映像,而只需提供一个可运行的应用程序,该应用程序托管在Amazon AWS的预定义环境中。
Amazon让您在不同的预定义环境之间进行选择,并设置代表您运行应用程序所需的一切。 因此,如果您只想专注于应用程序本身而不是基础操作系统和服务器,那么此服务非常适合您。 但是,由于Elastic Beanstalk在后台使用EC2,S3,CodeCommit或Route 53等现有服务,因此您可以完全控制应用程序。
2.概念
Amazon Elastic Beanstalk定义了在整个服务中使用的一组术语,因此必须从头开始理解。 应用程序是一组组件,不仅包含您应用程序的不同版本,而且还包含应用程序部署到的环境的配置。 应用程序版本是可部署的工件,带有标签并存储在Amazon S3存储桶中。 因此,可以在以后的某个时间点还原它。 环境是一组Amazon AWS资源,用于运行特定版本的应用程序。 可以配置不同的环境,并且应用程序的版本可以在不同的环境中运行。
不同的环境可以同时存在,也可以为同一应用程序提供不同的版本。 环境由两层组成: Web服务器环境和工作环境 。 当Web服务器环境为HTTP请求提供服务时,工作环境从队列中读取消息并进行处理。 这样,应用程序可以使用“工作队列”模式将业务逻辑与服务HTTP请求分离。 环境配置包含环境的设置。 应用此配置将使Amazon AWS创建相应的资源。 现有模板可用于创建配置,因此称为配置模板 。
2.1 Web服务器环境
“ Web服务器环境”提供了运行应用程序的所有资源。 它由一台或多台用于部署应用程序的Web服务器组成。 这些Web服务器托管在Amazon AWS云内运行的EC2计算机上。 由于这些机器位于Elastic Load Balancer的后面,因此可以通过CNAME名称(例如myapp.us-west-2.elasticbeanstalk.com
。 使用Route 53服务将此CNAME别名为内部负载平衡URL。 通过使用域名系统(DNS),该Amazon服务提供了高可用性。 您环境中的EC2计算机可以属于“ Auto Scaling”组。
这意味着,如果负载增加,Amazon会自动增加实例数,另一方面,如果负载消失,则将机器数减少到至少一台机器。 在每台计算机上运行的“主机管理器”负责部署应用程序以及收集指标和事件。 它还会监视应用程序服务器并在必要时轮换日志文件。 如果环境的一个组件需要修补程序或更新,则“主机管理器”可以代表您执行此更新。 “安全组”为一组实例定义防火墙规则。 Web服务器环境中的基本安全组允许访问应用程序的端口80(HTTP)。 如果需要使用数据库,则可以*定义更多具有更细粒度控制的安全组。
2.2工人环境
“工作人员环境”提供了运行消耗来自Amazon SQS队列的消息的工作程序应用程序的所有资源。 因此,它不仅提供运行您的工作程序的EC2计算机,还提供一个SQS队列,可用于将消息从“ Web服务器环境”传输到“工作环境”,然后再传输回来。 在“工作人员环境”的每个实例上运行的守护程序将从队列中拉出请求,并将其传递到您的应用程序。
2.3设计考虑
在开始应用程序之前,必须仔细考虑它的设计。 十分重要的一方面是可伸缩性。 基本上,有两种方法可以处理应用程序上不断增加的负载。 第一种方法是增加每台计算机的可用硬件资源,使其可以处理更多负载。 尽管这种方法可用于负载增加很小的范围,但它不能满足任意需求。
第二种方法是增加正在运行的服务的数量(水平扩展),因为它允许在必要时添加更多计算机。 第二种方法是Amazon Elastic Beanstalk遵循的方法,因为对可用EC2实例的严格监视允许在必要时在自动扩展组中设置新实例。 但这也意味着该应用程序的设计和编写可以扩展。 水平扩展不是构建需要更多硬件的单片应用程序,而是将负载分配到尽可能无状态的任意数量的较小服务上,以便可以在运行时添加新服务。
Web服务器环境前面的负载平衡器将在可用服务之间分配传入请求,要求每个服务可以处理其中一个。 开发适用于AWS Elastic Beanstalk的应用程序时的另一个重要点是安全性。 可以使用SSL对传入和传出Web服务器环境的数据进行加密。 因此,必须从一个外部认证机构(例如VeriSign或Entrust)获得有效的证书。 请注意,SSL加密在环境的负载平衡器处结束,并且它与Web服务器之间的流量通常不加密。
为了能够在必要时启动和停止其他实例,您的应用程序不应在每个节点的本地存储上存储任何数据,因为一旦关闭实例,该数据将被擦除,而在启动另一个实例时,该数据将不存在。 。 因此,必须关注持久性存储。 Amazon AWS提供了可用于您的应用程序以存储状态的不同服务:
- Amazon S3:此服务可用于在云中存储任意数量的数据。
- Amazon Elastic File System:EFS可以安装在您的EC2实例上,并且可以像普通文件系统一样使用。
- Amazon Elastic Block Store:EBS卷已附加到EC2实例,可用于与文件系统或数据库结合使用。
- Amazon DynamoDB:此服务在Amazon云中提供NoSQL数据库。
- Amazon Relational Database Service:RDS管理可以由您的应用程序使用的六个不同的关系数据库引擎(Amazon Aurora,PostgreSQL,MySQL,MariaDB,Oracle和Microsoft SQL Server)。
如果世界各地的用户遇到不同的延迟时间,您可以使用Amazon CloudFront使其自动在世界范围内分发您的应用程序和存储,并将用户路由到最近的可用站点。 最后但并非最不重要的一点是,如果有必要,您可以让Amazon更新并修补您的环境。 这些更新以“滚动批次”的形式执行,即Amazon获取第一批EC2实例并终止它们。
新实例出现后,可以处理下一批。 是否应处理下一批的条件可以基于时间策略或新计算机的运行状况。 通过此过程可以使站点保持运行状态,而应用程序的新旧服务将在短时间内运行。 为此,您当然必须设计应用程序,以使旧版本的服务可以读取新版本已经写入的数据。
3. Java Web应用程序
既然我们已经学到了很多有关Amazon Elastic Beanstalk背后的概念的知识,那么我们应该动手实践,并开发一个小型应用程序,它使用Apache Tomcat公开一个简单的REST接口。
3.1简单的REST-API
首先,我们使用以下命令行创建一个新的maven项目:
mvn archetype:generate -DgroupId=com.javacodegeeks.ultimate.aws.eb -DartifactId=tomcat-web-service -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
之后,我们将使用以下结构创建一个名为tomcat-web-service
的新目录:
|-- pom.xml
`-- src
|-- main
| `-- webapp
| `-- index.jsp
| `-- WEB-INF
| `-- web.xml
原型已经为我们创建了一个web.xml
和一个index.jsp
文件。 后者可用于稍后在云中简单地测试第一个版本,因此我们尚未删除JSP页面。 web.xml
文件需要进行一些编辑:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>tutorial-webapp</display-name>
<servlet>
<servlet-name>RestServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.javacodegeeks.ultimate.aws.eb</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>RestServlet</servlet-name>
<url-pattern>/tutorial-service/*</url-pattern>
</servlet-mapping>
</web-app>
使用XML元素display-name
我们定义如何在Apache Tomcat服务器内部标记应用程序。 servlet
和servlet-mapping
元素定义了正在侦听请求的Servlet类及其应侦听的URL模式。 在我们的例子中,我们使用模式/tutorial-service/*
,即所有看起来像http://://tutorial-service/*
URL都将被该servlet处理。
上下文名称是通过我们部署到tomcat中的战争档案的名称定义的。 参数jersey.config.server.provider.packages
告诉我们将用来实现REST-API的JAX-B实现,它应该扫描哪个Java包以查找注释。 为此,我们必须在maven项目中创建以下目录结构: src/main/java/com/javacodegeeks/ultimate/aws/eb
。 为了告诉maven我们要使用哪个版本的JAX-B实现,我们将以下依赖性信息块复制到您的pom.xml
文件中:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jersey.version>2.26</jersey.version>
<junit.version>4.12</junit.version>
<commons-logging>1.1.3</commons-logging>
<log4j.version>1.2.17</log4j.version>
<javax-ws-rs-api.version>2.1</javax-ws-rs-api.version>
<aws-sdk.version>1.11.106</aws-sdk.version>
<db.dynamodb.local-endpoint>false</db.dynamodb.local-endpoint>
</properties>
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${javax-ws-rs-api.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>${aws-sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
除了纯API合约( javax.ws.rs-api
),我们还定义使用jersey-container-servlet
作为JAX-B实现,并使用jersey-hk2
进行对jersey的依赖项注入。 由于Amazon SDK使用commons-logging
我们也进行了相同的操作。 作为日志记录服务,我们选择了经典的log4j
实现。 目前, junit
和jersey-client
依赖项仅用于我们的集成测试。
拥有可以在本地执行的集成测试可以极大地简化开发,因为我们不必每次都将新版本的应用程序上载到AWS云中。 当前不需要aws-java-sdk-bom
依赖关系,但是由于在接下来的步骤中将使用SDK,因此我们已经在此处包含了它。 pom.xml
的build
部分再次有点长:
<build>
<finalName>tomcat-web-service</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.1</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest*</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<includes>
<include>**/*IntegrationTest*</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<url>http://localhost:8080/manager</url>
<server>localhost</server>
<path>/${project.build.finalName}</path>
<contextFile>${project.basedir}/src/test/tomcat7-maven-plugin/context.xml</contextFile>
</configuration>
<executions>
<execution>
<id>start-tomcat</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run-war</goal>
</goals>
<configuration>
<fork>true</fork>
</configuration>
</execution>
<execution>
<id>stop-tomcat</id>
<phase>post-integration-test</phase>
<goals>
<goal>shutdown</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
maven-compiler-plugin
用于定义构建的源版本和目标版本。 这是必需的,因为所提供的服务已安装了特定的Java版本,因此我们必须编译可在目标环境中执行的工件。 surefire和failsafe插件用于执行本地junit和集成测试。 最后, tomcat7-maven-plugin
允许我们在构建中启动和停止嵌入式Apache Tomcat服务器,以执行集成测试。
此步骤缩短了开发和测试之间的往返时间,因为我们不必每次都启动或重新启动外部安装的服务器。 由于我们将不使用任何Tomcat 8特定功能,因此tomcat7-maven-plugin
应该足够了。 正如tomcat7-maven-plugin
的配置已经表明的那样,我们需要一个专用的context.xml
文件来进行tomcat集成测试。 因此,以下内容被放入src/test/tomcat7-maven-plugin/context.xml
:
<?xml version='1.0' encoding='utf-8'?>
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
</Context>
完成上述所有准备工作之后,我们就可以准备第一堂课:
package com.javacodegeeks.ultimate.aws.eb;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
@Path("/tutorial")
public class TutorialResource {
private static final Log LOGGER = LogFactory.getLog(TutorialResource.class);
@GET
@Produces("text/json")
@Path("/list-all-courses")
public Response listAllCourses() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Listing all courses.");
}
List tutorials = new ArrayList<>();
tutorials.add(new Tutorial("Linus Meyer", "Linux"));
tutorials.add(new Tutorial("Bill Famous", "Microsoft"));
tutorials.add(new Tutorial("Josh Hotspot", "Java"));
return Response.status(200).entity(tutorials).build();
}
}
用@Path
注释该类,以告知JAX-RS框架,该类本身中使用的所有相对URL路径均应以/tutorial
作为前缀。 为了简单起见,我们目前只有一个方法: listAllCourses()
。 URL的一部分在方法级别用另一个@Path
注释表示。 使用注释@Produces
和媒体类型text/json
指定此REST资源的调用将返回JSON字符串。
最后,我们通过使用专用注释@GET
来告诉框架此方法是GET请求。 当前,我们没有任何持久性存储,因此我们无法从数据源加载数据。 因此,我们对课程列表进行了硬连线,并返回了Tutorial
对象的列表。 Tutorial
类是一个简单的值对象:
public class Tutorial {
private String author;
private String title;
public Tutorial() {
}
public Tutorial(String author, String title) {
this.author = author;
this.title = title;
}
public String getAuthor() {
return author;
}
public String getTitle() {
return title;
}
public void setAuthor(String author) {
this.author = author;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tutorial tutorial = (Tutorial) o;
if (author != null ? !author.equals(tutorial.author) : tutorial.author != null) return false;
if (title != null ? !title.equals(tutorial.title) : tutorial.title != null) return false;
return true;
}
@Override
public int hashCode() {
int result = author != null ? author.hashCode() : 0;
result = 31 * result + (title != null ? title.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Tutorial{" +
"author='" + author + '\'' +
", title='" + title + '\'' +
'}';
}
}
我们需要有一个默认的构造函数,以便JSON库可以从字符串表示形式反序列化Tutorial
类型的对象。 getter和setter方法允许JSON库设置相应的属性值。 在此示例中,我们还实现了equals()
和hashCode()
因为我们想稍后在集成测试中比较Tutorial的实例。 如前所述,现在可以将应用程序已经构建和部署到AWS上是一种选择,但是要使部署可用还需要一些时间。
因此,我们只需编写一个集成测试即可验证我们的实施是否按预期工作,以将不必要的上载保存到AWS云(并节省时间)。 在上面的pom.xml
文件中,我们配置了集成测试,并在其类名称中使用IntegrationTest
命名。 因此,我们在src/test/java
下创建一个具有以下内容的类:
public class TutorialIntegrationTest {
@Test
public void testListAllCourses() {
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080")
.path("/tomcat-web-service/tomcat-web-service/tutorial/list-all-courses");
Response response = target.request().get();
assertThat(response.getStatus(), is(200));
List tutorials = response.readEntity(new GenericType>(){});
assertThat(tutorials.size(), is(3));
assertThat(tutorials, hasItem(new Tutorial("Linus Meyer", "Linux")));
assertThat(tutorials, hasItem(new Tutorial("Bill Famous", "Microsoft")));
assertThat(tutorials, hasItem(new Tutorial("Josh Hotspot", "Java")));
}
}
方法testListAllCourses()
的第一行创建一个新的REST客户端,而第二行提供服务器上的主机,端口和路径。 对于我们的本地测试, localhost
是合适的。 Tomcat默认在端口8080上运行。URL作为部署到Tomcat的war文件名称的第一部分组成。 我们已经使用XML元素finalName
在pom.xml
文件中定义了它。
使用request()
返回的对象上的get()
方法发出GET请求。 如果一切正常,则Web服务应返回状态码200。在这种情况下,还应该返回Tutorial
对象的列表。 方法readEntity()
允许我们使用具有两个泛型类型的GenericType
实例来定义它。 一种用于List
,一种用于Tutorial
内部类型。 结果列表应该恰好包含三个条目,每个课程一个条目。 要在本地测试所有内容,我们在本地终端上发出以下命令:
mvn clean verify
由于verify
阶段在integration-test
阶段之后,因此此调用还将运行先前编写的集成测试:
[INFO] --- tomcat7-maven-plugin:2.2:run-war (start-tomcat) @ tomcat-web-service ---
[INFO] Running war on http://localhost:8080/tomcat-web-service
[INFO] Creating Tomcat server configuration at D:\development\glassfish\glassfish-5.1\awseb\awseb\tomcat-web-service\target\tomcat
[INFO] create webapp with contextPath: /tomcat-web-service
[...]
[INFO]
[INFO] --- maven-failsafe-plugin:2.12.4:integration-test (default) @ tomcat-web-service ---
[INFO] Failsafe report directory: D:\development\glassfish\glassfish-5.1\awseb\awseb\tomcat-web-service\target\failsafe-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.javacodegeeks.ultimate.aws.eb.TutorialIntegrationTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.443 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO]
[INFO] --- tomcat7-maven-plugin:2.2:shutdown (stop-tomcat) @ tomcat-web-service ---
[...]
[INFO]
我们可以看到,tomcat服务器是在集成测试之前和之后启动的。 在构建结束时,文件tomcat-web-service.war
驻留在我们项目的target
目录中。 这是我们现在要上载到Amazon Elastic Beanstalk的应用程序。
3.2使用Web界面进行部署
如果您尚未创建Amazon AWS账户,则现在应这样做,方法是将您的浏览器指向以下URL ,然后单击标记为“创建AWS账户”的链接。 在以下步骤中,您将必须提供典型的个人信息以及有效的信用卡。 后者是必需的,以便允许Amazon为您使用的资源开账单。 创建新帐户后,您就有资格获得“免费套餐”。
在前12个月中,您目前可以在EC2上使用长达750个小时的计算能力,在S3上使用5 GB的标准存储。 这对于我们的教程来说绰绰有余。 设置可用的AWS账户后,即可创建第一个应用程序。 因此,将浏览器指向以下URL,并填写应用程序的名称以及可选的描述:
下一页询问我们是要设置Web服务器环境还是工作环境。 我们的示例REST应用程序最适合第一类环境。 因此,我们单击“创建Web服务器”。
对于Web服务器环境,我们必须设置配置和类型。 我们选择“ Tomcat”作为预定义配置,选择“单个实例”作为类型。 通过这种方式,Amazon为我们提供了一个已安装Apache Tomcat服务器的EC2实例。 此时我们不选择自动缩放,因为对于我们的示例而言,一个实例就足够了。
以下页面要求指定应用程序版本。 因此,我们选择第二个选项,并选择我们的Maven构建之前生成的war文件。
或者,我们还可以提供指向之前已上传到Amazon S3或选择的Amazon示例应用程序之一的工件的链接。
如前所述,我们的应用程序获得了自己的CNAME。 因此,可以使用以下页面提供CNAME。 “检查可用性”使我们可以验证名称是否仍然可用。 如果我们打算使用关系数据库或虚拟私有云网络。 在我们的简单示例中,我们不需要这两种资源,因此只需单击“下一步”
在“配置详细信息”部分中,我们可以选择服务器类型。 一个t2.micro实例足以进行我们的实验,但是如果您愿意,可以选择一个更大的实例。 EC2文档更详细地描述了可用的实例类型。 其余输入字段可以保留不变,因为我们对实例光盘或运行状况报告没有任何特定要求。 也不需要EC2**对。
环境标签可用于标识成本分配报告中的环境,或通常可用于管理环境和权限。 对于我们的第一个示例应用程序,标签不是必需的,但是您可以*提供它们。
“权限”页面允许定义实例配置文件和服务角色。 实例配置文件是IAM角色,您的应用程序使用该IAM角色与其他AWS服务进行通信,而该服务角色将用于监视环境。
最后,概述页面会显示所有信息以供验证。 如果您对选择感到满意,则可以单击“启动”,然后让Amazon AWS为您创建所有资源。 该过程完成后,您可以在控制台中看到一个新环境:
单击此环境将导致以下仪表板:
在这里,您可以查看环境的所有事件,运行版本和配置。 现在应用程序已启动并运行,我们可以将浏览器指向以下URL:
正如预期的那样,浏览器将显示一个包含三个Tutorial
项目的JSON数组。
3.3使用CLI进行部署
当您不必经常使用Web控制台时,就足够了。 如果您追求自动化,则可以使用awccli
工具从命令行界面(CLI)创建环境。 为此,您首先必须安装python和pip 。 这两个网站均包含有关如何为通用操作系统安装和配置这两个工具的信息。 运行python和pip后,只需使用以下命令即可安装awscli
:
pip install awsebcli --upgrade --user
为了能够直接运行eb
命令,必须将其包含在路径中。 在Windows系统上,您必须将以下路径添加到PATH
环境变量中: %USERPROFILE%\AppData\Roaming\Python \Python36\scripts
。 在基于Linux的系统和基于macOS的系统上,您无需修改任何环境变量。 第一步是通过在maven项目的根文件夹中调用命令eb init
来初始化默认设置:
~/eb $ eb init
Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) us-east-2 : US East (Columbus)
14) ca-central-1 : Canada (Central)
15) eu-west-2 : EU (London)
(default is 3): 5
接下来,您必须提供您的AWS凭证:
You have not yet set up your credentials or your credentials are incorrect
You must provide your credentials.
(aws-access-id):
(aws-secret-key):
下一个问题是每次您要部署软件的新版本时,都要创建一个新的应用程序版本。 因为我们确实想创建一个新版本,所以选择1:
Select an application to use
1) [ Create new Application ]
(default is 1): 1
现在,您可以输入应用程序的名称:
Enter Application Name
(default is "eb"): tomcat-web-service
Application eb has been created.
与在Web控制台中一样,我们必须为我们的应用程序选择一个预配置的平台。 我们在这里选择Tomcat,而不是Java(!),否则我们的应用程序将不得不打开自己的端口并处理请求。
Select a platform.
1) Node.js
2) PHP
3) Python
4) Ruby
5) Tomcat
6) IIS
7) Docker
8) Multi-container Docker
9) GlassFish
10) Go
11) Java
(default is 1): 5
最后,您必须说是否要使用SSH连接到EC2实例。 在这个简单的示例中,我们不想这样做:
Do you want to set up SSH for your instances?
(y/n): n
由于eb
工具不知道工件的位置,因此我们必须使用存储在.elasticbeanstalk/config.yml
下的YAML文件来指定它,其内容如下:
deploy:
artifact: target/tomcat-web-service.war
现在,您可以使用以下命令部署工件:
eb deploy --staged
3.4使用DynamoDB
到目前为止,我们的示例应用程序尚未存储任何数据。 如前所述,一旦当前计算机关闭,我们将丢失本地计算机上的所有状态,因为自动缩放机制不再需要它。 因此,我们必须将应用程序的状态存储在外部数据存储中。 经常使用的一种方便选择是Amazon的NoSQL数据库“ DynamoDB”。 在DynamoDB中,数据存储在表中,例如关系数据库中。
表格是项目的集合,每个项目都包含一个或多个属性。 属性是键/值对。 与关系数据库相比,没有架构,即每个项目可以具有不同的属性。 例如,您可以创建一个表来存储事件。 每个事件都是一个项目,描述特定事件的数据使用其创建时间,事件类型等属性进行存储。每个项目都有一个哈希键和可选的附加排序键。 哈希键用于将大型表中的项目分布在不同的分区上。 如果表具有排序键,则该表用于以排序方式存储具有相同哈希键的项目。
这样可以简化以某种哈希键遍历所有项目的方式。 分区允许在不同的计算机上分布数据,因此是一种可水平扩展应用程序的机制。 了解了DynamoDB的基础知识之后,我们可以使用另一个资源扩展示例应用程序,该资源使我们可以创建和删除应用程序所需的表。 我们将通过使用亚马逊的AWS SKD来做到这一点; 因此,我们必须在其中的DynamoDB部分定义一个依赖项:
com.amazonaws
aws-java-sdk-dynamodb
具体版本是从aws-java-sdk-bom
依赖关系推导出来的,我们在本教程的开始部分就已经在dependencyManagement
中定义了该dependencyManagement
。 接下来,我们创建一个新的类DbResource
:
@Path("/db")
public class DbResource {
private static final Log LOGGER = LogFactory.getLog(DbResource.class);
@GET
@Produces("text/json")
@Path("/list-all-tables")
public Response listAllTables() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Listing all tables.");
}
AmazonDynamoDB dynamoDB = createClient();
ListTablesResult listTablesResult = dynamoDB.listTables();
List tableNames = listTablesResult.getTableNames();
return Response.status(200).entity(tableNames).build();
}
public AmazonDynamoDB createClient() {
String property = System.getProperty("tutorial.dynamodb.local-endpoint");
Boolean localEndpoint = Boolean.valueOf(property);
if (!localEndpoint) {
return AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.EU_CENTRAL_1)
.build();
} else {
return AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(
new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "eu-central-1"))
.withCredentials(new AWSCredentialsProvider() {
@Override
public AWSCredentials getCredentials() {
return new AWSCredentials() {
@Override
public String getAWSAccessKeyId() {
return "dummy";
}
@Override
public String getAWSSecretKey() {
return "dummy";
}
};
}
@Override
public void refresh() {
}
})
.build();
}
}
}
此类公开REST URL /db/list-all-tables
,该URL返回DynamoDB实例上所有现有表的列表。 因此,它将创建AmazonDynamoDB
的实例,该实例由方法createClient()
返回。 此方法的代码查询系统属性tutorial.dynamodb.local-endpoint
以了解是否使用具有给定端点和虚拟凭据的本地运行DynamoDB,或者是否使用DefaultAWSCredentialsProviderChain
派生此信息。 此特定实施按以下顺序搜索AWS凭证:
- 环境变量:AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY
- Java系统属性:aws.accessKeyId和aws.secretKey
- 配置文件存储在用户的主目录中
- EC2容器提供的凭据
- EC2元数据服务提供的凭证
由于我们的Web服务将部署在EC2实例上,因此凭据由AWS环境提供,因此不得存储在我们的应用程序内部。 如果我们使用本地端点,则可以使用虚拟值和我们选择的区域。 此处说明了如何安装和运行本地DynamoDB实例。 通过在maven插件tomcat7-maven-plugin
的configuration
中放置以下代码段,可以在启动Tomcat服务器时自动设置system属性:
true
为了在将功能部署到云之前测试上述功能,我们编写了以下小型集成测试:
@Test
public void testListAllTables() {
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080")
.path("/tomcat-web-service/tomcat-web-service/db/list-all-tables");
Response response = target.request().get();
assertThat(response.getStatus(), is(200));
List tutorials = response.readEntity(new GenericType>(){});
assertThat(tutorials.size(), is(0));
}
测试代码仅调用新的URL并验证是否返回了空字符串列表。 同样,我们可以编写代码来创建和删除表:
public Response removeAllTables() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Removing all tables.");
}
AmazonDynamoDB dynamoDB = createClient();
ListTablesResult listTablesResult = dynamoDB.listTables();
List tableNames = listTablesResult.getTableNames();
for (String table : tableNames) {
dynamoDB.deleteTable(table);
}
return Response.status(200).entity(tableNames).build();
}
@GET
@Produces("text/json")
@Path("/create-all-tables")
public Response createAllTables() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating all tables.");
}
AmazonDynamoDB dynamoDB = createClient();
List tableNames = new ArrayList();
createTable(dynamoDB);
return Response.status(200).entity(tableNames).build();
}
private void createTable(AmazonDynamoDB amazonDynamoDB) {
List attributeDefinitions = new ArrayList();
attributeDefinitions.add(new AttributeDefinition().withAttributeName("tutorial").withAttributeType("S"));
List keySchema = new ArrayList();
keySchema.add(new KeySchemaElement().withAttributeName("tutorial").withKeyType(KeyType.HASH));
CreateTableRequest request = new CreateTableRequest()
.withTableName("tutorials")
.withKeySchema(keySchema)
.withAttributeDefinitions(attributeDefinitions)
.withProvisionedThroughput(
new ProvisionedThroughput()
.withReadCapacityUnits(1L)
.withWriteCapacityUnits(1L));
DynamoDB dynamoDB = new DynamoDB(amazonDynamoDB);
Table table = dynamoDB.createTable(request);
try {
table.waitForActive();
} catch (InterruptedException e) {
LOGGER.error("Failed to wait for table to become active: " + e.getLocalizedMessage(), e);
}
}
尽管删除现有表的代码或多或少是不言自明的,但是方法createAllTables()
定义了一个表来存储教程。 该属性tutorial
用作哈希键。 对于我们的示例应用程序,读和写容量为1个容量单位就足够了。 对于现实应用而言,这可能是不够的。 最后,代码等待表准备就绪。 请注意,在创建表时,我们不必指定所有属性。 现在,我们可以修改现有方法listAllCourses()
以使用新的tutorials
表:
@GET
@Produces("text/json")
@Path("/remove-all-tables")
@GET
@Produces("text/json")
@Path("/list-all-courses")
public Response listAllCourses(@QueryParam("author") String authorQuery) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Listing all courses.");
}
List tutorials = new ArrayList<>();
AmazonDynamoDB amazonDynamoDB = DbResource.createClient();
Map expValues = new HashMap<>();
expValues.put(":a", new AttributeValue(authorQuery));
QueryRequest queryRequest = new QueryRequest("tutorials");
queryRequest.setKeyConditionExpression("author = :a");
queryRequest.setExpressionAttributeValues(expValues);
QueryResult queryResult = amazonDynamoDB.query(queryRequest);
List> items = queryResult.getItems();
for (Map item : items) {
AttributeValue author = item.get("author");
AttributeValue title = item.get("title");
Tutorial tutorial = new Tutorial();
tutorial.setAuthor(author.getS());
tutorial.setTitle(title.getS());
tutorials.add(tutorial);
}
return Response.status(200).entity(tutorials).build();
}
首先,我们获得对AmazonDynamoDB
客户端的引用。 它的方法query()
采用QueryRequest
的实例对请求的表执行查询。 在这个简单的示例中,我们要搜索给定作者提供的所有教程。 作者作为查询参数传递到URL。 在JAX-RS中,这意味着我们为方法listAllCourses()
指定了一个参数,并使用@QueryParam("author")
注释。
传递给注释的值表示URL中查询参数的名称,该参数应作为String
传递给方法。 查询的条件写为: author = :a
。 字符串:a
是一个属性,我们必须为此提供一个值。 这是通过构造一个HashMap
并将key :a
的值放入其中来完成的。 然后在QueryRequest
上设置此HashMap
。
调用queryResult.getItems()
是一个包含地图实例的列表。 每个地图代表一个项目,其键/值对存储在地图内部。 因此,我们遍历此列表,并将每个项目转换为类Tutorial
的实例,然后将Tutorial
列表返回给REST方法的调用者。 Java对象到JSON字符串的转换是由框架完成的。 现在,我们可以调用以下URL来创建表并列出所有作者:
http://.eu-central-1.elasticbeanstalk.com/tomcat-web-service/db/remove-all-tables
http://.eu-central-1.elasticbeanstalk.com/tomcat-web-service/db/create-all-tables
http://.eu-central-1.elasticbeanstalk.com/tomcat-web-service/tutorial/list-all-courses?author=test
请在字符串<your-app>
和该区域上方的URL中替换为<your-app>
的CNAME和区域。 删除所有表当然不是必需的,但是在这种情况下它并没有害处,因为我们只删除存在的表。 现在由您决定实现一种将数据插入表中的方法。
3.5使用RDS
在DynamoDB旁边,您还可以使用关系数据库。 因此,我们在AWS控制台中导航到我们的环境,然后选择菜单项“配置”。 在配置页面的下部,我们找到一个名为“创建新的RDS数据库”的链接:
单击此按钮后,将显示以下页面
在此页面上,我们必须指定RDS实例的基本配置详细信息。 由于我们没有快照,因此在相应的字段中选择“无”。 作为数据库引擎,我们在5.6.37版中选择了“ MySQL”。 该数据库应在具有5 GB存储空间的“微型”实例上运行。 “删除策略”确定了我们删除实例后应如何处理。 由于这是一个教程,因此我们不关心数据,而是让Amazon删除实例及其所有数据。
或者,您也可以指定创建数据库快照。 对于我们的示例应用程序,“单个可用区”也足够。 提供主用户名和密码后,我们可以单击“应用”。 现在最多可能需要10分钟才能使RDS数据库可用。 同时,我们可以调整pom.xml
文件中的Maven dependencies
列表,并添加mysql连接器,如下所示:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
请注意,我们必须将此依赖项的范围设置为compile
,因为所引用的jar文件应放置在我们战争的lib文件夹中。 否则,MySQL驱动程序类将在运行时不可用。 接下来,我们创建一个名为RdsResource
的新类:
@Path("/rds")
public class RdsResource {
private static final Log LOGGER = LogFactory.getLog(RdsResource.class);
@GET
@Produces("text/json")
@Path("/list-all-tables")
public Response listAllTables() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Listing all tables.");
}
List tableNames = null;
try {
tableNames = listAllTablesIntern();
} catch (SQLException e) {
return Response.status(500).entity("Listing all tables failed: " + e.getLocalizedMessage()).build();
}
return Response.status(200).entity(tableNames).build();
}
private List listAllTablesIntern() throws SQLException {
List tableNames = new ArrayList<>();
try (Connection connection = createConnection()) {
if (connection != null) {
try (Statement stmt = connection.createStatement()) {
ResultSet resultSet = stmt.executeQuery("show tables");
while (resultSet.next()) {
String tableName = resultSet.getString(1);
tableNames.add(tableName);
}
}
}
}
return tableNames;
}
public static Connection createConnection() throws SQLException {
if (System.getProperty("RDS_HOSTNAME") != null) {
try {
Class.forName("com.mysql.jdbc.Driver");
String dbName = System.getProperty("RDS_DB_NAME");
String userName = System.getProperty("RDS_USERNAME");
String password = System.getProperty("RDS_PASSWORD");
String hostname = System.getProperty("RDS_HOSTNAME");
String port = System.getProperty("RDS_PORT");
String jdbcUrl = "jdbc:mysql://" + hostname + ":" + port + "/" + dbName;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Connecting to JDBC-URL: " + jdbcUrl);
}
Connection con = DriverManager.getConnection(jdbcUrl, userName, password);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Connection to JDBC-URL: " + jdbcUrl + " successful.");
}
return con;
} catch (ClassNotFoundException e) {
LOGGER.error("Could not load driver: " + e.getLocalizedMessage(), e);
}
}
return null;
}
}
它将提供一个以/rds/list-all-tables
结尾的URL,并返回所有可用MySQL表的列表。 因此,方法listAllTables()
调用私有方法listAllTablesIntern
,如果没有异常发生,则返回状态码为200的HTTP响应;如果列出表失败,则返回状态码为500的HTTP响应。
私有方法listAllTablesIntern()
利用另一个私有方法来获取与数据库的连接: createConnection()
。 在基于Tomcat的环境中,所有必要的信息都通过系统属性传递给应用程序。 通过查询这些预定义的系统属性,我们可以构造以下形式的JDBC URL:
jdbc:mysql://" + hostname + ":" + port + "/" + dbName
在实际的应用程序中,这可能成为以下形式的URL:
jdbc:mysql://aa1im36v00yvfox.cwazv5kmikco.eu-central-1.rds.amazonaws.com:3306/ebdb
有了JDBC连接,我们可以发出一条列出所有现有数据库表SQL语句:
try (Statement stmt = connection.createStatement()) {
ResultSet resultSet = stmt.executeQuery("show tables");
while (resultSet.next()) {
String tableName = resultSet.getString(1);
tableNames.add(tableName);
}
}
我们将由JAX-RS框架呈现的列表中的表名列表返回到JSON数组中。 在云端更新示例应用程序的版本之后,我们可以在浏览器中打开以下URL:
http://.eu-central-1.elasticbeanstalk.com/tomcat-web-service/rds/list-all-tables
再次:请在上述URL中将字符串<your-app>
和区域替换为您的应用程序的CNAME和区域。 这应该显示一个空的JSON数组。 设置完如上所述的所有内容之后,我们现在可以实现创建和删除应用程序表的方法:
@GET
@Produces("text/json")
@Path("/remove-all-tables")
public Response removeAllTables() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Removing all tables.");
}
List tableNames = new ArrayList<>();
List tables;
try {
tables = listAllTablesIntern();
} catch (SQLException e) {
return Response.status(500).entity("Listing all tables failed: " + e.getLocalizedMessage()).build();
}
for (String table : tables) {
try (Connection connection = createConnection()) {
if (connection != null) {
try (Statement stmt = connection.createStatement()) {
stmt.executeUpdate("drop table " + table);
tableNames.add(table);
}
}
} catch (SQLException e) {
LOGGER.error("Removing all tables failed: " + e.getLocalizedMessage(), e);
return Response.status(500).entity("Removing all tables failed: " + e.getLocalizedMessage()).build();
}
}
return Response.status(200).entity(tableNames).build();
}
@GET
@Produces("text/json")
@Path("/create-all-tables")
public Response createAllTables() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating all tables.");
}
List tableNames = new ArrayList<>();
try (Connection connection = createConnection()) {
if (connection != null) {
try (Statement stmt = connection.createStatement()) {
stmt.executeUpdate("create table tutorials (" +
"id int not null auto_increment, " +
"author varchar(30) not null," +
"title varchar(30) not null," +
"primary key (id)" +
")");
tableNames.add("tutorials");
}
}
} catch (SQLException e) {
LOGGER.error("Creating tables failed: " + e.getLocalizedMessage(), e);
return Response.status(500).entity("Creating tables failed: " + e.getLocalizedMessage()).build();
}
return Response.status(200).entity(tableNames).build();
}
上面的代码使用已经说明的方法createConnection()
,因此可以轻松理解。 创建和删除表的两条SQL语句是使用JDBC API的executeUpdate()
方法发出的。 使用完每个语句和连接后,我们将关闭它们以释放资源。 请注意,在现实世界中的应用程序中,您可能需要合并连接,以便将现有连接重新用于下一个请求。 修改插入和查询教程的代码由读者决定。
4.具有Spring Boot的Java Web应用程序
上一章介绍了如何使用Apache Tomcat环境运行简单的REST API服务器。 通过流行的Spring Boot框架可以实现相同的目的。 In this chapter we are therefore going to build a Spring Boot application that runs inside a “Java” environment on AWS and not within a “Tomcat” environment.
Note that we can also build the Spring Boot application to run within the Tomcat environment, but for demonstrating purposes we are running our application in standalone mode. First, we create a new maven project with the following command:
mvn archetype:generate -DgroupId=com.javacodegeeks.ultimate.aws.eb -DartifactId=spring-boot-web -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
Next, we let our project inherit from the Spring Boot parent project:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
This provides us all necessary versions but does not add any dependencies. Hence, we must add them to our pom.xml
:
<properties>
<jersey.version>2.26</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The main dependency is spring-boot-starter-web
. The other ones are used for our integration tests. spring-boot-maven-plugin
is a maven plugin that packages the application such that we can start it easily from the command line. Now we can implement the simplest application possible:
@RestController
@EnableAutoConfiguration
public class Example {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Example.class, args);
}
}
The main()
method starts the Example
class as controller and passes the optional arguments from the command line to the run()
method of the SpringApplication
. Annotating the class with @RestController
turns it into a REST endpoint. The method home()
just returns a static string. Its annotation @RequestMapping("/")
denotes that it should be called in case the user requests the root URL of our application.
In the Amazon AWS cloud, the proxy server in front of our application will route all incoming traffic to port 5000. This forces us to start our application on this specific port. Hence, we create a file application.properties
with the following content and place it under src/main/resources
:
server.port=5000
Now we are able to start the application on the command line:
mvn spring-boot:run
This will produce an output similar to the following (shortened) one:
[...]
[INFO] --- spring-boot-maven-plugin:1.5.9.RELEASE:run (default-cli) @ spring-boot-web ---
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.9.RELEASE)
[...]
Pressing Ctrl+C
will terminate the application. As we do not want to test all the code manually after having it uploaded to the cloud, we write a small integration test like the following one:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Example.class },
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class ExampleTest {
@Test
public void testHelloWorld() {
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:5000").path("/");
Response response = target.request().get();
assertThat(response.getStatus(), is(200));
}
}
It is a simple junit test that runs with a SpringRunner
. The annotation SpringBootTest
tells the framework which resource we want to test and the port to use. The actual code of our test is simple and uses a jersey client to invoke the URL http://localhost:5000/
.
After we have received the response, we verify its status code. Knowing that everything works as expected locally, we create a new environment on Amazon Beanstalk. The procedure is the same as before, but now we chose the “Java” instead of the “Tomcat” environment:
Once the new environment has been created, we can enter the following URL into our web browser and check that it works:
http://spring-boot-web-env.eu-central-1.elasticbeanstalk.com/
Please replace spring-boot-web-env
and eu-central-1
with the CNAME and region of your environment. Now you should see the string “Hello World” in your browser. Note that we use the standard port 80 to invoke the REST API and not the port 5000, as the proxy in front of our application listens on the standard port and only dispatches this to port 5000 of our application.
5.下载源代码
This was an Amazon Elastic Beanstalk Tutorial.
You can download the full source codes of this example here:
翻译自: https://www.javacodegeeks.com/2017/12/amazon-elastic-beanstalk-tutorial.html
elastic pdf