Kubernetes у мікросервісному підході. Як ми налаштували розгортання динамічних оточень для оптимізації розроблення
Усі статті, обговорення, новини про DevOps — в одному місці. Підписуйтеся на DOU | DevOps!
Мене звати Ігор Прокопьєв, я Back-End Solution Architect у Plarium. Компанія спеціалізується на розробленні ігор та інших продуктів для гравців і співробітників.
Моя команда розробляє платформи для паблішингу ігор та залучення користувачів. Ми працюємо над десктопним застосунком Plarium Play, ігровим порталом plarium.com, форумом для взаємодії гравців та іншими проєктами.
У статті я розповім про те, як використовується Kubernetes у мікросервісному підході, як налаштоване розгортання динамічних оточень і чому це суттєво покращує робочі процеси в команді. Також продемонструю, як ми оптимізували розроблення та масштабували його на декілька паралельних версій.
Ця стаття буде корисною для Backend-розробників та DevOps-інженерів, які на базовому рівні засвоїли Kubernetes і планують працювати з ним далі. Якщо ви замислюєтеся над переведенням хостингу до Kubernetes та шукаєте підходи для полегшення управління проєктами, цей матеріал стане вам у пригоді.
Чому ми вирішили перейти на Kubernetes
Ігрові платформи Plarium, за які ми відповідаємо, сьогодні налічують приблизно 50 мікросервісів. Їхня кількість поступово зростала, і певної миті стало складно ними керувати.
Через це в нас виникли наведені далі запити.
1. Спрощення менеджменту мікросервісів та полегшення їх збирання й деплойменту
Велика кількість мікросервісів потребує детального планування, координації та контролю процесу розгортання. Для кожного сервісу важливі управління та нагляд.
Інший аспект управління мікросервісами — це проблема масштабування їхніх окремих складових для забезпечення високої доступності та продуктивності.
У мікросервісі іноді можуть виникати помилки та несправності. У такому разі його потрібно перезапустити або перемістити на інший вузол, щоб забезпечити стійкість системи. Автоматизація цих процесів значно полегшує і пришвидшує роботу.
2. Спрощення безперервної інтеграції та безперервного доставлення, тобто CI/CD-процесів
Безперервна інтеграція (Continuous Integration, CI) і безперервне доставлення (Continuous Delivery, CD) — це підхід до розроблення програмного забезпечення, що автоматизує та спрощує процеси збирання, тестування й розгортання застосунку. Усе це спрямовано на швидке й надійне доставлення нових функцій, оновлень і виправлень багів у середовищі.
Для CI/CD потрібно налаштувати інфраструктуру, що гарантує автоматичне збирання, тестування та розгортання застосунку. Щоб реалізувати CI/CD-процеси, можуть знадобитися різні інструменти та сервіси, як-от системи збирання (наприклад, Jenkins), системи контролю версій (наприклад, Git), контейнеризація (наприклад, Docker) тощо. Крім того, важливо забезпечити синхронізацію середовищ.
3. Паралельна розробка декількох спринтів одночасно
У нас велика команда розробників, тому до одного спринту йшло одразу багато завдань, і його було складно тестувати та релізити. Наприклад, на проєкті Plarium Play спринти підвʼязані під версії платформи. Оптимальне розв’язання цієї проблеми — робити одразу декілька паралельних спринтів, тобто починати розробку нової версії до того, як зарелізили поточну. Так ми пришвидшуємо процес і розподіляємо таски між версіями.
Щоб працювати одночасно над кількома версіями, нам потрібно було налаштувати швидке та автоматизоване розгортання нових оточень з одразу всіма потрібними мікросервісами.
Це також дало б змогу швидко робити hotfix — виправляти помилки в тій версії, що вже на проді. Щоб не зупиняти процес розроблення інших версій, можна створити окреме оточення під ту, яка на проді, і швидко розв’язати проблему.
4. Можливість запускати автоматичні тести для перевірки інтеграції між сервісами
Ми зіткнулися зі ще однією проблемою, повʼязаною з великою кількістю мікросервісів. Буває, що розробник робить завдання і випадково ламає готову функціональність. Щоб такого не траплялося, використовуються unit-тести, але вони не перевіряють інтеграцію та взаємодію між мікросервісами. Нам потрібні були інтеграційні тести, які в разі pull-request розгортали повноцінне динамічне оточення. Це захистило б нас від «зламаних» оточень і не гальмувало процес розроблення.
Після аналізу наявних інструментів ми обрали Kubernetes. Це оркестратор контейнерів, за допомогою якого можна створювати, балансувати, масштабувати проєкти й управляти ними в одному місці.
Kubernetes дає змогу керувати одночасно кількома кластерами й перевикористовувати налаштовану інфраструктуру за допомогою локального кластера minikube.
Щоб задовольнити запити щодо паралельного розроблення декількох спринтів одночасно й запускання автоматичних інтеграційних тестів між сервісами, ми вирішили розробити механізм динамічних оточень. Далі я розповім і покажу, як були використані можливості Kubernetes для налаштування.
Розгортання нового динамічного оточення з усіма залежностями
Оточення — це ізольований набір мікросервісів та допоміжної інфраструктури, як-от база даних, система кешування Redis, система черг RabbitMQ, розгорнутий в окремому середовищі й до якого є доступ через унікальні доменні адреси.
Основна проблема розгортання нових оточень — забагато дій, які треба виконати. Нам потрібні база даних, система черг, кеш, додаткові залежності, а також нові доменні адреси з реєстрацією для кожного оточення. На розгортання нового оточення для Plarium Play знадобилося б приблизно 2 тижні роботи DevOps-інженера та розробника. Тому до переходу на Kubernetes ми цього не робили взагалі.
Створення нових оточень потрібне для паралельного розроблення та гнучкості в тестуванні нової функціональності. Зараз ми створюємо окреме оточення під кожен реліз Plarium Play. На прикладі тестового сервісу покажу, як це робиться.
Створюємо точку входу Gateway та 5 мікросерверів, від яких залежить Gateway. Робимо Gateway залежним від MS SQL Server, RabbitMQ, Redis.
Залежності мікросервісу Gateway
Ми розробили спеціальний Jenkins Pipeline, щоб створювати динамічні оточення.
Для опису Kubernetes-ресурсів мікросервісів використовується Helm. Helm — це менеджер шаблонів проєктів для Kubernetes. Опис кожного шаблону ресурсів мікросервісу має назву Helm Chart.
Цей Pipeline підвантажує всі наявні Helm Charts з нашого репозиторію чартів і дає змогу обрати, яку комбінацію сервісів потрібно розгорнути. У разі вибору Helm Chart також підтягуються залежності проєкту від інших. Після вибору чартів і підвантаження залежностей Jenkins Pipeline пропонує вибрати Git Branch у кожному мікросервісі, з якого буде розгорнуто динамічне оточення.
Jenkins Pipeline. Створення динамічного оточення
Запускаємо білд та отримуємо нове динамічне оточення. Під час запускання білда ініціюється процес збирання докер-образів кожного мікросервісу з вибраної користувачем гілки Git. Процес збирання образів відбувається паралельно, тому не триває довго. Після збирання всіх потрібних докер-образів відбувається розгортання оточення, де кожен мікросервіс розгортається із зібраного докер-образу.
Jenkins Pipeline після створення динамічного оточення
Kubernetes Dashboard зі створеним динамічним оточенням
Отже, під час створення оточення для сервісу Gateway автоматично була розгорнута потрібна інфраструктура — MS SQL, Redis, RabbitMQ, а також мікросервіси.
Автоматична міграція користувачів до створеного оточення
Після створення нове динамічне оточення для проєкту Plarium Play не буде порожнім: воно міститиме певний контент і тестових користувачів, щоб QA-спеціалістам було легше проводити тестування.
В окремому вебзастосунку задаються потрібні userID, які копіюватимуться до динамічного оточення під час його створення.
Вебзастосунок із користувачами, які копіюватимуться до динамічного оточення
Для міграції ми розробили механізм запускання за допомогою Nuke. Nuke — це бібліотека .net core, яка здатна спростити процес написання та запуску скриптів, що можуть знадобитися під час налаштування CI/CD. Під час білду образу мікросервісу також білдиться супутній образ бази даних. У супутньому образі міститься проєкт із FluentMigrator, який складається з міграцій схеми бази даних.
У Helm Chart проєкту за допомогою init containers розгортається супутній образ бази даних мікросервісу перед створенням оточення.
spec: initContainers: - name: database-migrationб image: "{{ .Values.image.repository }}-database:{{ .Values.image.tag | default .Chart.AppVersion }}"Усередині супутнього контейнера з міграцією бази даних запускається процес Nuke, який застосовує потрібні FluentMigrator міграції, після чого запускається міграція користувачів з ID, указаних у вебзастосунку.
Користувачі будуть мігрувати з еталонного стабільного оточення із заздалегідь визначеною назвою.
Динамічна зміна git branches у незалежному оточенні та можливості Code Freeze
Часом є потреба змінити гілку вже після створення оточення. Наприклад, я створив оточення з гілки, а потім зрозумів, що мені потрібно змінити її — але я не хочу видаляти оточення, тому що в мене вже є створені тестові користувачі або певний їхній стан.
Для цього ми зробили інший Jenkins Pipeline, який відображає створені динамічні оточення, дає змогу вибрати одне з них і подивитися, з яких мікросервісів воно складається. Також він показує, з яких Git Branch розгорнуті мікросервіси, дає змогу змінити Git Branch і застосувати функцію Code Freeze.
Jenkins Pipeline. Зміни динамічного оточення
Цей механізм дуже корисний під час підготовки Release Candidate. Коли QA-команда починає тестування, то є змога заморозити будь-які зміни на оточенні та отримати стабільний очікуваний результат під час релізу.
Робота з базою даних у незалежному оточенні
Коли ми створюємо оточення, воно завжди потребує своєї бази даних. Щоб підключитися до бази даних, застосовується Service Type ресурсу Kubernetes під назвою LoadBalancer.
Наш Kubernetes Cluster розташований не в Cloud, а на виділених серверах, тому для підключення до бази даних ми використали плагін MetalLB. Він дає змогу задати діапазон IP-адрес, які плагін використовуватиме для ресурсів LoadBalancer.
Kubernetes Dashboard, ресурс MetalLB
Тепер для бази даних можна вибрати тип ресурсу LoadBalancer та отримати IP-адресу, за допомогою якої до неї можна приєднатися.
Ресурс LoadBalancer з IP-адресою
Інтеграційні тести для забезпечення взаємодії між сервісами
У нас була потреба запобігти ситуації, коли розробник під час зміни функціональності ламає інтеграцію з іншими сервісами. Зазвичай для перевірки інтеграції між модулями використовують unit-тести, але їх запускають для одного конкретного мікросервісу й не перевіряють взаємодію з іншими сервісами. За таких умов можлива ситуація, коли буде зламана інтеграція між сервісами. Цих ризиків можна повністю уникнути завдяки інтеграційним тестам на етапі pull request.
У репозиторії з кожним проєктом розміщується проєкт інтеграційних автоматичних тестів. Написанням цих тестів займається команда Automation QA. Усі репозиторії мають налаштування «Мінімум 1 успішний білд» для pull request.
Налаштування репозиторію
Під час pull request в будь-яку гілку розгортається динамічне оточення. Мікросервіс, у який було створено pull request, збирає docker image із source гілки та автотести для нього, а всі інші сервіси, від яких залежить мікросервіс, розгортаються з тегом latest. Після цього також перевіряється наявність інших мікросервісів, у яких серед залежностей є поточний.
Візьмімо приклад із сервісом Gateway, що залежить від сервісу Service1. Коли ми зробимо pull request у Service1, запустяться інтеграційні тести для Service1 та Gateway для того, щоб зміни в Service1 не зламали функціональність у Gateway. Результат кожного набору інтеграційних тестів можна побачити в Jenkins Pipeline, що запускається під час створення pull request.
Результат проходження інтеграційних тестів
Якщо автотести не пройшли, то змержити pull request не буде змоги.
UI, що відображає неможливість merge pull request
Якщо інтеграційні тести не пройшли, то розробник має змогу одразу подивитися, що саме було зламано. Кожен тестовий кейс видає trace-id.
Результат інтеграційного тестування, приклад Trace-Id
Для трасування запитів ми використовуємо Jaeger. Кожний мікросервіс записує трасування до Jaeger, після чого за Trace-Id можна відстежити повний шлях запиту від початку до кінця, разом із усіма параметрами.
Jaeger Dashboard з результатами tracing
Результати роботи
Завдяки переходу на Kubernetes ми отримали змогу гнучко розробляти та швидко розгортати повноцінні динамічні оточення.
З того моменту пришвидшилося розроблення нових версій лаунчера Plarium Play. Крім того, тепер ми розробляємо декілька версій одночасно.
Завдяки інтеграційному тестуванню покращилася якість функціональності, що потрапляє на тестові оточення. Такий підхід дає змогу зменшити кількість регресійного тестування, що заощаджує час команді Manual QA.
Для складних у повторенні багів розробники можуть використати динамічні оточення та попросити QA-спеціалістів відтворити баг на конкретному оточенні, після чого подивитися tracing запитів або навіть продебажити код на динамічному оточенні. Одним зі способів використання динамічних оточень стала одночасна робота декількох розробників над одним завданням.
Під час створення цього підходу я насамперед думав про спрощення роботи команд розроблення та QA. Після реалізації всі команди відзначили простоту, швидкість і зручність динамічних оточень, а також переваги підходу, як порівняти зі статичними оточеннями, якими ми користувалися раніше. Єдиним недоліком, на мою думку, є складність реалізації, але вона виправдовує отриманий результат.
Немає коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів