Наверх ▲

Релиз-менеджмент в Badoo

Юрий Насретдинов Юрий Насретдинов Ведущий разработчик в Badoo, работаю в отделе «платформы». Один из основных разработчиков «облака» (системы для распределенного запуска cli-скриптов по расписанию). Также занимался deployment и системой переводов. В веб-разработке около 10 лет, из которых 3 года в Badoo. Илья Агеев Илья Агеев QA Lead в Badoo. Руководит процессами тестирования и выкладки.

Илья Агеев: Добрый день, меня зовут Илья Агеев. Я работаю в компании Badoo и руковожу тестированием и выкладкой кода. Рядом со мной стоит Юрий Насретдинов, он PHP-разработчик. Этот доклад мы с ним будем делать вместе.

Для начала стоит объяснить, что такое Badoo. Badoo – это сайт знакомств, на котором реализована возможность знакомства людей, находящихся недалеко друг от друга. Разработка сайта началась в 2005 году, в 2006 году сайт был успешно запущен. На сегодняшний день это крупная социальная сеть, в которой насчитывается свыше 140 миллионов пользователей из 180 стран мира. Ежедневно на сайте происходит около 160 тысяч регистраций. Основная управляющая структура Badoo находится  в Лондоне, в России в основном располагаются технические подразделения.

Развертывание – это непростая задача, с которой рано или поздно сталкивается любая компания, в которой ведется командная разработка и задействовано больше 1 сервера. У нас в Badoo используется около 1000 серверов на двух площадках, одна из которых находится в Европе, а другая – в Америке.

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

На дворе 2011 год. Мы в Badoo уже попробовали разные системы контроля версий – был CVS, SVN, Mercurial. На момент начала того периода, о котором я сейчас рассказываю, у нас был SVN.

Была одна ветка под названием “trunk”, где размещали свои задачи разные отделы разработчиков – разработчики PHP, JavaScript, верстальщики. Выпуск релиза у нас происходит 2 раза в неделю, по вторникам и четвергам. На схеме хорошо видно, что в случае релиза мы не всегда были уверены в том, что в релиз попадают законченные задачи.

Как это все отображалось в системе отслеживания (англ. tracking system)? У нас были следующие статусы: “Оpen” (задача только создана), “In progress” (выполняется) и “Resolved” (выполнено) – это все статусы для задач в разработке. Если возникали ошибки, мы открывали задачу повторно (статус “Reopened” соответственно). Если все было хорошо, устанавливали статус “Past on DEV”. Следующий статус – это “Past on Stage”. Для нас “Stage” означает препродакшн, некоторое окружение, в котором мы смотрим задачу. Последний статус – “Past on Product”; после присвоения этого статуса задача закрывалась .

Здесь особое внимание стоит обратить на поле “svn revision”, в которое разработчик вводил число, которое он получал после обновления (англ. commit), то есть заголовок ветки, в которой содержится его код. Как мы понимали, что код находится на стадии “Stage” или попал в продакшн? К нам на почту приходило письмо с уведомлением о том, что выложена некая «версия (англ. revision) номер…». Номером было число, которое я уже упоминал выше. Нам приходилось смотреть тикеты в программе Jira, сопоставлять все эти числа, и уже на основе этого делать вывод о том, где наш код находится.

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

Что такое переводы? При написании шаблонов разработчики используют особый язык devel. Это текст, который приходит в технических заданиях. Он написан на скриншотах. Следующий шаг – это парсинг таких шаблонов, при котором шаблоны разделяются на структуру файла и лексемы и записываются в базу. После этого переводчики получают возможность переводить эти лексемы на разные языки. На следующем этапе происходит запуск определенного набора скриптов, который генерирует нам столько вариантов шаблона, сколько языков мы поддерживаем. На сегодня у нас около 40 языков.

Первая схема выкладки

Наш прежний порядок выкладки кода выглядит примерно так. Мы выгружаем заголовок “trunk” из SVN на специальную машину (у нас она называется “main”). На этой машине мы собираем код, «прогоняем» переводы, собираем “loop” (это специальный файл-архив, о нем более подробно расскажет Юрий), отправляем то, что у нас получилось, на “Stage”, смотрим, все ли хорошо, и после этого выкладываем на продакшн. Раскладка на продакшн делалась через mscp в несколько потоков. По времени это занимало около 40 минут на 2 платформы.

Какие же недостатки были в этой схеме? Их масса. В выкладку попадали незавершенные задачи. Было неудобно отслеживать состояние кода по письмам. Для задачи нельзя было выполнить «откат», поскольку обновления (англ. commits) попадали в ветку неупорядоченно. Невозможно было добавить что-то в прошлый релиз. Для релиза можно было сделать «откат», но только на то, что было до этого. Грубо говоря, весь код переключался на старую версию, включали полностью новую версию.

Помимо этого, выкладка готовых задач тормозилась из-за недопереведенных или неработающих задач. Пока переводчики переводят одни задачи, другие задачи, которые уже готовы, вынуждены ждать своей очереди. Это происходило из-за того, что все задачи находились в одном файле “loop”.

Ну, и еще один недостаток. Код выкладывался медленно и несинхронно – как я уже говорил, нам требовалось около 40 минут на 2 платформы. Я специально поставил в конце многоточие – здесь можно найти еще массу других недостатков.

Как же изменить ситуацию? Очевидно, что задачи нужно было разделять и «отвязывать» друг от друга, чтобы они попадали в выкладку завершенными. Естественно, для этого процесса лучше всего подходит механизм веток в системах контроля версий. Мы выбрали Git, поскольку в нем очень удобно создавать ветки, сливать ветки, работать с ветками.

Вторая схема выкладки

Что мы стали делать? У нас есть одна стабильная ведущая ветка (англ. master), для каждой задачи создается отдельная ветка, в которой полностью ведется ее разработка. После того, как эта ветка будет протестирована в devel-окружении, происходит слияние этой ветки с основной веткой (master). Все это видно на схеме. Поскольку задачи попадают в основную ветку законченными и протестированными, основную ветку можно выкладывать в любой момент – есть гарантия того, что незаконченных задач в ней нет.

Отдельно хочу ответить, как мы переходили на Git с SVN. Мы использовали схему “Git or SVN”: в то время, как одни отделы у нас уже работали на Git, другие продолжали использовать SVN. Переход был плавным.

Изменение схемы

Чтобы отслеживать выполнение задач с помощью Jira мы внесли изменения в рабочий процесс (англ. workflow). Мы ввели статусы, которые помогают оценить состояние кода. Начало новой схемы похоже на предыдущую – есть статусы “Оpen”, “In progress”, “Resolved”, которые выставляет разработчик.

Что здесь нового? После того, как задача завершена, она попадает на контроль качества (англ. QA) в devel-окружении. Контроль качества либо повторно открывает задачу, если все плохо, либо ставит следующий статус “To Merge”. Он означает, что можно сливать ветку задачи с основной веткой.

Следующий статус присваивается автоматически. Это статус “To Deploy”. После того, как ветка задачи сливается с основной веткой, в Git срабатывает скрипт-хук (англ. hook), который и меняет статус задачи. Это статус “On Stage”, когда код попадает в препродакшн-окружение, мы это прекрасно видим по состоянию задачи. Здесь мы проверяем задачу и отправляем ее на продакшн.

Обратите внимание на то, что повторно открыть задачу можно только до того момента слияния ветки задачи с основной веткой. После того, как мы сделали слияние и «прогнали» переводы, мы, к сожалению, не имели возможности выполнить «откат» для этой задачи согласно этой схеме. Но в ней уже появилось состояние задач, и нам не требовалось смотреть  никакие “svn revision” в почте.

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

Конечно, у этой схемы есть и недостатки. Все те недостатки, которые были после выкладки на машину “main” в предыдущей схеме, в этой схеме остались.

Ветки “shot” и “build”

Перейдем ко второму этапу нового развертывания. Для того чтобы о нем говорить, мне потребуется ввести два новых понятия. Это понятия “shot” и “build”. Что это такое? Это ветки в Git. Shot – это специальное окружение для каждой конкретной задачи “On Stage”, препродакшн, где мы смотрим, как эта задача работает в отдельности.

Build – это ветка, в которой мы агрегируем все эти задачи вместе и проверяем, как они работают в совокупности.

На этой схеме более подробно рассказано, как мы поступаем на втором этапе. У нас по-прежнему есть основная ветка (master). Задачи берут начало в ней и делаются в отдельных ветках. Но, в отличие от предыдущей схемы, в  которой мы после тестирования выполняли слияние ветки задачи с основной веткой, мы ее выкладываем в ветку shot, в специальное препродакшн-окружение. Проверили задачу, написали интеграционные тесты. Создаем ветку build. Она также берет начало в основной ветке. Все задачи, которые мы хотим выкладывать, по очереди сливаются с этой веткой. Проверили build, выполнили тесты, выложили на продакшн и только после этого осуществили слияние с основной веткой (master). Таким образом, в этой ветке (master) всегда содержится точная копия того кода, который у нас сейчас есть на продакшн.

Кластеры

Также на втором этапе мы ввели такое понятие, как кластеры. Что это такое? Все очень просто: «боевые» серверы на продакшне тематически разделены на серверы под скрипты, серверы для биллинга и так далее. Мы получили возможность создавать ветки build для каждого кластера отдельно, для совокупности кластеров, всех кластеров вместе. Очень удобно, если готова, например, задача для биллинга, можно сразу ее выложить на сервер биллинга, не ждать Back Office, который по каким-то причинам еще переводится или выполняет отладку.

Изменения в Jira

Для этой схеме мы также кое-что изменили в Jira. Это очень простые изменения – мы просто добавили статусы, которые показывают изменения кода. К предыдущей схеме мы добавили статусы “In Shot” и “In Build”.

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

Изменения в переводах

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

Передача файлов: uftp

Стоит упомянуть еще одно нововведение, которое появилось на втором этапе нового развертывания. Это uftp. Что это такое? Это просто протокол передачи данных UDP поверх FTP.

Напомню: раньше выкладка на обе платформы занимала 40 минут. Большую часть из этих минут занимало копирование loop-файла на машины. Несмотря на то, что это осуществлялось многопоточно, это было подключение к каждой конкретной машине и «заливка» файла на нее. Теперь мы этого не делаем. Теперь мы передаем файл по UDP, а конечная машина сохраняет этот файл у себя. Таким образом мы ускорили заливку файла до 30 секунд для одной платформы.

Преимущества

Итак, какие преимущества мы получили на втором этапе нового развертывания?

Для любой задачи в любой момент можно выполнить «откат». Благодаря реализации на ветках Git, мы можем делать все что угодно. Можно добавлять задачи в предыдущие релизы. Как? Очень просто: берем старую build-ветку, добавляем туда ветку новой задачи, раскладываем ее везде.

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

Еще одно преимущество: ничто не «тормозит» выкладку задачи, если она завершена. Никого не надо ждать. Не нужно ждать, пока другие отделы завершат работу над своим функционалом и отладку. Не нужно ждать, пока другие задачи будут доделаны. Если задача готова, она может быть выложена.

Также стоит упомянуть про ускорение. Раньше выкладка занимала 40 минут, сейчас 3, причем главным образом это время удалось сократить за счет использования uftp. Теперь это время составляет, как я уже сказал, 30 секунд на все машины одной платформы.

Наши планы

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

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

Также можно осуществить управление файлами конфигурации через БД. Что здесь имеется в виду? Часть файлов конфигурации у нас лежит в репозитории, и для того чтобы поменять какую-то настройку, нам нужно генерировать loop и делать выкладку. Я думаю, что этот процесс тоже можно скорректировать, вынеся файлы конфигурации из репозитория, например, в базу данных.

У меня все. Передаю слово Юрию Насретдинову, он более подробно расскажет о технических нюансах.

Юрий Насретдинов: Илья меня уже представил. Я постараюсь вам рассказать о технической реализации нашей выкладки.

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

Допустим, у нас есть файл, в котором нет ничего, кроме пустой файловой системы размером 512 мегабайт. Потом мы запускаем команду Linux “losetup”, которая ассоциирует этот файл с блочным устройством. Затем мы это блочное устройство монтируем и получаем директорию с файлом. Мы копируем в эту директорию все файлы, которые нам нужны, размонтируем этот файл и архивируем его. Так получается окончательный вариант, с помощью которого мы выполняем выкладку. Это файл размером примерно 100 мегабайт, в котором содержится образ файловой системы, где есть все нужные нам файлы.

Мы копируем этот файл на все наши серверы, используя 20 потоков, и проделываем с ним обратную операцию – разархивируем, монтируем и получаем некоторую директорию, где содержатся все наши файлы. Путь к документу (англ. document route) является символической ссылкой и указывает на последний смонтированный loop. Если выкладка завершится успешно, мы просто «перебрасываем» эту символическую ссылку на новый loop. Это атомарная операция, она очень быстрая и очень простая.

Как Илья уже говорил, все это вместе занимает порядка 40 минут. Это время включает в себя и загрузку loop на другие площадки. На данный момент мы «заливаем» его только на американскую площадку, это делается 1 раз и занимает около 5 минут, после чего мы начинаем уже раскладку по всем машинам.

Какие здесь есть недостатки?

В принципе, я уже их озвучил. Из-за своего большого размера loop-файл медленно копируется на другие платформы по протоколу TCP-IP. Потом мы размещаем этот файл на всех машинах внутри площадки. Если у нас есть 500 машин, скопировать на все эти машины файл размером 100 мегабайт быстро не получится. Вследствие этого, у нас налицо несинхронное обновление кода. В течение этих 30 минут у нас на каких-то машинах уже имеется новая версия кода, а на каких-то ее нет.

Как мы решали эту проблему?

Допустим, у нас есть loop-файл, пусть он относится к редакции (англ. revision) 78. Мы берем предыдущий файл, который у нас был, и считаем между ними двоичный diff. Для этого, в принципе, можно использовать все что угодно – допустим, хD либо s_diff. Мы используем очень простой скрипт, делающий поблочное сравнение. Это достаточно хорошо работает: между двумя файлами размером 512 мегабайт diff составляет всего лишь 100 килобайт. Затем мы этот diff копируем на другую платформу, где, естественно, уже есть предыдущая версия. Поэтому мы этот diff просто применяем как патч и получаем два одинаковых loop-файла на двух платформах, пересылать нужно только разницу между ними. В принципе, можно было бы использовать rsync, но это менее надежно. Мы просто считаем md5 от этого файла, убеждаемся в том, что все хорошо, - значит, раскладывать его можно. Иначе просто сложнее делать.

Как мы делаем раскладку внутри платформы?

У нас есть наш файл, мы используем специальную утилиту под названием  uftp. Она работает по протоколу UDP. Она была предназначена для того, чтобы копировать данные через спутники (там долго ждать ответа на ping). Но, в принципе, у нее есть одно свойство, которое нам подходит. Uftp не выполняет копирование на все машины, а создает группу для групповой передачи (англ. multicast), объединяет все машины, на которые мы раскладываем файл, в один адрес групповой передачи, после чего выполняет отправку по UDP только на него. При этом маршрутизаторы для этого протокола имеют такие ограничения, что нагрузка на сеть уменьшается во много раз. Так что все это работает очень и очень быстро.

Итак, мы загрузили наш файл. Распаковываем его, через SSH заходим на все машины и выполняем установку loop-файла. Для этого мы используем нашу самописную библиотеку libpssh. Она основана на библиотеке libssh, о которой, наверное, вы все слышали. Наша библиотека просто содержит небольшие модификации, позволяющие работать через epoll и подключаться ко всем серверам асинхронно. Эта библиотека доступна на GitHub, ее можно найти по ключевому слову libpssh. Она позволяет подключаться ко всей нашей 1000 серверов за 3-4 секунды. Затем мы исполняем скрипт, который устанавливает loop, и с помощью тех же самых манипуляций получаем новую версию. Все это занимает порядка 3 минут, включая ввод loop на другую платформу.

Какие проблемы с производительностью у нас есть?

Мы оптимизировали только загрузку loop на другие платформы и «раскидывание» файлов внутри площадки. Мы сделали это настолько хорошо, что теперь у нас большая часть времени уходит на то, чтобы архивировать и доставать из архива  loop-файлы. Понятно, что если у нас есть 512 мегабайт, то такой файл быстро сжать не получится. Это займет секунд 15 даже на очень хорошем оборудовании. Нет такого, чтобы в следующую секунду у нас была уже новая версия сайта. Задержка при переключении с одного loop-файла на другой составляет несколько секунд. Это не так уж плохо, но на самом деле у нас есть мысли о том, как это можно улучшить. Используя двоичные diff, мы могли бы загружать их на другие платформы и вообще ничего не сжимать. На самом деле, сжатие было нужно только для того, чтобы уменьшить количество трафика.

Как я уже сказал, у нас есть целых 2 зависимости. Это libpssh и uftp, все остальное – это чистый PHP-код, который все это делает. На самом деле, мы собираемся избавляться от неPHP-кода.

Вот, собственно, и все. Спасибо за внимание.

Вопросы и ответы

Вопрос из зала: У меня целый ряд вопросов. Как вы описываете зависимости между build-ветками и кластерами?

Юрий Насретдинов: Кажется, Илья показывал это на презентации. Я не уверен, что смогу это сейчас показать. У нас есть набор build-веток и кластеров. Каждый build может быть разложен на какой-то набор кластеров. При этом они не могут иметь общих кластеров, раскладываться на одни и те же кластеры.

Реплика из зала: Мой вопрос заключался немного в другом.

Юрий Насретдинов: А в чем?

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

Юрий Насретдинов: Мы все файлы кладем на все машины.

Реплика из зала: Сама идея использования build-веток заключается в том, что их можно класть на отдельные кластеры.

Юрий Насретдинов: Мы можем их класть на отдельные кластеры, если нам это нужно. Но мы раскладываем один и тот же код на все машины.

Вопрос из зала: Окей, следующий вопрос. Как вы декомпозируете шаблоны для переводов? У вас есть шаблон, вам нужно к нему подготовить перевод, вы, соответственно, декомпозируете его на лексемы и потом выбираете из него текст для перевода. Как это происходит?

Илья Агеев: Как я уже говорил, система переводов – это сложная штука. Она разбирает .tpl-файлы, шаблоны на лексемы и структуру файла и кладет это в базу. У нас есть очень удобный интерфейс, который позволяет человеку видеть, откуда взята та или иная лексема, из какого она файла. Помимо этого, логика системы переводов построена так, что если лексема попала в базу и была переведена, например, в прошлый раз, а в новом шаблоне есть та же лексема, ее не надо будет переводить – она «подтянется» автоматически.

Вопрос из зала: Это понятно. Непонятно, чем именно вы декомпозируете сам шаблон .tpl. Какое программное обеспечение этим занимается?

Илья Агеев: Для этого у нас используется скрипт, написанный на PHP.

Вопрос из зала: Он написан вручную или с использованием каких-то библиотек?

Илья Агеев: Вручную на PHP.

Вопрос из зала: Хорошо, спасибо, следующий вопрос. Почему вы используете uftp, а не torrents? Насколько я понимаю, uftp дает полную аналогию «поведения» torrents. Судя по всему, вы это пробовали. Какой профит по сравнению с применением torrents?

Илья Агеев: Мы их не пробовали, потому что это дольше. Мы знаем, что их использует Facebook, Yandex тоже пытался использовать, не знаю, как сейчас дела обстоят. Не могу сказать, используют они это или нет. Мы просто попробовали uftp, это оказалось быстро и неплохо, и мы остановились на этом варианте.

Вопрос из зала: Мой вопрос заключался вот в чем… Если torrents – это медленно, то насколько это медленнее? Вы имеете какие-то оценки?

Илья Агеев: Нет, мы не замеряли.

Вопрос из зала: Хорошо, следующий вопрос. Вы все время говорите про две платформы. Какие это платформы?

Илья Агеев: Как я уже говорил, у нас 2 платформы. Это 2 дата-центра – в Европе и в Америке.

Вопрос из зала: У меня всего 3 вопроса осталось. Скажите, как долго ваши разработчики учились побеждать слияние при переходе на Git?

Илья Агеев: Это было совсем не сложно. Это несложная операция.

Реплика из зала: То есть проблем с конфликтами не было?

Илья Агеев: Были проблемы с конфликтами, но это не так страшно. Как мы делали? Подготовили документацию. Грубо говоря, у нас был документ в Jira, в котором было описано, какие шаги делать.

Вопрос из зала: Хорошо, последний вопрос. Чем автоматизируется shot-ветка и как описывается «боевое» окружение для него? «Боевое» окружение как строите и эмулируете? То есть у вас shot тестируется не на «боевом» окружении, а на его дубликате?

Илья Агеев: Это одна из машин, которая точно так же располагается в продакшне, имеет доступ ко всем сервисам «боевым», к базе данных… Мы просто выгружаем код на эту машину.

Вопрос из зала: Я правильно понимаю, что вы на время тестирования выключаете машину из «боя»? Проводите тестирование, а потом обратно включаете в «бой»?

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

Реплика из зала: Спасибо.

Вопрос из зала: Есть еще один вопрос именно по поводу процесса. Используете ли вы такие средства, как GitFlow и оригинальный рабочий процесс (англ. workflow), который она представляет?

Илья Агеев: Нет, не используем.

Реплика из зала: Ну, посмотрите, потому что там процессы лучше поставлены для разработки и более безопасно, чем у вас.

Вопрос из зала: Еще один вопрос. Как я понимаю, ваш loop содержит не только PHP-код. Что там еще находится? Это какая-то медиа-информация для пользователей либо непосредственно перечисляются серверы, сервисы и всё остальное?

Юрий Насретдинов: Там содержится весь код, который у нас используется для PHP, также там содержится JavaScript и CSS-картинки, которые используются для отображения сайта.

Вопрос из зала: А какую систему поддержания конфигурации вы используете? Puppet, Chef – что-нибудь в этом роде?

Юрий Насретдинов: Puppet.

Реплика из зала: Спасибо.

Вопрос из зала: Илья, у меня есть к вам два вопроса. Один короткий. Зачем при развертывании вы передаете не просто архив, а что-то с файловой системой, пакуете это все дело? Почему бы просто архивчик не передать?

Юрий Насретдинов: Отвечу вместо Ильи. Это разархивируется намного быстрее. Один файл распаковывается быстрее, чем 10 тысяч файлов, согласитесь?

Вопрос из зала: Хорошо. И второй вопрос: при развертывании целиком, как вы «накатываете» изменения на базу, если у вас, например, поменялась структура.

Юрий Насретдинов: С помощью Alter Table.

Реплика из зала: Нет, а что происходит? Допустим, Alter Table у вас не мгновенно произойдет. У вас есть 2 кода. Один работает с одной структурой, а второй – с другой. Как это все синхронизируется?

Илья Агеев: Миграция – это непростой процесс, он небыстрый, поэтому в той схеме, про которую я сегодня рассказывал, обновления базы данных нет. Я могу рассказать, как это происходит. Обновление таблиц у нас выполняют администраторы, которые занимаются собственно базами данных. Обновления выполняются в тот период, когда идет наименьшее количество трафика (в основном ночью, это время администраторы сами выбирают). Соответственно, если нужно выполнить замену (англ. alter) в базе, разработчики поступают так: просто пишут 2 версии кода – тот код, который работает по-старому, и тот, который работает с новой схемой. После того, как администраторы «накатят» изменения на базу, мы просто выключаем старую схему и включаем новую. Вот и все.

Вопрос из зала: Здравствуйте, меня зовут Михаил. Я так понял, что у вас есть 2 команды, которые работают над релизом – переводчики и разработчики. А не думали ли вы сделать 2 разные выкатки для кода и переводов? Делать их просто синхронно, чтоб 2 команды друг друга не ждали?

Илья Агеев: Я сейчас рассказывал про новую схему. Мы как раз добились того, что команды друг друга не ждут. Как только создается shot, переводчики сразу получают возможность переводить. В это же время тестировщики тестируют этот код из shot-ветки. Люди работают параллельно.

Реплика из зала: Спасибо.

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

Илья Агеев: На подготовку loop?

Реплика из зала: Да.

Илья Агеев: Раньше это занимало около 15 минут, но благодаря бинарным diff, о которых рассказал Юрий, оно сократилось.

Вопрос из зала: Ну, diff – это уже выкладка. А сколько занимает подготовка файла? Сколько времени нужно, чтобы собрать то, что должно войти в релиз?

Илья Агеев: Где-то около 5 минут всё это занимает.

Вопрос из зала: Настолько автоматизировано?

Илья Агеев: Да.

Реплика из зала: Отлично.

Вопрос из зала: Здравствуйте, меня зовут Иван. Подскажите, пожалуйста, каким образом у вас выполняется «откат» произвольной ветки?

Илья Агеев: Очень просто. Наш build – это ветка Git, мы «откатываем» обновление слияния. Когда мы собираем build, другие ветки собираются с ним. Соответственно, чтобы что-то «откатить», мы «откатываем» слияние. Если у нас что-то не получается – есть конфликт или что-то еще, мы можем собрать новую build-ветку, с которой точно так же сольются ветки все остальные задачи, в которых нет проблем.

Реплика из зала: Спасибо.

Вопрос из зала: Скажите, пожалуйста, как вы действуете в случае, если развертывание предполагает обновление серверной части? К примеру, нужно обновить версию PHP или установить расширение (англ. extension). Как вы справляетесь с такого рода сложностями?

Илья Агеев: У нас этим занимаются администраторы, а не релиз-инженеры.

Вопрос из зала: А сколько shot-веток тестируется одновременно? Или каждый функционал тестируется независимо, на отдельной машине?

Юрий Насретдинов:  На отдельной машине мы не тестируем, это все находится на одной физической машине, просто в разных директориях. 5 shot-веток за один раз тестировать вполне нормально.

Вопрос из зала: А сколько по времени в среднем занимает тестирование одной shot-ветки?

Илья Агеев: Нельзя назвать какую-то конкретную цифру, потому что тут все зависит от удачи.

Реплика из зала: Хотя бы диапазон назовите…

Илья Агеев: От 5 минут до 2 часов.

Вопрос из зала: То есть вы достаточно мелкие функционалы берете?

Илья Агеев: Да. Мы стараемся при декомпозиции дробить задачи на как можно более мелкие части. В этом случае весь этап завершается быстрее.

Реплика из зала: Спасибо.

Илья Агеев: Не за что.

Вопрос из зала: Добрый день! У меня вопрос: я так понял, что тестируется shot и тестируется релиз, в который попали несколько таких shot-веток, - я прав?

Илья Агеев: Да. Именно так.

Вопрос из зала: В полном объеме выполняются и автоматические тесты, и тестирование вручную? Это такое удвоение…

Илья Агеев: Да. Когда мы тестируем задачу в отдельности, мы пишем интеграционный тест, который тестирует эту задачу. А когда у нас есть build, мы тестируем и эту задачу, и регрессию, чтобы остальные задачи тоже не сломались.

Вопрос из зала: То есть тестирования релиза вручную у вас нет?

Илья Агеев: К сожалению, от тестирования вручную никуда не деться. В небольшом объеме оно присутствует.

Реплика из зала: Спасибо.

Вопрос из зала: У меня тоже есть небольшой вопрос. Каким образом у вас синхронизируется статика?

Юрий Насретдинов: На самом деле я немножко наврал насчет того, что у нас есть все картинки. У нас есть отдельные серверы, на которые мы сначала загружаем статику, после этого уже применяется прописанная в коде версия статического контента. Мы просто идем последовательно. На это немного времени уходит.

Илья Агеев: Грубо говоря, каждый build просто имеет свой номер и свою версию статики. Вся эта статика уже есть на серверах.

Реплика из зала: У меня не вопрос, а ответ на вопрос, который ребята не дали. Про раскладку новых версий PHP, расширений и всего прочего. Все делается очень просто. Во-первых, выбирается какое-то место для карантина, куда выкладывается код, который не должен ломать существующий код PHP. Как правило, сначала это сервер разработки, потом часть машин в «боевом» кластере, после чего осуществляется уже полная раскладка. В 99 % случаев новые инструменты можно разложить так, чтобы не потребовалось менять старый код. Если его поменять все же надо, то он переписывается с тем, чтобы он автоматически определял, в каком окружении мы работаем. К примеру, мне нужна функциональность для сброса кэша акселератора. Я сделал некий универсальный внешний API. У нас был момент, когда мы переходили с APC на E Accelerator. Для APC была одна функция, для E Accelerator другая. Надо было написать такой код, который автоматически определит, где мы находимся и как нам нужно выполнить ту или иную операцию.

Реплика из зала: Это по вопросу, касавшемуся релиза. Был еще вопрос по схемам баз данных. Как правило, все релизы схемы базы данных сводятся к двум основным случаям. В первом случае у нас добавляется новое поле или таблица. Во втором у нас удаляется какое-то поле или таблица. Как правило, это очень просто синхронизируется с кодом. В случае, когда у нас происходит добавление нового поля, сначала это поле добавляется, а потом раскладывается код. В случае удаления поля сначала вычищается тот код, который использовался, а потом выполняется команда drop.

Бывают ситуации, когда у нас есть какой-то хитрый релиз, для которого необходимо сделать сайт недоступным. Последний раз такое было в 2007 году. В этом случае выбираем ночное время или время, когда основные клиенты спят, ставим заглушку «Извините, мы спим», и за полчаса все делаем.

Вопрос из зала: Вы рассказали, как происходит «откат» неудачных слияний. Допустим, у вас релиз, в котором 5 новых функциональностей. Вы его протестировали, все хорошо. Но какой-то тестировщик пропустил какой-то момент, и после релиза ваш сайт не работает. Из вашего доклада следует, что у вас отсутствует предыдущая версия релиза.

Юрий Насретдинов: У нас есть предыдущая версия. На самом деле их даже несколько.

Вопрос из зала: Но у вас есть основная ветка, текущий «боевой» сайт. В какой момент вы делаете тэгирование?

Юрий Насретдинов: На самом деле у нас в системе есть loop. Его старые версии мы тоже храним. Они смонтированы, их сразу несколько. Мы можем переключаться между ними. Всегда можно «перебросить» символическую ссылку на предыдущую версию в продакшн. Не нужно использовать Git или задействовать что-то еще. Сами build-ветки предыдущие тоже остаются, они никуда не деваются. В любой момент их можно взять и использовать.

Вопрос из зала: А в какой момент вы делаете предыдущую build-ветку? У вас же происходит релиз, в которой попал тот или иной функционал, потом вы сделали слияние. Где здесь предыдущая ветка? Или вы от основной ветки делаете тэг? Есть же какие-то стандартные подходы… Есть же какой-то тэг, на который в SVN можно переключиться с помощью switch, в Git можно использовать check out. Не совсем понятно просто, зачем вам система контроля версий, если у вас есть файлы, которые лежат не под SVN или Git, а сами по себе. И все то же самое хранится в системе контроля версий…

Илья Агеев: Все так, у нас все это хранится в системе контроля версий. Сейчас расскажу, зачем мы оставляем несколько loop-файлов. Подготовка loop-файла занимает какое-то время, чтобы его не тратить, мы оставляем несколько старых loop-файлов. Если мы что-то выложили, и у нас все сломалось, мы просто переключимся на старый loop-файл. У нас будет время подготовить новый loop-файл более качественно и правильно. Даже после слияния какой-то ветки с основной, ее код попадает в основную ветку, но сама она никуда не девается. В основной ветке у нас останется код самой основной ветки плюс код ветки, для которой выполнено слияние. Мы можем взять код этой ветки, выполнить для нее check out, и у нас будет код этой ветки.

Вопрос из зала: Хорошо. Еще вопрос. Насчет loop-файлов. Допустим, вы в основную ветку отправили все изменения, которые у вас есть, и сделали от этого отдельную ветку, релиз мартовский. Почему вы просто не держите на «боевых» серверах клон вашего репозитория и не делаете просто check out на эту ветку и потом точно так же не переключаетесь на новую версию кода с помощью символической ссылки? Зачем вам такие сложности с файлами и со всем остальным?

Юрий Насретдинов: В принципе, на самом деле тут большой разницы нет. Тут дело просто в том, что с помощью loop-файлов все можно делать намного быстрее. В одном случае мы копируем 10 тысяч файлов и даже больше, а в другом случае мы копируем 1 файл.

Реплика из зала: На самом деле, нет. Потому что когда вы делаете с помощью репозиториев, у вас 2 репозитория. Один с текущей версией «боевого» сайта работает, в второй с тем, что будет «накатываться». Вы делаете Git-пул асинхронно ночью или в любой момент времени – неважно, потому что ничего уже не будет попадать в новый релиз. После этого делается check out на новую ветку. Это, на самом деле, очень быстрый процесс в Git. После этого меняете символическую ссылку.

Юрий Насретдинов: Вы понимаете, что check out должен идти в 500 потоков со всех серверов?

Реплика из зала: Так вы делаете check out с соседней копии, а «боевой» сайт продолжает работать…

Юрий Насретдинов: Вы понимаете, что обращение к Git-серверу будет в 500 потоков?

Вопрос из зала: Это такая большая проблема?

Илья Агеев: Да.

Реплика из зала: Ну, делать и запускать ночью…

Юрий Насретдинов: Почему мы должны делать выкладку только ночью?

Илья Агеев: Немного неправильно полагать, что сегодня мы создали релиз, который будем выкладывать завтра, и за это время в релиз ничего не попадет. На самом деле есть очень много вещей, например, маркетинговых, которые надо выкладывать очень быстро. Де-факто в build могут добавляться какие-то вещи. Соответственно, мы создали build, протестировали его, и нам надо этот код туда добавить. Представьте, что мы на всех этих серверах, на двух своих площадках набираем Git-пул, чтобы «подтянуть» изменения. Это очень большой трафик, это очень долгий процесс. Мы это пробовали, на самом деле. Имейте в виду, что у нас еще 2 платформы, Америка и Европа. Применять loop быстрей.

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Олег Илларионов

Олег Илларионов

Разработчик социальной сети ВКонтакте.

Олег Илларионов (ВКонтакте) рассказывает о создании XMPP-сервера для крупнейшей соцсети Рунета и не только.

Борис Вольфсон

Борис Вольфсон

Борис Вольфсон занимается веб-разработкой и разработкой программного обеспечения с 2003 года. Карьеру начал в качестве программиста компании «Систем-Софт» в Оренбурге. С 2008 года – руководитель проектов и руководитель регионального отдела разработки в компании Softline.

Борис Вольфсон рассказывает о применении сбалансированной системы показателей в организациях, использующих Agile-методологию для разработки ПО.

Александр Кудымов

Александр Кудымов

Проектировщик интерфейсов, канбан-мастер.

Евгений Кобзев и Александр Кудымов (СКБ "Контур") объясняют, почему им стал тесен скрам и на какие грабли не стоит наступать.