- PVSM.RU - https://www.pvsm.ru -

.NET Core на Linux, DevOps на коне

Мы развивали DevOps как могли. Нас было 8 человек, и Вася был самым крутым по Windows. Внезапно Вася ушел, а у меня появилась задача вывести новый проект, который поставляет Windows-разработка. Когда я высыпал на стол весь стек Windows-разработки, то понял, что ситуация — боль…

Так начинается история Александра Синчинова на DevOpsConf [1]. Когда из компании ушел ведущий специалист по Windows, Александр задался вопросом, что теперь делать. Переходить на Linux, конечно же! Александр расскажет, как ему удалось создать прецедент и перевести часть Windows разработки на Linux на примере реализованного проекта на 100 000 конечных пользователей.

.NET Core на Linux, DevOps на коне - 1

Как легко и непринужденно доставлять проект в RPM, используя TFS, Puppet, Linux .NET core? Как поддерживать версионирование БД проекта, если разработка впервые слышит слова Postgres и Flyway, а дедлайн послезавтра? Как интегрировать с Docker? Как мотивировать .NET-разработчиков отказаться от Windows и смузи в пользу Puppet и Linux? Как решать идеологические конфликты, если обслуживать Windows в продакшн нет ни сил, ни желания, ни ресурсов? Об этом, а также о Web Deploy, тестировании, CI, о практиках использования TFS в существующих проектах, и, конечно, о сломанных костылях и работающих решениях, в расшифровке доклада Александра.

Итак, Вася ушел, задача на мне, девелоперы ждут с вилами с нетерпением. Когда я окончательно осознал, что Васю не вернуть — приступил к делу. Для начала оценил процент Win VM в нашем парке. Счёт был не в пользу Windows.

.NET Core на Linux, DevOps на коне - 2

Так как мы активно развиваем DevOps, я понял, что нужно что-то менять в подходе выноса нового приложения. Решение было одно — по возможности перевести все на Linux. Google мне помог — на тот момент уже был портирован .Net под Linux, и я понял, что это решение!

Почему .NET core в связке с Linux?

На это было несколько причин. Между «платить деньги» и «не платить» большинство выберет второе — как и я. Лицензия на MSDB стоит около 1 000 $, обслуживание парка виртуальных машин Windows исчисляется сотнями долларов. Для большой компании это большие затраты. Поэтому экономия — первая причина. Не самая важная, но одна из весомых.

Виртуальные машины Windows занимают больше ресурсов, чем их братья из Linux — они тяжелые. Учитывая масштаб большой компании мы выбрали Linux.

Система просто встраивается в существующий CI. Мы считаем себя прогрессивными DevOps’ами, используем Bamboo, Jenkins и GitLab CI, поэтому большая часть у нас крутится на Linux.

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

Требования

Первое и главное — удобство нового решения для разработчиков. Не все из них оказались готовы к переменам, особенно после произнесенного слова Linux. Разработчики хотят любимую Visual Studio, TFS c автотестами по сборкам и смузи. Как происходит доставка в продакшн — им не важно. Поэтому мы решили не менять привычный процесс и оставить для Windows-разработки всё без изменений.

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

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

Дедлайн — вчера.

Группа Win разработки

С чем тогда работала команда Windows?

.NET Core на Linux, DevOps на коне - 3

Сейчас я могу уверенно сказать, что IdentityServer4 — это классная бесплатная альтернатива ADFS с аналогичными возможностями, или что Entity Framework Core — рай для разработчика, где можно не заморачиваться написанием SQL скриптов, а описывать запросы в БД в терминах ООП. Но тогда, на обсуждении плана действий, я смотрел на этот стэк как на шумерскую клинопись узнавая лишь PostgreSQL и Git.

На тот момент мы активно использовали Puppet как систему управления конфигурацией. В большинстве наших проектов мы применяли GitLab CI, Elastic, балансировали высоконагруженные сервисы с помощью HAProxy, следили за всем с помощью Zabbix, связки Grafana и Prometheus, Jaeger, и все это крутилось на железяках HP c ESXi на VMware. Всем знакомо — классика жанра.

.NET Core на Linux, DevOps на коне - 4

Посмотрим и попытаемся понять, что же происходило до того, как мы затеяли все эти вмешательства.

Что было

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

.NET Core на Linux, DevOps на коне - 5
Раньше это были сплошные форточки. TFS использовал несколько Build-агентов, на которых собиралось множество проектов. В каждом агенте по 3-4 worker-a, чтобы распараллелить задачи и оптимизировать процесс. Дальше, согласно релизным планам, TFS доставлял свежеиспеченный Build на Windows-сервер приложений.

К чему мы хотели прийти

Для доставки и разработки используем TFS, а запускаем приложение на Linux Application server, и между ними какая-то магия. Этот Magic Box и есть соль предстоящей работы. Перед тем, как разобрать его по частям, сделаю шаг в сторону и скажу два слова о приложении.

Проект

Приложение предоставляет функциональность для оперирования предоплаченными картами.

.NET Core на Linux, DevOps на коне - 6

Client

Существовало два типа пользователей. Первый получал доступ, авторизуясь по сертификату SSL SHA-2. У второго был доступ по логину и паролю.

HAProxy

Дальше клиентский запрос попадал в HAProxy, который решал следующие задачи:

  • первичная авторизация;
  • терминирование SSL;
  • тюнинг HTTP запросов;
  • трансляция запросов.

Проверка сертификата клиента шла по цепочке. Мы — authority и можем себе такое позволить, так как сами выдаем сертификаты клиентам сервиса.

Обратите внимание на третий пункт, чуть позже вернемся к нему.

Backend

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

Экономия c HAProxy

Кроме двух контекстов, по которым ходил каждый из клиентов, существовал еще контекст identity. IdentityServer4 как раз позволяет авторизоваться, это бесплатный и мощный аналог для ADFS — Active Directory Federation Services.

Запрос в identity обрабатывался в несколько шагов. Первый шаг — клиент попадал в бэкенд, который обменивался данными с этим сервером и проверял наличие токена для клиента. Если не находил — запрос возвращался обратно на тот контекст, с которого он пришел, но уже с редиректом, и с редиректом шел на identity.

Второй шаг — запрос попадал на страницу авторизации в IdentityServer, где клиент регистрировался, а в базе данных IdentityServer появлялся тот самый долгожданный токен.

Третий шаг — клиент редиректился обратно на контекст, с которого он пришел.

.NET Core на Linux, DevOps на коне - 7

У IdentityServer4 есть особенность: ответ на обратный запрос он возвращает по HTTP. Как ни бились с настройкой сервера, как ни просвещались документацией, но мы каждый раз получали первоначальный запрос клиента с URL, который пришел по HTTPS, а IdentityServer возвращал тот же самый контекст, но с HTTP. Мы были в шоке! И перевели все это через контекст identity на HAProxy, а в хедерах пришлось модифицировать протокол HTTP на HTTPS.

В чем же улучшение и где сэкономили?

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

Как должно работать

Итак, как я обещал — Magic Box. Мы уже понимаем, что гарантированно движемся в сторону Linux. Давайте сформулируем конкретные задачи, которые требовали решения.

.NET Core на Linux, DevOps на коне - 8

Манифесты 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.

Пройдемся по компонентам.

Magic Box

Состоит из четырех частей.

.NET Core на Linux, DevOps на коне - 9

Linux Build-агент. Linux, потому что мы под него собираем — логично. Эта часть выполнялась в три шага.

  • Настроить worker-ы и не один, так как предполагалась распределенная работа над проектом.
  • Установить .NET Core 1.х. Почему именно 1.х, когда уже доступно 2.0 в стандартном репозитории? Потому что, когда мы начинали разработку, стабильной версией была 1.09, и проект было решено делать под неё.
  • Git 2.x.

RPM-repository. RPM-пакеты нужно было где-то хранить. Предполагалось, что мы будем использовать тот же самый корпоративный RPM-репозиторий, который доступен всем Linux хостам. Так и поступили. На сервере репозитория настроен webhook который скачивал из указанного места требуемый RPM-пакет. Версию пакета webhook’у сообщал Build-агент.

GitLab. Внимание! GitLab здесь используется не разработчиками, а отделом эксплуатации для контроля версий приложения, версий пакетов, контроля состояния всех Linux-машин и в нём хранится рецептура — все манифесты Puppet.

Puppet — разруливает все спорные моменты и доставляет именно ту конфигурацию, которую мы хотим, из Gitlab.

Начинаем погружаться. Как происходит доставка DLL в RPM?

Доставка DDL в RPM

Допустим, у нас есть рок-звезда разработки на .NET. Он использует Visual Studio и создает релизную ветку. После этого загружает ее в Git, и Git здесь — TFS-сущность, то есть это репозиторий приложения, с которым работает разработчик.

.NET Core на Linux, DevOps на коне - 10

После чего TFS видит, что прилетел новый коммит. Какое приложение? В настройках TFS есть метка, какими ресурсами обладает тот или иной Build-агент. В данном случае он видит, что мы собираем .NET Core проект и выбирает Linux Build-агент из пула.

Build-агент получает исходники, выкачивает необходимые dependencies c репозитория .NET, npm и т.д. и после сборки самого приложения и последующей упаковки отправляет RPM-пакет в RPM-репозиторий.

С другой стороны происходит следующее. Инженер отдела эксплуатации занимается непосредственно выкаткой проекта: меняет версии пакетов в Hiera в репозитории, где хранится рецептура приложения, после чего Puppet триггерит Yum, забирает новый пакет из репозитория, и новая версия приложения готова к использованию.

.NET Core на Linux, DevOps на коне - 11

На словах все просто, но что происходит внутри на самом Build-агенте?

Упаковка DLL RPM

Получены исходники проекта и задача на сборку от TFS. Build-агент запускает сборку самого проекта из исходников. Собранный проект доступен в виде множества DLL файлов, которые упакованы в zip-архив для снижения нагрузки на файловую систему.

ZIP-архив выкидывается в директорию сборки пакета RPM. Дальше Bash-скрипт инициализирует переменные окружения, находит версию Build, версию проекта, путь до директории сборки, и запускает RPM-build. По окончании сборки пакет публикуется в локальный репозиторий, который находится на Build-агенте.

Дальше, с Build-агента на сервер в RPM-репозитория отправляется JSON-запрос с указанием имени версии и билда. Webhook, про который я раньше говорил, выкачивает этот самый пакет из локального репозитория на Build-агенте и делает новую сборку доступной для установки.

.NET Core на Linux, DevOps на коне - 12

Почему именно такая схема доставки пакета в репозиторий RPM? Почему нельзя сразу отправить собранный пакет в репозиторий? Дело в том, что это условие для обеспечения безопасности. Такой сценарий ограничивает возможность несанкционированной загрузки RPM-пакетов посторонними людьми на сервер, который доступен всем Linux-машинам.

Версионирование БД

На консилиуме с разработкой выяснилось, что ребятам ближе MS SQL, но в большинстве non-Windows проектов мы уже вовсю использовали PostgreSQL. Так как мы уже решили отказаться от всего платного, то стали использовать PostgreSQL и здесь.

.NET Core на Linux, DevOps на коне - 13

В этой части хочу рассказать, как мы осуществляли версионирование БД и как выбирали между 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-агентах сканирует исходники. Второй — Windows Indexer. Он не был отключен, и на Build-агентах в реальном времени индексировалось все в процессе деплоя.

Третий — Npm install. Оказалось, что в большинстве Pipelines мы использовали именно этот сценарий. Чем он плох? Процедура Npm install запускается при формировании дерева зависимостей в package-lock.json, где фиксируются версии пакетов, которые будут использоваться для сборки проекта. Минус в том, что Npm install каждый раз подтягивает актуальные версии пакетов из интернета, а это немалое время в случае большого проекта.

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

Решение

  • Исходники в исключения AV.
  • Отключение индексации.
  • Переход на npm ci.

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

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

Сейчас немного о конфигурации репозитория. Исторически мы используем Nexus для управления репозиториями, в том числе Internal REPO. В этот внутренний репозиторий поставляются все компоненты, которые мы используем для внутренних целей, например, самописные мониторинги.

.NET Core на Linux, DevOps на коне - 14

Мы также используем NuGet, так как он лучше кэширует по сравнению с другими пакетными менеджерами.

Результат

После того, как мы оптимизировали Build-агентов, среднее время сборки сократилось с 12 минут до 7.

Если посчитать все машины, которые мы могли бы использовать для Windows, но перевели на Linux в этом проекте, мы сэкономили порядка $10 000. И это только на лицензиях, а если учитывать содержание — больше.

Планы

На следующий квартал заложили в план работу над оптимизацией доставки кода.

Переход на пребилд Docker-образа. TFS — классная штука со множеством плагинов, которые позволяют интегрировать в Pipeline, в том числе, и сборку по триггеру, допустим, Docker-образа. Этот триггер мы хотим сделать на тот самый package-lock.json. Если каким-то образом меняется состав компонентов, которые используются для сборки проекта — у нас собирается новый Docker-образ. В дальнейшем он используется для развертывания контейнера с собранным приложением. Сейчас этого нет, но планируем перейти на микросервисную архитектуру в Kubernetes, который активно развивается в нашей компании и давно обслуживает продакшн решения.

Резюме

Призываю всех выкинуть Windows, но это не потому, что я не умею ее готовить. Причина в том, что большая часть Opensource-решений — это Linux-стек. Вы хорошо сэкономите на ресурсах. На мой взгляд, будущее за решениями Open Source на Linux с мощным комьюнити.

Профиль спикера Александра Синчинова на GitHub [2].

DevOps Conf [1] — это конференция по интеграции процессов разработки, тестирования и эксплуатации для профессионалов от профессионалов. Именно поэтому проект, о котором рассказывал Александр? реализован и работает, а в день выступления проведено два успешных релиза. На DevOps Conf на РИТ++ [3] 27 и 28 мая будет еще больше подобных кейсов от практиков. Еще можно вскочить в последний вагон и подать доклад [4] или не торопясь забронировать [5] билет. Встретимся в Сколково!

Автор: Александр Титов

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/linux/315526

Ссылки в тексте:

[1] DevOpsConf: https://devopsconf.io/

[2] на GitHub: https://github.com/alextuning

[3] DevOps Conf на РИТ++: http://devopsconf.io/moscow-rit/2019

[4] подать доклад: https://conf.ontico.ru/lectures/propose/?conference=dc2019-rit

[5] забронировать: https://conf.ontico.ru/conference/join/rit2019.html?popup=3

[6] Источник: https://habr.com/ru/post/448496/?utm_source=habrahabr&utm_medium=rss&utm_campaign=448496