Что такое CI/CD, если не пересказывать буквы
Непрерывная интеграция и непрерывная доставка — не инструменты, а практики. Первая означает, что каждый участник команды вливает свою работу в общую ветку не реже раза в день, и каждое вливание проверяется автоматически. Вторая — что любое прошедшее проверки изменение находится в выкатываемом виде. Автоматизация есть в обеих, различает их не она: первая говорит о частоте вливаний, вторая — о состоянии уже проверенного изменения. Кто и когда принимает решение о выкатке — вопрос уже внутри второй. Человек там стоит на одном шаге: выбирает момент выкатки, а не подтверждает качество — качество подтверждают проверки конвейера. Поставленный на этот шаг как последняя проверка, он превращает его в очередь на подпись.
Вторая буква читается двояко: за ней стоит и доставка, и развёртывание. Непрерывное развёртывание — то же состояние, что и доставка, только решение о выкатке принято заранее и один раз, поэтому отдельного ручного шага не остаётся; это не соседний вариант на той же полке, а частный случай. Проверочная формула: вы занимаетесь непрерывной доставкой, если могли бы выкатывать автоматически, но решили этого не делать.
Как устроен конвейер: граф, а не цепочка
Конвейер — направленный граф задач с явными условиями запуска. Задачи одного этапа выполняются параллельно, а задача с объявленной зависимостью от конкретной другой стартует, не дожидаясь всего этапа. Условие запуска задаётся явно: по умолчанию зависимая задача не выполняется, если та, от которой она зависит, упала, но задачу можно объявить незначащей — и её падение не роняет конвейер. Уборку и оповещение объявляют выполняемыми независимо от исхода, иначе о падении никто не узнает и мусор останется лежать. Поэтому «зелёный конвейер» означает «прошли все блокирующие задачи», а не «прошли все задачи».
Артефакт здесь — то, что получилось на выходе и поедет дальше: собранный файл, архив или образ; упаковка — шаг, на котором результат складывают в такой артефакт. Шаги точнее описываются ролями, чем фиксированным списком:
- получить код,
- получить зависимости,
- получить артефакт,
- проверить артефакт,
- доставить артефакт.
Граница между сборкой и упаковкой зависит от языка: для компилируемого это разные шаги, для интерпретируемого сборки в привычном смысле может не быть вовсе, и упаковка — вся сборка. Главное правило: проверять надо то, что поедет. Проверки, стоящие после упаковки, запускаются на самом артефакте, а не на исходниках рядом с ним. Если тесты гоняются на исходниках в окружении шага, проверено одно, а выложено другое.
Что запускает конвейер и кому что позволено
Конвейер запускается не только коммитом. Событий несколько:
- коммит в ветку,
- открытие и обновление запроса на слияние,
- расписание,
- ручной запуск,
- запуск из другого конвейера.
У разных событий разный набор шагов и разные права. Поэтому число проверок на ветке и после слияния не совпадает: это разные события, а не сбой. Граница права на выкатку задаётся не тем, куда положили значение секрета, а тем, кому и на какой срок его выдали. Чем именно задача получает доступ — долгоживущим секретом или коротким токеном под конкретный репозиторий и ветку — разобрано в материале Секреты в CI: пять типовых утечек. Здесь остаётся одно: круг задач, которым система сборки вообще выдаёт доступ к выкатке, задаётся защищённой веткой и защищённым окружением.
Зелёная галочка и красное слияние
Зелёная галочка у коммита — агрегат нескольких независимых проверок, а не один конвейер. Она бывает зелёной, пока часть проверок помечена необязательной, ещё не завершилась или вовсе не запускалась на этом событии. Что именно блокирует слияние, задаётся правилами защиты ветки, то есть уровнем выше конвейера.
Ветка бывает зелёной, а слияние красным по двум причинам. Первая — текстовый конфликт: слияние вообще не вычисляется, проверять нечего. Вторая — смысловая поломка: два изменения по отдельности исправны, вместе ломаются, потому что каждое проверялось против более раннего состояния общей ветки. Системы ведут себя по-разному: в одних событие запроса на слияние выкладывает временный коммит слияния и проверяет его, в других проверка идёт на исходной ветке и целевую не учитывает. Прогон на результате слияния уменьшает класс «ветка зелёная, общая красная», но не закрывает его: результат слияния вычисляется под событие, а движение целевой ветки нового события не создаёт — проверка сама не перезапускается и остаётся зелёной на устаревшем основании, даже там, где сам результат слияния система уже пересчитала. Отсюда требование подтянуть ветку перед слиянием и очередь слияний, где каждое изменение перепроверяется уже на актуальной вершине. Как само изменение продвигается по веткам и что делать с переключателями функций — в материале Когда dev, stage и master разошлись: как выкатывать фичи.
Почему на сервере падает то, что локально проходило
Конвейер пишется так, будто окружение чистое, но выполняться на чистом окружении он не обязан. Свежая машина на каждую задачу — поведение агентов, которые даёт сама платформа, а собственные агенты по умолчанию постоянные. Расхождение между «написано как для чистого» и «выполняется на обжитом» — источник отказов: конвейер работает только на том агенте, где однажды что-то доустановили руками.
Шаг получения кода может брать укороченную историю вместо всей: в одних системах это поведение по умолчанию, в других глубина задаётся настройкой шага. Поэтому всё, что смотрит в историю, падает только в конвейере: вычисление версии по меткам, сравнение с предыдущим состоянием, отбор проверок по изменившимся файлам. Укорочена ли копия у вас — отвечает git rev-parse --is-shallow-repository.
Один и тот же коммит обязан давать один и тот же результат. Жалоба «код не менялся, вчера собиралось, сегодня нет» — это не про конвейер, а про плавающие входы: нет файла блокировок зависимостей, базовый образ взят по подвижной метке, инструмент ставится последней доступной версией. Нестабильная проверка — та же потеря сигнала с другой стороны: зелёный после третьего перезапуска не утверждает ничего.
Что едет наружу: артефакт, кеш и откат
Правило «собрать один раз» состоит из двух половин: артефакт собирается однажды и переносится по средам неизменным, а отличия сред подаются снаружи в момент запуска. Если адреса, имена баз и ключи попали внутрь при сборке, артефакт собран под одну среду, и «тот же самый» в следующей среде означает «другой».
«Тот же артефакт поехал дальше» — проверяемое утверждение только при неизменяемом обозначении: слепок содержимого артефакта либо привязка к идентификатору коммита, из которого он собран. Подвижную метку можно переписать, и тогда тождество ничем не подтверждается.
Кеш и артефакт решают разные задачи: артефакт — результат, который передаётся дальше по конвейеру или наружу, кеш — только ускорение. Конвейер, который перестаёт проходить при пустом кеше, сломан, и обнаруживается это в худший момент. Что ускорять раньше остального и сколько это даёт, когда конвейер идёт дольше, чем человек готов ждать, — в материале Сборка идёт долго: что ускорять и в каком порядке.
Откат — часть конвейера, а не отдельное геройство. Автоматическая выкатка держится не на том, что не сломается, а на том, что быстро вернётся: конвейер умеет выкатить предыдущий неизменный артефакт тем же путём, каким выкатил новый, — отдельной задачей с ручным запуском. Без отработанного возврата непрерывное развёртывание опаснее ручной выкатки, а не безопаснее.
Изменение схемы данных возвратом не покрывается: артефакт обратим, схема нет — отсюда требование совместимости в обе стороны и отдельного шага раньше выкатки кода. Как это выглядит на живой выкатке — в материале Деплой без кластера: что осталось живым. Конвейер отдаёт команду среде и на этом заканчивается: как именно старая версия заменяется новой — отдельный механизм.
Тупик: перенос локальных команд в конвейер
Первым делом в конвейер переносят те же команды, которые работают на своей машине, и считают настройку законченной. Не помогает, потому что на своей машине годами накапливалось подразумеваемое: глобально поставленные инструменты, заданные переменные, скачанные зависимости, вчерашний результат сборки и полная история репозитория. В конвейере всё это нужно объявить явно. Падение оказывается не в коде, а в условиях запуска, и искать его идут в код.
Позиция редакции
Ценность даёт частота интеграции, а не длина конвейера. Команда, которая вливает работу в общую ветку не реже раза в день на человека и проверяет каждое вливание, получает больше, чем команда с длинным списком проверок и ветками, живущими неделями. Сервер сборки без частой интеграции остаётся сервером сборки.
Частая интеграция работает, только пока сигнал честный. Красная общая ветка чинится раньше новой работы, а нестабильная проверка чинится или выводится из блокирующих. Позиция неверна, если выкатка физически редка и обставлена внешними условиями: сертифицируемое или встроенное ПО, поставка на площадку заказчика. Там конвейер ценен воспроизводимостью артефакта и доказуемостью того, что выложено именно проверенное, а частота слияний не решает ничего.