Привет!
В наступившем новом году мы планируем всерьез развивать темы контейнеров, Cloud-Native Java и Kubernetes. Логичным продолжением этих тем на русском языке будет рассказ о фреймворке Quarkus, уже рассмотренном в хорошей статье на Хабре. Сегодняшняя статья посвящена не столько устройству "субатомной сверхбыстрой Java", сколько тем перспективам, которые Quarkus привносит в Enterprise.
Java и JVM по-прежнему исключительно популярны, но при работе с бессерверными технологиями и облачно-ориентированными микросервисами Java и другие языки для JVM применяются все реже, так как занимают слишком много места в памяти и слишком медленно загружаются, из-за чего плохо подходят для использования с короткоживущими контейнерами. К счастью, в настоящее время эта ситуация начинает меняться благодаря Quarkus.
Введение
Чем плотнее я занимаюсь DevOps, контейнерами и бессерверными технологиями, тем чаще обнаруживаю, что пишу мой контейнеризованный код в легких контейнерах или FaaS на Python или JavaScript. Java просто слишком тяжел для начальной загрузки, чтобы использовать его в бессерверном фреймворке. Что касается работы с микросервисами, JavaScript или Python обеспечивают более быструю загрузку и более компактные контейнеры, благодаря чему оказываются эффективнее Java.
Python и JavaScript – самые лучшие языки для создания облачно-ориентированных микросервисов
Языку Java больше 20 лет, и во времена его зарождения мир был совершенно иным, нежели сейчас. С появлением JVM были решены огромные проблемы – мы получили возможность единожды написать код и запускать его на множестве платформ и операционных систем. Контейнеры позволяют упаковывать приложения, библиотеки и ресурсы операционной системы в отдельные емкости, и каждый такой контейнер может работать где угодно. Портируемость, которую обеспечивает JVM, теперь не так актуальна. В свое время мы были готовы нести дополнительные издержки для обеспечения портируемости, но теперь эти времена прошли. Теперь требуется быстрая работа с минимальными задержками и реактивные приложения, которые всегда будут доступны. Контейнеры и инструменты для оркестрации контейнеров, например, Kubernetes, обеспечивают такие возможности независимо от языка программирования.
Компании, переходящие к использованию микросервисных архитектур, берут имеющиеся у них Spring-сервисы, написанные Java, связывают их в увесистые jar-архивы, добавляют JDK и запускают в контейнере, работающем на основе Linux. Такое решение работает, но вам приходится управляться с тяжелыми контейнерами размером по 500МБ, которые приводятся в состояние доступности за 10-30 секунд каждый; это серьезная проблема. После миграции многие компании медленно переходят на использование Python, оставляя сервисы серверной части на Java, а, в конце концов, останавливаются на FaaS.
Бессерверные технологии и FaaS сегодня очень популярны, поскольку позволяют сосредоточиться на написании функций, не беспокоясь при этом об инфраструктуре. Как бы то ни было, все они работают в контейнерах, но облачный провайдер управляет их жизненным циклом. Самое приятное, что, спустя определенное время, провайдер полностью останавливает контейнер и возобновляет его работу только после следующего вызова, то есть, вы оплачиваете только время фактической работы. Первый вызов функции может продлиться несколько больше обычного, это и есть знаменитый холодный старт. Дело в том, что контейнеру необходима первичная загрузка. При использовании Python или JavaScript это не такая большая проблема, но в случае с Java первичная загрузка может занимать 10-15 секунд, а это уже приговор и одна из причин снижения популярности Java. Сейчас нам нужен код, способный запуститься, выполнить задачу, а затем остановиться. Мы не хотим иметь дел со множеством потоков или долгоиграющими процессами, нам нужны короткоживущие процессы, которые могут загружаться очень быстро.
Знакомство с Quarkus
Если вы читаете технические блоги или следите за новостями, то, возможно, полагаете, что бессерверная парадигма захватывает мир, и все воспринимают ее с крайним энтузиазмом. Сейчас стартап может писать функции и предоставлять их в облаке как услугу – благодаря использованию JavaScript – а также масштабировать их для поддержки миллионов пользователей, без необходимости управлять при этом инфраструктурой. Правда, существует еще и реальный мир за пределами Кремниевой долины: финансовые институты, государственное управление, розничная торговля и множество других отраслей, обслуживаемых при помощи миллионов строк на Java, переписывать которые слишком накладно. Поэтому приходится принять как данность тот факт, что в этих отраслях остается и дальше пользоваться тяжеловесными контейнерами.
GraalVM и, в частности, Substrate VM, сегодня открывают двери для славного и долгого будущего языка Java. GraalVM – это универсальная виртуальная машина для выполнения приложений, написанных на JavaScript, Python, Ruby, R и языках для JVM, в частности, Java, Scala или Kotlin. Самое классное, что GraalVM позволяет заранее (в AOT-режиме) компилировать программы в нативный исполняемый файл. Это означает, что вы можете компилировать ваш код Java непосредственно в машинно-специфичный код. Получаемая в результате программа не работает на Java HotSpot VM, но использует все необходимые компоненты, в частности, управление памятью, планирование потоков с иной реализации виртуальной машины, которая называется Substrate VM. Substrate VM написана на Java, и ее код компилируется в нативный исполняемый файл. Получаемая в результате программа быстрее запускается и, соответственно, дает более низкие издержки в использовании памяти по сравнению с Java VM. Это отлично, но, вероятно, вы думаете: заблаговременная компиляция? Она же противоречит базовой идее, ради которой создавалась JVM, то есть, использованию единожды написанного кода везде! Это же безумие!!! Однако, подумайте сами: теперь у нас есть контейнеры, а им не нужна JVM. Обычные контейнерные приложения, создаваемые при помощи Spring boot, имеют лишний уровень абстрагирования, который совершенно не нужен в мире, где есть Kubernetes. У вас есть приложение Java, работающее на JVM внутри контейнера, этот контейнер остается неизменным, поскольку в наши дни готовый продукт – это контейнер, а не приложение. Теперь мы упаковываем контейнеры, а не WAR-файлы. Поэтому, все издержки, связанные с использованием приложения на JVM внутри контейнера становятся бесполезными, и AOT становится весьма логичным решением, если вы собираетесь упаковывать ваши приложения в контейнеры.
Правда, AOT-компиляция серьезно ограничивает динамические возможности Java (загрузка классов во время исполнения, рефлексия, прокси, т.д.). На практике это означает, что 90% экосистемы Java без изменений работать не будет. Соответственно, экосистема Java должна адаптироваться. Есть и хорошие новости: большинство из этого можно сделать во время сборки!
В этом и заключается сила Quarkus. Он использует GraalVM и предоставляет экосистему, поддерживающую AOT-компиляцию во время сборки; таким образом, при помощи Java можно создавать нативные двоичные файлы. Благодаря Quarkus, GraalVM поступает в распоряжение Java-разработчиков.
Приступаем к работе с Quarkus
Как было объяснено выше, Quarkus обеспечивает заблаговременную компиляцию для Java-приложений, и таким образом получается экосистема сверхзвуковой субатомной Java; Quarkus отличается сверхскоростной загрузкой – и Java возвращается в игру на поле облачно-ориентированной разработки. Меня годами так не воодушевляла какая-либо новая технология – и я в этом не одинок.
Почитайте руководство для начинающих – и убедитесь сами. По-прежнему есть множество компаний, использующих Java+JPA внутри контейнера, но в такой конфигурации загрузка может занимать 15 секунд, а в случае Quarkus – 0,005!
Статистика Quarkus
Вы используете ту же IDE и тот же инструментарий, к которым привыкли в мире Spring Boot. Для сборки вашего проекта используете Maven или Gradle. Проект можно запускать непосредственно в IDE и поверх нее, вам доступна горячая live-перезагрузка при любых изменениях, и перезапускать приложение при этом не требуется. Quarkus – это не Spring, поэтому, если вы используете Spring Boot, то придется выполнить миграцию Spring-специфичного кода. К счастью, в Quarkus предусмотрен уровень совместимости для внедрения зависимостей Spring, что сильно упрощает работу. Фреймворк Quarkus соответствует стандартам, что означает легкость в портировании и поддержке его кода.
Процесс разработки Quarkus
Quarkus может использоваться в режиме разработки, этим он напоминает Spring Boot. С ним вы также можете упаковывать ваш проект в толстые jar. Это очень удобно для тестирования и отладки вашего кода, поскольку поддерживается live-перезагрузка; но для выхода в продакшен вам потребуется заблаговременная компиляция. Весь этот процесс показан на следующей схеме:
- Сначала собираете приложение в вашей любимой IDE, а затем можете запустить его в режиме разработчика при помощи: “
mvnw compile quarkus:dev
”, как поступили бы с приложением Spring Boot. Также можете упаковать его в толстый jar. - Как только закончите предыдущий этап, и результат вас устроит – вы готовы к созданию двоичного файла Java, просто запустите: “
mvnw package -Pnative
”. На это потребуется некоторое время, поскольку в ходе заблаговременной компиляции будет создаваться нативный код! Когда этот шаг будет завершен, у вас в распоряжении окажется сверхмалый и сверхбыстрый исполняемый файл, но работать он сможет лишь на вашей платформе/OS, то есть, он не портируется! Но и это нормально, поскольку мы можем поместить его в контейнер – и таким образом обеспечить портируемость. Вот как это делается:./mvnw package -Pnative -Dnative-image.docker-build=true
4 – и мы вынимаем исполняемый файл из контейнера Docker, то есть, выполняем нативную сборку внутри контейнера и создаем двоичный файл. Этот прием может не сработать у вас на ноутбуке, если его операционная система отличается от целевой платформы, указанной в файлеDockerFile
, генерируемом Quarkus в ходе создания проекта. - Затем, после того как у вас будет двоичный файл, просто создаем образ на основе файла docker.
docker build -f src/main/docker/Dockerfile.native -t quarkus-quickstart/quickstart
. - Наконец, приложение можно запустить в Docker или Kubernetes:
docker run -i --rm -p 8080:8080 quarkus-quickstart/quickstart
Возможности Quarkus
В Quarkus гораздо больше возможностей, чем в нативном коде Java.
- Унификация императивных и реактивных возможностей: позволяет комбинировать привычный императивный код с неблокирующим кодом, написанном в реактивном стиле.
- Разработчику приятно: унифицированная конфигурация, Zero config, live-перезагрузка в мгновение ока, оптимизированный обтекаемый код для 80% распространенных случаев и гибкий код для оставшихся 20% случаев, генерация нативных исполняемых файлов без суеты, live-кодинг.
- Поразительно быстрая загрузка, невероятно малая резидентная область памяти (да, речь не только о размере кучи!), что обеспечивает почти мгновенное вертикальное масштабирование и очень плотное использование памяти при оркестрации контейнеров на таких платформах как Kubernetes. См. подробнее.
- Quarkus предлагает целостный, приятный в использовании full-stack фреймоврк, в несущие структуры которого внедрены первосортные библиотеки, которые вы знаете и любите. Подробнее.
- Поддерживаются библиотеки Hibernate, JPA, REST, JWT, т.д.
- Поддерживаются конфигурации, развернутые в Kubernetes и OpenShift
- Открытая трассировка (open tracing) с использованием Jaeger
- Поддержка Kotlin
- Обмен сообщениями при помощи Kafka, Camel…
- И многое другое, познакомьтесь со списком расширений!
Экосистема Quarkus
Короче говоря, теперь можно запускать традиционные транзакционные сервисы JPA/JTA в супербыстрых легковесных контейнерах – как в облаке, так и на территории предприятия.
Пример Quarkus
В этом разделе давайте в упрощенном виде рассмотрим руководство для начинающих, чтобы составить впечатление о мощи Quarkus.
Простейший способ создать новый проект Quarkus – открыть окно командной строки и выполнить в нем следующую команду:
mvn io.quarkus:quarkus-maven-plugin:0.12.0:create
-DprojectGroupId=org.acme
-DprojectArtifactId=getting-started
-DclassName="org.acme.quickstart.GreetingResource"
-Dpath="/hello"
Таким образом генерируется проект Maven с GreetingResuce, предоставляющий конечную точку /hello. Также генерируются докерные образы Dockerfile для нативных файлов и jvm (традиционные образы в виде толстых jar). Код получается очень чистым и простым:
@Path("/hello")
public class GreetingResource { @GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
Для запуска приложения используйте: ./mvnw compile quarkus:dev
Приложение упаковывается при помощи ./mvnw package. В результате получается 2 jar-файла:
- getting-started-1.0-SNAPSHOT.jar – содержит только классы и ресурсы проектов. Это обычный артефакт, получающийся в результате сборки Maven;
- getting-started-1.0-SNAPSHOT-runner.jar – это исполняемый jar. Учтите, что это не сверх-jar, здесь есть и зависимости, они копируются в каталог target/lib.
Запустить приложение можно с помощью: java -jar target/getting-started-1.0-SNAPSHOT-runner.jar
Затем нужно скачать и установить GraalVM и задать переменную окружения GRAALVM_HOME
.
Теперь можно создать нативный исполняемый файл при помощи: ./mvnw package -Pnative -Dnative-image.docker-build=true
.
Вот как создается образ Docker: docker build -f src/main/docker/Dockerfile.native -t quarkus-quickstart/quickstart
.
Теперь его можно запустить при помощи любого движка оркестрации контейнеров, в случае, если вы используете minishift:
kubectl run quarkus-quickstart --image=quarkus-quickstart/quickstart:latest --port=8080 --image-pull-policy=IfNotPresent
kubectl expose deployment quarkus-quickstart --type=NodePort
Вот и все!; теперь у вас есть контейнер с REST-сервисом на Java, запускающийся за 0.004 секунд!
Вывод
Теперь понятно, почему меня так восхищает фреймворк Quarkus, поддерживаемый Red Hat. Я действительно считаю, что он изменит технологический ландшафт Java и обеспечит большим традиционным предприятиям реальную возможность миграции в облако.
Kubernetes + Knative + Quarkus меняют правила игры в облачно-ориентированной разработке и порадуют любого Java-разработчика.
В этом репозитории – множество интересных примеров!
Автор: ph_piter