Студия разработки сайтов и приложений

Netspark.ru

Docker и виртуализация локальных проектов

Когда какое-то время занимаешься разными проектами, возникает нужда в некоторой виртуализации рабочего пространства. Одному проекту нужен mysql, другому postgresql, третьему mariadb. Для четвертого нужен solr, у пятого старая версия php, и так далее. Даже если не дошло до конфликтов между версиями программ, держать все окружения включенными постоянно — не прикольно, а выключать/включать то что нужно руками — долго и можно запутаться.

Мне всегда хотелось, чтобы можно было запустить условную командочку work on my project, и чтобы все нужные сервисы поднялись, а ненужные — не мешались. А потом написать work on another project, и чтобы окружение переключилось на другой проект. И чтобы можно было легко перенести то или иное окружение на другой компьютер. И чтобы все они были одинаковыми, чтобы я не запутался: одинаковые названия БД, юзеры, пароли — чтобы отличались только тем, что действительно должно отличаться.

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

Когда до меня добралось осознание, что виртуализация необходима — ребята вокруг активно пользовались Vagrant. Но я как-то почитал матчасть про особенности работы, про скорость старта, и приуныл. Докер тогда был еще совсем молодой, но судя по статьям про него — то, что надо. Так оно и оказалось, контейнеры стартовали — и стартуют сейчас — меньше чем за секунду. Красота!

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

  • изначально (один раз) создаю контейнер с ОС, например Debian
docker run -it --name root-container debian /bin/bash
  • обычным способом (apt install etc) устанавливаю все что нам может понадобиться в общем случае: LAMP, русификацию консоли, расширения сервера и php, xdebug, ssh и т.д.
  • проверяю, что всё работает, и коммичу контейнер в образ:
docker commit root-container graker/my-root-container

Это всё. Теперь для каждого проекта я могу создать отдельный контейнер из этого общего образа.

docker run -it --name project1 -v ~/devel/project1:/var/www graker/my-root-container /bin/bash

И тогда для каждого проекта в консольке можно написать:

docker start -i project1

И окружение немедленно создастся, а консолька прибиндится к этому контейнеру под рутом. Если выйти из консольки — контейнер остановится, и не потеряет изменений. Снова сделаем docker start -i project1 — он снова запустится.

После старта контейнеру нужно поднять сервисы (сервер, БД, и т.п.) Для этого я держу в контейнере скриптик, в котором написано примерно

service apache2 start
service mysql start
service ssh start
# ...

И запускаю его сразу после старта. Это наверное главное неудобство всего процесса — необходимость вручную выполнять вторую команду после старта. Автоматизированно решить эту проблему мне не удалось: если при создании контейнера указывать скрипт, то он выполняется, но не включается интерактивный режим. А он нужен, так что все равно приходится выполнять вторую команду — входить в интерактивный режим. Так или иначе, Ctrl+R и одинаковое название скрипта во всех контейнерах (startup.sh) легко решает эту проблему, запускаю я скрипт быстро и машинально.

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

При этом к /var/www контейнера подмонтирована директория с кодом проекта, соответственно, я могу просто открыть ее в IDE и работать.

Если мне захочется поотлаживать код через xdebug, отладку можно пробросить через ssh-туннель.

Если понадобится работать над новым проектом, я из существующих контейнеров выберу наиболее близкий (по установленному софту) к необходимому, а дальше:

docker commit my-old-container graker/image-for-new-project
docker run -t -i --name my-new-project graker/image-for-new-project /bin/bash

И у меня создан контейнер для нового проекта, в котором есть всё, что было в старом. Можно просто почистить старые данные (например drop database drupal; create database drupal) — и работать.

Если понадобится перенести на другой компьютер, можно сохранять/разворачивать контейнеры одной строчкой (docker save/docker load). Да, архивы получатся большие, но это не такая частая операция, на самом деле.

Дополнительная плюшка: в каждом контейнере сохраняется свой файлик .bash_history. Что это значит? Это значит, что если я запускаю в каком-то проекте трехэтажную команду в консоли, скажем, что-нибудь с drush, или artisan, то она никуда не денется. Я могу не трогать проект полгода, а потом запустить контейнер и найти эту команду в истории, поскольку в ней будут команды, релевантные проекту, а не те, что я потом полгода запускал.

Иногда для того или иного проекта создаю дополнительные контейнеры. Но только если так действительно проще и быстрее. Например, Solr используется лишь некоторыми проектами, поэтому поставить контейнер с солром из официального докер-образа и подключить к нему рабочий проект — гораздо быстрее, чем ставить солр руками внутри контейнера. Но если бы солр использовался всегда, было бы проще один раз настроить его внутри контейнера, а потом закоммитить и наследовать.

И наоборот: можно было бы предположить, что рационально держать СУБД отдельным контейнером и подключать к нему проектные контейнеры для работы каждого со своей базой. Оно было бы правильно для организации сервера с несколькими сайтами, но для локальной работы это требует больше действий: подключать каждый раз контейнер к БД, создавать новую базу для каждого проекта, помнить как для каждого она называется и т.д. Вместо этого у меня в каждом контейнере стоит свой MySQL (или аналог), а база всегда называется одинаково: по имени фреймворка, например drupal, или laravel. И юзер, и пароль всегда тоже одинаковый. Освобождает память для более важных вещей.

В результате контейнеры занимают несколько больше места, но на терабайтном HDD мне этого вообще не заметно. А вот то, что меньше действий требуется — наоборот, заметно.

В целом это полностью описывает весь мой флоу по виртуализации проектов и переключению между ними. За несколько лет он прижился и всю разработку я веду только так — в контейнерах докера. Были у меня еще попытки автоматизировать установку Друпала в контейнеры (сделать сборку, или ставить из драш-дампа). Но я обычно стараюсь заниматься крупными/долгими проектами, поэтому необходимость создавать новые контейнеры под новые сайты возникает редко. Через это выгода от автоматизированной установки оборачивается геморроем по поддержанию сборки/дампа в актуальном состоянии. Тем более, для большого проекта все равно потом руками собирать. В общем, не прижилось.

Из недостатков я заметил, что не совсем понятно, как настроить PHPStorm, чтобы запускать тесты прямо из IDE по хоткеям. Приходится вручную их запускать из консоли, что требует чуть больше действий (переключиться на консоль, запустить phpunit с возможными аргументами). Неприятно, но не критично.

Кроме того, когда-то было ограничение для не-дебиан-подобных систем: не более 20 ГБ на контейнер. В openSUSE пару раз в это влетал когда-то, приходилось отцеплять огромную БД от основного контейнера в другой. Но на убунте такого ограничения нет, поэтому не знаю, может его уже вообще давно нет.

Комментарии