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

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

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

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

Как легко и непринужденно доставлять проект в 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 на коне

Так как мы активно развиваем 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 на коне

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

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

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

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

Что было

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

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

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

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

Проект

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

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

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 на коне

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

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

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

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

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

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

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

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 на коне

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

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

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

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

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

Упаковка DLL RPM

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

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

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

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

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

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

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

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

В этой части хочу рассказать, как мы осуществляли версионирование БД и как выбирали между 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 на коне

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

Результат

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

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

Планы

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

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

Резюме

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

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

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

Источник: habr.com