В рубрике DOU Labsмы приглашаем IT-компании делиться опытом собственных интересных разработок и внутренних технологических инициатив. Вопросы и заявки на участие присылайте на valentina@dou.ua.
Эта история о том, как мы в EVO разрабатывали Vagga — инструмент для управления зависимостями, сборки контейнеров и менеджмента процессов на машинах разработчиков. Мы стремились стандартизировать рабочее окружение и навсегда избавить коллег от отслеживания зависимостей вручную, а также подготовить почву для микросервисов. В некоторой степени нам это удалось — мы сделали Vagga, и это полностью open source проект.
Когда я присоединился к prom.ua (теперь — группа компаний EVO) в январе 2014 года, Docker’у еще не было даже года от роду. Тем не менее, уже на собеседовании мы говорили о том, чтобы попробовать его в компании. Смешно вспоминать, но тогда я отозвался о технологии как о чем-то дико новом и магическом и даже высказал свои опасения по этому поводу. За последующий год я узнал о контейнерах так много, что мы написали свою систему контейнеризации. Ниже — обо всем по порядку.
Мотивация
Когда речь заходит о контейнеризации, большинство компаний в первую очередь запускают в контейнерах боевые сервера. Мы же решили начать с рабочих мест разработчиков. У нас было на то две причины.
Первая — простая и банальная — у нас было около 100 зависимостей. Некоторые из них были настолько старые, что их не было на PyPI (Python Package Index). Кроме того, чтобы собрать Javascript и CSS файлы, нужно было установить Ruby и Node.js. В общем, разворачивание окружения на новой машине занимало кучу времени.
Вторая причина — размер команды. В 2014 году это была компания, которая делает один проект. 35 человек, которые ежедневно вносят правки в prom.ua. Для одного проекта это уже довольно большая команда, и мы были готовы расти еще больше. Как вы думаете, как часто в такой команде возникают проблемы, вроде «works on my machine»? Насколько повсеместно находят баги, связанные с неправильными версиями зависимостей? Сколько времени занимает выяснение, что зависимость нужно было обновить, но этот конкретный разработчик этого не сделал?
Конечно же, мы ухватились за Docker, как за спасательный круг. Первое, что мы выяснили, — использовать docker напрямую практически не возможно. Например, вот как можно запустить какой-нибудь проект для работы:
docker run -i -t --rm --volume=$(pwd):/work --workdir=/work my-app:v1.2.3 my command
Ну и это безумие нужно вводить каждый раз. Поэтому, первый вариант контейнеризации был набором shell-alias’ов поверх докера, чтобы упростить запуск команд.
Зарождение Vagga
Очень быстро стало понятно, что мы хотим не только избавиться от длинных docker’овых команд, но и упростить и документировать наши внутренние команды. Вот некоторые примеры того, что мы часто запускаем:
paster serve development-my.ini
paster celery development-my.ini worker
make test test_db_name=my_database
webpack --progress --colors --watch
Первое, что мы хотели — это уменьшить порог входа, чтобы команды, описанные выше, выглядели вот так:
Vagga run
Vagga celery-worker
Vagga test
Vagga make
Чаще всего это делают с помощью команды «make». Однако, «make» — это инструмент для сборки, а значит, вы не получите красивого списка команд с описаниями:
$ Vagga Available commands: run Run application celery-worker Run celery worker build-static Build static files (JS, CSS, sprites) test Run short set of unit and functional tests
Проблема зависимостей
Но это был лишь первый шаг. Вторая проблема заключается в добавлении зависимости в проект. Старый подход состоит из множества шагов:
1) Добавляем пакет в requirements.txt;
2) Пишем всем разработчикам на почту, чтобы обновились;
3) Уведомляем админов, чтобы установили его на всех серверах.
Ну и ждём, пока самые ленивые увидят ошибку, отвлекут от дел всю команду, чтобы помогли выяснить, в чем проблема, и потом услышат негодование: «Ну так письмо же было! Видел?»
Можете догадаться, какой кошмар происходит, когда зависимость нужно откатить обратно или обновить еще раз. Ну или если пакет нужно втянуть только в ветку репозитария. А что там представлять, большинство из вас, думаю, это всё еще наблюдает у себя на рабочих местах.
Как это починить? Vagga смотрит на «requirements.txt» (ну или «package.json», «Gemfile» и т.д.) и создаёт новую версию контейнера. Версия контейнера — это хеш от списка зависимостей.
Это означает, что если вы локально поменяли «requirements.txt» — контейнер пересоберётся. Если вы сделали «git push», а ваш коллега сделал «git pull», то у него соберётся точно такой же контейнер. Если вы переключились в ветку — вы получите те зависимости, на которые эта ветка рассчитывает. И если ваш CI (Continuous Integration) запускает тесты, то он тоже проверит их на правильных зависимостях.
Микросервисы
Я пришел в компанию EVO из небольшого игрового стартапа. Мы там всё делали, используя микросервисы. Для этого у нас был свой менеджер процессов. Его логику мы и реализвали в Vagga. Когда вы делаете сервис, вы тестируете его в окружении других сервисов. Чтобы не терять время, нам часто нужно запускать много сервисов одновременно. Быстро. Легко. Одной командой. И также быстро и легко их останавливать. Насовсем. Не беспокоясь, что какой-то из них останется работать где-то в фоне. Старый. Запущенный 3 дня назад. Вы же знаете о чём я говорю, правда?
Чтобы не терять время на отладку, нужно также быстро понимать, что один из сервисов упал. Vagga по-умолчанию поступает очень просто — убивает при этом все процессы, которые запустила. Это самый простой и надёжный способ показать, что что-то не так. В наших играх это происходило довольно часто — запускаешь проект, проходишь уровень-другой, потом замечаешь, что один из неинтерактивных (то есть не заметных сразу, но тем не менее важных для данного теста) сервисов не поднялся.
В web это заметно меньше и реже. Но тем не менее, мы начали запускать postgres и redis правильной версии. Мы включаем полнотекстовые индексы с помощью elasticsearch. Мы постепенно включаем все зависимости, которые раньше не запускали вместе с приложением, в главную команду запуска. Это удобно.
Где же суперсила?
В каждом проекте у нас есть, как правило, три команды:
vagga run
vagga test
vagga doc
Это значит, что любой — от программиста до тестировщика или менеджера проекта — может запустить проект. И это значит, что когда к вам приходит новый сотрудник, он моментально может приступить к работе:
— Он запустит «Vagga run» и зайдёт в браузере по ссылке, которую отпечатает эта команда;
— Он пойдёт в документацию и сразу же сможет починить в ней неясности, потому что знает, куда писать и как её потом собрать;
— Он посмотрит на команды, которые запускает «Vagga run», и увидит, какие конфиги там прописаны, какие процессы запускаются, какие базы данных используются;
— Он попробует что-то поправить и увидит результат, так как «Vagga run», как правило, включает в себя настроенный live-reload;
— Он запустит просто «Vagga», посмотрит, что там есть, ну например: запуск celery-задач, миграции базы данных, разные режимы запуска приложения, нагрузочные тесты, ну и, в конце концов, даже команда для выкатки проекта на боевые сервера (но без необходимых ключей, как вы можете догадаться);
— Он уже знает, как запустить тесты и как добавить еще один тест (просто посмотрев в определение команды «Vagga test»);
— Он уже знает, как добавить в проект зависимость так, чтобы не испортить никому
другому рабочий день
И всё это на расстоянии одной команды. Команды, которая в некотором смысле, сама себя документирует. И когда ваш новый сотрудник попробует это всё, вы сможете обсудить более сложные вопросы и уделить больше времени непосредственно решаемым задачам.
Итоги
Разработка Vagga была непростой. Мы столкнулись с самыми разными проблемами, начиная от хитростей всевозможных менеджеров пакетов до тонкостей
настройки linux-дистрибутивов и багов ядра linux. Но сложнее всего оказалось донести до пользователей необходимость поменять их устои. Большинство программистов считают, что проблемы с обновлением и установкой зависимостей у них достаточно редки, чтобы изучать какой-то новый инструмент. Так мыслят и внутри компании, и такие же отзывы мы слышали, когда говорили об этом на конференциях.
Мы же, те, кто давно пользуется Vagga, уже не представляем, зачем это делать по-другому. Так или иначе, вопросы про неправильные версии зависимостей исчезают из наших ежедневных забот и освобождают наше время для более полезных задач.
Vagga — это open source проект. Мы хотим, чтобы его попробовали и вы. Надеюсь, он и вас избавит от головной боли и позволит вам делать ваш продукт лучше.