А зачем?
Если вы работали на macos в docker окружении, то наверняка сталкивались с проблемой в производительности при volume mount, например, при работе над php проектом, операции с файловой системой хоста (обновление пакетов composer, ребилд контейнеров symfony, etc.) занимают просто неадекватное количество времени. Об особенностях работы docker'а на macos написано уже множество статей, а также workaround'ов как заставить его работать быстрее. В этой небольшой статье покажу как в решении этой проблемы Mutagen помог мне с php проектом и быть может поможет вам.
Что такое Mutagen
Mutagen - мощный инструмент для файловой синхронизации и сетевой переадресации, он является быстрой альтернативой стандартного volume mount средствами docker и при этом субъективно более удобной в сравнении с Docker Sync или NFS Mount и может быть легко добавлен в конечном проекте.
Описание гласит:
Mutagen’s file synchronization uses a novel algorithm that combines the performance of the rsync algorithm with bidirectionality and low-latency filesystem watching.
Хорошо, low-latency filesystem watching это как раз то, что нам нужно.
Установка Mutagen
В первую очередь необходимо установить mutagen (логично). В примере покажу установку с помощью brew, она тривиальна за исключением необходимости tap'нуть нужное хранилище:
$ brew tap mutagen-io/mutagen
$ brew update
$ brew install mutagen
После установки рекомендую создать скрипт автокомплита, потому что мы же все любим автокомплит)
Делается это единожды, с помощью встроенного генератора нужного shell скрипта:
С недавних пор, при установке через brew, вместе с mutagen'ом создается файл автокомплита в /opt/homebrew/share/zsh/site-functions
, что удобно и не вынуждает дописывать что-то в .zshrc
# ZDOTDIR="${HOME}/.zsh"
$ mutagen completion zsh > "${ZDOTDIR}/compoteion/mutagen.sh"
echo 'source "${ZDOTDIR}/completion/mutagen.sh"' >> .zshrc
В качестве оболочки для которой генерируется скрипт, помимо zsh возможны bash, fish и даже powershell - удобненько).
На хостовой машине mutagen работает как демон, запускаемый командой:
$ mutagen daemon start
а чтобы не делать этого постоянно, он умеет добавлять (и удалять) себя в автозапуск через
$ mutage daemon register # unregister
После всех этих манипуляций можно получить список sync'ов, чтобы проверить что все установилось без проблем:
$ mutagen sync list
--------------------------------------------------------------------------------
No synchronization sessions found
--------------------------------------------------------------------------------
Как настроить работу с Mutagen'ом
Часто для создания рабочего окружения мы пользуемся docker-compose - им удобно собирать всю инфраструктуру, которая может пригодиться. И для того чтобы воспользоваться преимуществами синхронизации файлов через mutagen, нам необходимо внести изменения в docker-compose.yml, в которых отключим volume mount для директории с проектом и добавляем volume'ы в которые будет идти синхронизация:
$ diff --git a/docker-compose.yml b/docker-compose.yml
index 124dfb9..d94338e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -2,3 +2,15 @@ version: "3.8"
# Add new services, volumes, networks or
# override declared in .environment/docker-compose.yml services
# if it's necessary
version: "3.8"
+
+volumes:
+ storage-php:
+ storage-nginx:
+
services:
nginx:
depends_on:
- php
image: nginx:1.21.1
env_file: .env
ports:
- ${PUBLISHED_NGINX_PORT}:8080
volumes:
- - ../public:/var/www/app/public
+ - storage-nginx:/var/www/app/public
php:
image: php:8.1
working_dir: /var/www/app
env_file: .env
volumes:
- - ../:/var/www/app
+ - storage-php:/var/www/app
+ - storage-nginx:/var/www/app/public
ports:
- ${PUBLISHED_FPM_PORT}:8080
user: root
extra_hosts:
- "host.docker.internal:host-gateway"
и перезапустим окружение. В моем случае поднятие проекта сделано через команду в makefile'е и включает в себя composer install как последний этап, из-за чего при первом запуске увидел такое сообщение:
Composer could not find a composer.json file in /var/www/app
To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage
make: *** [docker-start] Error 1
Что логично - выполняемый composer install
не может найти файл composer.json в созданных volume'ах т.к. файлы еще не перенесены в контейнер, поэтому добавим такое действие в makefile:
DOCKER=docker-compose --env-file=./.env --file=./docker-compose.yml
.PHONY: mutagen-sync # Sync app volume with mutagen
mutagen-sync:
${DOCKER} images php | awk '{ if (NR!=1) { print $$1 } }' | ( read container; \
mutagen sync create \
--name=${COMPOSE_PROJECT_NAME} \
--default-file-mode-beta=0644 \
--default-directory-mode-beta=0755 \
--sync-mode=two-way-resolved \
--ignore=.git/,.idea/,.DS_Store \
. docker://root@$$container/var/www/app )
Что действие делает: оно создает новый sync в mutagen между volume'мом с именем из переменной окружения COMPOSE_PROJECT_NAME, задаст права для файлов и директорий которые будут синхронизированы в volume, установит режим синхронизации и добавит в игнор то, что нет смысла синхронизировать.
Важно то, что mutagen подключается прямо к контейнеру по его имени, но при запуске docker-compose сам создает контейнерам имена по собственному шаблону, поэтому чтобы точно сказать mutagen'у куда подключаться, мы просто awk'аем список контейнеров по имени сервиса из docker-compose.yml.
Как пользоваться
Максимально просто - после всех манипуляций описанных выше, достаточно после поднятия docker-compose выполнить make mutagen-sync
, и увидеть в консоли
$ make mutagen-sync
Created session sync_ka1nSJCfLPlOnLxiXyPe4ScFQ0WtyOmikqlAPaGnxos
Это значит что сессия синхронизации успешно создана. Для дальнейшего мониторинга статуса синхронизации и ее остановки добавим в makefile пару действий:
.PHONY: mutagen-terminate # Terminate mutagen app sync
mutagen-terminate:
mutagen sync terminate ${COMPOSE_PROJECT_NAME}
.PHONY: mutagen-monitor # Stats of mutagen syncing
mutagen-monitor:
mutagen sync monitor ${COMPOSE_PROJECT_NAME} --long
Теперь make mutagen-monitor
покажет подробное real-time состояние sync'а проекта:
$ make mutagen-monitor
Name: php-project
Identifier: sync_ka1nSJCfLPlOnLxiXyPe4ScFQ0WtyOmikqlAPaGnxos
Labels: None
Configuration:
Synchronization mode: Default (Two Way resolved)
Maximum allowed entry count: Default (2⁶⁴−1)
Maximum staging file size: Default (18 EB)
Symbolic link mode: Default (Portable)
Ignore VCS mode: Default (Propagate)
Ignores: None
Alpha configuration:
URL: /Users/tonysol/Projects/php-project
Watch mode: Default (Portable)
Watch polling interval: Default (10 seconds)
Probe mode: Default (Probe)
Scan mode: Default (Accelerated)
Stage mode: Default (Mutagen Data Directory)
File mode: Default (0600)
Directory mode: Default (0700)
Default file/directory owner: Default
Default file/directory group: Default
Beta configuration:
URL: docker://root@php-project_php_1/var/www/app
Watch mode: Default (Portable)
Watch polling interval: Default (10 seconds)
Probe mode: Default (Probe)
Scan mode: Default (Accelerated)
Stage mode: Default (Mutagen Data Directory)
File mode: Default (0644)
Directory mode: Default (0755)
Default file/directory owner: Default
Default file/directory group: Default
Status: Watching for changes
а make mutagen-terminate
остановит созданный sync по его имени - безопасно если есть несколько параллельно живущих проектов docker-compose.
И вот пример списка sync'ов после запуска:
$ mutagen sync list
--------------------------------------------------------------------------------
Name: php-project
Identifier: sync_ka1nSJCfLPlOnLxiXyPe4ScFQ0WtyOmikqlAPaGnxos
Labels:
None
Alpha:
URL: /Users/tonysol/Projects/php-project
Connection state: Connected
Beta:
URL: docker://root@php-project_php_1/var/www/app
Connection state: Connected
Status: Watching for changes
Статус Watching for changes
говорит о том что файлы успешно синхронизированы. Убедиться в этом можно, перейдя в консоль контейнера и запустив composer install
.
Результат
Самое интересное - а насколько быстрее стала работа.
Использование mutagen'а обкатывалось на тестовом проекте основанном на Symfony. При этом для docker включены опции:
✔ Use gRPC FUSE for file sharing (используется macFUSE 4.2.4)
✔ Use Docker Compose V2
✔ Use the new Virtualization framework
Содержание ~/.docker/daemon.json
:
{
"builder": { "gc": { "defaultKeepStorage": "20GB", "enabled": true } },
"experimental": false,
"features": { "buildkit": true }
}
Выделенные docker'у ресурсы:
CPUs: 4 | Memory: 6.00 GB | Swap: 2 GB | Disk image size: 59.6 GB
Перед каждым замером времени в консоли, полностью удалялись директории vendor/
и var/cache/dev/
Docker volume mount
root@22d281c05041:/var/www/app# time composer install --quiet
real 6m50.509s
user 2m26.838s
sys 1m27.755s
root@22d281c05041:/var/www/app# time bin/console cache:clear
// Celearing the chae for the dev environment with debug true
[OK] Cache for the "dev" environment (debug=true) was successfully created.
real 0m35.079s
user 0m6.226s
sys 0m3.308s
root@22d281c05041:/var/www/app#
Mutagen
root@9a6d7a272f38:/var/www/app# time composer install --quiet
real 0m25.678s
user 0m18.139s
sys 0m10.999s
root@9a6d7a272f38:/var/www/app# time bin/console cache:clear
// Celearing the chae for the dev environment with debug true
[OK] Cache for the "dev" environment (debug=true) was successfully created.
real 0m9.664s
user 0m7.445s
sys 0m2.211s
root@9a6d7a272f38:/var/www/app#
Выводы, субъективные впечатления и etc.
Несмотря на то что все описание было в контексте php, все эти манипуляции применимы не только для обхода проблемы с docker volume в целом, но и, например, для real-time файловой синхронизации с выделенным сервером по ssh+scp.
Файловые операции внутри контейнера выполняются со скоростью нативного окружения, например
rm -Rf vendor/
в случае mutagen выполнялся практически мгновенно, в то время как при volume mount это занимало ощутимое время.Несмотря на то что замеры проводились по 4 раза, такой разбег при выполнении
composer install
сохраняется (разумеется с определенной долей погрешности), но учитывая что дляcache:clear
разница не настолько значительная, тяжело предположить причины такого провала скорости.-
Задержки между синхронизацией, даже если они случаются, не ощущаются:
1) Задержка при volume mount так же присутствует (связана с синхронизацией между VM и host).
2) IDEA сама по себе не настолько быстро обновляет структуру проекта.
Судя по системному монитору ресурсов (и htop'у), увеличение нагрузки при работе с mutagen если и есть, то не заметное.
У mutagen есть свои инструменты орекстрации - Compose (заменяющий docker-compose, конфигурируемый прямо в docker-compose.yml) и Projects, но это для меня заклинания следующего уровня, поэтому здесь я их не рассматривал.
Реализация Compose основана на Docker Compose V2, поэтому рекомендуется включить использование V2 в настройках Docker Desktop, и имеет определенные ограничения.
Комментарии (10)
AlexGluck
13.05.2022 13:48Чем бы дитя не тешилось, лишь бы линукс не ставить)
ALexhha
14.05.2022 08:41+1Чем бы дитя не тешилось, лишь бы линукс не ставить)
В больших компаниях есть понятие обязательного корпоративного стандарта. И часто в их списки входят windows и/или macos. Линукс не встречал ни разу. Так что и сам "мучаюсь" последние 3 года на macos
zvermafia
13.05.2022 16:38Работал на iMac проц. (2 ядер, 4 потока), скорость записи на диск ~500MB/s, использовал docker desktop.
Перешел на ПК Windows (12 ядер, 24 потока), скорость записи на диск ~5300MB/s, использовал docker desktop с опцией WSL2 engine.
Не заметил разницу что билд образа, что установка пакетов composer внутри контейнера......занимают просто неадекватное количество времени...
Вообще такого не было. О чем вы вообще?!TonyKentnarEarth Автор
13.05.2022 18:36А на mac'е это был выделенный volume или mount на хост?
zvermafia
14.05.2022 19:11использовал опцию -v в CLI, и директиву volumes в конфиг файле для docker compose
jgudmund
14.05.2022 23:00Хм, какб была же уже ранее edge ветка у докера с этим самым мутагеном. Сейчас virtiofs уже используется.
BullDER
И насколько это релевантно после релиза virtiofs?
TonyKentnarEarth Автор
На момент теста VirtioFS еще не был доступен, надо будет сравнить насколько он будет быстрее/медленнее, чем предложенный вариант
Vladivo
VirtioFS, по ощущениям, несколько медленнее, но в целом оказался достаточно хорош, чтобы мы сразу отказались от мутагена. Маунтим около двух гигабайт с node_modules, yarn cache и Cypress. Билдим и гоняем e2e тесты в докере.
glebovgin
Мы на рабочих проектах в итоге отказались от mutagen в пользу virtiofs. Медленнее не стало уж точно.