Всем привет, меня зовут Александр Данковцев, я ведущий инженер команды Антимонолит. В этой статье я расскажу вам, как строился монолит CI/CD Авито.
Мы поговорим о нашей промежуточной архитектуре, хуках предварительного получения, о том, что такое сборка и развертывание, как запускаются автотесты и какие проверки происходят при слиянии.
Давайте также рассмотрим действия после слияния.
Прежде чем начать рассказ, я представлю пару концепций, которые будут использоваться дальше:
- Кандидат на выпуск — это версия кода, которая в случае успеха пройдет тестовую проверку и будет развернута в рабочей среде.
- Слайс-кандидат релиза — это ветвь Git, созданная из мастера в процессе запуска сборки релиза.
Постановочная архитектура Авито
Начнем со схемы промежуточной архитектуры.
Здесь вращается сам сайт Авито и компоненты, из которых он состоит:
Сайт Авито, как и любой другой микросервис в промежуточной среде, разворачивается в промежуточный кластер Kubernetes. У нас есть отдельное пространство имён avito-site-tests, в котором располагаются ресурсы сайта: базы данных, баунсеры, служба очередей, Сфинкс, прочие репликации, всякие демоны.
Это сделано в целях экономии ресурсов кластера.
Непосредственно каждая ветка сайта разворачивается в отдельном неймспейсе, а все бэкенды привязываются к ресурсам, которые расположены в отдельном неймспейсе.
Особняком стоит Frontend Delivery Network (FDN), куда загружается статика сайта.
Эту инфраструктуру очень сложно развернуть: нельзя развернуть всю постановку одной кнопкой.
Поэтому постановка разделена на релизы Helm. Сами ресурсы мы выкатываем отдельным релизом, потому что это требуется очень редко.
Ресурсы — база данных, Sphinx, Redis, RabbitMQ. Кроме того, демоны монолита обновляются отдельно в зависимости от кандидатов на выпуск или по требованию.
На схеме это демоны PGQ, потребители DataBus и потребители QaaS. Мы вынесли их в отдельный деплой, чтобы повторная накатка не создавала заново базу данных, так как база данных довольно большая (около 10 ГБ) и ее пересоздание подразумевает сброс всех данных, созданных в процессе ее работы, т.е.
всех созданных рекламных объявлений и другие ресурсы будут потеряны.
Мы получим несогласованное состояние с другими сервисами.
Развертывание ресурсов начинается в 7 утра, когда все разработчики еще спят, и к утру у нас уже есть готовая инфраструктура.
Это позволяет нам тестировать, в том числе, асинхронное взаимодействие компонентов сайта.
После выкатки ресурсов заново создается выпуск крон, демонов и потребителей.
Каждый сайт из второго столбца схемы разворачивается отдельно.
Грубо говоря, это происходит путём пуша в ветку: мы закоммитили, пушили, и ветка собралась в отдельное пространство имён.
Статика разворачивается отдельно, об этом я расскажу позже.
Каждый бэкенд сайта — Kubernetes Pod — состоит из пяти контейнеров:
Нгинкс — это точка входа плюс некоторый статический возврат. PHP-fpm — сам бэкэнд. Посланник-ядро , который отвечает за проксирование служб, выполняет роль разрешения и поддержания активности DNS. Например, мы можем постучать http://localhost:8888/service-item и попадаем в сервис-пункт. Все это нужно для большей продуктивности постановки.
Нетрамеш добавляет контекст к исходящим запросам: заголовок X-Source. Он также служит динамической заменой восходящих сервисов с использованием входящего заголовка X-Route. Формат: X-Router: src_host:src_port=dst_host:dst_port. Навигатор — межкластерный маршрутизатор, преобразующий ClusterIP в набор PodIP во всех доступных дата-центрах.
Давайте посмотрим на стек сетевой подготовки.
Когда мы запрашиваем пользовательскую ветку сайта, запрос проходит через интерфейс nginx. В нашем случае это одно из подмножеств avi-http, которое отправляет запросы к Ingress. С Ingress мы попадаем в маршрутизирующий шлюз.
Routing-gateway смешивает заголовок X-Route и затем пересылает запрос API-шлюзу.
API-шлюз балансирует, куда отправить запрос: либо это будет Сайт Авито, либо какая-то API-композиция.
API-композиция — это десктопный сайт на Node.js или кастомный сервис, или любой другой сервис на любом другом языке.
Качественные ворота
Когда мы в общих чертах разберемся, как выглядит постановка, давайте посмотрим на гейты качества, которые используются для Сайта Авито.Основные этапы прохождения коммита следующие:
- Перехватчики предварительного получения Git.
- TeamCity: CI/CD запускает тесты, линтеры и т. д.
- Объедините проверки, флаги и обязательства.
- Действия после слияния.
Допустим, мы решили что-то исправить.
Крючки предварительного получения
Перехватчики предварительного получения помогают проверить, все ли в порядке с коммитом, прежде чем отправлять его в репозиторий.У нас не так много таких крючков.
Мы следим за тем, чтобы каждый коммит содержал название проблемы Jira. Существуют также базовые проверки синтаксиса.
Например, если точка с запятой была поставлена неправильно, ловушка предварительного получения сообщит, что произошла синтаксическая ошибка.
Также проверяем стиль, например, если отсутствует необходимое пространство между операторами, линтер сообщит об этом и не позволит протолкнуть ветку.
Плюс есть проверка, что коммит наш доменный, принадлежит Авито, а не какой-то левый.
Что происходит в TeamCity
Вот максимально упрощенный граф зависимостей нашего CI/CD; Я собрал основные этапы прохождения:У нас есть всевозможные проверки схем, проверки DI, модульные тесты, но это только верхушка айсберга.
Из всей схемы я рассмотрю самый основной и сложный процесс — прохождение сквозных тестов от сборки Docker до запуска теста.
Сборка
Перед сборкой кода сайта подтягиваем мастер.Это необходимо, чтобы избежать теневых коммитов и отловить ситуации на ранних стадиях, когда одновременно разрабатывались две ветки, и кто-то заморозил конфликтующую логику.
После подтягивания мастера образ непосредственно собирается и затем отправляется.
При построении образа мы используем шаблоны build-image и push-image. Образ сборки большой и тяжелый, с кучей зависимостей, C-библиотек и прочих утилит, которые нужны для сборки самого кода.
Во время выполнения эти зависимости не должны существовать.
То есть мы собираем какой-то артефакт на этапе сборки кода, после чего собранная статика загружается в Ceph. А остальная часть собранного кода запекается в образ Docker и позже отправляется.
Вот как работает код сборки:
- Установка зависимостей композитора.
- Генерация кода, например, генерация клиент-магазина.
- DI-сборка, мы используем Symfony.
- Сборка фронтенда.
Собирается npm, встраивает Twig в кеши.
- Развертывание хранимых процедур.
Получаем бесплатного пользователя, назначаем ему схему, ищем пропуск и накатываем туда хранилище.
- Сборка словаря — это файловое представление справочных данных из базы данных или сервисов.
- Если все прошло успешно, то последний этап — сборка артефактов, то есть генерация app.toml, swagger, rev.txt. Rev.txt — это идентификатор сборки, среда, в которой она была собрана, и другая отладочная информация.
Развертывать
Когда образ собран и отправлен, приходит следующая сборка TeamCity — деплой.Процесс выглядит следующим образом:
- Развертывание в кластере Kubernetes.
- Проверка доступности хоста: Curl выполняется с небольшим прогрессивным шагом, пока хост не вернет код 200. Helm предоставляет информацию о том, что он успешно развернут, проходят проверки работоспособности и т. д.
- Регистрация хоста в шлюзе маршрутизации.
- Автоматическое уведомление в Slack о том, что хост собрался.
Автотесты
Хост собрался, пришло время провести сквозные тесты.Проще говоря, сначала мы работаем над сборкой Build E2E. Папка с тестами собрана в отдельный архив и закинута в Artifactory. Там много связей из E2E-тестов, я их показал на первой картинке про TeamCity. Для простоты рассмотрим один из них — E2E Test Suite. В этой сборке настраивается какой тип тестов запускать, например web или api, или указываются какие-то дополнительные параметры, например, относящиеся к конкретному авито-юниту.
Эта сборка общается с сервисом запуска параллельного автотеста: он отправляет ему задачу, сервис ее выполняет и получает ответ.
Если копнуть немного глубже, сборка, которая запускает тесты, представляет собой мета-раннер TeamCity, который запускает через Docker небольшое приложение, реализующее клише параллельного клиента.
Parallel-client делает запрос к параллельному менеджеру и передает ему все метаданные сборки: какие тесты какому юниту принадлежат, какой артефакт был запущен в Artifactory. Параллельный менеджер, получив результат, ставит все в очередь и отвечает клиенту, что «все ок, я принял результат».
После этого клиент начинает периодически опрашивать параллельный менеджер на предмет информации о том, прошли тесты или нет. Когда задача ставится в очередь на выполнение теста, подключается параллельный рабочий процесс.
Рабочий считывает очередь задач, которые были поставлены перед ним для выполнения тестов, и находит в данных метаинформацию о месте хранения этих тестов.
В метаинформации есть ссылка на скачивание архива, который заранее был загружен на Artifactory. Воркёр скачивает архив с тестами и запускает команду для их запуска.
Тесты выполняются в Selenium, который запускается через сайт через Firefox, Chrome или любой другой браузер.
Тесты также используют файловую систему (fs-qa), куда сохраняют скриншоты, html-страницы и т. д. Воркёр смотрит на результат выполнения: при успехе код ответа — 0, при провале — 1. После этого параллельный работник помещает каждую выполненную часть задачи в отдельную очередь «результатов выполнения теста».
Параллельный менеджер прослушивает эту очередь.
Когда менеджер получает результаты, он загружает их в бэкэнд репортеров тестов, где хранятся отчеты о том, что такой-то тест был выполнен столько-то раз и другая информация.
Параллельный клиент с помощью команды cli запрашивает окончательную информацию от серверной части генератора отчетов о том, что все тесты пройдены.
Там указано, сколько тестов пройдено, например 150 из 170. На основании этой информации билд становится зеленым или красным.
Также после получения этой информации в TeamCity создается артефакт со ссылкой на репортер фронтенд-тестов.
Мы можем войти в него из TeamCity и визуально увидеть полную отчетность о том, какие тесты прошли и как долго они выполнялись.
Вот как схематически выглядит весь процесс:
Каждый E2E-тест создает свою коллекцию тестов, а каждая коллекция тестов создает свои отчеты, и их довольно много.
При запуске ветки для сайта мы получаем семь E2E-пакетов, а к релизу их около 50. Когда все отчеты собраны и биты поступили в тайник, мы можем пойти в тайник и заморозить ветку.
Проверяет на слияние
Помимо стандартной проверки на одобрение и неудачных или неудачных сборок, мы используем собственный плагин, который проверяет, можно ли объединить ветку с основной.
Плагин Merge анализирует diff, какие файлы были изменены, и устанавливает флаги для текущего запроса на извлечение.
Если запрос на включение включал изменения в файлы PHP, он вносит изменения в серверную часть, если были изменения в JS или CSS, он вносит изменения во внешний интерфейс.
Также отмечаются случаи, когда произошли изменения в папке с E2E-тестами, например, изменения тестов покупателя.
В результате работы плагина появляется пул-реквест с затронутыми в нем флагами: фронтенд, бэкенд и покупательские тесты.
Название флага | Значение | Состояние |
Изменения в бэкэнде | Да | *.
php |
Изменения во внешнем интерфейсе | Нет | *.
css, *. js |
Изменения E2E для покупателей | Нет | тесты/e2e/BuyerTests/* |
Изменения в базе данных | Да | *.
sql |
Но мы не хотим, чтобы каждое изменение read.me требовало 30 проверок.
Мы хотим, чтобы при касании любого текстового файла было только одно одобрение.
Это достигается путем маркировки каждой проверки списком флагов, которые она должна активировать.
Имя Линтера | Список флажков |
Юнит-тесты | Изменения в бэкэнде |
ФронтендCI | Изменения во внешнем интерфейсе |
ДокерСборка | Изменения в бэкэнде, Изменения во внешнем интерфейсе, Изменения в базе данных |
ПокупательE2E Suite | Изменения в E2E для покупателей |
Если мы не трогали папку с покупателями, то проверки не будут обязательными, а пул-реквесты не будут требовать, чтобы они были зелеными.
Если вы затронули SQL, значит, вам необходимо пройти интеграционные тесты по базе данных и так далее.
Действия после слияния
Когда все тесты пройдены, все условия выполнены, все необходимые галочки отмечены, мы наконец можем объединить ветку с мастером.Но это еще не все.
После этого у нас работает механика after-merge. Это реализуется плагином в нашем тайнике, который слушает изменения при слиянии и запускает соответствующие сборки в TeamCity. Прежде всего, он делает удаление хоста, что экономит нам ресурсы: Kubernetes не резиновый.
Ceph также не является резиновым, поэтому после слияния статика очищается.
И в конце плагин отменяет регистрацию шлюза маршрутизации.
Вот и все, мы в мастере.
А что насчет релизов?
В статье я не рассматривал релизы Авито.В принципе они очень похожи на описанный выше процесс.
В релизах это просто не пул-реквест к мастеру, а обрезка ветки.
В остальном смысл тот же: сборка сайта, запуск E2E-тестов, просто их больше, и вместо проверки в stash на мерж-чек валидация релиз-инженерами.
Теги: #программирование #Kubernetes #ci/cd #Build system #staging
-
Обучение Microsoft Обязательно
19 Oct, 24 -
Отправка Почты В Codeigniter
19 Oct, 24 -
Универсальное Зарядное Устройство Usb
19 Oct, 24 -
Мамут И Вексельберг Продадут Корбина
19 Oct, 24