staging environment что это
Руководство по CI/CD в GitLab для (почти) абсолютного новичка
Или как обзавестись красивыми бейджиками для своего проекта за один вечер ненапряжного кодинга
Наверное, у каждого разработчика, имеющего хотя бы один пет-проект, в определённый момент возникает зуд на тему красивых бейджиков со статусами, покрытием кода, версиями пакетов в nuget… И меня этот зуд привёл к написанию этой статьи. В процессе подготовки к её написанию я обзавёлся вот такой красотой в одном из своих проектов:
В качестве среды разработки использовалась VS Code c расширением GitLab Workflow (для валидации файла настроек прямо из среды разработки).
Краткое введение
CD — это когда ты только пушнул, а у клиента уже всё упало?
Что такое CI/CD и зачем нужно — можно легко нагуглить. Полноценную документацию по настройке пайплайнов в GitLab найти также несложно. Здесь я кратко и по возможности без огрехов опишу процесс работы системы с высоты птичьего полёта:
Таким образом, имеем:
Соответственно, задача при настройке CI/CD сводится к тому, чтобы создать набор задач, реализующих все необходимые действия для сборки, тестирования и публикации кода и артефактов.
Потому, что когда появилась необходимость создать приватные репозитории под пет-проекты, на GitHub’e они были платными, а я — жадным. Репозитории стали бесплатными, но пока это не является для меня поводом достаточным переезжать на GitHub.
Потому что там настройка элементарная — даже не требуются знания командной строки. Интеграция с внешними провайдерами git — в пару кликов, импорт SSH-ключей для отправки коммитов в репозиторий — тоже, пайплайн легко настраивается даже не из шаблона.
Исходная позиция: что имеется и чего хочется
Описанные требования органично ложатся на следующую модель пайплайна:
Собираем конфигурацию
Готовим аккаунты
Создаём новый проект
Переходим в Atrifacts, жмём Create feed
Жмём Connect to feed, выбираем Visual Studio, из блока Machine Setup копируем Source
Идём в настройки аккаунта, выбираем Personal Access Token
Создаём новый токен доступа
Копируем созданный токен — после закрытия модального окна значение будет недоступно
Заходим в настройки репозитория в GitLab, выбираем настройки CI/CD
Раскрываем блок Variables, добавляем новую
На этом предварительная настройка завершена.
Готовим каркас конфигурации
Теперь при запуске пайплайна с хранилища образов Microsoft будет скачан указанный образ, в котором и будут исполняться все задачи из конфигурации.
Следующий этап — добавить stage‘ы. По умолчанию GitLab определяет 5 этапов:
Ничего не мешает объявить их явно, впрочем. Порядок, в котором указаны этапы, влияет на порядок, в котором они выполняются. Для полноты изложения, добавим в конфигурацию:
Для отладки имеет смысл получить информацию об окружении, в котором исполняются задачи. Добавим глобальный набор команд, который будет выполняться перед каждой задачей, с помощью before_script :
Осталось добавить хотя бы одну задачу, чтобы при отправке коммитов пайплайн запустился. Пока что добавим пустую задачу для демонстрации:
Всё логично — по умолчанию runner’ы (отвечающие за исполнение скриптов задач, и предоставляемые GitLab’ом) используют bash для исполнения команд. Можно исправить это дело, явно указав в описании задачи, какие теги должны быть у исполняющего пайплайн раннера:
Отлично! Теперь пайплайн выполняется.
Продолжим создание скелета конфигурации, добавив все задачи, описанные выше:
Получили не особенно функциональный, но тем не менее корректный пайплайн.
Настройка триггеров
Из-за того, что ни для одной из задач не указаны фильтры срабатывания, пайплайн будет полностью исполняться при каждой отправке коммитов в репозиторий. Так как это не является желаемым поведением в общем случае, мы настроим фильтры срабатывания для задач.
Вспомним набор требований — сборка и тестирование только для merge request, упаковка и отправка в Azure DevOps — для merge request и пушей в мастер, генерация документации — для пушей в мастер.
Для начала настроим задачу сборки кода, добавив правило срабатывания только при merge request:
Теперь настроим задачу упаковки на срабатывания на merge request и добавление коммитов в мастер:
Как видно, всё просто и прямолинейно.
Также можно настроить задачу на срабатывание только если создан merge request с определённой целевой или исходной веткой:
Настройка сохранения артефактов
Во время выполнения задачи build job у нас будут созданы артефакты сборки, которые можно переиспользовать в последующих задачах. Для этого нужно в конфигурацию задачи добавить пути, файлы по которым нужно будет сохранить и переиспользовать в следующих задачах, в ключ artifacts :
Пути поддерживают wildcards, что определённо упрощает их задание.
Если задача создаёт артефакты, то каждая последующая задача сможет их получить к ним доступ — они будут располагаться по тем же путям относительно корня репозитория, по которым были собраны из исходной задачи. Так же артефакты доступны для скачивания на сайте.
Теперь, когда у нас готов (и проверен) каркас конфигурации, можно переходить собственно к написанию скриптов для задач.
Пишем скрипты
Естественно, есть некоторые нюансы, из-за которых мы несколько усложним команды.
Собираем данные покрытия кода
Coverlet после запуска тестов выводит в консоль статистику по запуску:
GitLab позволяет указать регулярное выражение для получения статистики, которую потом можно получить в виде бейджа. Регулярное выражение указывается в настройках задачи с ключом coverage ; в выражении должна присутствовать capture-группа, значение которой и будет передано в бейдж:
Здесь мы получаем статистику из строки с общим покрытием по линиям.
Публикуем пакеты и документацию
Оба действия у нас назначены на последний этап пайплайна — раз уж сборка и тесты прошли, можно и поделиться с миром наработками.
Для начала рассмотрим публикацию в источник пакетов:
Если в проекте не присутствует файл конфигурации nuget ( nuget.config ), создадим новый: dotnet new nugetconfig
Зачем: в образе может быть запрещён доступ на запись к глобальным (пользовательской и машинной) конфигурациям. Чтобы не ловить ошибки, просто создадим новую локальную конфигурацию и будем работать с ней.
Теперь настроим создание документации:
Лирическое отступление про docfx
Раньше при настройке проекта я указывал источник кода для документации как файл решения. Основной минус — документация создаётся и для тестовых проектов. В случае, если это не нужно, можно задать такое значение узлу metadata.src :
Промежуточный итог
Вот такую простую конфигурацию можно составить буквально за полчаса и пару чашек кофе, которая позволит при каждом запросе слияния и отправке в мастер проверять, что код собирается и тесты проходят, собирать новый пакет, обновлять документацию и радовать глаз красивыми бейджиками в README проекта.
Кстати о бейджиках
Ради них ведь всё и затевалось!
Бейджи со статусами пайплайна и покрытием кода доступны в GitLab в настройках CI/CD в блоке Gtntral pipelines:
Бейдж со ссылкой на документацию я создавал на платформе Shields.io — там всё достаточно прямолинейно, можно создать свой бейдж и получать его с помощью запроса.
Azure DevOps Artifacts также позволяет создавать бейджи для пакетов с указанием актуальной версии. Для этого в источнике на сайте Azure DevOps нужно нажать на Create badge у выбранного пакета и скопировать markdown-разметку:
Добавляем красоты
Выделяем общие фрагменты конфигурации
Во время написания конфигурации и поисков по документации, я наткнулся на интересную возможность YAML — переиспользование фрагментов.
Как видно из настроек задач, все они требуют наличия тега windows у раннера, и срабатывают при отправке в мастер/создании запроса на слияние (кроме документации). Добавим это во фрагмент, который будем переиспользовать:
И теперь в описании задачи можем вставить объявленный ранее фрагмент:
Названия фрагментов должны начинаться с точки, чтобы не быть интерпретированными как задача.
Версионирование пакетов
При создании пакета компилятор проверяет ключи командной строки, и в их отсутствие — файлы проектов; найдя узел Version, он берёт его значение как версию собираемого пакета. Выходит, чтобы собрать пакет с новой версией, нужно либо обновить её в файле проекта, либо передать как аргумент командной строки.
Добавим ещё одну хотелку — пусть младшие два номера в версии будут годом и датой сборки пакета, и добавим пререлизные версии. Добавлять эти данные в файл проекта и проверять перед каждой отправкой можно, конечно — но можно ведь это делать и в пайплайне, собирая версию пакета из контекста и передавая через аргумент командной строки.
Данную задачу решает следующий скрипт:
Добавляем скрипт в задачу pack and deploy job и наблюдаем сборку пакетов строго при наличии заданной строки в сообщении коммита.
Итого
Потратив примерно полчаса-час времени на написание конфигурации, отладку в локальном powershell и, возможно, пару неудачных запусков, мы получили несложную конфигурацию для автоматизации рутинных задач.
Конечно, GitLab CI/CD гораздо обширнее и многограннее, чем может показаться после прочтения этого руководства — это совершенно не так. Там даже Auto DevOps есть, позволяющий
automatically detect, build, test, deploy, and monitor your applications
Теперь в планах — сконфигурировать пайплайн для развёртывания приложений в Azure, с использованием Pulumi и автоматическим определением целевого окружения, что будет освещено в следующей статье.
GitLab CI: Учимся деплоить
В данной статье речь пойдет об истории успеха воображаемого новостного портала, счастливым владельцем которого являетесь вы. К счастью, вы уже храните код проекта на GitLab.com и знаете, что для тестирования можно использовать GitLab CI.
Теперь вам интересно, можно ли пойти дальше и использовать CI еще и для развертывания проекта, и если да, то какие возможности при этом открываются.
Чтобы не привязываться к какой-либо конкретной технологии, предположим, что ваше приложение является простым набором HTML-файлов, никакого выполнения кода на сервере, никакой компиляции JS assets. Деплоить будем на Amazon S3.
У автора нет цели дать рецепты для конкретной технологии в этой статье. Наоборот, примеры кода максимально примитивны, чтобы слишком на них не зацикливаться. Смысл в том чтобы вы посмотрели на фичи и принципы работы GitLab CI в действии, а потом применили их для вашей технологии.
Начнем с начала: в вашем вымышленном приложении пока еще нет поддержки CI.
Начало
Развертывание: в данном примере результатом развертывания должно быть появление набора HTML-файлов в вашем бакетe (bucket) S3, который уже настроен для хостинга статических вебсайтов).
Добиться этого можно множеством различных способов. Мы будем использовать библиотеку
awscli от Amazon.
Полностью команда развертывания выглядит следующим образом:
Пуш кода в репозиторий и развертывание — это два разных процесса
Попробуем автоматизировать этот процесс с использованием GitLab CI.
Первое автоматическое развертывание
При пуше на GitLab код автоматически развертывается с помощью CI
Также, не стоит забывать о переменных окружения, полученных из AWS Console:
Должно сработать, однако держать секретные ключи в открытом виде (даже в приватном репозитории) — не самая лучшая идея. Посмотрим, что с этим можно сделать.
Сохранение секретов
В GitLab есть специальный раздел для секретных переменных: Settings > Variables
Все данные, помещенные в этот раздел станут переменными окружения. Доступ к этому разделу есть только у администратора проекта.
Раздел variables можно удалить из настроек CI, но лучше давайте воспользуемся им для другой цели.
Создание и использование публичных переменных
С увеличением размера файла конфигурации становится удобнее хранить некоторые параметры в его начале в качестве переменных. Такой подход особенно актуален в случае, когда эти параметры используются в нескольких местах. Хоть в нашем случае до такого пока что не дошло, в демонстрационных целях создадим переменную для названия бакета S3:
Тем временем посещаемость вашего сайта повысилась, и вы наняли разработчика, чтобы он вам помогал. Теперь у вас есть команда. Посмотрим как работа в команде влияет на рабочий процесс.
Работа в команде
Теперь в одном и том же репозитории работают два человека, и использовать ветку master для разработки более не целесообразно. Поэтому вы принимаете решение использовать различные ветки для разработки новых фич и написания статей и мержить их в master по мере готовности.
Проблема в том, что при текущей настройке CI не поддерживает работу с ветками — при пуше в любую ветку на GitLab происходит развертывание текущего состояния master на S3.
Мы хотим избежать развертывания каждой ветки на сайте
С другой стороны, было бы неплохо иметь возможность предпросмотра изменений, внесенных из веток для выделенной функциональности (feature-веток).
Создание отдельного пространства для тестирования
Патрик (разработчик, которого вы недавно наняли) напоминает вам, что существует такая функциональность, как
GitLab Pages. Как раз то, что нужно — место для предпросмотра новых изменений.
Для размещения вебсайта на GitLab Pages ваша конфигурация CI должна удовлетворять трем простым требованиям:
После добавления кода из примера для сайтов на чистом HTML файл настройки CI выглядит следующим образом:
Всего в нем содержатся две задачи: одна ( deploy ) проводит развертывание сайта на S3 для ваших читателей, а другая ( pages ) на GitLab Pages. Назовем их соответственно «Production environment» и «Staging environment».
Развертывание всех веток, кроме master, будет проводиться на GitLab Pages
Среды развертывания
GitLab поддерживает работу со средами развертывания. Все, что вам нужно сделать, это назначить соответствующую среду для каждой задачи развертывания:
GitLab отслеживает все ваши процессы развертывания, так что вы всегда можете увидеть, что в текущий момент развертывается на ваших серверах:
Также, GitLab хранит полную историю всех развертываний для каждой среды:
Итак, мы оптимизировали и настроили все, что только могли, и готовы к новым вызовам, которые не заставят себя долго ждать.
Работа в команде, часть 2
Опять. Это опять произошло. Стоило вам только запушить свою feature-ветку для превью, как спустя минуту Патрик сделал то же самое и тем самым перезаписал содержимое staging. #$@! Третий раз за сегодня!
Идея! Используем Slack для оповещений о развертываниях, чтобы никто не пушил новые изменения, пока старые не закончили развертываться.
Оповещения Slack
Настроить оповещения Slack несложно. Надо лишь взять из Slack URL входящего вебхука…
… и передать его в Settings > Services > Slack вместе с вашим логином Slack:
Поскольку вы хотите получать уведомления только о развертываниях, в показанных выше настройках можно убрать галочки на всех пунктах, кроме “Build”. Вот и все, теперь вы будете получать оповещения о каждом развертывании:
Работа в большой команде
Со временем ваш сайт стал очень популярным, а ваша команда выросла с двух до восьми человек. Разработка происходит параллельно, и людям все чаще приходится ждать в очереди для превью на Staging. Подход “Проводите развертывание каждой ветки на Staging” больше не работает.
Пришло время вновь модифицировать рабочий процесс. Вы и ваша команда пришли к соглашению, что для выкатывания изменений на staging-сервер нужно сначала сделать мерж этих изменений в ветку “staging”.
Разработчики проводят мерж своих feature-веток перед превью на Staging
Само собой, при таком подходе на мерж тратятся дополнительное время и силы, но все в команде согласны, что это лучше, чем ждать в очереди.
Непредвиденные обстоятельства
Невозможно все контролировать, и неприятности имеют свойство случаться. К примеру, кто-то неправильно смержил ветки и запушил результат прямо в production как раз когда ваш сайт находился в топе HackerNews. В результате тысячи человек увидели кривую версию сайта вместо вашей шикарной главной страницы.
К счастью, нашелся человек, который знал про кнопку Rollback, так что уже через минуту после обнаружения проблемы сайт принял прежний вид.
Rollback перезапускает более раннюю задачу, порожденную в прошлом каким-то другим коммитом
Для того, чтобы запустить развертывание вручную, перейдите на вкладку Pipelines > Builds и нажмите на вот эту кнопку:
И вот ваша компания превратилась в корпорацию. Над сайтом работают сотни человек, и некоторые из предыдущих рабочих практик уже не очень подходят к новым обстоятельствам.
Ревью приложений
Следующим логическим шагом является добавление возможности развертывания временного инстанса приложения каждой feature-ветки для ревью.
В нашем случае для этого надо настроить еще один бакет S3, с той лишь разницей, что в этом случае содержимое сайта копируется в “папку” с названием ветки. Поэтому URL выглядит следующим образом:
А так будет выглядеть код, замещающий задачу pages :
Обратите внимание на то, что переменная S3_BUCKET_NAME определена внутри задачи — таким образом можно переписывать определения более высокого уровня.
Визуальная интерпретация такой конфигурации:
Технические детали реализации такого подхода сильно разнятся в зависимости от используемых в вашем стеке технологий и от того, как устроен ваш процесс развертывания, что выходит за рамки этой статьи.
Реальные проекты, как правило, значительно сложнее, чем наш пример с сайтом на статическом HTML. К примеру, поскольку инстансы временные, это сильно усложняет их автоматическую загрузку со всеми требуемыми сервисами и софтом “на лету”. Однако это выполнимо, особенно, если вы используете Docker или хотя бы Chef или Ansible.
Про развертывание при помощи Docker будет рассказано в другой статье. Честно говоря, я чувствую себя немного виноватым за то, что упростил процесс развартывания до простого копирования HTML-файлов, совершенно упуская более хардкорные сценарии. Если вам это интересно, рекомендую почитать статью «Building an Elixir Release into a Docker image using GitLab CI».
А пока что давайте обсудим еще одну, последнюю проблему.
Развертывание на различные платформы
В реальности мы не ограничены S3 и GitLab Pages; приложения разворачиваются на различные сервисы.
Более того, в какой-то момент вы можете решить переехать на другую платформу, а для этого вам нужно будет переписать все скрипты развертывания. В такой ситуации использование gem’а dpl сильно упрощает жизнь.
В приведенных в этой статье примерах мы использовали awscli в качестве инструмента для доставки кода на сервис Amazon S3. На самом деле, неважно, какой инструмент вы используете и куда вы доставляете код — принцип остается тот же: запускается команда с определенными параметрами и в нее каким-то образом передается секретный ключ для идентификации.
Инструмент для развертывания dbl придерживается этого принципа и предоставляет унифицированный интерфейс для определенного списка плагинов (providers), предназначенных для развертывания вашего кода на разных хостинговых площадках.
Задача для развертывания в production с использованием dpl будет выглядеть вот так:
Так что если вы проводите развертывание на различные хостинговые площадки или часто меняете целевые платформы, подумайте над использованием dpl в скриптах развертывания — это способствует их единообразию.
Улучшаем тестирование путем использования реального трафика
TL;DR Чем ближе к реальности ваши тестовые данные, тем лучше. Попробуйте Gor — автоматическое перенаправление production трафика на тестовую площадку в реальном времени.
Здесь в Granify мы обрабатываем огромное количество генерируемых пользователями данных, наш бизнес построен на этом. Мы должны быть уверены что данные собираются и обрабатываются правильно.
Вы даже не представляете насколько странными могут быть данные пришедшие от пользователей. Источником могут быть прокси-серверы, браузеры о которых вы никогда не слышали, ошибки на клиентской стороне, и так далее.
Не важно сколько у вас тестов и фикстур, они просто не могут покрыть все случаи. Трафик с production всегда будет отличаться от ожидаемого.
Более того, мы может просто все сломать при обновлении версии, даже если все тесты прошли. На моей практике это случается постоянно.
Есть целый класс ошибок которые очень непросто найти автоматизированным и ручным тестирование: concurrency, ошибки в настройке серверов, ошибки которые проявляются при вызове команд только в определенном порядке, и многое другое.
Но мы можешь сделать несколько вещей что бы упростить поиск таких багов и улучшить стабильность системы:
Всегда тестируем на staging
Наличие Staging среды обязательно, и она должна быть идентична production. Использование таких средств как Puppet или Chef сильно облегчает задачу.
Вы должны требовать что бы разработчики всегда в ручную тестировали свой код на staging. Это помогает найти самые очевидные ошибки, но это все еще очень далеко от того что может случится на production трафике.
Тестируем на реальных данных
Есть несколько техник для позволяющих протестировать ваш код на реальных данных (я рекомендую использоваться обе):
1. Обновлять только 1 из production серверов, таким образом часть ваших пользователей будет обрабатываться новым кодом. Эта техника имеет несколько минусов: часть ваших пользователей может увидеть ошибки, и вам возможно придется использоваться «липкие» сессии. Это довольно похоже на A/B тестирование.
2. Воспроизведение production трафика (log replay)
Илья Григорик написал замечательную статью про нагрузочное тестирование используя log replay технику.
Все статьи что я читал на эту тему упоминают log replay как средство для нагрузочного тестирования используя реальные данные. Я же хочу показать как использовать эту технику для ежедневного тестирования и нахождения ошибок.
Такие программы как jMeter, httperf or Tsung имеют поддержку log replay, но она либо в зачаточном состоянии либо сфокусированная на нагрузочном тестировании а не эмуляции реальных пользователей. Чувствуете разницу? Реальные пользователе это не только набор запросов, очень важно правильный порядок и время между запросами, различные HTTP заголовки и так далее. Для нагрузочного тестирования это порой не важно, но для поиска ошибок это критично. К тому же эти средства сложны в настройке и автоматизации.
Разработчики очень ленивы. Если вы хотите что бы ваши разработчики использоватли какую либо программу/сервис, он должен быть максимально автоматизирован, а еще лучше что бы оно работало так что никто ничего не заметил.
Воспроизводим production трафик в автоматическом режиме
Я написал простую программу Gor
Gor — позволяет автоматические воспроизводить production трафик на staging в реальном времени, 24 часа в сутки, с минимальными затратами. Таким образом ваша staging среда всегда получает порцию реально трафика.
Gor состоит из 2-ух частей: Listener и Replay сервера. Listener устанавливается на production веб серверы, а дублирует весь трафик на Replay сервер на отдельной машине, который уже направляет его на нужные адрес. Принцип работы показан на диаграмме ниже:
Gor поддерживает ограничение количества запросов. Это очень важная настройка, так как staging как правильно использует меньше машин чем production, и вы может выставить максимальное кол-во запросов в секунду которое может выдержать ваша staging среда.
Вы можете найти подробную документацию на странице проекта.
Так как Gor написан на Go, для запуска мы можете просто использовать уже скомпилированный дистрибутив Downloads
В Granify, мы используем Gor в production в течении некоторого времени, и очень довольны результатами.