А зачем?
Если вы работали на 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, и имеет определенные ограничения.
