Привет, Хабр. Меня зовут Владимир Утратенко, я — Head of Infrastructure and Security в Uzum Market. У меня богатый опыт найма DevOps-инженеров, ведь последние 6 лет я — нанимающий менеджер. А ещё много лет подряд занимаюсь DevOps как моделью разработки. Сегодня мы поговорим про боли в CI/CD, которые часто упускают из вида DevOps-специалисты, лиды и CTO.

Занявшись наймом на постоянной основе, я провёл собственное маленькое исследование, антинаучное и на нерелевантной выборке. Но вывод всё равно интересный: 25 из 30 DevOps-инженеров на собеседовании путают CI и CD. Более того, когда мы берём их на работу, успехи у новичков получаются так себе, всё работает не настолько хорошо, как мы ожидали.

Как определить наличие CI? 

Парня на фото зовут Джез Хамбл, он известный пионер DevOps-движения. У него есть классный простой тест, как определить, есть ли у вас в проекте CI:

  1. Пушат ли все инженеры в trunk/master/main (не в feature branch) ежедневно свои изменения?

  2. Каждый ли коммит вызывает запуск модульных (unit) тестов?

  3. Если сборка ломается, исправляют ли её обычно за 10 минут?

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

А теперь самое удивительное: на вопрос «Доводилось ли тебе видеть в жизни непрерывную поставку?» почти все опрошенные отвечают отрицательно. Они не встречали непрерывную поставку в реальной жизни!

Лет 25 назад, когда Agile был уделом маргиналов, все в IT жили примерно так же, как в строительстве — то есть использовали SDLC-модель.

SDLC, или Software Development Life Cycle, — это каскадная модель разработки ПО, когда мы ставим задачу, долго анализируем, проектируем, разрабатываем, тестируем, возвращаем в разработку, снова тестируем... Наверное, кто-то до сих пор так работает. Так вот, в SDLC есть этап интеграции, когда всё сделанное в течение полугода, начинали сводить в единое целое. На это уходил ещё месяц, а то и два. 

А затем придумали CI (Continuous Integration) — непрерывную интеграцию. Ага, ту самую, которую не видел почти никто из собеседованных мной. Впервые о CI упоминается в 1991 году, но лишь с 2001 года появляются первые CI-инструменты вроде Cruise Control и Hudson. Кстати, на Hudson основан Jenkins, который мы все так любим.

Как мы храним код

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

Также мы обычно ограничиваем количество веток и чистим неактуальные, но не всегда. Иногда долго храним разные версии логики в feature-ветках. 

Автотесты под регресс храним в другом репозитории, описание пайплайна — в третьем, а описание инфраструктуры — в четвёртом. И хорошо, если у нас вообще есть нагрузочные тесты, и они где-то хранятся, кроме головы performance-инженера.

К чему это всё приводит? Когда нужно собрать проект, особенно на новом месте, приходится использовать разные источники истины. Необходимо поддерживать их актуальность, включая множество веток бизнес-логики, иначе будем страдать: что-то обязательно упустим, и сборка не будет работать.

Как можно улучшить хранение кода?

У меня есть решение из 2018 года — ноу-хау, записывайте! Речь про герметичные репозитории, которые улучшают накопление знаний. Их так назвали по аналогии с жанром «герметичных детективов», когда несколько персонажей, среди которых есть убийца, заперты в каком-то помещении и не могут его покинуть. Так вот, герметичные репозитории — это как герметичные детективы, только репозитории (без убийц). В них есть всё, что тебе необходимо:

  • бизнес-логика;

  • скрипты сборки; 

  • тесты;

  • версии схемы БД;

  • описание пайплайна;

  • описание инфраструктуры и окружений;

  • установочные скрипты;

  • манифесты и шаблоны.

Благодаря герметичным репозиториям нам проще копить знания по проекту, у нас меньше проблем и упущений. А если проблема появляется, нам проще её решить, потому что не надо собирать всё из разных источников истины. Но есть и свои сложности: нужно синхронизировать состояние и следить за актуальностью материалов. Это сложнее, чем положить всё описание окружений в один репозиторий, а потом полчаса гонять Terraform, разбирая длинные листинги. Впрочем, вариант рабочий: команда делает меньше ошибок, быстрее разрабатывает, тестирует и интегрирует.

Автотесты и CI

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

Всё, что сложнее Unit-тестов, часто пишут автотестеры или SDET, которые обычно на время подключаются к вашей команде. Поэтому они могут написать не те тесты, что нужны сейчас, а что-то на своё усмотрение, ведь автотестов всегда не хватает. Да ещё и делают это на своей тестовой платформе или фреймворке, в которых ничего не понятно, в том числе как всё это хозяйство поддерживать.

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

Может показаться, что дешевле и легче развернуть и протестировать вручную, чем писать автотесты. Но это не так. Ручные процессы крадут ваше время. Вместо того, чтоб потратить один рабочий день на написание автотестов, вам придётся практически ежедневно тратить по несколько часов на ручное тестирование. А с автотестами ещё и скорость, и качество решений выросло бы на порядок. 

Коммиты в mainline

В начале статьи мы говорили про ежедневные коммиты. Ведь мы любим доделывать работу максимально полно, чтобы всё было! Это значит, что дописав свою часть фичи, мы отправляем её на Pull/Merge Request в пару тысяч строк — на ревью тимлиду или команде. И пусть весь мир подождёт!

И он ждёт… И request тоже.

Mainline убегает вперёд, потому что разработка не останавливается, наши коллеги продолжают работать. В доработанном нами куске кода параллельно делают другие фичи. Этим может заниматься кто-то один, а может и несколько команд. В результате с Merge Request приходит сразу много кода и правок. 

Разгребать это бывает больно. Мы знаем, как выглядят глаза людей, которые во время сборки релиза разбирают огромные Pull/Merge Request, несколько недель ночами не спят, чтобы собрать в кучу всё, что до этого сделали разработчики. Разбирают диффы, пытаются понять, что к чему относится, выясняют, как правильно «скрестить бульдога с носорогом». И чем «интереснее» спроектирована система, тем больше времени займёт эта интеграция.

Решение есть — чаще коммититься. Чем чаще мы забираем свежий код, тем проще интеграция. Если вы не можете определиться, насколько чаще, то договоритесь с разработчиками попробовать несколько коммитов  в mainline каждый день, или хотя бы раз в пару дней. Уверяю, всем станет легче, а разработчики будут добрее. 

Что ещё вам поможет:

  • branch by abstraction;

  • feature flags;

  • keystone Interface.

Скорость сборки

Сегодня пользователи CI-систем обычно уже умеют собирать код по коммиту. Хотя ещё десять лет назад многие собирались с кнопки, но потом оказалось, что Jenkins умеет отслеживать коммиты.

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

Я предлагаю повысить скорость сборки, хотя бы первой после коммита. Что-то мы можем оптимизировать кешами, мультистейдж билдами, если пользуемся Docker. Тогда первая сборка будет двигаться быстро или быстро сломается. Зато мы раньше получим обратную связь, починим и вернёмся к чистому окружению для новой итерации.

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

Тестовое окружение

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

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

Да, точная копия прода для тестирования — это, чаще всего, утопия, потому что получается слишком дорого. Можно урезать ресурсы, обезличить данные или найти другой компромисс. Вопрос лишь в том, насколько он будет велик. Главное, чтобы тестовый контур оставался максимально похож на прод. Если у вас это не так, попросите у CTO деньги на тестовое окружение.

Что делать с CD? 

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

Тестирование и CD

Часто релиз проходит регрессионное тестирование в течение месяца, причём это делается вручную. От такого выгорит кто угодно.

В результате пропускают в прод баги, а потом ночью будят DevOps-инженера, потому что кто-то не настроил оповещения при ошибках.

Чтобы защититься от этого, можно использовать подход shift-left on testing: поймали проблему в проде, и не просто исправили её, а подумали, как исправить ещё и на UAT — приёмочном тестировании. Поймали на приёмочном — замечательно, нужно подумать, как поймать на предыдущем этапе. Поймали на предыдущем — продолжаем искать на более раннем этапе. И так пока не упрёмся в минимальные unit-тесты и не сделаем так, чтобы обнаруживать эту проблему у разработчика на компьютере и не пускать даже в Git.

Изменения большим пакетом

У многих возникают трудности, когда продуктовые команды несут большие пакеты изменений. Люди строят звездолёты, а потом эти звездолёты падают на взлёте. Подскажу решение: уменьшайте размер пакетов.

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

Нет времени на скрипты и автотесты

Ещё бывает так, что вам некогда закрывать техдолг и писать автотесты и скрипты. Но тогда CD не работает, Runbook’и, регрессионные тесты и накатку изменений приходится делать вручную.

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

Всё уже настроено

Я часто на собеседованиях слышу от DevOps-инженеров, что в прошлом проекте они всё уже настроили, заскучали и теперь ищут другой. Если вы тоже так думаете, то, скорее всего, вы смотрите не туда. 

Возможно, вы слышали про Кайдзен. Это процесс непрерывных улучшений, который пришел из производственной системы Toyota. Согласно ему, если нечего улучшать, то стоит посмотреть на соседей. Скорее всего, вы улучшали в пределах узкой темы, автоматизировали, а вокруг все просто работали. Как в старом анекдоте, когда в одной большой корпорации внедрили автоматизацию, CI/CD, а релизный цикл как был три месяца, так и остался.

Вся проблема в разработчиках?

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

Выводы

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

В рамках ежедневной работы по CI/CD мы многое делаем классно. Умеем автоматизировать сборки и тестирование чего угодно, эффективно работать под запрос. Но у нас не всегда есть время подумать, а не ерунду ли мы делаем и где скрыт корень проблем. Полезная практика — поразмыслить над своим процессом CI/CD и понять, что это действительно сложные и мощные инструменты. Они напоминают GOTO или сабмодули, но с ними точно так же легко всё сломать и впустую потратить много денег компании. Поэтому подходить к ним нужно максимально осознанно, насколько это вообще возможно. Желаю вам удачи на этом пути!

Полезные ссылки

Комментарии (6)


  1. shrum
    18.06.2024 02:25

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


    1. QuantumBoy
      18.06.2024 02:25

      "devops - это не специалист, а культура" :)


      1. shrum
        18.06.2024 02:25

        Ой да неужели? А как же тогда тысячи вакансий на hh? Они типа кого ищут? Инопланетянен?


    1. AlpineSlowpoke Автор
      18.06.2024 02:25

      Доводы для убеждения в чём?


      1. shrum
        18.06.2024 02:25

        В необходимости внедрения всех этих вещей о которых вы рассказали в статье. Попробуйте убедить разработчика в каком флоу ему лучше работать, что тбд лучше фича-веток, или что комитов нужно 2-3 штуки в день и желательно меньше ста строк каждый, о внедрении тестирования, имхо даже при наличии нормального тимлида о юнит-тестировании в 80% случаев можно только мечтать.


  1. AlpineSlowpoke Автор
    18.06.2024 02:25

    -