Русский English Тэги View Sergey Zolotaryov's profile on LinkedIn Вход
Дешёвый Java/Tomcat сервер на RaspberryPi3
Постоянная ссылка 30-04-2016 anydoby java linux

Зачем всё это?

Дело в том, что недавно мой VPS, который служил мне верой и правдой лет 8, стал стоить не 20 долларов в месяц, а 60. Я сказал, что, пожалуй, это дороговато, и выключил сайт на месяц. За этот месяц я купил Raspberry Pi 3 и настроил всё, что нужно, чтобы там работал Java сервер (мой сайт на сервлетах, да-да, есть ещё такие).

Итак, серверной ОС была выбрана Ubuntu server minimal (2015), хотя и очень хотелось поставить Gentoo. Увы, для Gentoo пока что нет официальной поддержки arm8, а то готовое, что я пробовал поставить, не завелось. Значит берём Ubuntu. Отключаем доступ рутом и с паролем, обмениваемся с коробочкой ключами и работаем безопасно.

Даже после свежей установки в системе не самые свежие версии пакетов, поэтому чтобы всё работало и устанавливалось веселее, можно сделать так:


apt-get update
apt-get dist-upgrade

После подключения по Ethernet к рутеру можно заходить на машинку через ssh.

Я не люблю ставить java через package management, потому что не вижу добавочной стоимости, так как JDK от Oracle всё равно без телодвижений не поставится. Устанавливаем Java, сначала качаем её с Oracle, распаковываем в usr/lib/jvm и заводим JAVA_HOME в /etc/environment, например вот так:


JAVA_HOME=/usr/lib/jvm/jdk1.8.0_91
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$JAVA_HOME/bin"

В ~/.bash_profile затем достаточно добавить


source /etc/environment

и перелогиниться.

Дальше ставим Tomcat. Через package management опять же ставить не охота. Поэтому его мы просто качаем с Apache. Я взял 8, но можно и поновее. Распаковываем, например, в /opt/tomcat.

Добавляем юзера tomcat и даём ему права на доступ ко всему кошачьему:


groupadd tomcat
useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat
chown -R tomcat:tomcat /opt/tomcat

Чтобы tomcat можно было запускать как сервис и чтобы он стартовал сам при запуске системы, нужно сделать вот такой вот файлик /etc/systemd/system/tomcat.service


# Systemd unit file for tomcat
[Unit]
Description=Apache Tomcat Web Application Container
After=syslog.target network.target

[Service]
Type=forking

EnvironmentFile=/etc/environment
# или явно указать JAVA_HOME
#Environment=JAVA_HOME=/usr/lib/jvm/jre
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
# тут мы явно хотим серверную VM
Environment='CATALINA_OPTS=-Xms256M -Xmx512M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.net.preferIPv4Stack=true'

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/bin/kill -15 $MAINPID

User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target

После этого можно попробовать всё запустить:


systemctl daemon-reload
systemctl enable tomcat
systemctl start tomcat
tail -f /opt/tomcat/logs/catalina.out

Смотрим логи и выходим. Не впечаталяет: он ведь на порту 8080, а надо 80. Защищённый порт юзер tomcat байндить не может, поэтому можно либо открыть дыру в безопасности и запускать tomcat рутом, либо воспользоваться более кошерным способом:


apt-get install authbind
touch /etc/authbind/byport/80
chmod 500 /etc/authbind/byport/80
chown tomcat /etc/authbind/byport/80

Если дальше нужен будет SSL (у меня есть), то аналогично добавляем порт 443. Далее нужно изменить файл /opt/tomcat/bin/startup.sh, находим, комментируем и заменяем:


#exec "$PRGDIR"/"$EXECUTABLE" start "$@"
exec authbind --deep "$PRGDIR"/"$EXECUTABLE" start "$@"

Меняем порты в /opt/tomcat/conf/server.xml:


    <Connector port="80" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               maxThreads="20"
               redirectPort="443" address="192.168.1.50" 
               URIEncoding="utf-8" useBodyEncodingForURI="true"/>

В атрибуте address нужно указать IP машины. В золотые времена, когда у меня был VPS, я указывал собственный айпи. Теперь я за NAT домашнего рутера и указываю то, что выведет вот эта команда (если интерфейс не eth0, поменяйте команду):


ifconfig eth0 | grep -oE '(([0-9]){1,3}\.){3}[0-9]{1,3}' | head -1

Сохраняемся и перезапускаем tomcat:


systemctl restart tomcat
tail -f /opt/tomcat/logs/catalina.out

Если в логе появилось что-то вроде этого, то всё в порядке:


org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-192.168.1.56-80"]

В общем на этом можно бы и остановиться, но мы ведь хотим, чтобы сервер летал, раздавая статику. Поэтому нам нужно настроить Tomcat APR - это нативный протокол, который использует Apache Portable Runtime для отсылки статического контента при помощи средств ОС, а не Java. Говорят, работает быстрее.


apt-get install libapr1-dev
apt-get install libssl-dev
cd /opt/tomcat/bin
tar -xpf tomcat-native.tar.gz
cd tomcat-native-*src/native
./configure && make && make install

Если по каким-то причинам в логе make вот такое


/usr/lib/jvm/jdk1.8.0_91/include/jni.h:45:20: fatal error: jni_md.h: No such file or directory

можно сделать линк и повторить:


ln -s /usr/lib/jvm/jdk1.8.0_91/include/linux /usr/lib/jvm/jdk1.8.0_91/include/arm

APR установилась в /usr/local/apr/lib. Чтобы tomcat это дело увидел, можно немного изменить /etc/systemd/system/tomcat.service


Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Djava.library.path=/usr/local/apr/lib'

Обновляем демонов, перезапускаем tomcat. Теперь в логе будет что-то похожее на это:


INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-apr-192.168.1.56-80"]

Это значит, что APR успешно нашёлся и tomcat автоматически создал другой коннектор. Если ваше приложение само раздаёт статику или вы используете spring и все урлы обрабатываются примерно вот так:


  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

то для того, чтобы разрешить tomcat самому раздавать статику, придётся добавить вот такой мэппинг:


  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.zip</url-pattern>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.pdf</url-pattern>
    <url-pattern>*.mp3</url-pattern>
    <url-pattern>*.gif</url-pattern>
    <url-pattern>*.rar</url-pattern>
    <url-pattern>*.bmp</url-pattern>
    <url-pattern>*.exe</url-pattern>
  </servlet-mapping>

default это стандартный сервлет tomcat, который обрабатывает статические ресурсы. Объявлять его в своём web.xml не надо.

А теперь как всё это обезопасить? Если вы хотите защитить все страницы и все статические ресурсы через SSL, то в web.xml достаточно добавить


  <security-constraint>
    <web-resource-collection>
      <web-resource-name>all</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
      <description>all traffic will go through ssl</description>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
  </security-constraint>

Все HTTP запросы теперь будут направляться на HTTPS. Вот только осталось его настроить. Если вы выбрали путь без APR, то нужно просто сделать сертификат и дать об этом знать tomcat:


keytool -genkey -keysize 2048 -keyalg RSA -alias tomcat -keystore mystore.jks

Важно помнить, что пока что нужно пароль для связки ключей и для tomcat использовать один и тот же (это известный баг).

Теперь можно ключ использовать в /opt/tomcat/conf/server.xml


    <Connector port="443" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
               address="192.168.1.56"
               maxThreads="20" SSLEnabled="true" scheme="https" secure="true"
               keystoreFile="${catalina.base}/conf/ssl/mystore.jks" keystorePass="mypass"
               clientAuth="false" sslProtocol="TLS" URIEncoding="utf-8" useBodyEncodingForURI="true"/>

Если вы выбрали путь нативный (как у меня, с APR, который использует OpenSSL), то придётся либо генерировать ключи при помощи OpenSSL, либо сконвертировать jks хранилище в формат PKCS12. Делается это просто:


keytool -importkeystore -srckeystore mystore.jks -destkeystore mystore.p12 -deststoretype PKCS12 -srcalias tomcat -srcstorepass mypass -deststorepass mypass -destkeypass mypass

Получится файл mystore.p12, в формате, понятном OpenSSL. Теперь можно из него вытащить приватный-публичный ключи и цепочку удостоверяющих серфитикатов (если таковые имеются :)).


openssl pkcs12 -in mystore.p12 -nocerts -out my.key.pem
openssl pkcs12 -in mystore.p12 -clcerts -out my.cert.pem
openssl pkcs12 -in mystore.p12 -cacerts -out my.chain.pem

Чтобы браузер не показывал, что ваш ключ нехороший, желательно подписать его за символические денежки, хотя бы вот здесь.

Теперь остаётся настроить коннектор для SSL с APR:


    <Connector port="443" maxHttpHeaderSize="8192"
                 address="192.168.1.56"
                 maxThreads="20"
                 enableLookups="false" disableUploadTimeout="true"
                 acceptCount="100" scheme="https" secure="true"
                 SSLEnabled="true"
                 SSLCertificateFile="${catalina.base}/conf/ssl/my.cert.pem"
                 SSLCertificateKeyFile="${catalina.base}/conf/ssl/my.key.pem" 
                 SSLCertificateChainFile="${catalina.base}/conf/ssl/my.chain.pem"
                 SSLPassword="mypass"
                 URIEncoding="utf-8" useBodyEncodingForURI="true"/>

Теперь при перезапуске в логе можно убедиться, что для SSL тоже создаётся APR коннектор.

Всё. Нет, не всё, не забудьте в рутере пропустить 80 и 443 порты на ваш внутренний IP, я долго тыкался, пока не вспомнил, что я этого не сделал :)

Добавить комментарий

alexander.abakumov
03-04-2017

Привет!

Какое-нить cloud solution не катит? Имхо минимального пакета услуг могло б и хватить. Если конечно на том ВПС работал только этот сайт.

(конечно Рапсберри Пи тоже весело) )

anydoby
03-04-2017

Пи точно веселее :)

Задача была бесплатно настроить сайт дома и так, чтобы за это каждый месяц не платить. И это, можно теперь домашний клауд сделать, машинки хорошие.

anydoby
03-04-2017

И это, я ж олдскул товарищ. Всё своё храню у себя дома, по возможности без клаудов

Предыдущая статья Установка JDK8 от Oracle в Gentoo без icetea Следующая статья Простая защита от root брут форс