- PVSM.RU - https://www.pvsm.ru -
Всем привет!
Компания Netсracker уже много лет разрабатывает и поставляет enterprise-приложения для мирового рынка операторов связи. Разработка таких решений достаточно сложна: в проектах участвуют сотни людей, а количество активных проектов исчисляется десятками.
Раньше продукты были монолитными, но сейчас мы уверенно двигаемся в направлении микросервисных приложений. Перед DevOps появилась достаточно амбициозная задача — обеспечить этот технологический скачок.
В итоге мы получили удачную концепцию сборок, которой хотим поделиться в качестве передовой практики. Описание реализации с техническими деталями будет достаточно объёмным, в рамках данной статьи мы не будем этого делать.
В общем случае сборка — это превращение одних артефактов в другие.
Тем компаниям, которые поставляют готовое ПО совершенно сторонней организации и получают за это деньги.
Вот как может выглядеть разработка без внешней поставки:
Если вы не сталкиваетесь с внешней поставкой, то многое, написанное ниже, покажется лишним или даже параноидальным.
На практике всё должно быть выполнено с соблюдением международных требований к используемым лицензиям и шифрованию, иначе возникнут как минимум юридические последствия.
Пример нарушения — взять код от библиотеки с лицензией GPL3 и встроить его в коммерческое приложение.
У нас накоплен большой опыт в сборке и поставке монолитных приложений.
Несколько Jenkins-серверов, тысячи CI job, несколько полностью автоматизированных сборочных конвейеров на базе Jenkins, десятки выделенных release-инженеров, своя экспертная группа по конфигурационному управлению.
Исторически подход в компании был такой: исходный код пишут разработчики, а конфигурацию сборочной системы придумывают и пишут DevOps.
В итоге у нас было две-три типовых сборочных конфигурации, рассчитанные на функционирование в корпоративной экосистеме. Схематично это выглядит так:

В качестве инструмента сборки обычно выступает ant или maven, и что-то реализовано общедоступными плагинами, что-то дописано самостоятельно. Это хорошо работает, когда в компании используется узкий набор технологий.
Микросервисы отличаются от монолитных приложений в первую очередь разнообразием технологий.
Получается очень много сборочных конфигураций как минимум под каждый язык программирования. Централизованный контроль становится невозможным.
Требуется максимально упростить сборочные скрипты и дать возможность разработчикам править их самостоятельно.
Кроме простой компиляции и упаковки (на схеме зелёным цветом), эти скрипты содержат много кода для интеграции с корпоративной экосистемой (на схеме красным цветом).
Поэтому было решено воспринимать сборку как «чёрный ящик», при котором «умное» сборочное окружение может решить все задачи, кроме непосредственно компиляции и упаковки.
В начале работы было непонятно, как получить такую систему. Принятие архитектурных решений для DevOps-задач требует наличия опыта и знаний. Как их получить? Возможные варианты ниже:
Для ответа на этот вопрос нужно понять, на каком этапе эволюции находятся наши подходы к сборкам. В общем случае задача проходит следующие уровни.

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

Надо выпускать две сборки в день стабильно и без ошибок. У нас есть Вася, он круто это делает, и никто, кроме него, на это время не тратит.

Дело зашло далеко. Необходимо 20 сборок в день, Вася не справляется, и вот уже сидит команда из десяти человек. У них есть начальник, планы, отпуска, больничные, мотивация, тимбилдинги, тренинги, традиции и правила. Это специализация, их работе надо учиться.
На этом уровне задача отделяется от конкретного исполнителя и тем самым превращается в процесс.
Результатом будет чёткое, отработанное, обкатанное и подправленное сотни раз описание процесса текстом.

Современные требования к сборкам растут: всё должно быть быстро, безотказно, в день надо обеспечить 800 сборок. Это критично, потому что без таких объёмов компания будет терять конкурентные преимущества.
Происходит затратная автоматизация, и уже пара квалифицированных DevOps могут поддерживать процесс в рабочем состоянии. Дальнейшее масштабирование уже не является проблемой.
Не всякая задача должна дойти до последней стадии автоматизации.
Зачастую один умелец с командной строкой решит проблемы легко и эффективно.
Автоматизация «замораживает» процесс, удешевляет эксплуатацию и увеличивает стоимость изменений.
Сразу перейти к автосборкам можно, но система будет неудобной, не будет успевать за требованиями бизнеса и в итоге морально устареет.

Мы используем следующую классификацию для определения уровней агрегации сборок.
L1. Маленькая самостоятельная часть большого приложения. Это может быть один компонент, микросервис или одна вспомогательная библиотека. L1 сборка — это решение линейных технических задач: компиляция, упаковка, работа с зависимостями. С этим отлично справляются maven, gradle, npm, grunt и другие сборочные системы. Их сотни.
L1 сборку нужно делать при помощи готовых third-party инструментов.
L2+. Интеграционные сущности. L1 сущности объединяются в более крупные образования, например в полноценные микросервисные приложения. Несколько таких приложений могут поставляться в связке как единое решение. Используем знак «+», потому что в зависимости от уровня агрегации сборки может быть присвоен уровень L3 или даже L4.
Пример таких сборок в мире third-party — подготовка Linux-дистрибутивов. Метапакеты там же.
Кроме достаточно сложных технических задач (как, например, эта: ru.wikipedia.org/wiki/Dependency_hell [1]). L2+ сборки часто являются конечным продуктом и поэтому имеют много процессных требований: система прав, закрепление ответственных людей, отсутствие юридических ошибок, снабжение различной документацией.
На уровне L2+ процессные требования приоритетнее автоматизации.
Если автоматическое решение не будет работать так, как это удобно заинтересованным людям, оно не будет внедрено.
L2+ сборки скорее всего будут выполняться проприетарным инструментом, заточенным именно под процессы компании. А вы думаете, пакетные менеджеры в Linux просто так придумывают?
Вся сборочная инфраструктура находится на закрытых серверах внутри корпоративной сети. В некоторых случаях возможны коммерческие облачные сервисы.
Во всех CI-процессах Интернет недоступен. Все необходимые ресурсы зеркалируются и кэшируются внутри компании. Частично даже github.com (спасибо тебе, npm!) Большинство таких вопросов решает Artifactory.
Поэтому мы спокойны, когда удаляют артефакты с maven central или закрывают популярные репозитории. Есть такой пример: community.oracle.com/community/java/javanet-forge-sunset [2].
Зеркалирование существенно сокращает время сборок, освобождает корпоративный интернет-канал. Меньшее количество критичных сетевых ресурсов повышает стабильность сборки.
Из Dev- и Staging- репозиториев удаляем всё, что старше 30 дней. Это помогает обеспечить всем равные возможности публикации, затратив конечный объём серверных дисков.
Release хранится вечно, при необходимости делается архивация.
Часто после сборок в системе остаются вспомогательные файлы, которые могут повлиять на другие сборочные процессы. Типичные примеры:
Поэтому лучше отказаться от единого окружения в пользу docker-контейнеров. В контейнерах должно находиться только необходимое для конкретной сборки ПО с фиксированными версиями.
DevOps поддерживает коллекцию сборочных docker-образов, которая постоянно пополняется. Сначала их было около шести штук, потом стало под 30, потом мы наладили автоматическую генерацию образа по списку ПО. Теперь просто указываем требования типа require('maven 3.3.9', 'python') — и окружение готово.
Нужно не просто организовать поддержку пользователей по обращениям, надо самим анализировать поведение собственной системы. Постоянно собираем логи, ищем в них ключевые слова, показывающие проблемы.
На «живой» системе достаточно написать 20-30 регулярных выражений, чтобы по каждой сборке можно было сказать причину её падения на уровне:
Если что-то упало, но ни одна известная проблема не обнаружена — это повод для пополнения коллекции масок.
Потом идём к пользователю и говорим, что у него падает сборка и это можно исправить таким-то образом.
Вы удивитесь, о скольких проблемах пользователи не сообщают в поддержку. Лучше чинить их заранее и в удобное время. Часто незначительную ошибку публикации игнорируют две недели, а в пятницу вечером выясняется, что это блокирует внешнюю выдачу.
В идеале вообще обеспечить полную автономность сборки, но чаще всего это невозможно. Для java-based сборок нужен хотя бы Artifactory для зеркалирования — см. выше про автономность. Каждая интегрированная система повышает риск сбоя. Желательно, чтобы все системы работали в приличном HA-режиме.
Мы производим любой вид сборок одной системой. Сборки всех уровней (L1, L2+) описаны программным кодом и вызываются через одну Jenkins job.
Однако такой подход не идеален. Лучше использовать механизмы автогенерации Jenkins job: например, 1 job = 1 git repository или 1 job = 1 git branch. Это позволит достичь следующего:
Запуск сборки — это вызов bash-скрипта «./build.sh». А дальше — любые сборочные системы, языки программирования и всё, что ещё потребуется для выполнения бизнес-задачи. Это обеспечивает подход к сборке как к «чёрному ящику».
Сборочный конвейер перехватывает публикации из «чёрного ящика» и кладет их уже в корпоративное хранилище. Для этого автоматически решаются скучные вопросы типа генерации имён docker-образов, выбора правильного репозитория для публикации.
В staging- и release-репозиториях всегда порядок. Требуется поддерживать специфику публикаций разных типов: maven, npm, файловых, docker.
Build.sh описывает, как собирать код, но для сборочного контейнера этого мало.
Необходимо также знать:
Мы выбрали удобный способ описания этой информации в виде yaml-файла, отдалённо напоминающего .gitlab-ci.yaml.
Пользователь может без выполнения команды «git commit» прямо при запуске сборки указать произвольные параметры.
У нас это реализовано через определение переменных окружения непосредственно из интерфейса Jenkins job.
Например, мы выносим в такой параметр сборки версию зависимой библиотеки и в некоторых случаях переопределяем эту версию на некую экспериментальную. Без подобного механизма пользователь должен был бы каждый раз выполнять команду «git commit».
Необходимо иметь возможность воспроизводить сборочный процесс не только на главном CI-сервере, но и на компьютере разработчика. Это помогает в отладке сложных сборочных скриптов. Кроме того, вместо Jenkins иногда будет удобнее использовать Gitlab CI. Поэтому сборочная система должна быть независимым java-приложением. Мы реализовали её как gradle plugin.
К публикации есть два противоположных требования, которые могут возникнуть одновременно.
С одной стороны, для долгосрочного хранения и релизного управления нужно обеспечить уникальность имён публикуемых артефактов. Это как минимум защитит артефакты от перезаписи.
С другой стороны, иногда удобно иметь один актуальный артефакт с фиксированным именем вроде latest. Например, разработчику не нужно каждый раз точно знать версию зависимости, можно просто работать с самой свежей.
Артефакт в таком случае публикуется под двумя и более именами, кому как удобно.
Например:
Что-то подобное maven делает в своём подходе к SNAPSHOT.
Сборку запустить может каждый. Это не нанесёт никому вреда, так как сборка только создаёт артефакты.
Сборка не может использовать ничего запрещённого в процессе своей работы.
Для этого реализована запись сетевого трафика и доступа к файловым кэшам. Мы получаем лог сетевой активности сборки в виде списка url с sha256 хэшами полученных данных. Дальше каждый url проходит валидацию:
Иногда поступает задача — предоставить перечень third-party ПО внутри какой-нибудь сборки. Для этого сделали простенький анализатор состава, который анализирует все файлы и архивы в сборке, опознаёт third-party по хэшам и делает отчёт.
Иногда может потребоваться найти исходный код, смотря на бинарный артефакт, собранный два года назад. Для этого надо отводить теги в Git автоматически при внешней выдаче, а также запретить их удаление.
Мы используем файловый репозиторий в Artifactory для этих целей. Там лежит вся вспомогательная информация: кто запустил, каковы были результаты проверок, какие артефакты опубликовались, какой git-хэш использован и т. д.
По результатам сборки мы храним следующую информацию:
При необходимости мы можем достаточно точно ответить на вопрос, как это было собрано.
Обязательно нужно уметь решать следующие задачи:
Обеспечивается жёсткая двухсторонняя связь сборки с git commit. А дальше из текста комментариев можно уже узнать про все ссылки на JIRA.
Отсутствие maven-кэша может увеличить время сборки на час.
Кэш нарушает изоляцию сборочного окружения и чистоту сборки. Эту проблему можно решить, определяя для каждого кэшированного артефакта его происхождение. У нас каждый файл кэша ассоциирован с https-ссылкой, по которой его когда-то скачали. Дальше обрабатываем чтение кэша как сетевое обращение.
Рост компании в географическом плане приводит к необходимости передавать файлы по 300 МБ между континентами. Тратится много времени, особенно если делать это приходится часто.
Git-репозитории, docker-образы сборочных окружений, файловые хранилища — всё надо аккуратно кэшировать. Ну и, конечно, периодически чистить.
Первый этап: делаем сборку и сразу, без лишних телодвижений, отдаём результат.
Второй этап: валидация, анализ, учёт и прочая бюрократия. Это можно сделать уже отдельной Jenkins job и без жёстких ограничений по времени.
И немного цифр
Автор: mister_reg
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/enterprise/290638
Ссылки в тексте:
[1] ru.wikipedia.org/wiki/Dependency_hell: https://ru.wikipedia.org/wiki/Dependency_hell
[2] community.oracle.com/community/java/javanet-forge-sunset: https://community.oracle.com/community/java/javanet-forge-sunset
[3] Источник: https://habr.com/post/421423/?utm_campaign=421423
Нажмите здесь для печати.