Несколько лет я был техническим лидером инфраструктуры для интеграционного тестирования на уровне компании в Amazon и в Google. Могу сказать, что подходы этих двух компаний к CI/CD значительно различаются.
В Amazon я проработал 11 лет (с 2009 по 2020 год). Я работал главным инженером в команде Developer Tools, которая управляла всей инфраструктурой и инструментами для CI/CD по всей компании. Мы обслуживали программное обеспечение, которое десятки тысяч сотрудников Amazon каждый день использовали для написания, проверки, сборки, тестирования и развёртывания кода.
В 2020 году я всё-таки решился на большие изменения и присоединился к Google, где провёл 4 года в качестве технического руководителя инфраструктуры для интеграционного тестирования — критически важной части инструментария CI/CD Google. Несмотря на схожесть сферы деятельности, технологические стеки различались кардинально.
Получив опыт работы в Google, другой компании с иным взглядом на процессы, я вернулся в Amazon. Без сомнений, взглянуть на вещи под другим углом было интересно и полезно.
Определимся с терминами
Для начала конкретизируем термины, чтобы мы все говорили об одном и том же:
- “CI/CD” означает непрерывную интеграцию (CI) и непрерывную доставку (CD). В основном это про то, как изменения в коде проходят путь от локальной среды разработчика до деплоя в продакшн.
- “Pre-Submit” (до отправки) относится к опыту разработчика до того, как он отправит код. Меня особенно интересовало, какие виды валидации можно провести с кодом до его отправки. Это может включать тестирование изменений в локальной среде разработчика или как часть процесса ревью кода.
- “Post-Submit” (после отправки) относится к опыту разработчика после того, как он отправил код. Как мы объединяем изменения, как мы деплоим их в тестовые окружения, максимально приближенные к продакшну, и проверяем их там, и как мы сопровождаем эти изменения до деплоя в продакшн.
- «Тестирование» в данном случае относится к интеграционному тестированию или end-to-end тестированию, а не к юнит-тестированию. Юнит-тесты легко запускать где угодно, но интеграционное тестирование требует, чтобы код был задеплоен в тестируемую систему и подключён к зависимостям, что значительно усложняет инфраструктуру.
Я пришёл к выводу, что Google отлично справляется с этапом Pre-Submit, но не так хорошо с Post-Submit, тогда как Amazon наоборот — хорош в Post-Submit, но слабее в Pre-Submit.
Введение в монорепозитории и микрорепозитории
Какое-то время это различие ставило меня в тупик, но теперь, кажется, я понял его основную причину. Google использует монорепозиторий, где более 120 тысяч инженеров работают с единым репозиторием без веток. Amazon использует десятки тысяч микрорепозиториев (мы называем их «наборы версий» — технически это не совсем микрорепозитории, но для целей этого обсуждения можно считать их таковыми). Примерно каждое сервисное приложение имеет свой микрорепозиторий (существует множество исключений, но для простоты мы их опустим).
Мой хороший друг Alex Xu, соучредитель ByteByteGo, написал отличную статью, в которой подробно рассматривается философия монорепозиториев и микрорепозиториев, поэтому я не буду полностью пересказывать её здесь, но освещу эту тему в контексте тестирования до и после отправки изменений.
Примечание: эта статья не предназначена для всестороннего обсуждения на тему «монорепозиторий vs микрорепозиторий». Её цель — сосредоточиться на тестировании до или после отправки изменений в контексте CI/CD, где данный аспект имеет значение.
Google, Pre-Submit
Когда более ста тысяч разработчиков используют один репозиторий без ответвлений, радиус влияния плохого коммита становится огромным. Существует ненулевая вероятность того, что ваш коммит заблокирует тысячи или десятки тысяч других инженеров. Когда я работал в Google, меня иногда блокировал случайный коммит случайного человека в случайной части компании, и я до конца не мог понять, почему. Google вложила значительные средства, чтобы иметь возможность запускать сквозные интеграционные тесты из локальной среды разработки или обзора кода против эфемерных, герметичных тестовых сред.
На самом деле, Google — это Google, и это была не одна инфраструктура, а четыре конкурирующие, дублирующие друг друга инфраструктуры, которые органично и независимо развивались в течение последних двадцати лет. Я внёс существенный вклад в их конвергенцию, которая, я уверен, будет продолжаться и после моей смерти. Это была большая работа, в которую были вовлечены более сотни инженеров. В целом, возможность запускать правильные сквозные интеграционные тесты в pre-submit была просто магической для моего рептильного мозга амазонца.
Google, Post-Submit
Как бы волшебно ни было тестирование до отправки (pre-submit), обратная сторона заключалась в том, что опыт тестирования после отправки (post-submit) оказался на удивление не таким впечатляющим. После своего первого коммита я с воодушевлением спросил у инженера из моей команды, когда я увижу эти изменения на проде. Он спокойно ответил: «В четверг». Я был крайне удивлён. Для моего «амазонского рептильного мозга» это было кощунством. Amazon твёрдо убежден, что каждое изменение кода должно попадать в продакшен в течение нескольких часов. Конечно, это амбициозно, и изменения кода, охватывающие несколько сервисов, действительно сложнее и занимают больше времени, но подавляющее большинство изменений всё же доходит до продакшена за несколько часов. Идея деплоя только в определённые дни и в определённое время казалась мне старомодной. Проблема, с которой сталкивается Google (со своим монорепозиторием), заключается в том, что одно изменение кода может затронуть сотни тысяч развёртываний. Группирование изменений помогает, но тогда один деплой может включать десятки или сотни независимых изменений — а это значит, что если деплой не удался, придётся искать виновника. И снова, Google есть Google — у нас было множество систем для поиска виновных, дублирующих и конкурирующих друг с другом (возможно, вы начинаете видеть закономерность).
Amazon, Pre-Submit
Философия использования микрорепозиториев предоставляет Amazon очень удобный встроенный механизм для уменьшения зоны воздействия при сбоях. Неправильный коммит может сломать только отдельный микрорепозиторий, но тесты после отправки обычно ловят ошибку и блокируют её до того, как она распространится на другие микрорепозитории и сломает их. Ваш неправильный коммит может раздражать членов команды, но он не сможет сразу же сломать работу десятков тысяч других людей (хотя это возможно позже, если тестирование после отправки в вашем микрорепозитории не выявит проблему).
Наличие встроенного механизма уменьшения «зоны воздействия» означало, что для нас не было критически важно инвестировать много ресурсов в инфраструктуру pre-submit тестирования, как это было необходимо Google. Это было приятным бонусом, но не обязательной необходимостью.
Дело не в том, что мы не хотели инвестировать в улучшение инфраструктуры pre-submit тестирования, но с ограниченным числом сотрудников мы получали больше отдачи, убедившись, что наша инфраструктура post-submit тестирования была на высшем уровне (поскольку проблемы неизбежно просочатся в post-submit, независимо от того, насколько хороши pre-submit тесты). Для понимания масштаба: Amazon, придерживаясь принципа экономии, инвестировал примерно десятую часть того, что потратил Google. Так что, несмотря на моё желание, чтобы мы финансировали и создавали временные тестовые среды для pre-submit тестирования, мне пришлось быть безжалостно прагматичным в том, где я рекомендовал моей организации делать кадровые инвестиции.
Конечно, сдвиг тестирования влево — это отличная штука. Выявить проблемы на этапе локальной разработки или, в худшем случае, на этапе код-ревью — гораздо лучше, чем обнаружить их после отправки. Чем позже в жизненном цикле разработки ПО обнаруживается ошибка, тем дороже выходит её исправление. И, оглядываясь назад, если бы я десять лет назад аргументировал необходимость инвестиций во временные, изолированные тестовые среды в Amazon, это более чем окупило бы вложения за счёт сэкономленного труда разработчиков. Но, как говорится, задним умом все крепки.
Amazon, Post-Submit
Что касается опыта с post-submit тестированием, то здесь Amazon превосходит всех. Микрорепозиторий — это гораздо более замкнутая среда, чем монорепозиторий, поэтому мы можем гарантировать, что каждое изменение кода пройдёт надлежащее тестирование и попадёт в продакшен в течение нескольких часов для конкретного сервиса. Всё усложняется, когда изменения затрагивают несколько сервисов и требуют синхронизации нескольких микрорепозиториев (монорепозиторий для этого гораздо удобнее!), но на самом деле подавляющее большинство изменений кода довольно локальны для одного сервиса. Философия Amazon касательно микрорепозиториев работает очень хорошо для независимых микросервисов, и Amazon очень верит в это, начиная с манифеста Безоса об API 2002 года, и построила всю концепцию команд 2-pizza вокруг этого. Когда вы открываете в браузере amazon.com, это не просто монолитное приложение в одном репозитории. Ваше действие приводит к обращению буквально к сотням или тысячам сервисов, которые находятся в стольких же микрорепозиториях, каждый из которых получает независимые фрагменты данных, чтобы вы могли разместить свой заказ на Amazon.
Заключение
Дискуссия о том, что лучше — монорепозиторий или микрорепозитории, примерно так же продуктивна, как споры о том, «какие питомцы лучше: кошки или собаки?» или «можно ли назвать сэндвич бутербродом?».
Я работал в обеих средах. Вот что я заметил: если вы накопили достаточно опыта с микрорепозиториями, вам, скорее всего, не понравятся монорепозитории, и наоборот. Инженеры из Meta или Google, приходя в Amazon, считают микрорепозитории неудобными и хотят убедить нас перейти на монорепо. Я же, в свою очередь, испытал почти физическое отторжение к монорепозиторию, когда работал в Google. Это очень личное предпочтение. Не думаю, что смогу убедительно, с опорой на данные, доказать, что одно из решений объективно лучше другого.
В конечном счёте, сложность в CI/CD для крупных систем всегда будет присутствовать. Google и компании, выбравшие монорепозитории, решили справляться со сложностью в одном месте. Amazon и компании, выбравшие микрорепозитории, предпочли решать совершенно другую сложность в другом месте. В общем, объём сложности примерно одинаков, просто она находится в разных местах.
Для того чтобы монорепозиторий работал, Google пришлось не только инвестировать во временные тестовые среды для pre-submit тестирования, но и в умный выбор тестов и снижение нестабильности тестов. Как определить, какие тесты запускать для конкретного изменения кода? Зависимости сборки моделируются в Blaze (система сборки Google), так что, теоретически, можно просто вычислить сборочный граф и выполнить все тесты для всех, кто зависит от пакета, который вы изменяете. Но когда сотни тысяч инженеров работают с одним и тем же репозиторием на протяжении десятилетий, эти графы зависимостей становятся огромными, и становится невозможно постоянно запускать миллионы тестов просто «на всякий случай».
Поэтому Google создал массу инфраструктуры для умного выбора тестов (чтобы сократить количество тестов, которые нужно запускать), и, как это часто бывает в Google, было множество дублирующих и конкурирующих усилий по уменьшению нестабильности тестов. Это было важно, потому что нестабильность тестов растёт экспоненциально. Если в вашем наборе N тестов, и каждый из них имеет вероятность успешного выполнения P%, вероятность успешного выполнения всего набора тестов будет P^N. Даже что-то настолько простое, как три теста с индивидуальной вероятностью успеха 99%, означает, что их совместная вероятность успеха всего 97% (99%³). Поэтому сокращение количества запускаемых тестов и уменьшение их нестабильности было крайне важным для того, чтобы поддерживать работу монорепозитория в масштабах Google и сохранять его в здоровом состоянии.
В целом эти инвестиции в инфраструктуру были огромными: 300 инженеров при стоимости $400 тыс. в год на каждого (используя здесь общедоступные данные с levels.fyi, где средняя зарплата инженера L4 составляет $278 тыс. в год, но нужно помнить, что инженеры обходятся компании намного дороже, чем их зарплата) легко превышают 100 миллионов долларов в год. Сделать так, чтобы монорепозиторий «работал», — это дорого.
Для того чтобы микрорепозитории работали, Amazon пришлось инвестировать в инфраструктуру, чтобы управлять безопасным и своевременным распространением изменений кода между микрорепозиториями, что тоже потребовало значительной команды. В мире монорепозиториев изменение становится сразу доступным для всех; в мире микрорепозиториев, если вы поставляете библиотеку или общий компонент, вам нужно передавать изменения между микрорепозиториями вручную — через push или pull.
Эта статья сосредоточена на аспекте тестирования в CI/CD, но отмечу, что монорепозиторий действительно упрощает такие вещи, как патчи безопасности и инициативы, охватывающие всю компанию. Кроме того, решение версий зависимостей может вызывать трудности в микрорепозиториях: что делать, если ваш микрорепозиторий использует FooBar-1.2, а одна из ваших зависимостей использует FooBar-1.3? Однако, чтобы не распыляться, я решил сосредоточиться на интеграционном тестировании до и после отправки для обеих компаний.
Что касается тестирования до и после отправки: в конечном счёте, это не должно быть «ИЛИ», это должно быть «И». Нам нужна отличная инфраструктура как для pre-submit, так и для post-submit тестирования. Безусловно, это требует целенаправленных инвестиций; но когда в компании работают десятки тысяч разработчиков, вложения в снижение их трудозатрат приносят огромные дивиденды, так что это окупается.
Больше актуальных знаний и навыков по тестированию можно приобрести на онлайн-курсах под руководством экспертов области.