Ми розвивали DevOps як могли. Нас було 8 людина, і Вася був найкрутішим за Windows. Раптом Вася пішов, а у мені постало завдання вивести новий проект, який постачає Windows-розробка. Коли я висипав на стіл весь стек Windows-розробки, то зрозумів, що ситуація — біль…
Так починається історія Олександра Сінчінова на DevOpsConf. Коли з компанії пішов провідний фахівець з Windows, Олександр запитав, що тепер робити. Переходити на Linux, звичайно ж! Олександр розповість, як йому вдалося створити прецедент і перевести частину Windows розробки на Linux на прикладі реалізованого проекту на 100 000 кінцевих користувачів.
Як легко і невимушено доставляти проект в RPM, використовуючи TFS, Puppet, Linux .NET core? Як підтримувати версіонування БД проекту, якщо розробка вперше чує слова Postgres і Flyway, а дедлайн післязавтра? Як інтегрувати з Docker? Як мотивувати .NET-розробників відмовитися від Windows і смузі на користь Puppet і Linux? Як вирішувати ідеологічні конфлікти, якщо обслуговувати Windows у продакшн немає ні сил, ні бажання, ні ресурсів? Про це, а також про Web Deploy, тестування, CI, про практики використання TFS у існуючих проектах, і, звичайно, про зламані милиці і працюючі рішення, в розшифровці доповіді Олександра.
Отже, Вася пішов, завдання на мені, девелопери чекають з вилами з нетерпінням. Коли я остаточно усвідомив, що Васю не повернути приступив до справи. Для початку оцінив відсоток Win VM у нашому парку. Рахунок був не на користь Windows.
Так як ми активно розвиваємо DevOps, я зрозумів, що потрібно щось змінювати в підході винесення нового додатка. Рішення було одне — за можливості перекласти все на Linux. Google мені допоміг - на той момент вже був портований .Net під Linux, і я зрозумів, що це рішення!
Чому .NET core у зв'язку з Linux?
На це було кілька причин. Між «платити гроші» і «не платити» більшість вибере друге— як і я. Ліцензія на MSDB коштує близько 1 $, обслуговування парку віртуальних машин Windows обчислюється сотнями доларів. Для великої компанії це є великі витрати. Тому економія - перша причина. Не найважливіша, але одна з вагомих.
Віртуальні машини Windows займають більше ресурсів, ніж їх брати з Linux вони важкі. Враховуючи масштаб великої компанії, ми обрали Linux.
Система просто вбудовується в існуючий CI. Ми вважаємо себе прогресивними DevOps'ами, використовуємо Bamboo, Jenkins і GitLab CI, тому більша частина у нас крутиться на Linux.
Остання причина— зручний супровід. Нам потрібно було знизити поріг входження для «супроводників» — хлопців, які розуміють технічну частину, забезпечують безперебійність і обслуговують сервіси з другої лінії. Вони вже були знайомі зі стеком Linux, тому їм набагато простіше зрозуміти новий продукт, підтримувати і супроводжувати, ніж витрачати додаткові ресурси, щоб розібратися з аналогічним функціоналом ПЗ для Windows платформи.
Вимоги
Перше та головне — зручність нового рішення для розробників. Не всі з них виявилися готовими до змін, особливо після вимовленого слова Linux. Розробники хочуть улюблену Visual Studio, TFS з автотестами з збірок і смузі. Як відбувається доставка в продакшн — їм не важливо. Тому ми вирішили не змінювати звичний процес і залишити для Windows-розробки все без змін.
Новий проект потрібний вбудувати в існуючий CI. Рейки вже були і всю роботу було необхідно виконати з урахуванням параметрів системи управління конфігурацією, прийнятих стандартів доставки і систем моніторингу.
Простота в підтримці та експлуатації, як умова для мінімального порогу входження для всіх нових учасників з боку різних підрозділів і відділу супроводу.
Дедлайн — вчора.
Група Win розробки
З чим тоді працювала команда Windows?
Зараз я можу впевнено сказати, що IdentityServer4 — це класна безкоштовна альтернатива ADFS з аналогічними можливостями, або що Entity Framework Core — рай для розробника, де можна не заморочуватися написанням SQL скриптів, а описувати запити в БД у термінах ОВП. Але тоді, на обговоренні плану дій, я дивився на цей стек як на шумерський клинопис дізнаючись лише PostgreSQL і Git.
На той момент ми активно використовували Ляльковий як систему керування конфігурацією. У більшості наших проектів ми застосовували GitLab CI, Еластичний, балансували високонавантажені сервіси за допомогою HAProxy, стежили за всім з допомогою Zabbix, зв'язки Grafana и Прометей, Єгер, і все це крутилося на залізяках HP c ESXi на VMware. Всім знайомо — класика жанру.
Подивимося і спробуємо зрозуміти, що вже відбувалося до того, як ми затіяли всі ці втручання.
Що було
TFS — це досить потужна система, яка не тільки доставляє код від розробника до кінцевої продакшн-машини, але також має набір для дуже гнучкої інтеграції з різними сервісами для забезпечення CI на кросплатформовому рівні.
Раніше це були суцільні кватирки. TFS використовував кілька Build-агентів, на яких збиралося безліч проектів. У кожному агенті по 3-4 worker-a, щоб розпаралелити завдання та оптимізувати процес. Далі, згідно з релізними планами, TFS доставляв новий Build на Windows-сервер додатків.
До чого ми хотіли прийти
Для доставки та розробки використовуємо TFS, а запускаємо додаток на Linux Application server,і між ними якась магія. Цей Чарівна коробка і є сіль майбутньої роботи. Перед тим, як розібрати його по частинах, зроблю крок убік і скажу два слова про додаток.
Проект
Додаток надає функціональність для оперування передплаченими картками.
Клієнт
Існували два типи користувачів. Перший отримував доступ, авторизуючись за сертифікатом SSL SHA-2. У другий був доступ за логіном і паролем.
HAProxy
Далі клієнтський запит потрапляв у HAProxy, який вирішував наступні завдання:
первинна авторизація;
термінування SSL;
тюнінг HTTP запитів;
трансляція запитів.
Перевірка сертифікату клієнта йшла по ланцюжку. Ми — влада і можемо собі таке дозволити, оскільки самі надаємо сертифікати клієнтам сервісу.
Зверніть увагу на третій пункт, трохи пізніше повернемося до нього.
Backend
Бекенд планували зробити на Linux. Бекенд взаємодіє з БД, підвантажує необхідний список привілеїв і потім, в залежності від того, якими привілеями має авторизований користувач, надає доступ для підписання фінансових документів та надсилання їх на виконання, або генерації якогось звіту.
Економія з HAProxy
Крім двох контекстів, якими ходив кожен із клієнтів, існував ще контекст identity. IdentityServer4 якраз дозволяє авторизуватися, це безкоштовний і потужний аналог для ADFS - Послуги Федерації Active Directory.
Запит у identity оброблявся в кілька кроків. Перший крок - клієнтпотрапляв у бекенд, який обмінювався даними з цим сервером і перевіряв наявність токена для клієнта. Якщо не знаходив — запит повертався назад на той контекст, з якого він прийшов, але вже з редиректом, і редиректом йшов наidentity.
Другий крок — запит потрапляв на сторінку авторизації в IdentityServer, де клієнт реєструвався, а в базі даних IdentityServer з'являвся той самий довгоочікуваний токен.
Третій крок — клієнт редиректився назад на контекст, з якого він прийшов.
У IdentityServer4 є особливість: відповідь на зворотний запит він повертає за HTTP. Як не билися з настроюванням сервера, як не просвічувалися документацією, але ми щоразу отримували початковий запит клієнта з URL, який прийшов по HTTPS, а Identity Server повертав той самий контекст, але з HTTP. Ми були в шоці! І перевели все це через контекст identity на HAProxy, а в хедерах довелося модифікувати протокол HTTP на HTTPS.
У чому ж поліпшення і де заощадили?
Ми зекономили гроші, використовуючи безкоштовне рішення для авторизації групи користувачів, ресурси, оскільки не виносили IdentityServer4 як окрему ноду в окремий сегмент, а використовували його спільно з бекендом на тому самому сервері, де крутиться бекенд програми.
Як має працювати
Отже, як я обіцяв Magic Box. Ми вже розуміємо, що гарантовано рухаємося в бік Linux. Давайте сформулюємо конкретні завдання, які вимагали вирішення.
Маніфести Puppet. Щоб доставляти та керувати конфігурацією сервісу та програми, потрібно було написати класні рецепти. Рулончик з олівцем красномовно показує як швидко і якісно це було зроблено.
Спосіб доставки. Стандарт - це RPM. Всі розуміють, що в Linux без нього ніяк, але сам проект після складання був набором виконуваних DLL-файлів. Їх було близько 150, проект досить важкий. Єдине гармонійне рішення - упаковати цю бінарщину в RPM і вже з неї розгортати додаток.
Версіонування. Нам потрібно було релізуватися дуже часто, і потрібно було вирішити, яким чином формувати ім'я пакета. Це питання рівня інтеграції з TFS. Build-агент у нас був на Linux. Коли TFS відправляє завдання обробнику - worker - на Build-агент, він передає йому ще і банк змінних, які потрапляють в environment процесу обробника. У цих змінних оточення передається ім'я Build, ім'я версії та інші змінні. Детальніше про цей у розділі «складання RPM-пакету».
Налаштування TFS зводилася до налаштування Pipeline. Раніше ми збирали на Windows-агентах всі Windows-проекти, а зараз з'являється Linux-агент - Build-агент, який потрібно включити в групу збірки, збагатити якимись артефактами, сказати, якого саме типу проекти будуть збиратися на цьому Build-агенті, і якось модифікувати Pipeline.
IdentityServer. ADFS не наш шлях, топимо за Open Source.
Пройдемося по компонентах.
Чарівна коробка
Складається із чотирьох частин.
Linux Build-агент. Linux, тому що ми під нього збираємо — логічно. Ця частина виконувалася за три кроки.
Налаштувати worker-и і не один, оскільки передбачалася розподілена робота над проектом.
Встановити .NET Core 1.х. Чому саме 1.х, коли вже доступно 2.0 в стандартному репозиторії? Тому що, коли ми починали розробку, стабільною версією була 1.09, і проект було вирішено робити під неї.
Git 2.x.
RPM-репозиторія. RPM-пакети потрібно було десь зберігати. Передбачалося, що ми будемо використовувати той самий корпоративний RPM-репозиторій, який доступний всім Linux хостам. Так і надійшли. На сервері репозиторію налаштовано веб-гачок який скачував із зазначеного місця необхідний RPM-пакет. Версію пакету webhook'у повідомляв Build-агент.
GitLab. Увага! GitLab тут використовується не розробниками, а відділом експлуатації для контролю версій програми, версій пакетів, контролю стану всіх Linux-машин і в ньому зберігається рецептура — всі маніфести Puppet.
Ляльковий — розрулює всі спірні моменти і доставляє саме ту конфігурацію, яку ми хочемо, з Gitlab.
Починаємо занурюватись. Як відбувається доставка DLL в RPM?
Доставка DDL в RPM
Припустимо, у нас є рок-зірка розробки на .NET. Він використовує Visual Studio і створює релізну гілку. Після цього завантажує її в Git, і Git тут - TFS-сутність, тобто це репозиторій програми, з яким працює розробник.
Після цього TFS бачить, що прилетів новий коміт. Який додаток? В налаштуваннях TFS є мітка, якими ресурсами володіє той чи інший Build-агент. У цьому випадку він бачить, що ми збираємо .NET Core проект і вибирає Linux Build-агент з пула.
Build-агент отримує вихідники, викачує необхідні залежно c репозиторія .NET, npm і т.д. і після збирання самої програми та наступної упаковки відправляє RPM-пакет в RPM-репозиторій.
З іншого боку відбувається наступне. Інженер відділу експлуатації займається безпосередньо викочуванням проекту: змінює версії пакетів у Hiera в репозиторії, де зберігається рецептура програми, після чого Puppet тригеріт Юм, забирає новий пакет з репозиторію, і нова версія програми готова до використання.
На словах все просто, але що відбувається всередині на самому Build-агенті?
Упаковка DLL RPM
Отримано вихідники проекту та завдання на збірку від TFS. Build-агент запускає складання самого проекту із вихідників. Зібраний проект доступний у вигляді безлічі DLL файлів, які упаковані в zip-архів для зниження навантаження на файлову систему.
ZIP-архів викидається в директорію складання пакету RPM. Далі Bash-скрипт ініціалізує змінні оточення, знаходить версію Build, версію проекту, шлях до директорії складання, і запускає RPM-build. Після закінчення зборки пакет публікується в локальний репозиторій, який знаходиться на Build-агенті.
Далі, з Build-агента на сервер у RPM-репозиторія надсилається JSON-запит із зазначенням імені версії та білда. Webhook, про який я раніше говорив, викачує цей самий пакет з локального репозиторію на Build-агенті і робить нову збірку доступною для установки.
Чому саме така схема доставки пакета в репозиторій RPM? Чому не можна відразу відправити зібраний пакет у репозиторій? Справа в тому, що це умова для забезпечення безпеки. Такий сценарій обмежує можливість несанкціонованого завантаження RPM-пакетів сторонніми людьми на сервер, який доступний усім Linux-машинам.
Версіонування БД
На консиліумі з розробкою з'ясувалося, що хлопцям ближче MS SQL, але в більшості non-Windows проектів ми вже використовували PostgreSQL. Так як ми вже вирішили відмовитися від платного, то стали використовувати PostgreSQL і тут.
В цій частині хочу розповісти, як ми здійснювали версіонування БД і як вибирали між Flyway і Entity Framework Core. Розглянемо їх плюси та мінуси.
Мінуси
Flyway йде тільки в один бік, ми не можемо відкотитися назад - Це суттєвий мінус. Порівнювати з Entity Framework Core можна за іншими параметрами з точки зору зручності розробника. Ви вже пам'ятаєте, що ми поставили на кут, і основним критерієм було не змінити нічого для Windows-розробки.
Для Flyway нам була потрібна якась обгорткащоб хлопці не писали SQL-запити. Їм набагато ближче оперувати в термінах ОВП. Написали інструкції з роботи з об'єктами БД, сформувався SQL-запит і виконався. Нова версія БД готова, прокаталася все добре, все працює.
У Entity Framework Core є мінус при великих навантаженнях він будує неоптимальні SQL-запити, і просадка по БД може бути суттєвою. Але бо у нас не високонавантажений сервіс, ми не обчислюємо навантаження сотнями RPS, ми прийняли ці ризики і делегували проблему майбутнім нам.
Плюси
Entity Framework Core працює з коробки та зручний розробці, а Flyway легко інтегрується в існуючий CI. Але ми що робимо зручно девелоперам:)
Процедура накату
Puppet бачить, що приходить зміна версії пакетів серед яких той, що відповідає за міграцію. Спочатку встановлює пакет, де містяться міграційні скрипти і функціонал зав'язаний на БД. Після цього рестартується додаток, який працює з БД. Далі йде встановлення компонентів, що залишилися. Черговість установки пакетів і запуску додатків описані в маніфесті Puppet.
Програми використовують чутливі дані, такі як токени, паролі до БД, все це підтягується в конфіг з Puppet master, де вони зберігаються в зашифрованому вигляді.
Проблеми TFS
Після того, як ми визначилися і зрозуміли, що у нас дійсно все працює, я вирішив подивитися, що діється зі збірками в TFS в цілому для відділу Win-розробки за іншими проектами – швидко чи ні збираємося/релізимося, і виявив істотні проблеми зі швидкістю .
Один із основних проектів збирається 12-15 хвилин — це довго, так жити не можна. Швидкий аналіз показав моторошну просадку по I/O, і це на масивах.
Проаналізувавши покомпонентно, я виділив три вогнища. Перший — "Kaspersky antivirus", який на всіх Windows Build-агентах сканує вихідні коди. Другий — WindowsIndexer. Він не був відключений, і на Build-агентах в реальному часі індексувалося все в процесі деплою.
Третій — NPM install. Виявилося, що в більшості Pipelines ми використовували саме цей сценарій. Чим він поганий? Процедура Npm install запускається при формуванні дерева залежностей package-lock.json, де фіксуються версії пакетів, які будуть використовуватися для збирання проекту. Мінус у тому, що Npm install щоразу підтягує актуальні версії пакетів з інтернету, а це чималий час у разі великого проекту.
Розробники іноді експериментують на локальній машині, щоб перевірити роботу окремої частини або проекту цілком. Іноді виходило, що локально все круто, але збирали, котилися — нічого не працювало. Починаємо розбиратися, в чому проблема ага, різні версії пакетів із залежностями.
Рішення
Вихідники у винятку AV.
Вимкнення індексації.
Перехід на npm ci.
Плюси npm ci, в тому, що ми збираємо дерево залежностей один раз, і отримуємо можливість надати розробнику актуальний список пакетів, з яким він може скільки завгодно експериментувати локально. Це заощаджує час розробників, які пишуть код
Конфігурація
Нині трохи про конфігурацію репозиторію. Історично ми використовуємо нексус для управління репозиторіями, у тому числі Internal REPO. У цей внутрішній репозиторій поставляються всі компоненти, які ми використовуємо для внутрішніх цілей, наприклад, самописні моніторинги.
Ми також використовуємо NuGet, тому що він краще кешує в порівнянні з іншими пакетними менеджерами.
Результат
Після того, як ми оптимізували Build-агентів, середній час зборки скоротився з 12 хвилин до 7.
Якщо порахувати всі машини, які ми могли б використовувати для Windows, але переклали на Linux в цьому проекті, ми заощадили близько $ 10. І це тільки на ліцензіях, а якщо враховувати зміст більше.
Плани
На наступний квартал заклали в план роботу над оптимізацією доставки коду.
Перехід на пребілд Docker-образу. TFS — класна штука з множиною плагінів, які дозволяють інтегрувати в Pipeline, в том числі, і збірку за тригером, припустимо, Docker-образу. Цей тригер ми хочемо зробити на той самий package-lock.json. Якщо якимось чином змінюється склад компонентів, які використовуються для складання проекту - у нас збирається новий Docker-образ. Надалі він використовується для розгортання контейнера з зібраним додатком. Зараз цього немає, але плануємо перейти на мікросервісну архітектуру в Kubernetes, який активно розвивається в нашій компанії і давно обслуговує продакшн рішення.
Резюме
Закликаю всіх викинути Windows, але це не тому, що я не вмію її готувати. Причина в тому, що більша частина Opensource-рішень - це Linux-стек. Ви добре заощадите на ресурсах. На мій погляд, майбутнє за рішеннями Open Source на Linux з потужним ком'юніті.
DevOps Conf — це конференція з інтеграції процесів розробки, тестування та експлуатації для професіоналів від професіоналів. Саме тому проект, про який розповідав Олександр? реалізовано та працює, а день виступу проведено два успішні релізи. на DevOps Conf на РИТ++ 27 і 28 травня буде ще більше подібних кейсів від практиків. Ще можна схопитися в останній вагон і подати доповідь чи не поспішаючи забронювати квиток. Зустрінемось у Сколково!