Алексей Меленчук
вся записка
дата
объём 5 мин чтения

Зачем нужен CI/CD, если выкатывать можно руками

Что означают буквы CI и CD на самом деле, чем доставка отличается от развёртывания и почему автоматический выкат — дешёвая половина, которую внедряют вместо дорогой.

Коротко. Ценность непрерывной интеграции не в том, что сборку запускает машина, а в том, что ошибка находится через десять минут после её появления, а не через три недели. Кнопка выката этого не даёт.

Команда говорит: у нас настроен CI/CD. Показывает конвейер — сборка образа, прогон, выкат по слиянию. Спрашиваешь: сколько времени основная ветка обычно стоит сломанной? Пауза. Потом: «ну, бывает, пару дней».

Это значит, что автоматизирован выкат, а непрерывной интеграции нет. Подменяют регулярно, и подменяют дешёвую половину на дорогую.

Что означают буквы

CI — непрерывная интеграция. Свойство не сервера, а того, как работает команда: изменения попадают в общую ветку часто, в идеале несколько раз в день, и каждое слияние проверяется. Сервер сборки — следствие, а не суть. Можно поставить самый навороченный конвейер и не иметь CI, если ветки живут по три недели.

CD — две разные вещи под одной аббревиатурой. Непрерывная доставка означает, что любой коммит из основной ветки готов уехать в бой, а решение об этом принимает человек. Непрерывное развёртывание — то же самое, но решение принимает конвейер. Разница не в технике, а в том, кто отвечает за момент. Разбор этого различия — суть книги Хамбла и Фарли «Continuous Delivery» (2010), и за пятнадцать лет она не устарела.

Почему автоматический выкат — дешёвая половина

Заменить пять команд в терминале на одну кнопку приятно, но выигрыш небольшой: выкат руками занимает минуты и делается редко.

Дорогая половина — обратная связь. Изменение сломало то, чего автор не трогал. Вопрос один: сколько прошло между «сломал» и «узнал».

Десять минут — автор ещё помнит, что делал, и правит за минуту. Три недели — правит другой человек, по чужому коду, среди пяти чужих изменений, попутно выясняя, какое из них виновато. Стоимость починки растёт с задержкой нелинейно, и растёт она в человеко-часах, а не в минутах простоя конвейера.

Вот это и покупается непрерывной интеграцией. Кнопка выката тут ни при чём.

Признак, что конвейер ничего не значит

В нём нет проверки, которая может упасть. Сборка образа падает только на синтаксической ошибке. Если дальше не прогоняется ничего, конвейер сообщает ровно один факт: код компилируется. Это выкат по кнопке, названный красивым словом.

Прогон идёт сорок минут. Тогда его перестают ждать. Сливают, уходят к следующей задаче, возвращаются на красное через час. Время прогона — несущий параметр, а не мелочь эксплуатации: за порогом терпения конвейер превращается в отчёт о прошлом.

Красная ветка — норма. Если сломанная основная ветка никого не останавливает, значит, её состояние ни на что не влияет, и проверка декоративна.

Сборка невоспроизводима. Собранное дважды из одного коммита даёт разные образы, и «работает у меня» остаётся живым аргументом. Про то, почему исходный код не доказывает свойств бинарника, есть отдельный разбор.

Что стоит завести раньше конвейера

Способ откатиться. Он важнее способа выкатить. Авария лечится возвратом на предыдущую версию за минуту, а не починкой на горячую; разбор причин идёт потом и спокойно. Если откат — это «пересобрать из старого коммита и надеяться», выкат автоматизировать рано.

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

Миграции, которые едут отдельно от кода. Схема и код меняются в разное время, и выкат по кнопке без расширить — переехать — сузить превращает любое изменение таблицы в простой.

Ветки — это и есть настройка CI

Спор про модель ветвления выглядит вкусовщиной, пока не поставить его рядом с определением: непрерывная интеграция — это частое попадание изменений в общую ветку. Модель, где ветка живёт неделями, буквально исключает CI, каким бы ни был конвейер.

Механика простая. Ветка на три дня расходится с основной на три дня чужих изменений, и слияние — отдельная работа с непредсказуемым сроком. Ветка на три часа расходится на три часа: конфликтов почти нет, а если есть, они мелкие и понятные автору.

Короткие ветки требуют, чтобы недоделанное умело ехать в бой выключенным — за флагом, за конфигурацией, за неиспользуемой ветвью кода. Это неудобно и это плата. Без неё частая интеграция упирается в «нельзя сливать, пока не доделано», и всё возвращается к трёхнедельным веткам.

Секреты в конвейере

Конвейеру нужны доступы: к реестру образов, к серверу, к базе для миграций. Три вещи, которые стоит проверить до первого выката.

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

Порядок, который я советую

Сначала — тест, который может упасть, и дисциплина не оставлять ветку красной. Потом — автоматический прогон на каждое слияние. Потом — откат одной командой. И только потом автоматический выкат.

Порядок не случайный: каждый следующий шаг опасен без предыдущего. Автоматический выкат при отсутствии проверок просто ускоряет доставку дефектов в бой — раньше между ошибкой и пользователем стоял человек с руками, теперь не стоит никто.

Когда всё это не окупается

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

Просьба одна — не называйте это CI/CD. Название задаёт ожидания: новый человек в команде слышит «у нас настроен CI/CD» и делает вывод, что основная ветка всегда рабочая. Если это не так, он узнает об этом в худший момент.