Ques/Help/Req Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes

XakeR

Member
Регистрация
13.05.2006
Сообщения
1 912
Реакции
0
Баллы
16
Местоположение
Ukraine
Статья ориентирована на тех разработчиков, кто разбил рабочий монолит на микросервисы и теперь думает, как быстрее зарелизить микросервисы на стенд для экспериментов.

В статье основной упор делается на приготовление одного из рецептов CI в монорепозитории, прохождение по стадиям настройки Spring Cloud Gateway, в конце добавлена щепотка мониторинга — подключение Sentry к микросервисам.

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


Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes0
Поднимаем стенд Spring микросервисов в Kubernetes

tproger.ru


Код статьи размещён в монорепозитории:


GitHub — alexandr-leonov/library-platform: Spring boot microservices test stage github.com


Автор: Александр Леонов, руководитель группы разработки одной из распределенных команд Usetech.

Дисклеймер:

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

Middle Go developer [Ocean] МТС, , можно удалённо, По итогам собеседования tproger.ru Вакансии на tproger.ru

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

Для начала проведём подготовку со стороны кода, выделив в отдельные модули важные части (будем дробить по бизнес-ценности), затем изолируем модули друг от друга и сделаем их самостоятельными. Spring Boot приложениями. В итоге, проект будет выглядеть так:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes1


Отлично, теперь у нас есть 4 отдельных разрозненных микросервиса, которые разные и под которые нужно переделывать фронт. Но нам, по возможности, этого бы не хотелось, поэтому пришла идея использовать Spring Cloud Gateway. Но не стоит сразу с места в карьер. В теории, если успех магазина по продаже книжек зайдёт, то его можно будет развести до целой платформы и потом продавать другим разработчикам сертификаты о её знании. Для того, чтобы безболезненно внедрять микросервисы, нам нужно хранить их конфигурации в одном месте, всё равно уже в монорепозитории делаем. Так как проект учебный, то почему бы не использовать стандартный Spring Config Service. Добавим к нашим разрозненным сервисам config-service и вынесем в специальную папку config все отличающиеся части application.yml файлов сервисов, унифицировав оставшиеся к одному стандартному виду, который будет тянуть всё содержимое из сервиса конфигурации.

В итоге получается вот такая структура:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes2


Новый сервис по коду — обычный spring boot application, но с особенностями:

1) В помнике присутствуют две зависимости:

org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-config-server

2) Над стартером приложения размещена аннотация @EnableConfigServer

Рассмотрим application.yml нового config-service:

# only for config service server: port: 8888 # порт на котором будет крутиться сервис конфигурации spring: application: name: config-service cloud: config: server: native: classpath:/config # папка, куда сервисы будут стучаться за своими конфигами profiles: active: native

Рассмотрим конфигурацию order-service, которую мы будем подгружать из конфиг сервиса config/order-service.yml:

server: port: 8084 management: endpoints: web: exposure: include: «*» properties: mark: order-service

А теперь увидим изменённый application.yml внутри сервиса order-service:

application: name: order-service config: import: configserver: # приложение будет стучаться за конфигом по этому адресу при старте приложения

Всё, теперь order-service будет подгружать конфигурацию из config-service. Помимо плюсов, стоит отметить, что будут минусы при написании тестов, т. к. нам нужно будет или поднимать config-service или копировать содержимое части order-service.yml в application-test.yml. Но мы пока о тестах не думали, поэтому вывели все конфигурации сервисов в наш config-service.

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

Отлично, унифицировав наши микросервисы, давайте избавимся от необходимости вносить правки на фронте, применяя один API Gateway для взаимодействия со всеми сервисами. Для этого создадим новый сервис — gateway-service, который будет использовать Spring Cloud Gateway и крутится на 8080 порту, как наш старый монолит.

Из особенностей gateway-service можно выделить только:

1) наличие необходимых зависимостей

org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-gateway org.springframework.boot spring-boot-starter-webflux

2) наличие в конфиге application.yml необходимых перенаправлений на сервисы:

spring: cloud: gateway: routes: — id: profile_route uri: predicates: — Path=/profiles/** — id: order_route uri: predicates: — Path=/orders/**

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

Нам поможет сборка отдельных модулей maven + одна из стратегий CI в монорепозитории через деплой по коммиту в отдельную ветку, созданную под отдельный сервис. Это проще для понимания, нежели чем стратегия определения изменений в конкретных директориях модулей.

Итак, напишем наш ci файл. Как и раньше будем использовать github-ci. В итоге, получим такой конфиг сборки артефактов наших сервисов и упаковки их в образы, в docker-hub:

.github/workflows/maven.yml (по стандартному шаблону github):

name: Common pipeline for all services on: push: branches: [ «config-service», «gateway-service», «order-service», «payment-service», «profile-service», «product-service» ] pull_request: branches: [ «config-service», «gateway-service», «order-service», «payment-service», «profile-service», «product-service» ] jobs: build-config-service: runs-on: ubuntu-latest if: github.ref == ‘refs/heads/config-service’ # по коммиту в ветку config-service происходит триггер джобы деплоя steps: — uses: actions/checkout@v3 — name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: ’17’ distribution: ‘temurin’ cache: maven — name: Build config-service artifact run: mvn -pl config-service clean install # сборка только нужного сервиса — name: Deploy app run: cd config-service && docker build -t ${{ secrets.DOCKER_HUB_LOGIN }}/config-service:latest -f- ./ < Dockerfile && docker login -u ${{ secrets.DOCKER_HUB_LOGIN }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} && docker push ${{ secrets.DOCKER_HUB_LOGIN }}/config-service:latest # сборка образа и его публикация в Docker Hub, перед этим необходимо создать там такие репозитории. build-gateway-service: runs-on: ubuntu-latest if: github.ref == ‘refs/heads/gateway-service’ steps: — uses: actions/checkout@v3 — name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: ’17’ distribution: ‘temurin’ cache: maven — name: Build gateway-service artifact run: mvn -pl gateway-service clean install — name: Deploy app run: cd gateway-service && docker build -t ${{ secrets.DOCKER_HUB_LOGIN }}/gateway-service:latest -f- ./ < Dockerfile && docker login -u ${{ secrets.DOCKER_HUB_LOGIN }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} && docker push ${{ secrets.DOCKER_HUB_LOGIN }}/gateway-service:latest build-order-service: runs-on: ubuntu-latest if: github.ref == ‘refs/heads/order-service’ steps: — uses: actions/checkout@v3 — name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: ’17’ distribution: ‘temurin’ cache: maven — name: Build order-service artifact run: mvn -pl order-service clean install -Dmaven.test.skip=true — name: Deploy app run: cd order-service && docker build -t ${{ secrets.DOCKER_HUB_LOGIN }}/order-service:latest -f- ./ < Dockerfile && docker login -u ${{ secrets.DOCKER_HUB_LOGIN }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} && docker push ${{ secrets.DOCKER_HUB_LOGIN }}/order-service:latest …

Ура, мы сделали CI из монорепозитория только тех сервисов, которые будем править. Но есть одно но: сервисы за нашим Spring Cloud Gateway не будут масштабироваться автоматически, т. к. выше мы их завязали на порты, т. е. Нам нужно будет править ещё и Spring Cloud Gateway. Давайте поднимем свой service discovery и подружим его со Spring Cloud Gateway. В свободном доступе все предлагают использовать Eureka, её и будем использовать.

Добавим ещё один сервис discovery-service, который будет ничем иным, как Eureka Server со следующими приметами:

1) Над стартером используется аннотация @EnableEurekaServer

2)

org.springframework.cloud spring-cloud-starter-netflix-eureka-server io.sentry sentry-spring-boot-starter-jakarta ${sentry.version}

3) во все существующие сервисы добавим над стартером аннотацию: @EnableDiscoveryClient

и зависимость в помники:

org.springframework.cloud spring-cloud-starter-netflix-eureka-client а ещё, убедимся, что у всех сервисов есть имя, это важно! spring: application: name: order-service # Для сервиса заказов

Теперь переделаем наш конфиг gateway-service:

spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: — id: product_route uri: lb://product-service predicates: — Path=/products/** — id: profile_route uri: lb://profile-service predicates: — Path=/profiles/**

Ура, вот теперь мы можем не трогать Spring Cloud Gateway при масштабировании наших сервисов. Работа через Eureka это, конечно, хорошо, но мы бы хотели стенд на kubernetes (т. к. монолит деплоился раньше туда). К тому же, там просто чудесные средства масштабирования приложений из коробки. В таком случае, давайте переведем наши сервисы в k8s:

Создадим пространство имён:

apiVersion: v1 kind: Namespace metadata: name: library-platform labels: name: library-platform На каждый сервис создадим свой конфиг деплоя: apiVersion: v1 kind: Service metadata: name: payment-service namespace: library-platform labels: app: payment-service spec: externalName: payment-service.library-platform.svc.cluster.local selector: app: payment-service-container ports: — name: payment-service port: 8083 protocol: TCP targetPort: 8083 nodePort: 30005 type: NodePort — apiVersion: apps/v1 kind: Deployment metadata: name: payment-service-deployment namespace: library-platform labels: app: payment-service-deployment spec: replicas: 1 selector: matchLabels: app: payment-service-container template: metadata: labels: app: payment-service-container spec: containers: — name: payment-service-container image: ${{ secrets.DOCKER_HUB_LOGIN }}/payment-service:latest imagePullPolicy: Always ports: — containerPort: 8085 name: rest — containerPort: 8888 name: config — containerPort: 8761 name: discovery — containerPort: 9092 name: kafka env: — name: CONFIG_SERVICE_HOST value: $(CONFIG_SERVICE_SERVICE_HOST) # Сетевая связность обеспечивается через стандартные механизмы наименования переменных сред k8s — name: CONFIG_SERVICE_PORT value: $(CONFIG_SERVICE_SERVICE_PORT) — name: EUREKA_HOST value: $(DISCOVERY_SERVICE_SERVICE_HOST) — name: EUREKA_PORT value: $(DISCOVERY_SERVICE_SERVICE_PORT)

В принципе на этом этапе, можно забыть про Eureka, т. к. далее мы всё равно переделаем наш gateway-service для работу с Kubernetes абстракцией под. Если бы не было Kubernetes, то Eureka была бы незаменимой вещью, но тут она может отдохнуть и остаться в конфигах, в памяти о былых заслугах.

Обновленный gateway-service.yml:

spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: — id: product_route uri: lb://product-service # роуты, которые работают через Eureka predicates: — Path=/products/** — id: profile_route uri: lb://profile-service predicates: — Path=/profiles/** — id: order_route uri: # роуты, которые работают только локально predicates: — Path=/orders/** — id: payment_route uri: http://${PAYMENT_SERVICE_HOST}:${PAYMENT_SERVICE_PORT}/payments/** # роуты, которые работают с k8s predicates: — Path=/payments/** eureka: client: service-url: defaultZone: http://${EUREKA_HOST}:${EUREKA_PORT}/eureka instance: preferIpAddress: true

Готово, давайте задеплоим наши поды, отмасштабируем payment-service и проверим, что всё работает, как мы задумывали:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes3


Т.к. у нас ещё жива Eureka давайте проверим, что она видит 2 поды:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes4


Постучимся через API Gateway к нашему сервису:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes5


Супер, а теперь постучимся за детализацией несуществующих платежей и проверим, что балансер Kubernetes распределяет трафик по двум подам:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes6


Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes7


Итого, из 6 минус идентификаторов — 2 ушли в ошибку на одной поде, 4 на второй (основная).

Мы достигли цели, осталось накрутить мониторинг Sentry, для наработки навыка работы с фоном ошибок в домашних условиях.

1) Зарегистрируемся в

2) Создадим своё пространство, в нём создадим Spring Boot проект:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes8


3) Добавим нужные зависимости в pom.xml по инструкции и dsn путь для отправки ивентов в payment-service.yml (см. код проекта по ссылке) + сделаем выставление значений через переменные среды в deployment файле payment-service.yaml k8s, для замены значений на лету.

Всё, теперь посмотрим как выглядит фон ошибок по нашим двум подам payment-service:

Поднимаем Full Spring стенд микросервисов из монорепозитория в Kubernetes9


Отлично, мы подняли свой стенд микросервисов из дробленого монолита с деплоем из монорепозитория и мониторингом Sentry. Конечно, можно продолжить улучшать то, что уже есть: пирамида тестов, безопасность через sso, зеркалирование трафика kubernetes, внедрение Vault конфигов, но это потом.

Надеюсь, статья была полезной и интересной. Спасибо за уделенное время!
 
198 207Темы
635 179Сообщения
3 618 416Пользователи
artvladimir2004Новый пользователь
Верх