ПРИМЕЧАНИЕ ДЛЯ РЕДАКЦИИ: AWS Elastic Beanstalk — это сервис оркестрации, предлагаемый Amazon Web Services для развертывания инфраструктуры, которая управляет различными сервисами AWS, включая EC2, S3, Simple Notification Service (SNS), CloudWatch, автоматическое масштабирование и Elastic Load Balancers.
Elastic Beanstalk обеспечивает дополнительный уровень абстракции над пустым сервером и ОС; вместо этого пользователи видят предварительно созданную комбинацию ОС и платформы.
Для развертывания требуется определить ряд компонентов: «приложение» в качестве логического контейнера для проекта, «версия», представляющая собой развертываемую сборку исполняемого файла приложения, «шаблон конфигурации», который содержит информацию о конфигурации для среды 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, который используется для запуска определенной версии приложения. Различные среды могут быть настроены, и версия приложения может работать в разных средах.
Разные среды могут существовать одновременно, а также могут обслуживать разные версии одного и того же приложения. Среда состоит из двух уровней: среды веб-сервера и рабочей среды . В то время как среда веб-сервера обслуживает HTTP-запросы, рабочая среда читает сообщения из очереди и обрабатывает их. Таким образом, приложения могут использовать шаблон «рабочая очередь», чтобы отделить бизнес-логику от обслуживания HTTP-запроса. Конфигурация среды включает в себя настройки среды. Применение этой конфигурации позволит Amazon AWS создавать соответствующие ресурсы. Существующие шаблоны могут использоваться для создания конфигураций и поэтому называются шаблонами конфигурации .
2.1 Среда веб-сервера
«Среда веб-сервера» предоставляет все ресурсы для запуска вашего приложения. Он состоит из одного или нескольких веб-серверов, на которых развернуто ваше приложение. Эти веб-серверы размещаются на компьютерах EC2, работающих в облаке Amazon AWS. Поскольку эти машины находятся за Elastic Load Balancer, к ним можно обратиться по имени CNAME, например myapp.us-west-2.elasticbeanstalk.com
. Это CNAME связывается с внутренним URL-адресом балансировки нагрузки с использованием службы Route 53. Этот сервис Amazon обеспечивает высокую доступность благодаря использованию Системы доменных имен (DNS). Машины EC2 вашей среды могут быть частью группы «Автоматическое масштабирование».
Это означает, что Amazon автоматически увеличивает количество экземпляров, если увеличивается нагрузка, и, с другой стороны, уменьшает количество машин, по крайней мере, до одной машины, если нагрузка исчезает. «Диспетчер хостов», работающий на каждой машине, выполняет задачу развертывания приложения и сбора метрик и событий. Он также контролирует сервер приложений и при необходимости поворачивает файлы журнала. Если одному компоненту среды требуется исправление или обновление, «Администратор хоста» может выполнить это обновление от вашего имени. «Группы безопасности» определяют правила брандмауэра для набора экземпляров. Основная группа безопасности в среде веб-сервера обеспечивает доступ к порту 80 (HTTP) вашего приложения. Если вам нужно использовать базу данных, вы можете определять другие группы безопасности с более детальным контролем.
2.2 Рабочая среда
«Рабочая среда» предоставляет все ресурсы для запуска рабочих приложений, которые используют сообщения из очереди Amazon SQS. Поэтому он не только обеспечивает компьютеры EC2, на которых выполняется ваше рабочее приложение, но и очередь SQS, которую можно использовать для передачи сообщений из «среды веб-сервера» в «рабочую среду» и обратно. Демон, работающий в каждом экземпляре «Рабочей среды», извлекает запросы из очереди и доставляет их в ваше приложение.
2.3 Рассмотрение проекта
Перед запуском приложения необходимо тщательно продумать его дизайн. Одним из важных аспектов является масштабируемость. По сути, есть два разных способа справиться с возрастающей нагрузкой на приложение. Первый способ — увеличить доступные аппаратные ресурсы для каждой машины, чтобы она могла справиться с большей нагрузкой. Хотя этот способ можно использовать для небольшого увеличения нагрузки, он не подходит для произвольных требований.
Второй способ — увеличить количество запущенных сервисов (горизонтальное масштабирование), так как это позволяет добавлять больше машин при необходимости. За этим вторым способом следует Amazon Elastic Beanstalk, поскольку тщательный мониторинг доступных экземпляров EC2 позволяет при необходимости настраивать новые экземпляры в группе автоматического масштабирования. Но это также означает, что приложение разработано и написано для возможности масштабирования. Вместо создания монолитного приложения, которому требуется все больше и больше оборудования, горизонтальное масштабирование означает распределение нагрузки по произвольному количеству небольших служб, которые как можно меньше сохраняют состояние, так что новые службы могут быть добавлены во время выполнения при необходимости.
Балансировщик нагрузки перед средой веб-сервера будет распределять входящие запросы среди доступных служб, требуя, чтобы каждая служба могла обрабатывать одну из них. Другим важным моментом при разработке приложения для AWS Elastic Beanstalk является безопасность. Данные, передаваемые в и из среды веб-сервера, могут быть зашифрованы с использованием SSL. Поэтому необходимо приобрести действительный сертификат в одном из внешних сертификационных органов (например, VeriSign или Entrust). Обратите внимание, что шифрование SSL заканчивается на балансировщике нагрузки среды и что трафик между ним и веб-серверами обычно не шифруется.
Чтобы иметь возможность запускать и останавливать дополнительные экземпляры, когда это необходимо, ваше приложение не должно хранить никаких данных в локальном хранилище каждого узла, поскольку эти данные будут удалены после закрытия экземпляра и его не будет при запуске другого экземпляра. , Следовательно, нужно заботиться о постоянном хранении. Amazon AWS предлагает различные сервисы, которые можно использовать из вашего приложения для хранения состояния:
- Amazon S3: этот сервис можно использовать для хранения произвольных объемов данных в облаке.
- Amazon Elastic File System: EFS может быть смонтирована на вашем экземпляре EC2 и использоваться как обычная файловая система.
- Amazon Elastic Block Store: тома EBS подключены к экземплярам EC2 и могут использоваться в сочетании с файловыми системами или базами данных.
- Amazon DynamoDB: эта служба предоставляет базу данных NoSQL в облаке Amazon.
- Сервис реляционных баз данных Amazon: RDS управляет шестью различными механизмами реляционных баз данных (Amazon Aurora, PostgreSQL, MySQL, MariaDB, Oracle и Microsoft SQL Server), которые могут использоваться вашим приложением.
Если пользователи по всему миру испытывают разное время ожидания, вы можете использовать Amazon CloudFront, чтобы он автоматически распределял ваше приложение и хранилище по всему миру и направлял пользователя на ближайший доступный сайт. И последнее, но не менее важное: вы можете позволить Amazon обновлять и исправлять вашу среду, если это станет необходимым. Эти обновления выполняются в виде «скользящих пакетов», т. Е. Amazon берет первую партию экземпляров EC2 и завершает их.
После появления новых экземпляров можно обработать следующую партию. Условие, следует ли обрабатывать следующую партию или нет, может основываться либо на политике синхронизации, либо на работоспособности новых машин. Эта процедура позволяет поддерживать работу сайта, пока старые и новые сервисы вашего приложения работают в течение короткого периода времени. Чтобы это работало, вы, конечно, должны спроектировать свое приложение таким образом, чтобы старые версионные службы могли, например, считывать данные, уже записанные новой версией.
3. Веб-приложение Java
Теперь, когда мы узнали много нового о концепциях Amazon Elastic Beanstalk, нам нужно запачкать руки и разработать небольшое приложение, предоставляющее простой интерфейс REST с использованием Apache Tomcat.
3.1 Простой REST-API
Для начала мы создадим новый проект maven со следующей командной строкой:
1
|
mvn archetype:generate -DgroupId=com.javacodegeeks.ultimate.aws.eb -DartifactId=tomcat-web-service -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode= false |
После этого у нас есть новый каталог с именем tomcat-web-service
со следующей структурой:
1
2
3
4
5
6
7
|
|-- pom.xml `-- src |-- main | `-- webapp | `-- index.jsp | `-- WEB-INF | `-- web.xml |
Архетип уже создал для нас web.xml
и файл index.jsp
. Последний может быть использован для последующего простого тестирования первой версии в облаке, поэтому мы пока не удаляем страницу JSP. Файл web.xml
нуждается в некотором редактировании:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<! DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" < 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 > |
С помощью display-name
элемента XML мы определяем, как приложение будет помечено внутри сервера Apache Tomcat. Элементы servlet
и servlet-mapping
определяют класс Servlet, который прослушивает запросы, и шаблон URL, который он должен прослушивать. В нашем случае мы используем шаблон /tutorial-service/*
, то есть все URL, которые выглядят как http://://tutorial-service/*
будут обрабатываться этим сервлетом.
Имя контекста определяется через имя военного архива, который мы разворачиваем в tomcat. Параметр jersey.config.server.provider.packages
сообщает реализации JAX-B, которую мы собираемся использовать для реализации REST-API, какие пакеты Java следует сканировать на наличие аннотаций. Чтобы сделать это, нам нужно создать следующую структуру каталогов в нашем проекте maven: src/main/java/com/javacodegeeks/ultimate/aws/eb
. Чтобы сообщить maven, какую версию реализации JAX-B мы хотим использовать, мы копируем следующий блок информации о зависимостях в ваш файл pom.xml
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
< 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 на следующих шагах, мы уже включили его здесь. Раздел build
pom.xml
снова немного pom.xml
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
< 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 > < 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
:
1
2
3
4
|
<? xml version = '1.0' encoding = 'utf-8' ?> < Context > < WatchedResource >WEB-INF/web.xml</ WatchedResource > </ Context > |
Подготовив все, как описано выше, мы готовы развивать наш первый класс:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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
на уровне метода. То, что при вызове этого ресурса REST будет возвращаться строка JSON, указывается с аннотацией @Produces
и медиа-типом text/json
.
Наконец, мы сообщаем платформе, что этот метод является GET-запросом, используя выделенную аннотацию @GET
. В настоящее время у нас нет постоянного хранилища, поэтому мы не можем загрузить данные из источника данных. Поэтому мы жестко связываем список курсов и возвращаем список объектов Tutorial
. Класс Tutorial
— это простой объект значений:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
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
выше файле pom.xml
мы настроили, чтобы интеграционные тесты назывались с IntegrationTest
в имени класса. Следовательно, мы создаем класс в src/test/java
со следующим содержимым:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public class TutorialIntegrationTest { @Test public void testListAllCourses() { Client client = ClientBuilder.newClient(); .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, а во второй строке testListAllCourses()
хост, порт и путь на сервере. Для наших локальных тестов подходит localhost
. Tomcat запускается по умолчанию на порту 8080. URL-адрес состоит из первой части имени нашего файла военных действий, который развернут в Tomcat. Мы определили это в файле pom.xml
используя XML-элемент finalName
.
GET-запросы выдаются с использованием метода get()
для объекта, возвращаемого request()
. Если все в порядке, веб-сервис должен вернуть код состояния 200. В этом случае он также должен возвращать список объектов Tutorial
. Метод readEntity()
позволяет нам определить это, используя экземпляр GenericType
с двумя универсальными типами. Один для List
и один внутренний тип для Tutorial
. Полученный список должен содержать ровно три записи, по одной записи для каждого курса. Чтобы проверить все локально, мы запускаем следующую команду на локальном терминале:
1
|
mvn clean verify |
Поскольку этап verify
наступает после этапа integration-test
, этот вызов также запустит ранее написанные интеграционные тесты:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[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 Развертывание с использованием веб-интерфейса
Если вы еще не создали учетную запись Amazon AWS, сделайте это сейчас, указав в своем браузере следующий URL-адрес и нажав ссылку «Создать учетную запись AWS». На следующих этапах вам нужно будет предоставить типичную личную информацию и действующую кредитную карту. Последнее необходимо для того, чтобы Amazon выставил счет за использованные вами ресурсы. Когда вы создаете новую учетную запись, вы имеете право на «бесплатный уровень».
В течение первых 12 месяцев вы можете использовать до 750 часов вычислительной мощности в EC2, 5 ГБ в стандартной памяти на S3. Этого более чем достаточно для нашего урока. После того, как вы настроили рабочую учетную запись AWS, вы можете создать свое первое приложение. Поэтому укажите вашему браузеру следующий URL-адрес и введите название приложения, а также необязательное описание:
На следующей странице спрашивается, собираемся ли мы настраивать среду веб-сервера или рабочую среду. Наше примерное приложение REST лучше всего подходит для среды первого типа; следовательно, мы нажимаем «Создать веб-сервер».
Для среды веб-сервера мы должны установить конфигурацию и тип. Мы выбираем «Tomcat» в качестве предопределенной конфигурации и «Единственный экземпляр» в качестве типа. Таким образом, Amazon предоставляет нам экземпляр EC2 с установленным сервером Apache Tomcat. Мы не выбираем автоматическое масштабирование в этой точке, потому что для нашего примера достаточно одного экземпляра.
На следующей странице запрашивается указание версии приложения. Поэтому мы выбираем второй вариант и выбираем файл войны, который наша сборка maven создала ранее.
В качестве альтернативы мы могли бы также предоставить ссылку на артефакт, который мы загрузили ранее в Amazon S3, или выбрать одно из примеров приложений Amazon.
Как упоминалось ранее, наше приложение получает собственный CNAME. Поэтому CNAME может быть предоставлена на следующей странице. «Проверить наличие» позволяет нам убедиться, что имя все еще свободно. Если бы мы планировали использовать реляционную базу данных или виртуальную частную облачную сеть. В нашем простом примере нам не нужны оба ресурса, поэтому мы просто нажимаем «Далее»
В разделе «Подробности конфигурации» мы можем выбрать тип сервера. Для наших экспериментов достаточно экземпляра t2.micro, но если вы предпочитаете, вы можете выбрать более крупный экземпляр. Документация EC2 описывает доступные типы экземпляров более подробно . Остальные поля ввода можно оставить как есть, поскольку у нас нет особых требований к диску экземпляров или отчетам о работоспособности. Пара ключей EC2 также не требуется.
Теги среды можно использовать для идентификации сред в отчетах о распределении затрат или для общего управления средами и разрешениями. Для нашего первого примера приложения теги не нужны, но вы можете предоставить их.
Страница «Разрешения» позволяет определить профиль экземпляра и роль службы. Профиль экземпляра — это роль IAM, которая используется вашим приложением для связи с другими сервисами AWS, а роль сервиса — для мониторинга среды.
Наконец, на странице обзора представлена вся информация для проверки. Если вы удовлетворены своим выбором, вы можете нажать «Запустить» и позволить Amazon AWS создать все ресурсы для вас. После завершения процесса вы можете увидеть новую среду в консоли:
Щелчок по этой среде приводит к следующей панели инструментов:
Здесь вы можете увидеть все события вашей среды, работающей версии и конфигурации. Теперь, когда приложение запущено и работает, мы можем указать нашему браузеру следующий URL:
Как и ожидалось, браузер отображает массив JSON с тремя элементами Tutorial
.
3.3 Развертывание с использованием CLI
Использование веб-консоли достаточно, если вам не приходится часто ее использовать. Если вы стремитесь к автоматизации, вы можете использовать инструмент awccli
для создания сред из интерфейса командной строки (CLI). Для этого сначала нужно установить python и pip . Оба веб-сайта содержат информацию о том, как установить и настроить эти два инструмента для общих операционных систем. После того, как вы запустили python и pip, вы можете просто установить awscli
используя следующую команду:
1
|
pip install awsebcli --upgrade --user |
Чтобы иметь возможность напрямую запускать команду eb
, вы должны включить ее в свой путь. В системах Windows вам нужно добавить следующий путь к PATH
среды PATH
: %USERPROFILE%\AppData\Roaming\Python \Python36\scripts
. В системах на базе Linux и MacOS вам не нужно изменять какие-либо переменные среды. Первый шаг — инициализировать настройки по умолчанию, вызвав команду eb init
в корневой папке вашего проекта maven:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
~ /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:
1
2
3
4
|
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:
1
2
3
|
Select an application to use 1) [ Create new Application ] (default is 1): 1 |
Теперь вы можете ввести название приложения:
1
2
3
|
Enter Application Name (default is "eb" ): tomcat-web-service Application eb has been created. |
Как и в веб-консоли, мы должны выбрать предварительно настроенную платформу для нашего приложения. Здесь мы выбираем Tomcat, а не Java (!), Иначе нашему приложению придется открывать собственный порт и обслуживать запросы.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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. В этом простом примере мы не хотим делать это:
1
2
|
Do you want to set up SSH for your instances? (y /n ): n |
Поскольку инструмент eb
не знает, где находится наш артефакт, мы должны указать его, используя файл YAML, хранящийся в .elasticbeanstalk/config.yml
со следующим содержимым:
1
2
|
deploy: artifact: target /tomcat-web-service .war |
Теперь вы можете развернуть артефакт с помощью следующей команды:
1
|
eb deploy --staged |
3.4 Использование DynamoDB
До сих пор наше приложение не хранит никаких данных. Как указывалось ранее, мы потеряем все состояние на локальном компьютере, когда текущий компьютер отключится, потому что механизм автоматического масштабирования больше не нуждается в нем. Следовательно, мы должны хранить состояние приложения во внешнем хранилище данных. Один вариант, который часто оказывается полезным, — это база данных Amazon NoSQL «DynamoDB». В DynamoDB данные хранятся в таблицах, как в реляционных базах данных.
Таблица представляет собой набор элементов, каждый элемент состоит из одного или нескольких атрибутов. Атрибут является парой ключ / значение. В отличие от реляционных баз данных здесь нет схемы, т. Е. Каждый элемент может иметь разные атрибуты. Например, вы можете создать таблицу для хранения событий. Каждое событие является элементом, а данные, описывающие конкретное событие, хранятся с использованием таких атрибутов, как время его создания, тип события и т. Д. Каждый элемент имеет хеш-ключ и, необязательно, дополнительный ключ сортировки. Хэш-ключ используется для распределения элементов в больших таблицах по различным разделам. Если таблица имеет ключ сортировки, она используется для сортировки элементов с одинаковым хэш-ключом.
Это облегчает сортировку всех элементов с определенным хеш-ключом. Секционирование позволяет распределять данные по различным машинам и, следовательно, является механизмом для горизонтального масштабирования приложения. Зная основы DynamoDB, мы можем расширить наше приложение-образец другим ресурсом, который позволяет нам создавать и удалять таблицы, необходимые для нашего приложения. Мы сделаем это с помощью Amazon AWS SKD; следовательно, мы должны определить зависимость от его части DynamoDB:
1
2
|
com.amazonaws aws-java-sdk-dynamodb |
Конкретная версия выводится из aws-java-sdk-bom
, которую мы определили в dependencyManagement
в самом начале этого урока. Далее мы создаем новый класс DbResource
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
@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( .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
который возвращает список всех существующих таблиц в нашем экземпляре 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
Поскольку наш веб-сервис будет развернут на экземпляре EC2, учетные данные предоставляются средой AWS и поэтому не должны храниться в нашем приложении. В случае, если мы используем локальную конечную точку, мы можем использовать фиктивные значения и регион по нашему выбору. Как установить и запустить локальный экземпляр DynamoDB, объясняется здесь . Системное свойство может быть установлено автоматически при запуске сервера Tomcat, поместив следующий фрагмент внутри configuration
подключаемого tomcat7-maven-plugin
:
1
|
true |
Чтобы протестировать описанную выше функциональность перед ее развертыванием в облаке, мы пишем следующий небольшой интеграционный тест:
01
02
03
04
05
06
07
08
09
10
|
@Test public void testListAllTables() { Client client = ClientBuilder.newClient(); .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 и проверяет, что возвращается пустой список строк. Точно так же мы можем написать код для создания и удаления таблиц:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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()
метод createAllTables()
определяет таблицу для хранения учебных пособий. Атрибут tutorial
используется в качестве хэш-ключа. Для нашего примера приложения достаточно емкости чтения и записи, равной 1 единице емкости. Для реальных приложений это может быть недостаточно. Наконец, код ожидает готовности таблиц. Обратите внимание, что нам не нужно указывать все атрибуты при создании таблицы. Теперь мы можем изменить наш существующий метод listAllCourses()
чтобы использовать новую таблицу tutorials
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
@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
и ввода значения ключа :a
в него. Это HashMap
тогда установлено на QueryRequest
.
Результатом вызова queryResult.getItems()
является список, который содержит экземпляры карты. Каждая карта представляет элемент, его пары ключ / значение хранятся внутри карты. Следовательно, мы перебираем этот список и преобразуем каждый элемент в экземпляр нашего класса Tutorial
и возвращаем список руководств вызывающей стороне метода REST. Преобразование объектов Java в строки JSON выполняется с помощью фреймворка. Теперь мы можем вызвать следующие URL для создания таблиц и перечисления всех авторов:
1
2
3
|
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 |
В URL-адресах над строкой <your-app>
и регионом замените CNAME и регион вашего приложения. Удаление всех таблиц, конечно, не обязательно, но в этом случае это не наносит вреда, поскольку мы удаляем только те таблицы, которые существуют. Теперь вам нужно реализовать метод, который вставляет данные в таблицу.
3.5 Использование RDS
Рядом с DynamoDB вы также можете использовать реляционную базу данных. Поэтому мы переходим в Консоль AWS к нашей среде и выбираем пункт меню «Конфигурация». В нижней части страницы конфигурации мы находим ссылку «создать новую базу данных RDS»:
После нажатия на эту кнопку появляется следующая страница
На этой странице мы должны указать основные детали конфигурации для экземпляра RDS. Поскольку у нас нет существующего снимка, мы выбрали «Нет» в соответствующем поле. В качестве движка БД мы выбрали «MySQL» в версии 5.6.37. Предполагается, что эта БД будет работать на «микро» экземпляре с 5 ГБ памяти. «Политика удаления» определяет, что должно произойти, если мы удалим экземпляр. Поскольку это учебное пособие, мы не заботимся о данных и разрешаем Amazon удалить экземпляр и все его данные.
Кроме того, вы также можете указать, чтобы создать снимок базы данных. Для нашего примера приложения также достаточно «единой зоны доступности». После предоставления основного имени пользователя и пароля, мы можем нажать «Применить». Теперь может пройти до 10 минут, пока база данных RDS не станет доступной. А пока мы можем настроить список maven dependencies
в pom.xml
файле и добавить соединитель mysql, как показано ниже:
1
2
3
4
5
|
< dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < version >5.1.44</ version > </ dependency > |
Обратите внимание, что мы должны установить область действия этой зависимости compile
, поскольку указанный файл jar должен быть помещен в папку lib нашей войны. В противном случае классы драйверов MySQL были бы недоступны во время выполнения. Далее мы создаем новый класс с именем RdsResource
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
@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; } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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 ; } } |
Он предоставит URL, который заканчивается /rds/list-all-tables
и возвращает список всех доступных таблиц MySQL. Поэтому метод listAllTables()
вызывает закрытый метод listAllTablesIntern
и возвращает HTTP-ответ с кодом состояния 200, если исключение не произошло, или кодом состояния 500, если перечисление таблиц не удалось.
Частный метод listAllTablesIntern()
использует другой частный метод для получения соединения с базой данных: createConnection()
. В среде Tomcat вся необходимая информация передается приложению через системные свойства. Запрос этих предопределенных системных свойств позволяет нам создать URL JDBC в следующей форме:
1
|
jdbc:mysql: //" + hostname + ":" + port + "/" + dbName |
В реальном приложении это может стать URL-адресом следующей формы:
1
|
jdbc:mysql: //aa1im36v00yvfox.cwazv5kmikco.eu-central-1.rds.amazonaws.com:3306/ebdb |
Имея соединение JDBC в наших руках, мы можем выполнить инструкцию SQL, которая перечисляет все существующие таблицы базы данных:
1
2
3
4
5
6
7
|
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 в нашем браузере:
1
|
http: // .eu-central-1.elasticbeanstalk.com /tomcat-web-service/rds/list-all-tables |
Еще раз: замените в вышеуказанных URL-адресах строку <your-app>
и регион на CNAME и регион вашего приложения. Это должно показать пустой массив JSON. Настроив все как описано выше, мы теперь можем реализовать методы для создания и удаления таблиц нашего приложения:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@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(); } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@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 для создания и удаления таблиц выполняются через API JDBC с использованием его executeUpdate()
метода. Мы закрываем каждое утверждение и соединение после его использования, чтобы освободить ресурсы. Обратите внимание, что в реальном приложении вы можете объединить соединения, чтобы повторно использовать существующее для следующего запроса. Изменение кода, который вставляет и запрашивает учебники, остается за читателем.
4. Веб-приложение Java с Spring Boot
В предыдущей главе объяснялось, как использовать среду Apache Tomcat для запуска простого сервера REST API. То же самое можно сделать с помощью популярного фреймворка Spring Boot . Поэтому в этой главе мы собираемся создать приложение Spring Boot, которое работает в среде «Java» в AWS, а не в среде «Tomcat».
Обратите внимание, что мы также можем создать приложение Spring Boot для запуска в среде Tomcat, но в демонстрационных целях мы запускаем наше приложение в автономном режиме. Сначала мы создаем новый проект maven с помощью следующей команды:
1
|
mvn archetype:generate -DgroupId=com.javacodegeeks.ultimate.aws.eb -DartifactId=spring-boot-web -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode= false |
Далее мы позволяем нашему проекту наследоваться от родительского проекта Spring Boot:
1
2
3
4
5
|
< parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >1.5.9.RELEASE</ version > </ parent > |
Это предоставляет нам все необходимые версии, но не добавляет никаких зависимостей. Следовательно, мы должны добавить их к нашему pom.xml
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
< 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 > |
Основная зависимость есть spring-boot-starter-web
. Другие используются для наших интеграционных тестов. spring-boot-maven-plugin
это плагин maven, который упаковывает приложение так, что мы можем легко запустить его из командной строки. Теперь мы можем реализовать простейшее из возможных приложений:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@RestController @EnableAutoConfiguration public class Example { @RequestMapping ( "/" ) String home() { return "Hello World!" ; } public static void main(String[] args) throws Exception { SpringApplication.run(Example. class , args); } } |
main()
Метод запускает Example
класс как контроллер и передает необязательные аргументы из командной строки к run()
методу из SpringApplication
. Аннотирование класса с помощью @RestController
превращает его в конечную точку REST. Метод home()
просто возвращает статическую строку. Его аннотация @RequestMapping("/")
обозначает, что он должен вызываться в случае, если пользователь запрашивает корневой URL нашего приложения.
В облаке Amazon AWS прокси-сервер перед нашим приложением будет направлять весь входящий трафик на порт 5000. Это заставляет нас запускать наше приложение на этом конкретном порту. Следовательно, мы создаем файл application.properties
со следующим содержимым и помещаем его в src/main/resources
:
1
|
server.port= 5000 |
Теперь мы можем запустить приложение из командной строки:
1
|
mvn spring-boot:run |
Это приведет к выводу, аналогичному следующему (сокращенному):
01
02
03
04
05
06
07
08
09
10
11
12
|
[...] [INFO] --- spring-boot-maven-plugin:1.5.9.RELEASE:run (default-cli) @ spring-boot-web --- . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | ' _| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/= /_/_/_/ :: Spring Boot :: (v1.5.9.RELEASE) [...] |
Нажатие Ctrl+C
завершит приложение. Поскольку мы не хотим тестировать весь код вручную после его загрузки в облако, мы пишем небольшой интеграционный тест, подобный следующему:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@RunWith (SpringRunner. class ) @SpringBootTest (classes = { Example. class }, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class ExampleTest { @Test public void testHelloWorld() { Client client = ClientBuilder.newClient(); Response response = target.request().get(); assertThat(response.getStatus(), is( 200 )); } } |
Это простой тест junit, который запускается с SpringRunner
. Аннотация SpringBootTest
сообщает платформе, какой ресурс мы хотим протестировать и какой порт использовать. Фактический код нашего теста прост и использует клиент Джерси для вызова URL http://localhost:5000/
.
После получения ответа мы проверяем его код состояния. Зная, что локально все работает как положено, мы создаем новую среду на Amazon Beanstalk. Процедура та же, что и раньше, но теперь мы выбрали «Java» вместо «Tomcat»:
После создания новой среды мы можем ввести следующий URL в наш веб-браузер и убедиться, что он работает:
1
|
http: //spring-boot-web-env .eu-central-1.elasticbeanstalk.com/ |
Пожалуйста, замените spring-boot-web-env
и eu-central-1
на CNAME и регион вашей среды. Теперь вы должны увидеть строку «Hello World» в вашем браузере. Обратите внимание, что мы используем стандартный порт 80 для вызова API REST, а не порт 5000, поскольку прокси-сервер перед нашим приложением прослушивает стандартный порт и отправляет его только на порт 5000 нашего приложения.
5. Загрузите исходный код
Это было учебное пособие по Elastic Beanstalk от Amazon.
Вы можете скачать полные исходные коды этого примера здесь: