Хабрахабр

От пул-реквеста до релиза. Доклад Яндекс.Такси

В релизном цикле сервиса есть критически важный период — с момента, когда новая версия подготовлена, до момента, когда она становится доступна пользователям. Действия команды между этими двумя контрольными точками должны быть единообразны от релиза к релизу и, по возможности, автоматизированы. В своём докладе Сергей Помазанов alberist описал процессы, которые следуют за каждым пул-реквестом в Яндекс.Такси.

— Добрый вечер! Меня зовут Сергей, я руководитель группы автоматизации в Яндекс.Такси. Если вкратце, основная задача нашей группы — минимизация времени, которое разработчики тратят на решение своих задач. Сюда входит все: от CI до процессов разработки и тестирования.

Что наша разработка делает, когда код написан?

Для локального тестирования у нас есть большой набор тестов. Чтобы протестировать новую функциональность, мы для начала проверяем все локально. Если появляется новый код — его тоже нужно покрыть тестами.

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

Наш фреймворк позволяет запускать сервисы, заливать данные в базу перед каждым тестом, обновлять кэши, мокать все внешние запросы и т. Для тестирования у нас используется Google Test и самописный фреймворк на pytest, которым мы тестируем не только «питонячью» часть, но и «плюсовую». Достаточно функциональный фреймворк, который позволяет запустить все как угодно, что угодно замокать, чтобы у нас случайно не получились какие-то запросы вовне. д.

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

Происходит это в изолированном окружении. Стенд построен на технологии Docker и Docker Compose, где в каждом контейнере поднимаются свои сервисы, и все они взаимодействуют друг с другом. И тесты проходят в таком виде, как будто кто-то запускает мобильное приложение, нажимает на кнопочки, делает заказ. У них своя изолированная сеть, своя БД, свой набор данных. В основном все тесты требуют взаимодействия сразу множества сервисов и компонент. В этот момент виртуальные машинки ездят, довозят пассажира, дальше у пассажира списываются деньги, и всё в этом духе.

Естественно, мы тестируем только наши сервисы и только наши компоненты, потому что тестировать внешние сервисы мы не должны, и все внешнее мы мокаем.

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

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

Если для «плюсов» все просто, мы используем clang-format и проверяем, соответствует ли ему код или нет, то для Python мы используем целых четыре анализатора: Flake8, Pylint, Mypy и, кажется, autopep8. Еще один важный момент — проверка стиля.

Если есть возможность выбрать какой-то стиль оформления, то мы используем Google style. Эти анализаторы мы используем в основном в стандартной поставке. Единственное, что мы поправили, добавив свое, это проверку на сортировку импортов, чтобы импорты правильно сортировались.

Пул-реквесты у нас создаются в GitHub. После того, как у вас создан код, проверен локально, вы можете сделать пул-реквест.

TeamCity автоматически запускает все вышеозвученные тесты, проверяет их в автоматическом режиме, и в самом пул-реквесте отписывается о статусе прохождения, прошли ли тесты или нет. Создание пул-реквеста в GitHub дает множество возможностей, которые предоставляет нам TeamCity. То есть можно не заходя в TeamCity увидеть, прошло или нет, и перейдя по ссылке, понять, что пошло не так, и что нужно поправить.

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

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

Если вы правите код, который является частью высоконагруженного сервиса, для этого у нас можно сделать нагрузочные тесты. Помимо обычного тестирования иногда требуется проведение нагрузочного тестирование. Нагрузочное тестирование происходит через систему Лунапарк. В Python высоконагруженных сервисов мало, часть из них мы переписали на С++, но тем не менее, они еще остались, иногда имеют место быть. Танк позволяет проводить стрельбы по какому-то сервису, строить графики, делать разные способы нагрузки и показывать, какая нагрузка была в данный момент на сервисе и что какие ресурсы использовало. Она использует Яндекс.Танк, он в свободном доступе, можно его скачать и посмотреть. Или просто вручную залить и запустить его там. Достаточно через TeamCity нажать на кнопочку, соберется пакет, и дальше его можно будет раскатить куда надо.

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

На что мы обращаем внимание в процессе:

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

Также у нас есть правило, что при выкатке новая функциональность должна быть выключена, и включаться только после того, как она раскатится на все кластера и все дата-центры.

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

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

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

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

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

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

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

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

Возможно, вы будете записывать в эту базу бесконечное количество записей, и если не подумать об их архивировании, то возможны проблемы, база просто начнет бесконечно расти, и не хватит уже никаких ресурсов, никаких дисков и шардирования. Если вы делаете новые операции в БД или создаете новую, нужно учитывать, что данных может быть много. И также обязательно нужно делать во все базы запросы по индексам. Важно уметь архивировать данные, и хранить только оперативные данные, нужные в данный момент. Один маленький запрос в самую нагруженную центральную коллекцию может положить все. Запрос не по индексу может положить весь продакшен. Надо быть очень аккуратными.

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

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

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

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

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

Бэкенд, в свою очередь, имеет подключение ко всем нашим базам и может провести все нужные операции. Для проведения миграций мы пишем скриптик на Python, который умеет общаться с бэкендом. И если вам требуются длительные балковые операции, то нельзя обновлять сразу все, нужно делать это чанками по 1000–10000 с некоторыми паузами, чтобы случайно не положить данными операциями базу. Скрип запускается через админку запуска скриптов, далее выполняется, можно посмотреть его лог и результаты.

Когда код написан, отревьюен, протестирован, проведены все миграции, можно смело мержить его в GitHub и дальше приступать к релизу.

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

Это все делается при помощи TeamCity.

TeamCity делает git flow или его подобие. Начинается все со сборки пакетов. TeamCity это все производит, собирает пакеты, заливает их. Мы потихоньку уходим от git flow на свои наработки, которые нам показались более удобными. Прохождение тестов является обязательным для выкатки релиза. Дальше ждем, когда на этих пакетах пройдут тесты. Тесты используются те же, обычные и интеграционные. Если тесты не пройдены, то сначала надо разобраться и посмотреть, что в итоге пошло не так. Это на всякий случай, вдруг в собранном пакете есть проблемы, вдруг что-то недокопировано, вдруг что-то оказалось пропущено. На них проверяется уже собранный пакет, готовый, именно то, что пойдет в продакшен.

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

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

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

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

А если что-то пошло не так, если вдруг проблемы? Выкатка прошла, все замечательно.

Он делается по такому же принципу, как git flow, то есть ответвлением от ветки мастер. Собираем хотфикс. Создается отдельный пул-реквест от мастера, который вносит исправления, а дальше запускаемый из TeamCity скрипт его подмерживает, делает все необходимые операции, точно таким же образом собирает все пакеты и раскатывает дальше.

Движемся мы в сторону единого репозитория, когда в одном репозитории живет сразу множество сервисов. Под конец я хотел бы рассказать о направлении, в котором мы движемся. Для пул-реквестов, даже когда используется TeamCity, мы проверяем, какие файлы были затронуты, к каким сервисам они относятся. У каждого из них есть независимые выкладки: в тестинг, в релизы. Стремимся мы к максимальной изоляции сервисов друг от друга. По графу зависимости мы определяем, какие тесты нам в итоге нужно запустить и что проверить. На этом все, всем спасибо. Пока не очень получается, но мы стремимся к этому, чтобы множеству сервисов можно было жить в одном репозитории, иметь некий общий код и чтобы это не вызывало проблем и упрощало жизнь разработке.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть