(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

В разработке сейчас много хайповых понятий, те же микросервисы, Service Mesh, GitOps, Serverless. Обо всем этом как-нибудь напишу, а сегодня пару мыслей про Cloud Native.

Если подумать, то для того, чтобы сделать native приложение для облака, нужно не так уж много.

1) k8s должен как-то понимать, что приложение поднялось и может принимать траффик. Для этого облачный сервис должен реализовать probes, они же healthchecks.
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
Вообще их три, но в случае простейшего приложения хватит одной - liveness. Остальные две нужны если приложение долго стартует - startup или может быть недоступно в процессе работы - readyness.

2) облачное приложение не должно долго стартовать. Причина: k8s при добавлении новых нод или подов, или изменения их настроек Anti-affinity (возможности совместной работы на одной ноде) в любой момент может погасить под и поднять его на другом сервере. Да, можно указать startup probe чтобы траффик не шел на долго стартующее приложение. Можно указать maxUnavailable https://kubernetes.io/docs/tasks/run-application/configure-pdb/ для того, чтобы k8s оставлял запас подов при изменении их численности. Но все это обходные пути. Если вспомнить про Spring Boot, то я бы сказал что ему есть куда расти в плане оптимизации, не зря сейчас растет популярность его альтернатив, стартующих существенно быстрее: Quarkus, Micronaut, Helidon и использования native images.

3) cloud native приложение не должно зависеть от локального состояния - кэширования данных локально. Все критичные для работы данные должны хранится или в storage, или на клиенте. Причина все та же - k8s в любой момент может поднять под на другом сервере, локальный кэш при этом теряется.

4) для cloud native приложения крайне рекомендуется отбрасывать метрики и поддерживать distributed tracing - https://opentelemetry.io/docs/concepts/what-is-opentelemetry/. Причина: перенос в облако упрощает разработку, как правило идет рука об руку с микросервисами, следовательно, сервисов становится существенно больше, следовательно, должны быть инструменты для отслеживания их состояния и более точного понимания, где проблема.

Что на мой взгляд не относится к критичным требованиям для Cloud native приложений.

1) поддержка Docker. В Docker можно засунуть практически любое приложение, есть куча официальных образов. Даже IBM Websphere и WildFly, хотя использование их в облаке выглядит странным) Вижу проблему только с Windows native приложениями, но их остается не так уже много

2) поддержка внутри приложения cloud \ fault tolerance функций: circuit breakers, retries, timeouts, service discovery. Например, этот функционал реализуется в Spring Cloud библиотеке. Почему так? Потому что в связке k8s + service mesh все эти функции можно реализовать на уровне облака. Не хочу сказать, что Spring Cloud и его аналоги не нужны, но точно не обязательны для облака.

3) использование REST API. С одной стороны для HTTP траффика k8s + service mesh дает больше возможностей по маршрутизации, но с другой стороны tcp трафик тоже маршрутизируется.

#cloud_native #microservices #spring_boot #tracing
Всем привет!

Интересная статья про динамическую перезагрузку properties https://www.baeldung.com/spring-reloading-properties в Java приложении.
Два варианта:
1) Apache Сommons Сonfiguration
2) Spring Cloud
Первое работает через отслеживание изменений в файле, второе - управляемое извне перечитывание после вызова /refresh endpoint.

#java #cloud #spring_boot
Всем привет!

Среди первых постов этого канала был пост про k8s - для чего он нужен https://t.me/javaKotlinDevOps/6
Но я забыл рассказать про Service Mesh - что же она добавляет к k8s. Если вы не понимаете, что за Service Mesh такой - самой известной реализацией является Istio https://istio.io/latest/docs/ На его примере хочу рассказать про Service Mesh.

Начал искать материалы и сразу наткнулся на серию отличных статей:
https://habr.com/ru/companies/flant/articles/438426/
https://habr.com/ru/companies/flant/articles/569612/
https://habr.com/ru/companies/oleg-bunin/articles/726958/
Рекомендую их почитать, там есть и про устройство Istio на сетевом уровне, и про решаемые им задачи, и про вносимые данной технологией риски.

Для тех, кому лень читать поработаю продвинутой версией Yandex GPT.
Основные "плюшки" Istio:
1) возможность аутентификации и авторизации запросов как по адресам, url-ам, так и с помощью OAuth
2) продвинутые возможности балансировки - например, привязка клиента к конкретному серверу или геопривязка: перенаправление только на экземпляры сервиса, находящиеся в том же гео-кластере
3) реализация паттерна Circuit Breaker, повторов и настройка таймаутов для запросов
4) продвинутые возможности маршрутизации: по URL и http заголовкам
5) реализация канареечного развертывания и зеркалирования траффика
6) TLS Termination и Origination - снятие и добавление TLS шифрования на входе\выходе из namespace, что позволяет полностью переложить работу с сертификатами с разработки на сопровождение

Вывод следующий: Service Mesh - новый уровень абстракции поверх обычного облака (k8s). Да, он добавляет сложность, особенно в части сопровождения. Вносит небольшие задержки, которые в определенных случаях могут быть критичны. Требует ресурсов, что также может быть важно. Но если его "плюшки" вам полезны - оно того стоит)

#istio #k8s #service_mesh #cloud
Всем привет!

К двум моим статьям про k8s (https://t.me/javaKotlinDevOps/6) и Istio (https://t.me/javaKotlinDevOps/210) прямо напрашивается третья - про Openshift.

Тут может возникнуть вопрос - а что это вообще такое и почему напрашивается?)
Ответ: это "допиленный напильником" k8s от компании Redhat для enterprise.
Поэтому если вы работаете в enterprise, особенно кровавом, то тема актуальная)

Второй вопрос, который может возникнуть - какой такой западный enterprise в России сейчас?
Ответ: есть opensource версия https://habr.com/ru/companies/redhatrussia/articles/511800/

Итак, что же добавляет Openshift к k8s.

1) Istio - включен в дистрибутив

2) встроенный Docker Registry

Небольшое уведомление. Основной пласт возможностей, которые предоставляет Openshift, реализован за счет кастомных ресурсов - CRD - и оператора, который их обрабатывает.
https://kubernetes.io/docs/concepts/extend-kubernetes/operator/
https://habr.com/ru/companies/slurm/articles/556860/
Идем дальше.

3) настройки безопасности: ролевая модель https://docs.openshift.com/container-platform/4.8/rest_api/role_apis/role-apis-index.html, авторизация https://docs.openshift.com/container-platform/4.8/rest_api/authorization_apis/authorization-apis-index.html , OAuth https://docs.openshift.com/container-platform/4.8/rest_api/oauth_apis/oauth-apis-index.html

4) механизм шаблонов (ресурс типа Template) - позволяет в одном файле задать несколько ресурсов с параметрами. Значения параметров можно как задать в самом шаблоне (значения по умолчанию), так и передать в момент применения Template
https://docs.openshift.com/container-platform/4.8/rest_api/template_apis/template-apis-index.html

5) Имея механизм шаблонов, ролевую модель и настройку лимитов из k8s можно упростить создание новых namespace: для этого вводится проект - аналог namespace в Openshift. https://docs.openshift.com/container-platform/4.8/rest_api/project_apis/project-apis-index.html

6) самое интересное - концепция build-related ресурсов. Идея в том, что при изменении исходников или появлении нового образа в Docker происходит автоматическая сборка и установка новой версии приложения. Т.е. фактически мы получаем встроенный в кластер CI\CD.
https://docs.openshift.com/container-platform/4.10/cicd/index.html
https://docs.openshift.com/container-platform/4.8/rest_api/workloads_apis/buildconfig-build-openshift-io-v1.html
Данная концепция приводит к тому, что стандартный ресурс Deployment в Openshift заменяется на очень похожий DeploymentConfig, поддерживающий пересоздание подов при появлении нового кода https://docs.openshift.com/container-platform/4.8/rest_api/workloads_apis/deploymentconfig-apps-openshift-io-v1.html

7) само собой новая веб-консоль и новая CLI утилита (oc), поддерживающие все вышеописанные возможности.

Вот пожалуй и все основные отличия Openshift. В целом неплохо, хотя и не так революционно, как Istio

#openshift #istio #cloud #k8s
Всем привет!

В связи с развитием облачных решений стала актуальна технология Service Discovery. Это такая штука, которая позволяет регистрировать новые экземпляры сервиса, удалять несуществующие, проверять их доступность, а также отдавать эту информацию балансировщику, чтобы он мог перенаправить клиента на конкретный сервер. А в теории и сам Service Discovery может являться балансировщиком.
В облаке новые экземпляры сервисов появляются достаточно часто:
1) изменением одной настройки - числа реплик или настроек affinity
2) автоматически как результат восстановления требуемого числа подов после падения одного из них
3) автоматически при использовании HorizontalPodAutoscaler
4) автоматически при добавлении в кластер новых серверов и перераспределении подов для достижения равномерности нагрузки на сервера.
Поэтому понятно, почему в облаке эта технология очень востребована. В описании любого облачного решения вы встретите фразу, что оно реализует Service Discovery.
Самые популярные реализации сейчас - это Consul, распространённый везде, и Eureka, больше привязанная к миру Java. Оба поддерживают Spring-ом, а точнее Spring Cloud https://cloud.spring.io/spring-cloud-static/spring-cloud.html

Но как известно все новое, это хорошо забытое старое. Service Discovery был с нами с начала развития интернета. Ведь там стоит похожая задача - новые сайты появляются постоянно, и нужно перенаправить клиента на сервер, на котором этот сайт находится. Я о технологии DNS. Это по сути специфически реализованный Service Discovery. Более того, если взять k8s - он использует для разрешения внутренних DNS имен сервис CoreDNS, который позиционирует себя как DNS и Service Discovery https://coredns.io/ И еще факт: Consul, о котором я писал выше, поддерживает 2 протокола доступа: REST и ... DNS.

#cloud #service_discovery
Всем привет!

Сегодня расскажу про технологию native image.

Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости оптимизирует его, включая компиляцию в бинарное представление для конкретной процессорной архитектуры. И основные оптимизации надо отметить происходят именно здесь, а не при первичной компиляции. Еще важный момент - классы\библиотеки подгружаются в память не обязательно при старте приложения, а по мере использования. Все это называется JIT - Just in time компиляция. Влиять на нее можно с помощью ряда флагов запуска Java приложения - -server, -client.

Плюс такого подхода - JVM позволяет в 90% случаев игнорировать, на каком железе запускается Java приложение. Минус - долгий старт Java приложения плюс время для "разогрева" и выхода на рабочий режим.

Но с другой стороны с развитием Docker мы и так можем игнорировать особенности железа и ОС на хост-сервере, главное, чтобы там можно было запустить Docker. И наконец кроме долгого старта и разогрева собственно JVM у нас как правило есть Spring с кучей модулей, число которых растет, и в итоге время старта типичного Spring Boot приложения доходит до совсем неприличных величин.

Альтернатива - AOT - Ahead-of-Time compilation. В этом случае мы компилируем исходники в бинарный код в момент первичной компиляции. Причем как собственно приложение, так и JVM и все JAR. Получается такой native image монолит. Проект называется GraalVM https://www.graalvm.org/, официально поддерживается Oracle. Есть open-source версия, основанная на OpenJDK.

Плюс этого подхода - скорость запуска. Это критически важно в облаках, т.к. k8s может "случайно" рестартовать под при изменении конфигурации железа или настроек Deployment. Еще будет выигрыш в скорости обработки запросов, т.к. не тратится CPU и память в runtime на JIT компиляцию.

Какие минусы?

1) невозможна динамическая\ленивая загрузка библиотек\плагинов, classpath фиксируется в момент компиляции. К слову - у этого ограничения есть и плюсы, сложнее эксплуатировать уязвимости типа log4j injection - см. https://t.me/javaKotlinDevOps/4

2) вопрос - откуда компилятор узнает, какой код ему нужно добавить в наш native монолит? Ответ: он идет от метода main. Соответственно, код который явно не вызывается, а, например, вызывается через рефлексию, он не увидит. Соответственно, никакой рефлексии в ПРОМ коде. Что, надо сказать, в целом правильно)

3) аналогично просто так не заработает магия Spring, основанная на рефлексии и динамических прокси. Из чего следует, что мало добавить в Spring приложение AOT компилятор - нужно дорабатывать сам Spring, что и было сделано в Spring Boot 3.2. Другие фреймворки также придется дорабатывать. Например, Mockito до сих пор не работает в native image. Справедливости ради тут причина такая же, как в анекдоте про неуловимого ковбоя Джо - не нужен Mockito в native image)

4) если продолжить про Spring - загрузка бинов по условию: @ConditionalOnProperty, @Profile - тоже не заработает. Нужно указывать при сборке необходимый профиль, чтобы уже при компиляции нужные бины были обнаружены и добавлены в дистрибутив.

5) еще вопрос - но ведь среднее Java приложение + библиотеки + JVM = миллионы строк кода, что будет с компиляцией? Ответ - компиляция будет долгой, до 10 минут на spring boot hello world. Поэтому в документации Spring прямо сказано, что хотя Spring поддерживает запуск тестов в native image - делать так нужно только для интеграционных тестов, лучше на CI, а модульные запускать по старинке, т.к. тут критична скорость получения результата.

#jvm #performance #native_image #spring #docker #buildpacks #cloud #java_start_boost
Есть еще ряд интересных моментов. Я расскажу про них на примере Spring Boot native image.

Для борьбы с тем, что часть кода недостижима если идти от точки входа (метод main), есть два инструмента.
1) специальный tracing агент, который можно подключить к приложению, и он будет в runtime логировать такие скрытые вызовы. https://www.graalvm.org/22.3/reference-manual/native-image/metadata/AutomaticMetadataCollection/
2) далее можно создать т.наз. hints - подсказки AOT компилятору, что включить в native image, из того, что он не нашел сам - https://www.graalvm.org/latest/reference-manual/native-image/metadata/ Собственно, большая доля в адаптации фреймворка типа Spring для native image - подготовка таких hints, https://docs.spring.io/spring-boot/docs/3.2.1/reference/html/native-image.html

А что делать если в момент сборки еще не ясно - нужен native image или нет? Или нужны обе версии? Нет проблем - можно совместить оба режима JIT и AOT и создать артефакт, Spring Boot Executable Jar, с байткодом и всеми необходимыми для native image метаданными. И собрать из него native image позже в DevOps pipeline при необходимости.

Для Spring Boot есть два режима сборки. Основной - Native Image Using Buildpacks, в котором в итоге получается docker образ. Для него нужен только Docker на машине-сборщике. И т.наз. Native Build Tools - нужно устанавливать дистрибутив GraalVM, содержащий эти tools, в итоге получается бинарник для железа, на котором происходит сборка.

Итого - штука полезная, но только если вас категорически не устраивает время запуска приложения и все используемые вами фреймворки поддерживают native image.

#jvm #performance #native_image #spring #docker #buildpacks #cloud #startup_time
image_2024-02-05_18-02-46.png
173.1 KB
Всем привет!

Не так давно появилась такая технология как Function as a Service или сокращено FaaS. Ее цель - затащить в облако все кроме написания кода. Сегодня нашел неплохую картинку, иллюстрирующую ее на примере сервисов Google.

Слева - это как раз FaaS. Т.е. мы отдаем на откуп облачного провайдера железо и виртуальные машины (instance), управление контейнерами, runtime (JDK,.NET framework), веб сервер и даже веб контроллер (single-endpointed). И хоть тут это не указано - CI\CD pipeline.

Плюсы:
1) сопровождение и DevOps уходят на аутсорс.
Минусы:
1) vendor lock, хотя стандарт разрабатывается,
2) платим за время работы кода и платим достаточно дорого,
3) не подходит для выполняющихся долго или висящих в памяти постоянно процессов

#cloud #faas
Всем привет!

Есть такой интересный вопрос - можно ли поместить СУБД в облако?
Если отвечать на него строго технически - да, можно, для этого в k8s есть специальные типы объектов - StatefulSet https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ и PersistentVolume
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
которые обеспечивают ряд требуемых для СУБД характеристик:
1) подключенное хранилище не удаляется при каждой остановке пода
2) экземпляр StatefulSet имеет постоянное имя
3) процедура масштабирования StatefulSet в обе стороны последовательна - поды добавляются\удаляются по одному

Более того, в документации кубера есть пример с раскатыванием MySQL https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application
Причем с возможностью автоматического масштабирования на чтение. Пример на самом деле меня впечатлил: сотня строк манифестов yaml - и БД уезжает в облако.

Так ли все хорошо?
Конечно же нет)
Для начала я бы задал типичный "менеджерский" вопрос - какую задачу решаем?

Первый момент - горизонтальное масштабирование на чтение можно обеспечить для любой СУБД. Как в облаке, так и без облака. На запись - только для NoSQL хранилищ с поддержкой шардирования.

Второй момент: сопровождение СУБД - это не просто гарантия наличия работоспособного инстанса на ПРОМ. Это еще и периодические бэкапы, откаты на резервную копию, начальное заполнение БД данными, заполнение "горячего" кэша, выбор нового master если старому плохо (leader election), тонкие настройки репликации и многое другое, чего я просто не знаю. А знают - хорошие DBA. Т.е. получается даже если мы поместим обычную СУБД в облако - DBA все равно нужен. Причем DBA, умеющий в облако, а это, кажется, редкая птица)

Т.е. все плохо? И снова нет. Если есть потребность отдать БД "на аутсорс" в облако, то выход - использование хранилищ от облачных провайдеров, спроектированных для работы в облаке. Там все вышеописанные тонкости будут учтены. Примеры:
1) Amazon Aurora https://habr.com/ru/companies/oleg-bunin/articles/471686/
2) YDB https://cloud.yandex.ru/ru/services/ydb
3) Azure Cosmos DB for PostgreSQL https://learn.microsoft.com/en-us/azure/cosmos-db/postgresql/introduction

Ну или хотя бы использование заточенных под работу в облаке СУБД. Вот пара примеров, первыми попавшихся мне на глаза:
1) Tarantool https://habr.com/ru/companies/vk/articles/533308/
2) CockroachDB https://www.cockroachlabs.com/docs/v23.2/deploy-cockroachdb-with-kubernetes
Как можно заметить, в обоих случаях есть специальный оператор k8s для управления кластером.

P.S. Тема сложная, интересная, поэтому думаю это не последняя статья.

#k8s #storage #cloud #rdbms
Всем привет!

Как можно масштабировать нагрузку в облаке? Под облаком я понимаю k8s как некий стандарт.

1) "ручками". Простой, но не надежный способ. Отягощающий фактор - не всегда у сопровождения ПРОМ есть права на смену настроек Deployment, тогда требуется отдельный деплой

2) выставить число подов и limits, соответствующими максимальной нагрузке. Еще проще, но совсем не рационально в плане использования ресурсов

3) HorizontalPodAutoscaler https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ Позволяет менять число подов сервиса при превышении некого порога нагрузки по cpu и memory. Порог - это не процент использования, а соотношения заданного requests с текущим значением. Подходит если требуемая нагрузка может превышать возможности 1 пода, и если сервис поддерживает горизонтальное масштабирование - т.е. stateless.

4) VerticalPodAutoscaler https://habr.com/ru/companies/flant/articles/541642/ Как следует из название - масштабирует поды вертикально. Тут есть нюанс - на данный момент k8s не позволяет менять requests и limits у пода "на ходу". Данная фича называется In-Place Update of Pod Resources и пока находится в бета-версии https://github.com/kubernetes/enhancements/issues/1287
Сейчас VerticalPodAutoscaler может следующее:
а) на основе истории использования подом ресурсов рассчитать и выдать рекомендуемые значения
б) применить эти значения при очередном плановом рестарте
в) самому рестартовать под
Подходит для следующих случаев:
а) statefull сервисы типа хранилища данных в облаке StatefullSet, для которых горизонтальное масштабирование или невозможно, или не приветствуется
б) "легкие" сервисы, которым достаточно одной реплики (или двух по требованиям надежности)
Из особенностей - не гарантируется корректная работа совместно с HorizontalPodAutoscaler по одним и тем же метрикам cpu и memory. И не работает с Java приложениями в плане нагрузки по памяти, т.к. Java сама управляет памятью, а VerticalPodAutoscaler эти данные не видит.
Ну и главный минус - необходимость рестарта для применения настроек

5) как избавиться от необходимости рестарта? Дождаться полноценного внедрения In-Place Update of Pod Resources и доработки VerticalPodAutoscaler. Или использовать бета-версию In-Place Update of Pod Resources реализовать соответствующий k8s controller самому. Это уже сделано: https://piotrminkowski.com/2023/08/22/resize-cpu-limit-to-speed-up-java-startup-on-kubernetes/
Данная реализация не умеет сама рассчитывать рекомендуемые значения requests. Зато позволяет красиво проблему JVM приложений, о которой я уже писал ранее - проблему долгого старта. Java, а точнее Spring приложение, при старте производит достаточно ресурсоемкую настройку контекста. Речь идет про обычный Spring Boot без применения всех возможных способов ускорить запуск. Если память после первоначальной настройки так и остается занятой созданными бинами, то cpu при старте требуется сильно больше, чем при обычной работе. Что с этим делать?
а) Не выделять лишние cpu - время старта увеличивается в разы
б) Пойти по пути, описанному в п.2 - выдать максимум ресурсов. Тогда может получиться, что мы тратим cpu на быстрый старт приложения, а далее ресурсы не утилизируются
в) In-Place Update of Pod Resources
Конкретно по данной реализации - мне она кажется не идеальной, т.к. ресурс по cpu настраивается в 2 манифестах, это не наглядно и способствует внесению несогласованных правок. Но решение в целом - полезное.

#k8s #cloud #scalability
Всем привет!

Случайно наткнулся на старую статью - 2015 год - про переход с legacy на Service Oriented Architecture ака SOA.
И хочу сказать, что это хороший пример развития истории по спирали)

Что в статье актуально?
Заменяем слово SOA на микросервисы, и в целом все, что касается преимуществ микросервисной архитектуры и стратегии перехода на нее - актуально. Микросервисы = SOA 2.0 )))

REST оставляем, SOAP+XML заменяем на gRPC\GraphQL для тех случаев, когда требуется большая производительность и гибкость соответственно по сравнению с REST. К слову, недостаток производительности и гибкости - это основные проблемы SOAP. Ремарка - знаю места, где SOAP еще жив (интеграция с госорганами), но он в любом случае вымирает.

ESB, трудности реализации асинхронного взаимодействия - все эти задачи взяла на себя Kafka. Прорывной инструмент - быстрый, надежный (обеспечивает дешевую персистентность), opensource, простой с точки зрения разработчика. В т.ч. потому, что нет необходимости разрабатывать логику маппинга сообщений на брокере. Да, он реализует только одну из двух основных моделей асинхронного взаимодействия - Publisher-Subscriber - и не реализует Message Queue. Но понятно, что топиками можно пользоваться как заменой очередей, и в большинстве случаев проблем при этом не будет.

Облачные решения - за 10 лет из вызова превратились в новую реальность)

А вызов сейчас - внедрение AI. Как-то так)

#microservices #ai #cloud #kafka #rest