MSA управление качеством через CI-ворота

MSA управление качеством через CI-ворота

1. Причины, по которым система управления качеством была размещена в CI

В настоящее время я участвую в консалтинге MSA в роли инженера DevOps. Когда речь заходит о консалтинге, обычно на ум приходят такие аспекты дизайна, как границы сервиса или разделение доменов, но моя область ответственности не эта, а система управления качеством. Эта статья - запись о моей работе.

Когда говорят о управлении качеством, на ум приходят такие моменты, как стиль кода, статический анализ, покрытие тестами, сканирование уязвимостей изображений. Сам по себе этот список не нов. На самом деле сложность заключается в вопросе: «Где и с какой жесткостью это будет проводиться?» В MSA этот вопрос оказывается гораздо более актуальным.

Если разбивать сервис на более мелкие части, то и единицам развертывания становится существенно больше. То, что до вчерашнего дня было одной сборкой, разбивается на десять, двадцать, и каждый сервис выходит в эксплуатацию с разными циклами. В такой ситуации, если мы оставим качественные параметры на уровне рекомендаций «стараемся соблюдать», темпы роста сервисов обойдут нас. На самом деле, когда я только пришел, ситуация была именно такой.

Поэтому я сначала занялся управлением качеством. В итоге мы пришли к CI.

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

Ниже я расскажу, как мы интегрировали параметры качества в CI с этой точки зрения.

2. Кодирование агента и качество

Прежде чем перейти к проектированию ворот, у нас есть одно предположение. В наши дни сам способ написания кода меняется очень быстро. Значительная часть разработки осуществляется совместно с AI-агентами. Я вижу в этом изменение не угрозу, а возможность для качества.

Тестовый код - это яркий пример. Ранее писать достаточное количество тестов было расходным. Чтобы повысить покрытие до 80%, человек должен был вручную создать пограничные и исключительные случаи, и, поскольку это ограничивало время, приходилось обобщать только основные пути. Тестирование было самой отложенной задачей.

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

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

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

3. CI Качество Ворота

Принципы проектирования сами по себе просты. Ошибки дешевле фиксировать на ранних этапах и становятся дороже, чем дальше мы уходим. Все это заключается в том, чтобы внедрить этот факт в структуру пайплайна.

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

Раннее блокирование → Утверждение базовой линии → Проверка кандидатов на эксплуатацию → Оценка повышения статуса

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

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

В этом проекте на этапе построения (SI) контроль выпуска важен, поэтому мы приняли GitFlow за основу, а на этапе эксплуатации (SM), когда автоматизация достаточно развита, мы также наметили переход к Trunk-based. Таким образом, ворота определены как общая модель, а точки выполнения просто сопоставлены с соответствующей стратегией ветвления. Даже если стратегию изменяют, содержание проверки остается прежним.

Лично я считаю, что в среде, где MSA работает в монорепозитории, в конечном итоге Trunk-based будет более подходящим. В монорепозитории несколько сервисов разделяют один репозиторий, и если ветви release·develop·feature разветвляются, как в GitFlow, количество точек интеграции увеличивается по количеству сервисов. Также увеличиваются конфликты и затраты на слияние. Trunk-based позволяет работать с короткими задачами и часто объединять с trunk, так что само наличие ситуаций, когда изменения, находящиеся далеко друг от друга, сталкиваются одновременно, уменьшается.

Если сюда добавить то, о чем позже будет сказано, selective CI, это довольно хорошо сочетается. Даже если в репозитории много сервисов, проверяется только влияние изменений, поэтому CI-стоимость остается на приемлемом уровне, даже когда проводятся частые интеграции. Риски, возникающие от короткого цикла интеграции, поддерживаются качественными воротами. Поэтому в этом проекте мы начали с GitFlow на этапе SI, но натолкнувшись на стабильность эксплуатации, рекомендовали переход к Trunk-based.

[ Рисунок 1. Этапи проверки качества, функционирующие независимо от стратегии ветвления ]

image1.png

Разделив этапы и ответственности, стало ясно, «что это изменение должно пройти на каком этапе?» Ранее один рецензент брал на себя все в голове, теперь структура переходит к тому, что pipeline выполняет функции первой линии защиты.

При установке ворот возникла еще одна проблема. В в монорепозитории с множеством сервисов, если мы проверяем все за каждый отдельный шаг изменения, время CI быстро выходит из-под контроля. Исправив одну строку коммита и ждав 20 минут, никто не будет рад воротам.

Поэтому мы применили selective CI. Мы собираем только измененный модуль и тестируем только те сервисы, которые подверглись влиянию, следуя графу зависимостей. Но мы не сокращали бездумно. Когда меняются общие библиотеки, соглашения о событиях, инфраструктура или когда создается кандидат на релиз, мы указали на исключения и запускали всё без сокращений. Самое страшное — потерять возврат ради скорости, поэтому четко установлена линия: «сокращать, но потенциально опасные изменения обязательно расширять».

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

Поэтому мы сделали пункты проверки качества ворот опциональными, чтобы их можно было включать и отключать.

Тяжелые проверки, такие как статический анализ, интеграционное тестирование, тестирование покрытия, сканирование уязвимостей, были вынесены в параметры pipeline, чтобы в зависимости от ситуации можно было пропустить некоторые из них. Когда ворота полностью задерживают, люди в конечном итоге ищут более рискованные пути обхода CI. Лучше, чем сидеть и ждать, открыто обсудить, «что мы отключим на этот раз» внутри pipeline.

Конечно, за отключение есть цена. Мы оставили в журнале сборки, какие проверки были пропущены, а основные блокировки, такие как сбой сборки или сбой юнит-тестов, изначально исключены из списка опций, чтобы их нельзя было отключить ни при каких обстоятельствах. Цель опциона заключается не в том, чтобы ослабить контроль, а в том, чтобы привлечь даже «ситуацию, требующую быстрого выхода» в дизайн ворот и сделать её видимой.

4. Интенсивность блокировки (Hard Block / Soft Block / Warning)

Самое долгое раздумье при создании шлюза заключалось не в "что заблокировать", а в "что не блокировать".

Если блокировать всё сильно, то пайплайн быстро начнет вызывать ненависть. Если распределение заблокировано из-за одной ошибки в форматировании, все начинают искать способы обойти шлюз. Поэтому я разделил интенсивность блокировки на три уровня.

Уровень

Значение

Представляющий элемент

Hard Block

Без одобрения исключений слияние и повышение не допускаются

Ошибка сборки, ошибка юнит-тестов, критическая/высокая уязвимость, отсутствие обязательного одобрения

Soft Block

Условное одобрение при запись плана дополнения

Недостаточное покрытие, регресс производительности, средняя уязвимость, нестабильные интеграционные тесты

Warning

Не блокирует, а просто управляет тенденциями

Ошибки форматирования, низкая серьезность, недостающие документы, проблемы со стилем

Таким образом, вместо того чтобы быть "абсолютно закрывающей стеной", ворота стали "системой, реагирующей в зависимости от степени риска". Мелкие проблемы игнорируются, а опасные твердо фиксируются.

Поскольку интенсивность уменьшена, появляются возможности для обхода. Поэтому мы добавили вспомогательные правила. Если один и тот же пункт дважды подряд терпит неудачу, мы рассматриваем возможность повышения с Soft до Hard, а ошибки, связанные с основными бизнес-процессами, такими как платежи или расчеты, обрабатываются как Hard независимо от тестового уровня. Даже если ошибка одинакова, вес зависит от того, где она произошла.

В конце концов, определение того, что "не будет заблокировано", и решение о "пути, по которому нельзя уступать", — это одно и то же. Разделение интенсивности на три уровня также было механизмом для поддержания баланса между этими двумя аспектами.

5. Статический анализ и тестирование

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

Поскольку кодовая база написана на Kotlin, мы намеренно разделили инструменты на два. Анализ, специфичный для языка Kotlin, ведется с помощью detekt, форматирование и линтинг — с помощью ktlint, а измерение покрытия выделено отдельно с использованием Kover.

Увеличение количества инструментов не было целью. Detekt хорошо ловит характерные конструкции и смысловые дефекты Kotlin, а ktlint механически и последовательно заставляет придерживаться стиля. Их сильные стороны различаются. Если они отделены, то становится ясно, "в каком инструменте была выявлена эта ошибка", что значительно упрощает установление критериев для ворот.

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

Тестовый уровень

Момент выполнения

Цель

Unit Test

Определение PR / базовой линии

Точность логики внутри модуля

Integration (Testcontainers)

PR центр

Ранняя детекция регрессии в сервисных границах·интеграции

E2E smoke

Определение базовой линии

Поверхностная проверка регрессии перед развертыванием

E2E расширение

после стадии

Проверка основных потоков по стандартам версии

Покрытие не рассматривалось как абсолютный балл. Мы рассматривали его скорее как индикатор, показывающий риск, к которому тесты не дотянулись. Базовая линия установлена на уровне 70% для всего, 80% для ключевых доменов и нового кода, но мы считаем, что состояние, в котором ключевой путь не проверен, представляет собой более высокий риск, чем сами цифры. Привязываться только к цифрам всегда приводит к увеличению пустых тестов, которые проходят только ради прохождения.

Таким образом, мы установили одно операционное правило. Мы не принимаем число покрытия как абсолютный порог. Покрытие нового кода рассматривается как предшествующий индикатор, который предотвращает регрессии, поэтому мы смотрим на него относительно строгим образом, тогда как низкие значения существующего кода мы поднимаем постепенно, начиная с ключевых путей, а не блокируем их сразу. Как уже было сказано, используя агента, мы полагаем, что можно установить эту целевую линию на более высоком уровне в реальности.

6. неизменный артефакт

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

Поэтому мы закрепили принцип, что повышение в эксплуатацию не должно включать повторную сборку. Мы переносим именно тот артефакт, который прошёл проверку на стадии, с тем же digest, просто заново помечая его как операционный. Если SHA коммита, который мы проверили, отличается от SHA объекта повышения, само повышение блокируется. Такие вещи, как "пересобрать для эксплуатации", вообще исключены из процесса.

Хотя это может показаться несущественным, в MSA вес этого принципа совсем другой. Чем чаще независимые развертывания для каждой службы, тем больше значение гарантии о том, что «данные байты были действительно перенесены в эксплуатацию». Эта гарантия влияет на доверие к анализу сбоев и откатам. Если этой гарантии нет, при возникновении сбоя нужно сначала сомневаться в том, «совпадают ли проверенный и эксплуатационный варианты».

[ Рисунок 2. Процесс повышения с одинаковым digest без пересборки ]

image2.png

Управление уязвимостями тоже было включено в этот раздел. Я запустил сканирование изображений Trivy в CI-раннере, и уязвимости критического и высокого уровня были исключены из списка повышения, даже если изображение уже было загружено в реестр. Результаты были собраны в SARIF для централизованного просмотра трендов.

7. AI кодовая проверка

Когда я упорядочивал контроль качества, я узнал, что AI может помогать с кодовой проверкой на этапе CI, и решил попробовать его прикрепить.

Когда PR загружается, AI просматривает изменения и в предварительном порядке отмечает потенциальные дефекты, риски регрессии, пропуски тестов и возможность нарушения правил. Я связал это с пайплайном, и, честно говоря, работа превзошла мои ожидания.

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

  • Анализ изменений— Сначала резюмирует, какой файл и по какой причине был изменен одной строкой.

  • Потенциальные ошибки и улучшения— Указывает на неясные моменты в документации и зависимости от настроек, которые могут привести к ошибкам при развертывании.

  • Проблемы с безопасностью— Проверяет, нет ли возможности смешивания чувствительной информации, такой как API-ключи или учетные данные.

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

  • Дублирование и оптимизация— Помечает повторяющуюся логику или неиспользуемый код как кандидатов на рефакторинг.

  • Тестовое предложение— задаваем вопрос, достаточно ли покрытия по сравнению с воздействием изменений, и рекомендуется добавить необходимые случаи

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

Тем не менее, эту функцию в итоге не внедрили в реальную эксплуатацию.

Причиной этого была безопасность. Поскольку данные были высокочувствительными, было неизбежно быть осторожными с путями, по которым информация и код могли выходить наружу, а отправка изменений в внешние ИИ-сервисы была сложной. Отдельные вопросы – работает ли функция и разрешено ли её использовать в этой среде – были разными.

Поэтому мы остановились на том, что «проверка была проведена и эффект подтверждён, но сначала необходимо удовлетворить стандарты безопасности, прежде чем размещать её в эксплуатации». Если бы мы внедряли, то это должен был быть частный LLM, который не передаёт данные наружу, и в любом случае принцип оставался таковым: ИИ был бы лишь вспомогательным, а окончательное одобрение выдаёт человек-рецензент.

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

8. Заключение

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

Если обобщить то, что я особенно ощутил во время работы, то это выглядит так.

  • Большинство элементов качества естественнее и последовательнее всего обрабатываются на CI, где код поступает.

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

  • Ворота следует проектировать не только с точки зрения того, что блокировать, но и того, что не блокировать, чтобы сохранить свои позиции.

  • Если отпадёт принцип неизменяемости, то все предыдущие проверки станут бесполезными в один момент.

  • Технически возможное и то, что может быть внедрено в этой среде, – это разные вещи. Примером служит случай, когда эффект был достигнут, но внедрение было отложено из-за безопасности.

В MSA Consulting сначала привлекают внимание заметные результаты, такие как проектирование услуг или системы развертывания. Но тем, что поддерживает эти результаты на постоянном уровне качества с течением времени, я считаю тихо работающий CI Quality Gate, который работает каждый день.

Тим

Site footer