×Закрыть

Оптимальне Delivery: від ZIP-архіву до docker push

Привіт, мене звуть Олександр Нагірняк. Я співпрацюю з EPAM Ukraine як Lead Software Engineer. У цій статті я розповім вам про те, як зробити процес Delivery трохи зручнішим і безпечнішим для вашого психічного здоров’я. Усі поради в цьому тексті ґрунтуються на власному досвіді, набутому в процесі спроб, помилок і боротьби з дедлайнами. Буду радий продовжити обговорення в коментарях до матеріалу.

Disclaimer: усі персонажі й описані події вигадані. Будь-який збіг з реальними людьми чи подіями — випадковість. Англіцизми використано задля точнішого формулювання конкретних ситуацій.

Рутина

Розробка, доставка коду та його розгортання в середовищах ускладнюються з року в рік. Напевно, кожен з нас — розробників — хоч раз у житті писав свій монолітний застосунок і далі виконував рутинні операції: спаковував його до ZIP-архіву, завантажував на FTP, заходив через RDP на веб-сервер, звантажував підготовлений архів на 400 МБ, робив бекап поточної версії в папку на кшталт BackupMyBuglessApp, копіював нову версію, виконував IIS recycle pool чи TomCat restart.sh. Ну а далі, за класикою жанру, щось відмовлялося працювати, ви розуміли, що просто забули докинути іншу версію одного bin-файлу, і процедура розгортання повторювалася знову (а іноді — і не раз).

Якщо таких ситуацій з вами ніколи не траплялося, то я радий за вас. Розгортання — це не та процедура, за якою хочеться пускати сумну ностальгійну сльозу.

Звичайно, такий алгоритм дій не може бути прийнятним. Особливо, коли команда починає розширюватися, система зростає семимильними кроками, а функціональність зі спринта в спринт збільшується в рази. Тоді згодиться Shell або PowerShell (дякую, Білле!). Залишається лише обрати людину з команди, яка б зайнялася написанням коду на цих прекрасних інструментах. Переконаний, що таку людину ви знайдете без проблем. Найімовірніше, це будете ви.

Тривожні дзвіночки перших проблем

Минає тиждень кропіткої праці й пошуку на Stack Overflow інформації про те, як написати for-цикл у PowerShell. Скрипти написано, усі щасливі від того, що подвійне натискання лівої кнопки миші розгортає нову версію веб-застосунку. Уже фантазуєте про «велику червону кнопку», натискання якої запускатиме той самий процес.

Ваша команда працює злагоджено й професійно, у вас з’являється дедалі більше обов’язків, і ви вирішуєте делегувати процес розгортання і, наприклад, узяти один день відпустки, щоб трішки відпочити від написання коду. Здавалося б, нічого не віщує біди, поки на пошті чи будь-якому іншому месенджері, не з’явиться повідомлення від колеги з якоюсь подібною помилкою:

File C:\GreatestApp\Staging_MyBuglessApplication\scripts\copyAndDeploy.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please see «get- help about_signing» for more details.

На щастя, перший рівень помилок розв’язують через елементарний пошук. Але не все буває так просто, бо розгорнуто версію з критичною проблемою і потрібно швидко зробити rollback. Але, на жаль, файлу rollbackEverythingBad.ps1 ще немає, бо завдання на цю функціональність загубилося десь разом з низькопріоритетними бізнес-завданнями. Тож наступний робочий тиждень витрачаємо на написання такої функціональності, а також налаштування CI/CD-інструменту запуску тестів і використання скриптів, які були написані до цього.

Ну а потім усе закручується, стрімко летить час, і вже за рік система містить декілька (десятків) сервісів, які спілкуються через брокер-повідомлення, логи зберігаються десь у NoSQL-базі, на кшталт Elastic, налаштована система сповіщень і система горизонтального скейлінгу під кожний сервіс.

Усі проблеми стають дедалі масштабнішими. Особливо ті, що пов’язані з протягуванням тих самих bin-артефактів через середовища веб-серверів dev ->QA ->staging ->prepreprod ->preprod ->prod ->demoEnvironmentForInvestors. До того ж ще й зі zero-downtime-розгортанням, оскільки це важливо з погляду бізнесу.

Інший тип проблем — проблеми з використанням інфраструктурних сервісів, незалежно від того, розгорнуті вони у хмарному провайдері чи у власних серверах. А «вишенькою на торті» серед незручностей стає розгортання абсолютно нового середовища з усією інфраструктурою (бази даних, брокери повідомлень, load balancers тощо) десь у Середній Азії, бо замовник визначив стратегічною ціллю запустити бізнес у цьому регіоні й саме тепер.

Досвідчений оператор ПК програміст, читаючи між рядків, уже виокремив ключові проблеми розгортання коду в середовищах. Але все ж наведу їх у списку:

  • неправильне розуміння правильних потреб проекту в певний проміжок часу;
  • заплутаність процесу з протягування артефактів крізь середовища й rollback невдалих версій сервісів;
  • складність моніторингу логів, горизонтального скейлінгу сервісів і zero-downtime-розгортання тощо;
  • об’ємність налаштування абсолютно нового середовища, наприклад в іншому регіоні.

Докладно про проблеми розгортання і як їх розв’язати, з огляду на мій досвід, і йтиметься далі.

Розгортаємося правильно

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

Щоб уникнути холіварів, я не прив’язуватимуся до конкретної мови програмування. Лише наголошу, що вона може бути будь-яка, якщо: а) вона розв’язує проблему ефективно, б) легко знайдуться люди, які нею не лише пишуть, а й роблять це професійно. В іншому разі є ймовірність ніколи не вийти в продакшен.

Також я не розповідатиму вибір того чи іншого інструменту з Continuous Integration, Continuous Deployment. Майже всі сучасні CI/CD-інструменти можуть забезпечити зручну доставку коду в середовище як вбудовані функції або плагіни. Jenkins, TeamCity, GitLab CI — це все справа смаку, розміру проекту й кількості грошей у гаманці.

Тож ідемо далі. Чи не основний інструмент, що допомагає перемогти головні проблеми доставки коду й розгортання, — це Docker.

По-перше, він дає змогу уніфікувати ваші бінарники в один артефакт. Наприклад, у вас є один сервіс, написаний на Python. Він називається Ping. Цей сервіс робить HTTP-запит на інший сервіс під назвою Pong, написаний на .NET Core, і є тести, написані на Node.js, які перевіряють кінцевий результат. За допомогою Docker, незалежно від платформи, у результаті ви отримуєте готовий артефакт ― Docker Image, а згодом запущений Docker Container.

По-друге, можна задати цьому артефакту конкретну версію (тегнути), на кшталт users-2019.9.13, і завантажити на публічний або приватний Docker Registry: Docker Hub, AWS Elastic Container Registry тощо. Це дає змогу розв’язати проблему версій і надалі спрощує rollback, якщо той знадобиться.

У результаті ви можете взяти раніше завантажений артефакт і протягти його крізь усі потрібні середовища dev ->QA ->staging ->prod та запускати всюди, де встановлено Docker і/або є той чи інший Docker Orchestrator.

Звісно, у Docker Image є недоліки. Наприклад, у цей артефакт не можна успішно втиснути застосунок, який написано під .NET Framework 4.5 (двічі дякую, Білле!).

А ще один величезний недолік — те, що постійно забуваєш, як писати мапінги портів у docker-compose.yml (host port: container port vs container port: host port) :)

Отож створили ми той артефакт, поклали мегабайти в якесь своє приватне сховище.

Що далі

Далі потрібно цей артефакт десь розгорнути, наприклад, у тому чи іншому хмарному провайдері. Звісно, можна це зробити на власних серверах, але доведеться підтримувати всю інфраструктуру, враховуючи рівень операційної системи (а можливо, і заліза). Тому, якщо в продукту немає ніяких обмежень з розгортання в клауді, то варто брати клауд.

Розгортання на on-premise — це тема окремої статті, я не експерт з розгортання на власних машинах, тому не думаю, що маю право про це писати, хоча концепції досить схожі. Досвіду з розгортання на AWS у мене трохи більше, тому всі наступні приклади будуть орієнтовані саме на цю платформу. А тему розгортання під Azure залишимо євангелістам Microsoft.

Гаразд, маємо Docker Image з нашими бінарниками всередині. Маємо AWS-акаунт, API key / API secret уже видано. Можемо впевнено почати розгортання артефакту. Нині для цього є такі опції:

  1. Підняти власну Virtual Machine aka AWS EC2 instance, налаштувати систему логування, установити Docker, написати скрипти, що стягуватимуть Docker Image і запускатимуть з нього Docker Container.
  2. Використати одну з опцій готового сервісу AWS Elastic Container Service aka ECS:
    • ECS EC2 mode — автоматизація того, що написано в пункті вище.
    • ECS Fargate mode — абстракція того, де розгортають контейнер, якщо EC2 mode підіймає фізичну VM-ку у вас в AWS-акаунті, то Fargate запускає контейнери «десь».
  3. Використати AWS Elastic Kubernetes Service aka EKS — адаптований під AWS Kubernetes.

Вибір тієї чи іншої опції залежить від поставленого перед вами завдання. Якщо потрібно запуститися швидко й без DevOps-команди, попри те, як там усе працює «під капотом», можна взяти ECS. Коли потрібно такий собі «швейцарський ніж» з детальнішою системою моніторингу і є команда досвідчених інженерів інфраструктури, то правильнішим вибором був би EKS.

Звісно, таке різноманіття підходів і технологій було не завжди.

Від теорії до практики: ECS й Infrastructure as a Code

Тоді, коли стартував мій поточний проект в компанії, були доступні опції 1 і 2.1, тому логічно було піти шляхом більшої автоматизації. Ба більше, однією з вимог до продукту було zero downtime deployment, а ECS давав змогу легко це налаштувати.

Клауд має одну хитру особливість: дуже легко «підсісти» на його сервіси, особливо, коли потрібно зробити MVP за півроку й часу на вибір того чи іншого інструменту обмаль. І якщо розгортання на AWS ECS для пет-проекту чи стартапу на AWS Free Tier з декількома сервісами можна описати якось так, то розгортання 46 сервісів повноцінного продукту буде трошки важчим завданням. Особливо з налаштованими сповіщеннями про ліміти CPU, RAM тощо на кожний сервіс і конфігурацією сервісу (ConnectionStrings, ServiceAddress), яка зберігається в AWS Parameter Store. А також з купою сервісів самого AWS, зокрема:

  1. Application Load Balancer — задля збалансування вхідних реквестів і балансування взаємодії між сервісами (так, у нас деякі сервіси спілкуються через HTTP і проблеми з цим виникають украй рідко, але вони все ж є, про це нижче).
  2. Simple Queue Service / Simple Notification Service — це ті сервіси, що мають стрімкі підвищення навантаження (спайки), спілкуються через брокер повідомлень.
  3. ElastiCache — кешування за допомогою Redis.
  4. Elasticsearch — full-text search у логах і в активностях користувача.

І це все на (1)dev -> (2)QA -> (3)staging ->(4)prod чотирьох середовищах.

Створення й підтримка кожного релізу налаштувань вищезгаданих сервісів ― це не те, чим би ви хотіли займатися. Довіртеся моєму досвіду.

Згодиться підхід Infrastructure as a Code, тобто декларування в коді всього того, щоб ви хотіли бачити в себе в AWS-акаунті в кожному з середовищ. Навіть якщо буде потреба розгорнути середовище з усіма сервісами в новому регіоні.

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

Також варто сказати про доцільність використання цих інструментів: якщо ви розумієте, що проект стрімко розвиватиметься до кількох десятків сервісів та кількох десятків людей, і це все відбуватиметься в багатьох середовищах, то, на мою думку, варто інвестувати час у цей підхід.

Якщо це буде проект на 5-7 людей, а стрімке зростання передбачено лише після Series B, то витрачати час на цей підхід не варто. Тобто потрібно впіймати «правильну» мить, коли слід упроваджувати цей підхід.

Завжди є куди рухатися

Прогрес не стоїть на місці, наш проект також, тому завжди є миті, коли треба поліпшити систему. Аби тільки час на це був. Перше глобальне поліпшення доставки коду — це перенесення наших сервісів з ECS EC2 mode на ECS Fargate mode. Тож після суттєвого зниження цін на Fargate і через те, що в першому сценарії все ж таки треба реліз від релізу заходити на EC2 Instance й оновлювати машинки security-патчами, а з Fargate це робити не треба, бо за нас це робить AWS — вирішено переїхати на Fargate.

Інший і чи не основний момент, який потрібно вдосконалювати з погляду delivery ― це залежність між сервісами. Зі збільшенням кількості сервісів не завжди зрозуміло, який сервіс залежить від якого, а це головний біль і витрачений час для людини, що виконує розгортання. Тому вирішено розділити сервіси на три типи:

  1. Back-end for Front-end (BFF) — такий собі API Gateway, що має implicitflow аутентифікації і збирає потрібні дані для конкретної сторінки з внутрішніх сервісів (у внутрішньому networking AWS) уже за допомогою server-to-server взаємодії.
  2. Platform Services — сервіси, які мають значну різницю в бізнес-процесах.
  3. Core Services — спільні сервіси, що використовують для зберігання користувачів, відправлення нотифікацій, запису активностей тощо.

Тобто BFF може зробити HTTP-реквест на продуктовий сервіс або на Core Service, продуктовий сервіс може зробити HTTP-реквест на Core Service, або зробити PublishMessage, але не навпаки. Продуктові сервіси не можуть спілкуватися між собою, Core Services не можуть напряму спілкуватися між собою, якщо потрібно перекинутися кількома кілобайтами даних, то один Core Service також надсилає message в SNS topic.

Отже, зв’язаність між сервісами стає дедалі нижчою, і можна побудувати логічний порядок розгортання сервісів.

Висновки. Куди ж без них?

Найголовніше з погляду доставки коду та його розгортання надалі — це розуміння стадії продукту, його масштабів і перспектив розвитку. Навіть якщо взяти всі найліпші технології і практики, але водночас не мати чіткого розуміння, куди рухатися, то дорога з delivery-продукту може завести в невідомість.

Протягування артефактів крізь середовища, версіювання артефактів, rollback невдалих версій — усе це вирішувати значно приємніше з докером, ніж без нього. Запуск застосунків у кількох екземплярах, моніторинг логів, налаштування zero-downtime-розгортання швидше робити на клауді, тому ж самому AWS, ніж на власних серверах. А керувати інфраструктурою ефективніше за допомогою підходу Infrastructure as a Code.

Звісно, це все можна робити без вищезгаданих інструментів, завжди можна архівувати бінарники й розгортати вручну. 2007-го ж було так класно.

Сподіваюся, вам було цікаво прочитати про мій досвід, а поради, надані в матеріалі, стануть у пригоді. Якщо хочете подискутувати, поставити запитання або ж розповісти про власний досвід розгортання ― чекатиму вас у коментарях.

LinkedIn

10 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

После прочтения остались такие мысли:
1. Вы не смогли в MS Build (Windows Installer and Deployment) и настройку среды (DSC, Powershell and so on). Ну, не смогли и ладно.
2. Дальше вы пошли в контейнеризацию. Резонно сразу в Docker/lxc, а не Windows Containers и Docker Windows Container Type, если у вас там винегрет из технологий и платформ. Это уже обсудили ниже в комментариях. Почему у вас там винегрет оставим за скобками.
3.ELK. Elasticsearch для поиска в логах. У ыас куча микросервисов. А что у вас собственно вообще с логированием? Стандарты и бестпрактики, модель логирования?
4. Что у вас с тестированием этого зоопарка?

Отлично, спасибо за коментарии
1. Смогли, это был проект в предыдущей компании, он, на сколько я знаю, живой, работает, просто это все было гораздно сложнее и больнее настраивать, поддерживать, онбордить людей чем с подходами которые описаны ниже в статье
2. Смотрите, винегрет был описан в секции про Docker, что Docker как раз решает проблему доставки этого винигрета(если он конечно присутвует), на данном проекте в ЕПАМ, у нас чисто .NET Core(все сервисы), но у меня имелся опыт работы с продуктовыми компании Германии, США и вот у них такой зоопарк очень часто встречаеться и на практике могу сказать, что Docker очень хорошо это решает
3. С этим у нас все довольно таки хорошо CloudWatch + Elastic + AWS Lambda по вызову которой, можно отследить реквест начиная от первого запроса на BFF до Message Handler`a, который был вызван в результате того или иного сайд еффекта(такое себе дерево реквестов сгрупированное по CorrelationId и сортированое по времени) + алертинг система с интеграцией MS Teams + PagerDuty. Ежемесячно у нас около ~40 GB, понять в чем произошла проблема в среднем занимает минуты 2-5, в зависимости от человека, который будет смотреть. Логирование тема для отдельной статьи.
4. Unit/Integration тесты с помощью .NET Core TestServer на каждый сервис(50-200 тестов на сервис), запускаются на каждый push в ПР и на push в dev ветку + Automation Tests которые смотрят на енв и запускаються раз в день, их около 6-7к

А ще один величезний недолік — те, що постійно забуваєш, як писати мапінги портів у docker-compose.yml (host port: container port vs container port: host port) :)

Автор, остановитесь).

Постоянно забываю эту очередность, но ведь все мы люди :)

Звісно, у Docker Image є недоліки. Наприклад, у цей артефакт не можна успішно втиснути застосунок, який написано під .NET Framework 4.5

О_О Они сломали какую-то совместимость? Ещё недавно было можно. Требую деталей).

Я так понимаю вы говорите об hub.docker.com/...​icrosoft-dotnet-framework Хорошее замечание, в тоже самое время в статье скорее делался акцент на не очень перспективной успешности разворачивания, например, 2-ух летнего веб-приложения под .NET Framework 4.5, который раниться на IIS. Если у вас есть успешные кейсы разворачивания именно windows контейнеров, я бы хотел послушать ваш опыт, думаю он будет полезен

Можно взять чистый виндовый контейнер и набросать туда тот тулинг, какой душа пожелает — мы же не привязаны к готовым SDK-контейнерам от Майкрософт. Например, обратите внимание на мульти-таргет — вот это всё собиралось в одном контейнере, заточенном под CI. Просто нужен кастомный контейнер под себя, слово «нельзя» банально не отвечает действительности.

Да, вы правы, что

нельзя

тут будет скорее не правильным.
В теории можно все сделать, но на сколько это будет успех? Взлетит ли контейнер так сразу, как это происходит с Linux Containers + .NET Core? Какой будет размер результатируещего image? Поддерживают ли нужные оркестраторы windows контейнеры? Поставить все конечно можно, но будет ли это успех для delivery на практике? Как считаете?

В теории можно все сделать, но на сколько это будет успех? Взлетит ли контейнер так сразу, как это происходит с Linux Containers + .NET Core? Какой будет размер результатируещего image? Поддерживают ли нужные оркестраторы windows контейнеры? Поставить все конечно можно, но будет ли это успех для delivery на практике? Как считаете?

Понятно, что дауншифтиться из .NET Core во Framework в общем случае смысла 0. Но сделать такой деплоймент, о котором говориться в статье, можно не «в теории», а вполне легко, просто нужно исходя из этого смотреть на инфраструктуру с самого начала. Потому что делать наоборот — это неправильно в любом случае (а такой вопрос возможен только в случае «наоборот»), т.к. Фаргейт, естественно, останется за скобками.

Про размер имеджей — зависит от контекста. Если сильная нужда выкатить именно на Винде входит в конфликт с размером имеджей — выигрывает первое, само собой. Про взлёт — фактической разницы нет.

Если у вас есть успешные кейсы разворачивания именно windows контейнеров, я бы хотел послушать ваш опыт, думаю он будет полезен

Например, Windows-реализация GDI+ в некоторых аспектах работает иначе (лучше), чем libgdiplus на никсах. Соответственно, у нас было всё на ECS поверх EC2, никаких проблем.

Имеет смысл, довольно таки интересный опыт, спасибо

Подписаться на комментарии