ci/cd devops

Что такое CI и что такое CD

За аббревиатурой прячутся две разные вещи.

CI, Continuous Integration, непрерывная интеграция. Каждый коммит автоматически собирается и прогоняется через тесты. Смысл в том, чтобы ловить поломки сразу, в момент появления, пока изменение свежее и понятно, что именно его вызвало. Без CI поломки копятся незаметно и всплывают позже все разом.

С CD хитрее, потому что за этими буквами стоят два похожих, но разных понятия.

Continuous Delivery, непрерывная доставка. Пайплайн доводит код до состояния «готов к выкатке» и сам выкладывает его на тестовые окружения, но финальную кнопку выката на прод нажимает человек. Машина делает всю работу, а решение остается за людьми.

Continuous Deployment, непрерывное развертывание. Тут автоматизируется даже эта кнопка. Если все проверки прошли, код едет на прод сам, без участия человека.

Разницу между Continuous Delivery и Continuous Deployment любят спрашивать на собеседованиях, так что ее стоит запомнить. Оба понятия про автоматизацию доставки, но в первом случае последнее слово остается за человеком, а во втором весь путь до прода проходит автоматически.

Анатомия пайплайна

Пайплайн, это конвейер, по которому ваш код идет от коммита до прода. Запушили изменения, и дальше все происходит автоматически: код проверяется, собирается, тестируется и выкатывается. Слово «пайплайн» (pipeline) буквально означает «трубопровод». Код входит с одного конца сырым черновиком, проходит через серию фильтров и проверок и выходит с другого конца, на проде, уже проверенным. Если на каком-то фильтре не прошел, он застревает там и до прода не доезжает.

Дальше разберем эти фильтры по очереди, ровно в том порядке, в котором их проходит код.

Lint, проверка стиля

Первый и самый быстрый фильтр. Линтер проверяет код, не запуская его: правильно ли он отформатирован, нет ли неиспользуемых переменных, соблюдены ли принятые в команде правила оформления, нет ли подозрительных конструкций.

Стоит он первым по простой причине: проверка мгновенная и дешевая. Нет смысла запускать долгие тесты, если код просто грязно написан. Линтер ругается, пайплайн падает сразу, разработчик правит и пушит заново. Из инструментов это, например, flake8 для Python, eslint для JavaScript, golangci-lint для Go.

Test, прогон тестов

Дальше запускаются автоматические тесты. Юнит-тесты проверяют отдельные кусочки кода, работает ли конкретная функция правильно, а интеграционные смотрят, как эти куски работают вместе.

Это главный предохранитель от багов. Тесты убеждаются, что код реально работает и что новые изменения ничего не сломали в старом. Если тесты не прошли, значит, что-то сломано, и дальше код не едет.

Build, сборка

Здесь код превращается в готовый к запуску артефакт. Чаще всего это сборка Docker-образа: код упаковывается со всем окружением в образ.

Готовый образ отправляется в реестр, хранилище образов, откуда его потом возьмут для деплоя. Образ помечается версией, чтобы можно было понять, какой это билд, и при необходимости откатиться. Смысл этапа в том, чтобы получить единый артефакт, который дальше поедет по всем средам. Один и тот же образ тестируется на staging и потом запускается на проде, без пересборки.

Deploy to Staging, выкатка на тестовый сервер

Собранный образ разворачивается на staging, копии продакшена. Это среда, максимально похожая на боевую: те же настройки, похожие данные, та же конфигурация. Но пользователей там нет, поэтому сломать ничего не страшно.

Иногда баги вылезают только при настоящем развертывании, и на staging их как раз и ловят. Деплой сюда обычно автоматический.

Approve, ручное подтверждение

Здесь человек смотрит на staging, убеждается, что все в порядке, и нажимает кнопку «выкатывать в прод». Шаг не автоматический, это осознанное решение.

Он работает предохранителем перед самым ответственным действием. Даже если все проверки прошли, перед выкаткой к живым пользователям кто-то должен сказать: «да, я проверил, поехали». На критичных проектах вроде банков и платежей этот шаг обязателен. На некритичных его иногда убирают и деплоят в прод автоматически.

Deploy to Prod, выкатка в продакшен

Финал. Образ разворачивается на боевых серверах, туда, где живые пользователи. Обычно это происходит через rolling update, постепенную замену старой версии на новую, чтобы не было ни секунды простоя.

Если выключить все разом и поставить новое, будет простой. Если же менять по одной копии, пользователи ничего не заметят. И если новая версия окажется битой, можно остановиться и откатиться, не положив весь сервис.

Зачем вся эта цепочка

Смысл в том, чтобы отсеивать плохой код как можно раньше, там, где это дешево. Чем дальше по конвейеру, тем дороже обходится найденная проблема.

Каждый этап работает фильтром. Грязный код застрянет на линте, сломанный, на тестах, а до прода доезжает только то, что прошло все проверки. Это превращает выкатку из страшного ручного ритуала в рутину, которая случается по многу раз в день.

Как это выглядит в реальности

Пайплайн описывается в файле, который лежит прямо в репозитории рядом с кодом. В GitLab это `.gitlab-ci.yml`, в GitHub, файлы в `.github/workflows`. Все этапы описываются один раз, и дальше при каждом пуше пайплайн запускается сам.

Упрощенно это выглядит так:

stages:
  - lint
  - test
  - build
  - deploy
 
lint:
  script: flake8 .
 
test:
  script: pytest
 
build:
  script: docker build -t myapp .
 
deploy:
  script: ./deploy.sh

Это псевдокод для понимания идеи, здесь перечисляются этапы и что на каждом запускать. Реальные файлы сложнее, но логика та же.

Этапов может быть больше или меньше. Где-то добавляют проверку безопасности, сканирование на уязвимости, где-то заводят несколько окружений (dev, staging, preprod, prod), а где-то все проще. Построение и поддержка таких пайплайнов и есть одна из главных задач DevOps.

Основные инструменты CI CD Как делать пайплайн