Applications: Java, WildFly и Nginx.
Задача: описание этапов развёртывания типового web-приложения ("сервлета") в среде "WildFly".
Последовательность дальнейших действий такова:
1. Устанавливаем интерпретатор языка Java.
2. Устанавливаем контейнеризатор WildFly.
3. Устанавливаем Nginx и связываем его с WildFly.
4. Развёртываем Java-сервлет.
2. Устанавливаем контейнеризатор WildFly.
3. Устанавливаем Nginx и связываем его с WildFly.
4. Развёртываем Java-сервлет.
Подготовка среды исполнения Java-сервлетов.
Будем считать, что для работы нашего web-приложения требуется "Java v9", причём производства "Oracle".
Следуя предписаниям, загружаем с сайта "Oracle" последний стабильный релиз "Java SE JDK 9 (Linux)", где:
JRE (Java Runtime Environment) - это среда исполнения полностью готовых Java-приложений, предназначенная для пользователей; её ещё можно загрузить с сайта "http://java.com/|java.com".
JDK (Java Development Kit) - среда разработки Java-приложений - специальный пакет для разработчиков, включающий в себя документацию, различные библиотеки классов, утилиты, документацию, компилятор, а также систему исполнения JRE.
JDK (Java Development Kit) - среда разработки Java-приложений - специальный пакет для разработчиков, включающий в себя документацию, различные библиотеки классов, утилиты, документацию, компилятор, а также систему исполнения JRE.
Для работы "WildFly" в качестве сервера готовых приложений хватает и JRE, но на практике, с учётом того, что разработчики web-приложений часто не воспринимают отличия среды программирования от эксплуатационной и создают приложения зависимые от библиотек JDK - приходится использовать последнюю.
Установка "Java JRE/JDK" элементарна и сводится к распаковке в нужное место файловой системы дистрибутивного архива:
# mkdir -p /usr/lib/jdk
# tar -xvf ./jdk-9.0.1_linux-x64_bin.tar.gz -C /usr/lib/jdk
# tar -xvf ./jdk-9.0.1_linux-x64_bin.tar.gz -C /usr/lib/jdk
Сразу проверяем, запустится ли в нашей системе Java-интерпретатор как таковой:
# /usr/lib/jdk/jdk-9.0.1/bin/java -version
Не помешает сделать символическую ссылку на директорию с актуальной на данный момент инсталляцией JDK, чтобы в дальнейшем использовать её в конфигурации приложений, использующих Java:
# ln -s /usr/lib/jdk/jdk-9.0.1 /usr/lib/jdk/default
Регистрируем выбранную версию Java как предпочтительную в системе:
# update-alternatives --install /usr/bin/java java /usr/lib/jdk/jdk-9.0.1/bin/java 100
# update-alternatives --install /usr/bin/javac javac /usr/lib/jdk/jdk-9.0.1/bin/javac 100
# update-alternatives --install /usr/bin/javac javac /usr/lib/jdk/jdk-9.0.1/bin/javac 100
По мере выпуска и применения новых версий программного обеспечения в системе наверняка окажется зарегистрировано более одной версии Java. Какую-то из них желательно выбрать задействуемой по умолчанию:
# update-alternatives --config java
# update-alternatives --config javac
# update-alternatives --config javac
Сверяем текущую конфигурацию с нашими ожиданиями:
# update-alternatives --display java
# update-alternatives --display javac
# update-alternatives --display javac
Проверяем успешность регистрации приложения в системном окружении, вызывая его по короткому имени, без указания пути в файловой системе:
# java -version
Получение и развёртывание "WildFly".
Сервер приложений "Wildfly" - не вполне новый продукт. Это ребрендинг и развитие "JBoss Application Server", который в 2013-м году компания-разработчик "RedHat" явно отделила от своего коммерческого продукта "JBoss Enterprise Application Platform". Переименованием изменения не ограничились - в "WildFly" в качестве контейнера сервлетов вместо "Tomcat" стали использовать "Undertow".
"WildFly" - программный продукт с открытым исходным кодом, считается, что в его разработке участвует сообщество. Загрузка свободна, использование тоже - но поддержка платная (впрочем, она нам не требуется). Идём на сайт разработчиков, в раздел загрузки:
Для простого сервера web-приложений достаточно набора типа "Servlet-Only Distribution" (28 MB), но в реальности практически всегда приходится применять полный набор типа "Full & Web Distribution" (133 MB).
# cd /opt
# wget http://download.jboss.org/wildfly/10.1.0.Final/wildfly-10.1.0.Final.tar.gz
# tar -xzf wildfly-10.1.0.Final.tar.gz
# mv wildfly-10.1.0.Final wildfly-10
# wget http://download.jboss.org/wildfly/10.1.0.Final/wildfly-10.1.0.Final.tar.gz
# tar -xzf wildfly-10.1.0.Final.tar.gz
# mv wildfly-10.1.0.Final wildfly-10
Создаём символическую ссылку на директорию с актуальной инсталляцией "WildFly", чтобы в дальнейшем использовать её в конфигурации приложений, использующих таковой:
# ln -s /opt/wildfly-10 /opt/wildfly
После распаковки дистрибутива заводим пользователя, от имени которого будем запускать сервер приложений:
# groupadd --system wildfly
# useradd --system --home-dir /var/lib/wildfly --shell /bin/false --gid wildfly wildfly
# useradd --system --home-dir /var/lib/wildfly --shell /bin/false --gid wildfly wildfly
Я предпочитаю по возможности разделять сущности, оставляя исполняемые файлы сервера "WildFly" и его разделяемые библиотеки в "/opt/", а web-приложения и их данные вынести в директорию динамичных данных "/var".
# mkdir -p /etc/wildfly /var/lib/wildfly
Файловые ресурсы "WildFly" передаём во владение его пользователю:
# chown -R wildfly:wildfly /etc/wildfly /opt/wildfly-10 /var/lib/wildfly
# chmod -R o-rwx /etc/wildfly /opt/wildfly-10 /var/lib/wildfly
# chmod -R o-rwx /etc/wildfly /opt/wildfly-10 /var/lib/wildfly
Регистрация сервера приложений в "Systemd" и запуск сервиса.
Создаём файл описания параметров запуска и остановки "WildFly" в качестве сервиса, управляемого "Systemd":
# vi /etc/systemd/system/wildfly.service
[Unit]
Description=The WildFly Application Server
After=syslog.target network.target
[Service]
User=wildfly
Group=wildfly
LimitNOFILE=102642
Environment="WILDFLY_HOME=/opt/wildfly"
Environment="WILDFLY_CONFIG=standalone.xml"
Environment="WILDFLY_BIND=0.0.0.0"
Environment=LAUNCH_JBOSS_IN_BACKGROUND=1
PIDFile=/var/run/wildfly/wildfly.pid
ExecStart=/opt/wildfly/bin/standalone.sh -c ${WILDFLY_CONFIG} -b ${WILDFLY_BIND}
ExecStop=/opt/wildfly/bin/jboss-cli.sh --connect --command=:shutdown
StandardOutput=null
Restart=on-failure
[Install]
WantedBy=multi-user.target
Description=The WildFly Application Server
After=syslog.target network.target
[Service]
User=wildfly
Group=wildfly
LimitNOFILE=102642
Environment="WILDFLY_HOME=/opt/wildfly"
Environment="WILDFLY_CONFIG=standalone.xml"
Environment="WILDFLY_BIND=0.0.0.0"
Environment=LAUNCH_JBOSS_IN_BACKGROUND=1
PIDFile=/var/run/wildfly/wildfly.pid
ExecStart=/opt/wildfly/bin/standalone.sh -c ${WILDFLY_CONFIG} -b ${WILDFLY_BIND}
ExecStop=/opt/wildfly/bin/jboss-cli.sh --connect --command=:shutdown
StandardOutput=null
Restart=on-failure
[Install]
WantedBy=multi-user.target
Указываем "Systemd" перечитать и принять новую конфигурацию, а также явно активируем и запускаем новый сервис:
# systemctl daemon-reload
# systemctl enable wildfly
# systemctl start wildfly
# systemctl enable wildfly
# systemctl start wildfly
Сразу же можно удостоверится, прослушивает ли "WildFly" сетевые порты в ожидании запросов от пользователей:
# netstat -apn | grep -i java
tcp ... 0.0.0.0:8080 0.0.0.0:* LISTEN 5026/java
tcp ... 0.0.0.0:8443 0.0.0.0:* LISTEN 5026/java
tcp ... 127.0.0.1:9990 0.0.0.0:* LISTEN 5026/java
tcp ... 0.0.0.0:8443 0.0.0.0:* LISTEN 5026/java
tcp ... 127.0.0.1:9990 0.0.0.0:* LISTEN 5026/java
Настройка доступа к консоли управления "WildFly".
Все протоколы взаимодействия с "WildFly" мультиплексированы на два порта: TCP:8080 (практикуется ещё HTTPS на TCP:8443) для приложений и TCP:9990 для управления - это видно на выводе утилиты "netstat" в примере выше.
По умолчанию сразу после инсталляции "WildFly" управление таковым возможно лишь с адреса интерфейса "локальной петли", но наверняка понадобится доступ к этой подсистеме с удалённых узлов сети.
Открываем доступ извне, с определённого IP или диапазона таковых:
# vi /opt/wildfly/standalone/configuration/standalone.xml
....
<interfaces>
<interface name="management">
<!-- inet-address value="${jboss.bind.address.management:127.0.0.1}"/ -->
<inet-address value="${jboss.bind.address.management:0.0.0.0}"/>
</interface>
....
<interfaces>
<interface name="management">
<!-- inet-address value="${jboss.bind.address.management:127.0.0.1}"/ -->
<inet-address value="${jboss.bind.address.management:0.0.0.0}"/>
</interface>
....
Очень желательно явно ограничить перечень узлов сети, которым разрешён доступ к консоли управления "WildFly", например простейшим набором правил защитного экрана "iptables".
Подключение к подсистеме управления разрешено лишь после аутентификации соответствующих пользователей, аккаунты которых следует ещё создать:
# /opt/wildfly/bin/add-user.sh -u userOne -p passwordForUserOne
....
Added user 'userOne' to file '/opt/wildfly/standalone/configuration/mgmt-users.properties'
Added user 'userOne' to file '/opt/wildfly/domain/configuration/mgmt-users.properties'
Added user 'userOne' to file '/opt/wildfly/standalone/configuration/mgmt-users.properties'
Added user 'userOne' to file '/opt/wildfly/domain/configuration/mgmt-users.properties'
Теперь можно обратится посредством CLI-консоли к "WildFly" и отдать ему какую-нибудь команду - перезагрузки сервиса, например:
# /opt/wildfly/bin/jboss-cli.sh --connect controller=127.0.0.1 --command=:reload
Также созданному пользователю "userOne" доступен web-интерфейс управления "WildFly":
http://wildfly.example.net:9990/console/
Установка фронтального web-сервера "Nginx".
Несмотря на то, что "WildFly" вполне способен выступать в роли web-сервера, на практике для приёма и первичной обработки клиентских подключений удобнее использовать более легковесное приложение:
# aptitude install nginx
Конфигурация принимающего подключения пользователей web-сервера "Nginx" проста и сводится к описанию параметров "проксирования" всех запросов нижележащему "WildFly":
# vi /etc/nginx/nginx.conf
....
http {
....
# Велим Nginx не выдавать сведения о номере своей версии
server_tokens off;
# Отключаем проверку размера тела передаваемого WildFly запроса
client_max_body_size 0;
# Увеличиваем размер блоков данных, которыми обмениваются Nginx и WildFly
client_body_buffer_size 4M;
....
http {
....
# Велим Nginx не выдавать сведения о номере своей версии
server_tokens off;
# Отключаем проверку размера тела передаваемого WildFly запроса
client_max_body_size 0;
# Увеличиваем размер блоков данных, которыми обмениваются Nginx и WildFly
client_body_buffer_size 4M;
....
# vi /etc/nginx/sites-available/wildfly.example.net.conf
# Блок перехвата обращений посредством открытого HTTP и перенаправления таковых на HTTPS
server {
listen 80 default_server;
server_name wildfly.example.net;
access_log off;
error_log off;
rewrite ^(.+)$ https://wildfly.example.net$1 permanent;
}
# Блок описания web-сервиса приёма, терминирования SSL/TLS запросов и проксирования их нижележащему WildFly
server {
listen 443 ssl http2;
server_name wildfly.example.net;
access_log /var/log/nginx/wildfly.example.net_access.log;
error_log /var/log/nginx/wildfly.example.net_error.log;
# SSL Configuration
ssl on;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_certificate /etc/nginx/ssl/wildfly.example.net.crt;
ssl_certificate_key /etc/nginx/ssl/wildfly.example.net.key.decrypt;
# Перенаправление на страницу основного web-приложения, при запросе корня web-ресурса
location = / {
rewrite ^ $scheme://$host/site/ last;
}
# Отправляем все запросы на обработку в WildFly
location / {
proxy_pass http://localhost:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
# Включаем поддержку смешанных (HTTP/1.1 и WebSocket) запросов в одном потоке "клиент - прокси - сервер"
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80 default_server;
server_name wildfly.example.net;
access_log off;
error_log off;
rewrite ^(.+)$ https://wildfly.example.net$1 permanent;
}
# Блок описания web-сервиса приёма, терминирования SSL/TLS запросов и проксирования их нижележащему WildFly
server {
listen 443 ssl http2;
server_name wildfly.example.net;
access_log /var/log/nginx/wildfly.example.net_access.log;
error_log /var/log/nginx/wildfly.example.net_error.log;
# SSL Configuration
ssl on;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_certificate /etc/nginx/ssl/wildfly.example.net.crt;
ssl_certificate_key /etc/nginx/ssl/wildfly.example.net.key.decrypt;
# Перенаправление на страницу основного web-приложения, при запросе корня web-ресурса
location = / {
rewrite ^ $scheme://$host/site/ last;
}
# Отправляем все запросы на обработку в WildFly
location / {
proxy_pass http://localhost:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
# Включаем поддержку смешанных (HTTP/1.1 и WebSocket) запросов в одном потоке "клиент - прокси - сервер"
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Удаляем конфигурацию сайта "по умолчанию", активируем новую, проверяем её корректность и запускаем в работу:
# rm /etc/nginx/sites-enabled/default
# ln -s /etc/nginx/sites-available/wildfly.example.net.conf /etc/nginx/sites-enabled/wildfly.example.net.conf
# nginx -t
# /etc/init.d/nginx reload
# ln -s /etc/nginx/sites-available/wildfly.example.net.conf /etc/nginx/sites-enabled/wildfly.example.net.conf
# nginx -t
# /etc/init.d/nginx reload
Адаптация "WildFly" для работы в спарке с "Nginx".
С учётом того, что запросы пользователей будут приниматься web-сервером "Nginx", а обрабатываться скрытым за ним "WildFly", потребуется согласовать адреса FQDN и номера сетевых портов терминирования, которые должны сохранятся неизменными на протяжении всей цепочки обработки запроса, при передачи от сервера к серверу.
В самом простом случае достаточно уведомить наш web-сервис "WildFly" о параметрах терминирования клиентских запросов вышестоящим проксирующим web-сервисом, чтобы в ответах таковому вместо внутренних имени сайта "localhost", порта "8080" и протокола "http" было указано то, что видится клиентам снаружи. Для этого достаточно модифицировать всего пару секций в XML-файле конфигурации "WildFly":
# vi /opt/wildfly/standalone/configuration/standalone.xml
....
<!-- Блок перечисления активируемых при запуске "WildFly" сетевых портов. -->
<socket-binding-group name="standard-sockets" default-interface="public" ... >
....
<!-- Единственная точка входа клиентских запросов. -->
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<!-- Параметры терминирования запросов вышестоящим проксирующим web-сервисом. -->
<socket-binding name="proxy-https" port="443" />
....
</socket-binding-group>
....
<!-- Блок перечисления активируемых при запуске "WildFly" сетевых интерфейсов. -->
<interfaces>
....
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
</interfaces>
....
<!-- Блок описания конфигурации web-сервиса, обслуживающего HTTP-запросы. -->
<subsystem xmlns="urn:jboss:domain:undertow:3.0">
....
<server name="default-server">
....
<!-- Все запросы внутри схемы обработки данных проходят по HTTP. -->
<!-- (защита HTTPS реализована на вышестоящем web-сервере проксирования) -->
<http-listener name="default" socket-binding="http"
<!-- Деактивируем невостребованный в схеме обслуживания SSL/TLS вышестоящим Nginx параметр редиректа для обслуживания входящих HTTPS-запросов. -->
<!-- redirect-socket="proxy-https" -->
<!-- Указываем "WildFly", что запросы к нему проходят через web-"прокси", точка внешнего терминирования которого передаётся в параметрах "X-Forwarded-Host", "X-Forwarded-For", "X-Real-IP" и "X-Forwarded-Proto". -->
proxy-address-forwarding="true" />
....
</server>
....
</subsystem>
....
<!-- Блок перечисления активируемых при запуске "WildFly" сетевых портов. -->
<socket-binding-group name="standard-sockets" default-interface="public" ... >
....
<!-- Единственная точка входа клиентских запросов. -->
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<!-- Параметры терминирования запросов вышестоящим проксирующим web-сервисом. -->
<socket-binding name="proxy-https" port="443" />
....
</socket-binding-group>
....
<!-- Блок перечисления активируемых при запуске "WildFly" сетевых интерфейсов. -->
<interfaces>
....
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
</interfaces>
....
<!-- Блок описания конфигурации web-сервиса, обслуживающего HTTP-запросы. -->
<subsystem xmlns="urn:jboss:domain:undertow:3.0">
....
<server name="default-server">
....
<!-- Все запросы внутри схемы обработки данных проходят по HTTP. -->
<!-- (защита HTTPS реализована на вышестоящем web-сервере проксирования) -->
<http-listener name="default" socket-binding="http"
<!-- Деактивируем невостребованный в схеме обслуживания SSL/TLS вышестоящим Nginx параметр редиректа для обслуживания входящих HTTPS-запросов. -->
<!-- redirect-socket="proxy-https" -->
<!-- Указываем "WildFly", что запросы к нему проходят через web-"прокси", точка внешнего терминирования которого передаётся в параметрах "X-Forwarded-Host", "X-Forwarded-For", "X-Real-IP" и "X-Forwarded-Proto". -->
proxy-address-forwarding="true" />
....
</server>
....
</subsystem>
....
В конфигурационном файле "WildFly" есть секция описания поддержки соединений по специальном протоколу согласования (AJP) с вышестоящим web-сервером "Apache". Мы выбрали для этой роли "Nginx", так что прослушивание ненужного в схеме порта отключаем:
# vi /opt/wildfly/standalone/configuration/standalone.xml
....
<!-- Блок перечисления активируемых при запуске "WildFly" сетевых портов. -->
<socket-binding-group name="standard-sockets" default-interface="public" ... >
....
<!-- Деактивируем обслуживание невостребованного протокла AJP. -->
<!-- socket-binding name="ajp" port="${jboss.ajp.port:8009}"/ -->
....
</socket-binding-group>
....
<!-- Блок перечисления активируемых при запуске "WildFly" сетевых портов. -->
<socket-binding-group name="standard-sockets" default-interface="public" ... >
....
<!-- Деактивируем обслуживание невостребованного протокла AJP. -->
<!-- socket-binding name="ajp" port="${jboss.ajp.port:8009}"/ -->
....
</socket-binding-group>
....
Следует иметь в виду, что указанные в настройках конфигурационного файла "WildFly" параметры сетевого интерфейса могут переопределятся стартовым скриптом SystemV или Systemd.
Применение изменений в конфигурации требует перезапуска сервиса:
# systemctl restart wildfly
Обработка в "Nginx" некорректных ответов web-приложений.
Продолжая тему связки "WildFly" и фронтального web-сервера, приведу пример правил вынужденной корректировки ответов Java-приложений клиентам. Иногда - и не так уж редко - некомпетентные разработчики прикладных web-приложений не знают, как наладить корректную обработку параметров описания соединения "X-Forwarded-Host", "X-Forwarded-For", "X-Real-IP" и "X-Forwarded-Proto", поступающих от серверов проксирования и отвечают на запросы "как есть", полагая, что соседние компоненты работают на тех же FQDN и портах, что и они сами.
Приходится перехватывать ответы клиентам с указанием неправильных параметров источника и подменять их желаемыми:
# vi /etc/nginx/sites-available/wildfly.example.net.conf
....
# Блок описания web-сервиса приёма, терминирования SSL/TLS запросов и проксирования их нижележащему WildFly
server {
listen 443 ssl http2;
server_name wildfly.example.net;
....
# Отправляем все запросы на обработку в WildFly
location / {
proxy_pass http://localhost:8080;
....
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
....
# Перехват некорректных ответов компонентов web-приложения и подстановка требуемых
proxy_redirect ~*^http://(.+):8080(/.+)$ https://$1$2;
#
proxy_redirect ~*^http://(.+):80(/.+)$ https://$1$2;
#
proxy_redirect ~*^http://(.+)$ https://$1;
#
proxy_redirect ~*^https://(.+):80(/.+)$ https://$1$2;
}
}
# Блок описания web-сервиса приёма, терминирования SSL/TLS запросов и проксирования их нижележащему WildFly
server {
listen 443 ssl http2;
server_name wildfly.example.net;
....
# Отправляем все запросы на обработку в WildFly
location / {
proxy_pass http://localhost:8080;
....
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
....
# Перехват некорректных ответов компонентов web-приложения и подстановка требуемых
proxy_redirect ~*^http://(.+):8080(/.+)$ https://$1$2;
#
proxy_redirect ~*^http://(.+):80(/.+)$ https://$1$2;
#
proxy_redirect ~*^http://(.+)$ https://$1;
#
proxy_redirect ~*^https://(.+):80(/.+)$ https://$1$2;
}
}
Развёртывание Java-приложения в контейнере "WildFly".
В современном сервере приложений "WildFly" встроен и по умолчанию уже активирован функционал автоматической установки модулей и приложений. Достаточно разместить WAR/EAR-дистрибутив в директории "/opt/wildfly/standalone/deployments/", и он будет автоматически развёрнут в соответствие с предустановленными настройками.
Легко отслеживать успешность установки web-приложения, по специальным маркерным файлам, имя которых соответствует WAR/JAR-дистрибутиву, а расширение принимает вид вроде ".deployed" или ".failed", в зависимости от исхода развёртывания. Внутри маркерного файла сохраняется журнал событий установки - так легко выяснить, в чём проблема, буде она имеется.