awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

can-i-deploy и версионирование контрактов: гейт безопасности деплоя

Суть Проверенный pact доказывает согласие двух версий, а не безопасность консьюмера против прода. Версионируй pact-ы по git SHA, записывай, что где задеплоено, и гейти деплои на can-i-deploy: он читает matrix твоей версии против провайдера, реально стоящего в целевом env.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 17 min

checkout-web Прии добавляет фичу, которой нужно новое поле discount_cents. Она обновляет тест консьюмера, pact перегенерируется, и CI публикует его в брокер. Провайдер pricing Сэма ещё не выкатил discount_cents — задача верификации против latest-сборки pricing зелёная для старых взаимодействий, а новое всё ещё помечено pending, поэтому ни у кого сборка не красная. Пайплайн Прии весь зелёный, так что он деплоит checkout-web в прод. Через сорок секунд частота ошибок ползёт вверх: инстанс pricing, реально работающий в проде, на три версии позади latest у pricing, и он не возвращает discount_cents. Pact был проверен — просто не против той версии, что стоит там. Контрактное тестирование сделало своё дело, а деплой всё равно сломал прод, потому что гейт задавал не тот вопрос.

Зелёный pact — это попарный факт, а не факт безопасности деплоя

Урок 03 оставил тебя с брокером, полным результатов верификации. Каждый результат — точное, узкое утверждение: pact версии X консьюмера был проверен против версии Y провайдера, и он прошёл. Это действительно полезно, но отвечает на вопрос, который на момент деплоя никто на самом деле не задаёт. Вопрос на момент деплоя такой: если я выкачу версию X консьюмера в продакшен прямо сейчас, сработает ли она против версии провайдера, которая физически работает в продакшене? Верификация, что у тебя есть, — для некой версии Y, обычно latest CI-сборки провайдера, которая может быть на несколько деплоев впереди того, что стоит в проде.

Это и есть зазор, в который проваливается Hook. Pact был проверен против новейшей сборки pricing, в которой уже было discount_cents. Но в проде работал более старый pricing, а отдельные результаты верификации брокера ни словом не говорили о том, какая версия провайдера была где задеплоена. Куча зелёных галочек говорит тебе, какие пары версий тестировались вместе; она не говорит, является ли конкретная пара, которую ты вот-вот создашь в продакшене, — новый консьюмер, старый провайдер — одной из зелёных. Тебе нужен инструмент, который скрещивает твою версию с реальными обитателями целевого окружения, и этот инструмент — can-i-deploy.

Matrix: каждая протестированная пара и что где задеплоено

Брокер собирает всё это в matrix — таблицу каждой версии консьюмера × версии провайдера, которые когда-либо тестировались друг против друга, с результатом pass/fail каждого спаривания. В неё поступают две вещи. Во-первых, результаты верификации: когда pact версии X консьюмера проверяется версией Y провайдера, брокер записывает ячейку (X, Y) → pass/fail. Во-вторых, записи о деплоях: когда ты реально деплоишь версию в окружение, ты сообщаешь брокеру, так что он знает, что в проде сейчас стоит, скажем, pricing v56. Затем can-i-deploy делает один запрос: взять версию, которую я хочу задеплоить, найти версии каждого интегрированного приложения, что сейчас стоят в целевом окружении, и проверить, что каждая требуемая ячейка между ними зелёная.

Matrix для консьюмера checkout-web × провайдера pricing. В проде сейчас работает pricing v56.
версия checkout-webверсия pricingПроверено?can-i-deploy в прод (pricing=v56)?
v22 (a1b2c3d)v56 (задеплоено в проде)passДа — пара зелёная, exit 0
v23 (e4f5a6b)v56 (задеплоено в проде)passДа — пара зелёная, exit 0
v24 (7c8d9e0) — добавляет discount_centsv56 (задеплоено в проде)нет результатаНет — пара не проверена, exit 1, деплой заблокирован
v24 (7c8d9e0) — добавляет discount_centsv59 (latest CI, не задеплоено)passНеважно — v59 не в проде

Последние две строки — это весь урок. checkout-web v24 проверена — против pricing v59, сборки, в которой уже есть discount_cents. Но v59 не задеплоена; в проде по-прежнему работает v56. can-i-deploy --pacticipant checkout-web --version 7c8d9e0 --to-environment production полностью игнорирует результат для v59, не находит зелёной ячейки для (v24, v56), завершается с ненулевым кодом и останавливает пайплайн. Тот самый деплой, что вызвал пейджер Прии в Hook, — это деплой, в котором этот гейт отказывает.

Версии должны быть git SHA, а деплои должны записываться

Matrix работает, только если правильны две вещи учёта. Во-первых, номер версии. Сильная рекомендация Pact: версия pacticipant есть git commit SHA или содержит его (напр. 0.0.10+76a39e5). Причина механическая: версия должна меняться ровно тогда, когда мог измениться pact. Если ты версионируешь по semver из package.json, две разные сборки с разными контрактами могут делить версию 2.3.0, и брокер с радостью переиспользует устаревший результат верификации для не того кода. Git SHA меняется на каждом коммите, известен на момент деплоя и позволяет постить статус верификации обратно как статус коммита. Фичеветки автоматически получают отдельные версии от main. SHA — единственный идентификатор, гарантированно отслеживающий контракт.

Во-вторых, отслеживание деплоев. Брокер не может знать, что стоит в проде, если ты не скажешь ему в конце каждого деплоя через pact-broker record-deployment --pacticipant checkout-web --version 7c8d9e0 --environment production. Есть две разновидности. record-deployment — для того, что деплоится в известный инстанс: сервисы, консьюмеры, API; он автоматически помечает предыдущую версию в этом окружении как более не задеплоенную, потому что одновременно работает лишь одна версия. record-release — для артефактов, которые ты публикуешь, но не заменяешь: мобильные приложения в сторе, библиотеки в реестре, где много выпущенных версий сосуществуют, так что он не снимает с деплоя предыдущую. (Старые конфигурации использовали для той же цели environment-теги вроде prod; теги всё ещё работают, а брокеры начиная с 2.81.0 автоконвертируют тег с именем окружения в деплой, но deployed/released-версии — это модель на будущее.)

Почему это работает

Почему вызывать record-deployment после успешного деплоя, а не до? Потому что запись брокера призвана отражать физическую реальность — что реально обслуживает трафик, — чтобы can-i-deploy следующей команды получил правдивый ответ. Если ты запишешь деплой до того, как раскатка завершится, а раскатка затем провалится, брокер теперь считает, что в проде работает версия, которой там нет, и чей-то гейт даст зелёный свет деплою против провайдера, которого ещё не существует. Записывай это в самом конце, когда нет шанса на провал и старая версия полностью слита. Matrix честна ровно настолько, насколько честны записи о деплоях, которыми ты её питаешь; ложь о том, что задеплоено, побеждает гейт так же верно, как и его пропуск.

Независимая деплоябельность — весь выигрыш

Отступи на шаг и заметь, что это даёт. Без гейта на «безопасно ли деплоить?» можно ответить, лишь деплоя всё вместе синхронно или поднимая весь мир в общем окружении — ровно та проблема N², что убил первый урок. С гейтом каждый сервис отвечает на вопрос за себя, локально, против записанной правды каждого окружения: я могу задеплоить свою новую версию тогда и только тогда, когда у каждого приложения, уже стоящего в целевом env, есть со мной зелёный pact. Это и есть независимая деплоябельность — способность выкатывать один сервис по своему графику без координации связки релизов, — и это вся причина, ради которой существует юнит. Контрактные тесты ловят поломку; can-i-deploy — то, что позволяет тебе безопасно действовать по этому знанию, по одному сервису за раз.

Режим отказа, когда ты это пропускаешь, — ровно Hook: консьюмер, чьи новые ожидания прод-провайдер никогда не удовлетворял, проходит сквозь CI (его pact проверен против latest-версии провайдера, а не задеплоенной) и ломается в рантайме — заново внося ровно тот продакшен-инцидент, который контрактное тестирование должно было предотвратить. Верификация была реальной; решение о деплое её проигнорировало. Задача гейта — заставить решение о деплое использовать matrix вместо «на ощупь».

Расставь шаги по порядку

Упорядочьте шаги, которые делают can-i-deploy надёжным гейтом деплоя:

  1. 1 CI консьюмера версионирует pact по git SHA и публикует его в брокер
  2. 2 CI провайдера верифицирует pact, и брокер записывает ячейку (консьюмер, провайдер) как pass/fail
  3. 3 Перед деплоем пайплайн запускает can-i-deploy --version SHA --to-environment production
  4. 4 Брокер проверяет matrix для этой версии консьюмера против версии провайдера, сейчас стоящей в проде
  5. 5 При exit 0 деплой продолжается; затем пайплайн вызывает record-deployment, чтобы гейт следующей команды видел правду
Викторина

Pact консьюмера проверен зелёным против latest CI-сборки провайдера. Почему этого недостаточно, чтобы безопасно задеплоить консьюмер?

Викторина

Почему Pact рекомендует, чтобы версия pacticipant была (или содержала) git commit SHA, а не semver вроде 2.3.0?

Выбери лучший вариант

Ваш провайдер верифицирует pact-ы в CI, но никто не запускает can-i-deploy и никто не записывает деплои. Консьюмер добавляет поле, которое прод-провайдер ещё не возвращает. Как закрыть зазор?

Вспомните перед уходом
  1. 01
    Пройдитесь точно по тому, что проверяет can-i-deploy, и почему зелёная верификация против latest-сборки провайдера недостаточна, чтобы задеплоить консьюмер.
  2. 02
    Почему версионировать pact-ы по git SHA, и в чём разница между record-deployment и record-release? Почему вызывать их в конце раскатки?
Итог

После верификации провайдером у тебя есть брокер, полный зелёных результатов, но каждый из них — попарный факт: версия X консьюмера согласуется с версией Y провайдера — и это не вопрос, который задаёт момент деплоя. Момент деплоя спрашивает, безопасна ли твоя версия против версии провайдера, физически работающей в целевом окружении, которая может быть на несколько деплоев позади latest CI-сборки, против которой был проверен pact. can-i-deploy закрывает этот зазор: он читает matrix брокера (каждую протестированную пару консьюмер×провайдер плюс что где задеплоено), скрещивает версию, которую ты выкатываешь, с версиями, сейчас стоящими в целевом env, и завершается с ненулевым кодом, если какая-либо требуемая пара не проверена, блокируя деплой. Чтобы гейт был надёжным, должны быть правильны две записи: версионируй pact-ы по git SHA, чтобы версия менялась всякий раз, когда мог измениться контракт, и устаревший результат никогда не переиспользовался, и записывай, что задеплоено — record-deployment для сервисов (который снимает с деплоя предыдущую версию) и record-release для стор-артефактов (который нет) — вызываемые в конце каждой раскатки, чтобы брокер отражал реальность. Выигрыш — независимая деплоябельность: каждый сервис отвечает на свой вопрос безопасности деплоя за секунды из записанных фактов, без общего окружения и без синхронного релиза. Пропусти гейт — и ты выкатишь консьюмер, чьи новые ожидания задеплоенный провайдер никогда не верифицировал — зелёный пайплайн, сломанный прод — воссоздавая ровно тот инцидент, который контрактное тестирование было построено предотвратить.

Связанные уроки
Продолжить восхождение ↑Безопасная эволюция контрактов и пределы контрактного тестирования
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.