Перенос приложений в облако - желанный выбор для компаний, которым требуется эластичность, отказоустойчивость и скорость при оптимизации затрат.

Допустим, вы обдумали свои цели и поняли, что облако может помочь вашему бизнесу. Вы решаете создавать будущие системы для облака, но также хотите начать миграцию существующих традиционных приложений. Может возникнуть соблазн переместить их в том виде, в котором они есть сейчас, - подход, который называется «поднять и сдвинуть». Однако вы бы не воспользовались преимуществами облачной среды таким образом. Лучшим подходом было бы использование нативной облачной среды.

Spring - это популярный Java-фреймворк для создания любых приложений благодаря обширному каталогу библиотек и инструментов. Эта статья предназначена для инженеров-программистов и архитекторов, работающих со Spring и заинтересованных в изучении того, как создавать с его помощью нативные облачные приложения.

Я начну с определения того, что означает нативная облачная среда и каковы основные свойства этого подхода. Затем я проведу вас через несколько этапов процесса миграции, выделив, что вам следует изменить в приложении Spring, какие инструменты вы можете использовать, и аргументы в пользу тех или иных вариантов. В частности, я расскажу, как сделать приложение автономным с помощью встроенного веб-сервера и упаковки JAR, как использовать внешнюю конфигурацию и как улучшить его наблюдаемость с точки зрения журналов, показателей и проверок работоспособности.

Облачные приложения - это еще не все, но эта статья посвящена нескольким избранным аспектам. В Systematic я принимал участие в миграции наших традиционных приложений на облачные технологии, поэтому я поделюсь некоторыми идеями о том, какие проблемы у нас были и как мы их решали.

Определение нативности для облака

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

Нативные облачные технологии позволяют организациям создавать и запускать масштабируемые приложения в современных динамических средах, таких как общедоступные, частные и гибридные облака. Контейнеры, сервисные сети, микросервисы, неизменяемая инфраструктура и декларативные API-интерфейсы служат примерами этого подхода.

Эти методы делают слабосвязанные системы устойчивыми, управляемыми и наблюдаемыми. В сочетании с надежной автоматизацией они позволяют инженерам часто и предсказуемо вносить важные изменения с минимальными усилиями.

Это определение, данное Фондом облачных вычислений (CNCF), из которого я выделяю три основные группы информации, которые я называю Три P's Cloud Native:

  • Место. Собственные облачные приложения работают в динамических средах, таких как общедоступные, частные и гибридные облака.
  • Свойства. Нативные облачные приложения масштабируемы, слабо связаны, устойчивы, управляемы и наблюдаемы.
  • Практики. Собственная облачная разработка сочетается с надежной автоматизацией, что позволяет часто и предсказуемо вносить важные изменения с минимальными усилиями.

Облако - это место, где нативные облачные приложения не только работают, но и процветают, пользуясь такими характеристиками среды, как эластичность и доступ по запросу к вычислительным ресурсам.

Нативная облачная разработка обычно сочетается с практиками, обеспечивающими скорость, предсказуемость и стабильность. Автоматизация, процессы непрерывной доставки и образ мышления DevOps способствуют общему успеху «перехода на облачные технологии».

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

  • Масштабируемость. Нативные облачные приложения динамически масштабируются при увеличении или уменьшении рабочих нагрузок.
  • Слабая связь. Нативные облачные приложения состоят из частей, которые как можно меньше знают друг о друге.
  • Устойчивость. Нативные облачные приложения продолжают обеспечивать высокий уровень обслуживания даже в самых сложных условиях.
  • Наблюдаемость. Нативные облачные приложения предоставляют соответствующие выходные данные, из которых можно сделать вывод о внутреннем состоянии системы.
  • Управляемость. Облачные приложения можно легко и эффективно адаптировать извне и управлять ими.

Помня об этих свойствах, я расскажу вам о трех ключевых аспектах миграции традиционного приложения Spring в облачную среду.

Встроенные веб-серверы и JAR, полные чудес

Традиционно в экосистеме Java веб-приложения упаковываются как артефакты WAR и развертываются на таких серверах, как Tomcat, Wildfly или Jetty. У этого подхода есть несколько недостатков. Например, для оптимизации затрат (обслуживание серверов приложений обходится недешево), несколько приложений будут развернуты на одном сервере, создавая связь между ними и не позволяя им развиваться независимо. Сам факт того, что сервер требуется в среде, в которой развертывается приложение, приводит к жесткой зависимости для приложения, ограничивая его переносимость.

Вместо этого облачные приложения являются автономными и не ограничивают место развертывания, за исключением среды выполнения. Первое основное отличие состоит в том, что они упакованы как артефакты JAR, а не как WAR: отдельные, обычные JAR-файлы, для работы которых требуется только JVM. Джош Лонг, защитник разработчиков Spring, всегда говорит: Создавайте JAR, а не WAR. Однако облачному приложению все равно потребуется веб-сервер. Решение состоит в том, чтобы встроить веб-сервер внутрь самого приложения, чтобы сделать его полностью автономным.

От WAR к автономным JAR-файлам

Если у вас есть традиционное приложение Spring, упакованное как WAR и развернутое на внешнем веб-сервере, пора что-то менять. Spring Boot предоставляет вам все необходимое для этого. Во-первых, он поддерживает упаковку как WAR, так и JAR, что упрощает миграцию. Во-вторых, он поставляется с поддержкой встроенного веб-сервера. По умолчанию он автоматически настраивает экземпляр Tomcat, но вы можете легко заменить его на Undertow (лучший выбор, если вы пришли из Wildfly), Jetty или Netty.

Для сервера на основе сервлетов, такого как Tomcat, добавьте зависимость от Spring Web MVC в ваш build.gradle файл (или pom.xml).

implementation ‘org.springframework.boot:spring-boot-starter-web’

Для реактивного сервера, такого как Netty, добавьте зависимость от Spring WebFlux.

implementation ‘org.springframework.boot:spring-boot-starter-webflux’

Одной из удобных функций Spring Boot является управление зависимостями: использование начальных зависимостей, подобных предыдущим, упрощает управление версиями для всех связанных библиотек.

В нашем проекте в Systematic у нас были приложения Spring, упакованные как WAR и развернутые на внешнем Tomcat. Внося изменения, которые я только что обсудил, мы получили несколько преимуществ. Например, мы вернули контроль над веб-сервером, которым ранее управляла другая команда. Теперь мы можем настраивать встроенный сервер индивидуально и независимо для каждого приложения. Это большое достижение!

Настройка встроенного веб-сервера

В традиционном приложении Spring вы должны сконфигурировать сервер, например Tomcat, в таких файлах, как server.xml и context.xml. С другой стороны, Spring Boot предоставляет вам обширный и удобный набор свойств, который вы можете использовать для настройки встроенного веб-сервера (в ваших application.yml или application.properties файлах). Для более сложных настроек вы можете определить bean-компонент WebServerFactoryCustomizer. Один из наиболее распространенных сценариев использования такого bean-компонента - включение HTTPS и перенаправление всего HTTP-трафика на безопасное соединение.

Например, вы можете настроить порт сервера и пул потоков Tomcat в application.yml следующим образом:

server:
  port: 8080
  tomcat:
    threads:
      max: 100
      min-spare: 10

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

Кроме того, обработка приложений Spring Boot как JAR упрощает их контейнеризацию с помощью Docker. Вот что мы сделали. Spring Boot 2.3 делает это еще проще, предоставляя встроенные функции для упаковки приложений в виде образов Docker.

Внешняя конфигурация

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

  • дескрипторы ресурсов для баз данных, систем обмена сообщениями и хранилищ кешей;
  • URL-адреса вспомогательных служб, таких как REST API;
  • учетные данные для доступа к хранилищам данных и службам;
  • флаги функций.

Некоторые традиционные приложения сохраняют конфигурацию в той же кодовой базе, что и приложение, но это не идеально. Поскольку конфигурация представляет собой то, что различается между развертываниями, есть вероятность, что вы в конечном итоге будете хранить множество разных значений для каждой среды и заставлять себя создавать новую сборку из кодовой базы каждый раз, когда изменяется значение конфигурации. Кроме того, учетные данные никогда не должны храниться в виде обычного текста.

Если вы сделаете свою кодовую базу общедоступной, рискуете ли вы раскрыть какие-либо конфиденциальные данные? Если ответ положительный, то вам следует изменить подход к настройке.

Конфигурация в среде

Spring позволяет вам использовать свойства для настройки вашего приложения. Это пары ключ / значение, которые могут происходить из разных источников. Вы можете использовать абстракцию Spring Environment для доступа из единого интерфейса ко всем свойствам среды, в которой развернуто приложение, включая свойства JVM и переменные среды. Вдобавок к этому Spring Boot поддерживает еще больше источников свойств, каждый с разным приоритетом, и определяет механизмы перезаписи конфигурации для каждого развертывания.

Что вы хотите сделать, так это определить разумные значения по умолчанию для разработки в ваших application.yml или application.properties (как я показал в предыдущем разделе). Затем вы можете использовать Spring Boot для изменения конфигурации в зависимости от среды. Например, если вы хотите изменить значение свойства server.port, вы можете использовать системную переменную JVM -Dserver.port, которая имеет приоритет над файлами свойств.

Методология двенадцати факторов рекомендует хранить конфигурацию как переменные среды. Spring Boot тоже их поддерживает. Продолжая тот же пример, вы можете определить переменную среды SERVER_PORT, и Spring Boot распознает ее и сопоставит со свойством server.port.

В конце концов, Spring Boot будет использовать этот список приоритетов для определения значения свойства server.port:

1. Свойства системы JVM (-Dserver.port=9000)

2. Переменные среды (SERVER_PORT=8000)

3. Файлы собственности (server.port=8080)

В нашем проекте мы широко использовали переменные среды для настройки приложений, но этого недостаточно. Методология не учитывает некоторые аспекты конфигурации. Например, если вы хотите определить конфигурацию в среде, вам сначала нужно где-то ее сохранить, возможно, отслеживая в системе контроля версий для отслеживания и аудита. Отдельная кодовая база - это нормально, но как насчет учетных данных? Свойства Spring по умолчанию не поддерживают шифрование. Наконец, как поддерживать изменение конфигурации во время выполнения без перезапуска приложения?

Конфигурация на централизованном сервере

Spring Cloud Config решает все проблемы, описанные ранее, посредством централизованной настройки. Несколько приложений будут получать свою конфигурацию с центрального сервера, который может использовать разные стратегии для хранения свойств конфигурации. Например, вы можете использовать репозиторий Git для хранения свойств многих ваших приложений в структурированном виде и использовать HashiCorp Vault рядом с ним для хранения секретов. Spring Cloud Config будет собирать свойства из разных источников и передавать их приложениям.

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

Конфигурация в Kubernetes

Если вы развертываете свои приложения в Kubernetes, вы можете воспользоваться его встроенными функциями для управления конфигурацией (с ConfigMaps), учетными данными (с Secrets) и автоматическим обновлением приложений при изменении конфигурации. Вы даже можете использовать Spring Cloud Kubernetes, чтобы лучше интегрировать две системы и улавливать события обновления, отправляемые Kubernetes, точно так же, как вы это делаете с сервером конфигурации.

Наблюдаемость

Наблюдаемость - это определение состояния приложения по его выходным данным и не является чем-то новым для облака. Такие аспекты, как ведение журнала, мониторинг и оповещение, являются важными принципами любой инфраструктуры приложений, но мы должны переосмыслить их для облачных приложений. Поскольку целью является развертывание их в облаке, ситуация меняется по сравнению с развертыванием их на нескольких виртуальных машинах, управляемых локально с большой осторожностью. Относитесь к приложениям как к космическим зондам. Это очень красноречивая рекомендация, данная Кевином Хоффманом в его книге «Помимо двенадцатифакторного приложения». Какая телеметрия вам понадобится для удаленного управления и мониторинга ваших приложений?

Ведение журнала

Традиционные приложения обычно настраиваются для входа в файлы, включая настройки для хранения журналов, ротации, имен файлов и размеров. Наши приложения Spring были настроены именно так, используя Log4J для определения обработчиков, которые будут регистрировать файлы в соответствии с определенными ограничениями. С другой стороны, облачные приложения не заботятся о том, как журналы хранятся, обрабатываются или агрегируются.

Журналы следует обрабатывать как потоки событий и перенаправлять на стандартный вывод. Ответственность за сбор и хранение журналов переходит от приложений к платформе, на которой они работают. Затем есть какой-то внешний инструмент, который заботится об агрегации журналов. В нашем проекте мы используем Fluentd как часть стека EFK (Elastic, Fluentd, Kibana).

Показатели

Метрики - еще один важный аспект наблюдаемости, поскольку они предоставляют информацию о различных аспектах работы приложений. Нативные облачные приложения предназначены для предоставления любых данных, необходимых для проверки его внутреннего состояния и поведения. В традиционном сценарии вы развертываете библиотеки рядом с приложением на том же сервере. Вот что мы делали: на нашем сервере Tomcat мы использовали для развертывания наших приложений и таких инструментов, как Jolokia. Как я подчеркивал ранее, облачные приложения должны быть автономными, поэтому вы не хотите зависеть от этих других развертываний.

Мы решили эту проблему с помощью Spring Boot Actuator, мощного дополнения к Spring Boot, которое предоставляет захватывающие функции, которые делают его готовым к работе. Добавление Prometheus в наши приложения включало добавление зависимости к micrometer-registry-prometheus, включение конечной точки /actuator/prometheus через HTTP с помощью свойства management.endpoint.prometheus.enabled, и все. Конфигурация по умолчанию уже добавляет значимости вашему проекту. Вы все еще можете настроить его, например, добавив новые метрики и определив ограничения безопасности для конечной точки (полностью рекомендуется). Теперь все наши приложения предоставляют метрики в формате Prometheus, полученные сервером Prometheus и собираемые в Grafana, которые мы используем для визуализации и предупреждений.

Здоровье

Еще один аспект, связанный с наблюдаемостью, - это проверка работоспособности приложения. Spring Boot Actuator - это снова то, что мы использовали, чтобы справиться с этим. Эта библиотека предоставляет множество мощных функций, которые я рекомендую вам проверить. Один из них - это механизм проверки работоспособности. Вы можете открыть /actuator/health конечную точку через HTTP, установив свойство management.endpoint.health.enabled. Как и в случае с Prometheus, Spring Boot Actuator позволяет настраивать проверку работоспособности, добавляя собственные индикаторы работоспособности. Нашим приложениям требуется несколько вспомогательных сервисов, чтобы полностью обеспечить их функциональность. Некоторые из них, например Redis для хранения сеансов или MariaDB для хранения данных, автоматически включаются Spring Boot. Другие, например внешние службы RESTful, могут быть явно настроены путем реализации интерфейса HealthIndicator.

В целом, вы хотите проверить два разных состояния вашего приложения: живо ли оно и готово ли принимать подключения (например, если оно не может подключиться к базе данных, оно может быть недоступно для обработки любого запроса). Используя словарь Kubernetes, мы могли бы назвать их проверкой жизнеспособности и проверкой готовности. До Spring Boot 2.3 вы могли использовать конечную точку работоспособности в качестве проверки готовности и реализовать свою настраиваемую конечную точку для проверки работоспособности. Но теперь Spring Boot Actuator предоставляет оба из них прямо из коробки и предоставляет их через конечные точки /actuator/health/liveness и /actuator/health/readiness.

Заключение

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

Миграция традиционного Spring-приложения на облачное - очень приятное путешествие. В этой статье я рассмотрел три аспекта миграции и поделился с вами некоторыми советами и выводами из моего личного опыта в Systematic и за его пределами.

  • Нативные облачные приложения автономны. С помощью Spring Boot вы можете использовать встроенный веб-сервер и упаковать приложение как JAR.
  • Конфигурация для облачных приложений является внешней и никогда не сохраняется вместе с базой кода приложения. Spring Boot позволяет указать любую конфигурацию через переменные среды. Spring Cloud Config - это библиотека для настройки централизованного сервера конфигурации, которая имеет дополнительное преимущество, заключающееся в обеспечении отслеживаемости конфигурации и шифрования секретов. Kubernetes предоставляет ConfigMaps и Secrets для решения тех же проблем.
  • Наблюдаемость является ключом к облачным приложениям: относитесь к приложениям как к космическим зондам. Журналы следует обрабатывать как потоки событий, перенаправленные на стандартный вывод. Платформа отвечает за их сбор и хранение. Spring Boot Actuator предоставляет функции для предоставления метрик Prometheus и конечных точек проверки работоспособности, а также с точки зрения тестов работоспособности и готовности, используемых Kubernetes.

Был ли у вас опыт разработки в облачной среде? Вы думаете о переходе в облако? Я хотел бы услышать от вас о вашем опыте и проблемах, оставить комментарий под этой статьей или связаться со мной в Twitter или LinkedIn. Если вам понравилась моя статья, вы можете найти больше в моем блоге, где я пишу о облачной разработке, Spring и безопасности приложений.

Обо мне

Томас Витале - старший инженер-программист в компании Systematic, где он работал над функциями безопасности и конфиденциальности данных. В настоящее время он работает над модернизацией их платформ и приложений для облачного мира. Томас является автором книги Cloud Native Spring in Action, имеет степень магистра компьютерной инженерии, а также является сертифицированным профессионалом Spring Professional и сертифицированным разработчиком корпоративных приложений RedHat. Когда он не занимается разработкой программного обеспечения и не пишет о нем, Томас играет на пианино и любит путешествовать.

Ресурсы