Как мы настраивали процесс CI-CD для наших SOA-проектов

в 16:58, , рубрики: Ansible, automatization, build management, configuration management, continuos integration, continuous delivery, devops, django, docker, Jenkins, python, системы сборки

Как мы настраивали процесс CI-CD для наших SOA-проектов - 1
Хотел бы поделиться опытом настройки CI/CD в нашей компании, плюс, послушать советы, если у вас похожая структура проектов.

Кому, как мне кажется, данная статья может оказаться полезной:
— ваши проекты содержат несколько отдельных репозиториев с приложениями;
— вы хотите быть уверены, что каждый репозиторий проходит тесты;
— вы хотите быть уверены в совместимости версий между репозиториями;
— вы ещё не успели, но планируете, перевести свои проекты на докер;
— хотите посмотреть пару playbook'ов Ansible.

Очень рекомендую курс «Continuous Delivery Using Docker And Ansible». Мы оттакливались от него при разработке нашего решения.

Задачи для CI/CD

Один наш проект в среднем — это 4-5 репозиториев, взаимодействующих между собой по rest-api. Считается ли это микросервисной архитектурой или нет, не знаю точно, но, учитывая это, перед CI/CD мы ставили следующие задачи:
— в каждом репозитории в каждой из основных веток должен быть рабочий (протестированный) код;
— каждая эта ветка, плюс каждый тэг, должны быть полностью консистены между всеми репозиториями в проекте;
— должна быть возможность развернуть проект локально, как полноценно, так и отдельно любой репозиторий, для разработки;
— должна быть возможность развернуть проект на разных окружениях: testing, staging, production.

Итак, приступим.

Настройка CI/CD

Предварительный шаг

— мы перешли на git-flow. Оказалось, что наша кастомная vcs-workflow, по-сравнению с «классикой», избыточна и сложна, особенно для новичков;
— наш недельный спринт — это новая версия продукта. Каждая задача, будь то баг или фича, в таск-менеджере прикрепляется к конкретной версии. У каждого репозитория в конце спринта появляется тэг с новой версией, даже если именно в данной репе для данной версии ничего и не делали. Исключение, если ни один репозиторий из проекта за спринт не трогали;
— запретили напрямую пушить в ветки master, develop и release, только через пул-реквесты;
— повесили хук на пул-реквесты в вышеуказанные ветки для сборки и тестирования в Jenkins;
— запретили слияние пул-реквестов без удачного тестирования Jenkins'ом и без одобрения Code Review.

В качестве CI инструмента мы выбрали Jenkins, который запускает юнит тесты и интеграционные тесты api.
В качестве CD инструментов — Ansible + Docker.

Первый шаг, настройка отдельного репозитория

Мы изменили структуру каждого нашего репозитория внутри проекта:

app
  |-src
  |-docker
  |   |-ci
  |   |-develop
  |   |-release
  |-requirements
  |-Jenkinsfile
  |-Makefile

Настроенный хук на пул-реквесте скажет Jenkins'у, что репозиторий необходимо протестировать. Jenkins будет искать и исполнять Jenkinsfile. Последний последовательно вызывает команды Makefile для сборки контейнера и тестирования. Makefile запускает команды docker-compose из каталога ./docker/ci. Почему мы не настроили запуск команд docker-compose сразу из Jenkinsfile? Чтобы сохранить его универсальность для всех репозиториев. Т.е. разным репозиториям для сборки и запуска требуются разные команды docker-compose, и эти различия настроены в Makefile, который для Jenkinsfile всегда имеет одинаковый интерфейс сборки и запуска.

NB. В конце статьи находятся ссылки на репозитории с примерами.

Также в Makefile находятся команды по сборке и запуску репозитория локально в develop-режиме — настроен проброс с исходниками с хостовой машины внутрь докера, и достаточно будет только перезапустить docker-compose, что тоже сделано через make-команду, чтобы увидеть новые изменения. За это отвечает Makefile + ./docker/develop.

В ./docker/release находятся настройки сборки репозитория для среды testing/staging и пр. Они, настройки, будут использоваться позже.

Второй шаг. Настройка дополнительного devops-репозитория

Назначение общего репозитория — сохранение целостности проекта при разворачивании репозиториев, которые в него входят, а также в возможности интеграционного тестирования.

Структура репозитория

devops
  |-ansible
  |   |-plays
  |   |-roles
  |-projects
  |   |-project_1
  |   |   |-apps
  |   |   |   |-app_1
  |   |   |   |-app_2
  |   |   |   |-app_3
  |   |   |   |-...
  |   |   |-docker
  |   |   |   |-ci-api
  |   |   |   |-ci-selenium-gherkin
  |   |   |   |-develop
  |   |   |   |-testing
  |   |   |   |-staging
  |   |   |   |-production
  |   |   |-Makefile
  |-requirements
  |-Jenkinsfile
  |-Makefile

Сперва о том, как этот репозиторий выполняет интеграционное тестирование.
Не самое простое дело, попробую объяснить.
Как и в случае репозитория с приложением, здесь есть файлы Jenkinsfile и Makefile, которые при пул-реквесте запустят команды сборки и тестирования. Настройки сборки располагаются в ./projects/PROJECT/docker/ci-api, где «PROJECT» — название текущего проекта. Сборка включает в себя клонирование каждого репозитория в нужной ветке/тэге, запуск контейнера-тестировщика api.
«Нужная ветка/тэг» — это то, что мы пытается протестировать — либо общая ветка (master, develop, release) для всех репозиториев, либо тэг-версия проекта. Тэг необходимо проставить в каждом репозитории. Затем создать ветку в devops-репозитории с названием, совпадающим с «нужным». После этого можно делать пул-реквест.
Jenkins попытается собрать проект по выбранному тэгу/ветке, если в каком-то репозитории не найдётся такого — тестирование провалено. Если собрать проект удалось, то запустится «тестовый фреймворк», в качестве которого мы используем Postman и его утилиту для командной строки — Newman. Если тесты прошли успешно — на выходе мы сливаем пул-реквест и проставляем тестируемый тэг в devops-репозиторий. Наличие этого тэга говорит, что эта версия проекта протестирована.
Для запуска тестов Постмана, нам требуется ссылка расшаренной коллекции, которую вставляем в command контейнера.

Пока это единственный вид интеграционного тестирования, чуть позже мы добавим тестирование с помощью gherkin'a или selenium'a, по крайней мере, каталог docker/ci-selenium-gherkin уже есть.

Теперь про функции CD в данном репозитории.
Здесь, в ./ansible, находится пульт управления всем проектом по сборке образов и их доставке на разные сервера и окружения, а именно:
— develop.yml — настройки по разворачиванию всего проекта локально;
— make-images.yml — создание docker-образов с определённой версией проекта и пуш в докер-registry;
— deploy-and-run-images.yml — разворачивание проекта на серверах с разным окружением.

В начале каждого пункта указан playbook, который выполняет данный сценарий.
Запускаются они командой:

$ ansible-playbook -i ../testing.ini make-images.yml -e 'project=todo ver=2017.1'
где
- -i ../testing.ini -- файл inventory, в котором указан сервер для данного окружения, куда впоследствии надо будет сделать доставку проекта
- make-images-yml -- playbook
- -e 'project=todo ver=2017.1' -- дополнительные параметры, которые ожидает playbook, в данном случае указывается проект и тэг.

В ./ansible/plays/group_vars/all.yml находятся настройки проекта:
— какие репозитории относятся к данному проекту;
— какой docker-registry использовать, какие логин-пароль к нему;
— индивидуальные настройки для каждого окружения и пр.

Как вы могли заметить, хотя данный репозиторий полностью посвящён только одному проекту, всё равно мы передаём название проекта в параметры playbook'а, а также каталог проекта находится в каталоге projects. Это из-за того, что данный devops-репозиторий — это форк от master-devops-репозитория, от которого точно также форкнуты devops-репозитории других проектов. И подобная структура позволяет обмениваться кодом общих настроек и ansible команд между мастером-форками и между самим форками без угрозы что-то сломать. Точнее, каталог ansible — общий, и его рефакторинг легко можно переносить из мастера в форк и наоборот. А все частные проектные настройки находятся в своём отдельном каталоге в projects. И пул из мастера, либо из соседнего devops-репозитория — не будет конфликтовать с текущим.

Возвращаемся к каталогу docker/release в приложениях, в котором находится Dockerfile, отвечающий за сборку для окружения testing/staging и production, т.е. за всё, кроме develop. Сама по себе релизная сборка одного репозитория ничего полезного не даёт, только в совокупности с остальными репозиториями проекта. Ansible настроен таким образом, что для develop-сборки он возьмёт Dockerfile из каталога docker/develop каждого проекта, а для сборки под релизное окружение — из каталога docker/release.

Итого, у нас получилось сделать:
— возможность клонирования любого репозитория и запустить develop-версию
— каждый репозиторий проверяется Jenkins'ом;
— есть общий репозиторий, которая запускает интеграционные тесты по всем репозиториям по общей версии;
— ansible playbook: разворачивает локально и запускает все репозитории проекта в develop-режиме;
— ansible playbook: собирает образы в зависимости от выбранной схемы окружения и отправляет в докер-registry;
— ansible playbook: на сервере настраивает проект;
— ansible playbook: на сервере запускает приложение.

Ссылки на приложения для демонстрации системы:
todo_todo — форк с проекта todobackend.com. Изменил структуру и добавил тесты. Создаёт todo'шк;
todo_crm — создаёт пользователей, отправляет запрос в todo_todo, создаёт todo и привязывает его к пользователю;
todo_ops — devops-репозиторий с конфигами

Автор: vlfedotov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js