Docker контейнеры для web-разработчика под OS X

Всю свою текущую разработку я веду, используя docker контейнеры, и если под Linux такой подход не вызывает никаких проблем, то под OS X некоторые моменты могут отнять невероятное количество сил и времени.

Об одном из таких моментов я и хочу рассказать.

В моем случае разработка с иcпользованием docker выглядит так: на контейнере поднят весь необходимый мне софт. Папки с рабочим проектом смаплены с хост системы в контейнер. В контейнере для автобилда настроен либо grunt с grunt-contrib-watch, либо watchify, либо порой просто висит скриптик, использующий inotify-tools.

В случае веб-разработки цикл простой: я правлю на маке файлы проекта, watch утилитки стартуют билд, livereload обновляет web страничку. Но при таком подходе сразу возникает проблема — файловая система vboxsf входящая в Virtualbox Guest Additions невероятно медленная, отсюда:

  • watch утилитки начинают грузить систему процентов на 200;
  • даже небольшая сборка — например простая конкатенация файлов начинает занимать десятки секунд.

Это — неприемлемо.

Быстрое гугление показало, что nfs работает в разы быстрее, чем vboxfs. Настроить nfs сервер под OSX быстро и легко.

#включить nfs сервер
sudo nfsd enable
#отредактировать файл с папками для шаринга
sudo vi /etc/exports
# добавить папки которые надо сделать доступными например  /Users/user/my_web_project -mapall=ice
#проверить что нет ошибок
sudo nfsd checkexports
#проверить что папки подцпились
showmount -e


Дальше надо передать в docker ip адрес OSX хоста, чтобы можно было подцепить файловую систему:

#получить ip хоста висящего на интерфейсе virtual box
HOST_IP=`ifconfig vboxnet0 | tail -1 | awk '{print $2}'`
#передать в контейнер
docker run --name smap -p 3080:3080 -e HOST_IP="$HOST_IP" -d -t sentimeta/node_scikit_image


В контейнере надо накатить nfs клиента и замаунтить нужные папки:

#накатить клиента
sudo apt-get update
sudo apt-get install nfs-common
#замаунтить nfs osx папку
sudo mount -o nolock "$HOST_IP":/Users/user/my_web_project ~/my_web_project


И действительно, две проблемы уходят:

  • загрузка процессора около ноля;
  • билд теперь идет очень быстро.

Но появляется новая проблема — inotify события файловой системы nfs, на которых сидят все watch утилитки, проходят, но с задержками по 10-20 секунд.

Это — неприемлемо.

Поэтому было принято решение начать ловить inotify события на host машине и передавать информацию в docker контейнер.

Для решения проблемы я использовал следующие утилитки:



Сторона докера
Вешаем http веб сервер на 3080 порт:

watch -n0 'printf "HTTP/1.1 200 OK\n\n$(date && touch /home/user/my_web_project/watchhelper.tmp)\n" | nc -l -p 3080 >/dev/null && date'

Это самый настоящий веб сервер на bash висящий на 3080 порту, на каждый запрос он выполняет команду date и touch файла watchhelper.tmp, который надо положить вне исходников вашего проекта, который надо добавить в watch. Ниже я поясню, почему вне исходников проекта.

  • touch команда «трогая» файл вызывает inotify событие приводяещее к сборке;
  • date команда удобна для тестирования, ее вывод и есть ответ этого сервера.

Проверить, что работает, можно так:

  • На OSX получаем адрес boot2docker виртуальной машины:

    boot2docker ip 2>&1 | sed -n 2,2p | awk -F' ' '{print $9}'
    


И, ура — в браузере видим время, а в контейнере — мгновенное срабатывание watch утилиток.

Сторона OSX
Устанавливаем fswatch:

brew install fswatch 


Запускаем:

fswatch-run /Users/user/my_web_project/src "curl http://`boot2docker ip 2>&1 | sed -n 2,2p | awk -F' ' '{print $9}'`:3080"


Теперь при изменении любого файла в папке /Users/user/my_web_project/src будет вызван поднятый в контейнере веб сервер, который «потрогает» файл, что в свою очередь вызовет билд.

Файл watchhelper.tmp надо расположить вне исходников проекта по причине, что nfs все-таки пропускает inotify события и лежащий в исходниках файл вызовет вечный цикл curl touch touch событий.

Различные вопросы настройки докер контейнеров под макось я нерегулярно добавляю в проект на гитхабе istarkov/docker.

Если тема вызовет отклик — напишу еще несколько заслуживающих внимания вещей при использовании docker контейнеров при разработке на OS X.

PS:
Мне сообщили о небольшой проблеме с этим подходом.
Если редактор sublime text 3 и длина файла после внесения изменений осталась прежней, то изменения не попадают в nfs клиента, т.е. в docker.

Вылечить можно так — в настройках sublime, в файле Preferences.sublime-settings сменить крыжик {«atomic_save»: true} на {«atomic_save»: false} и все будет работать как надо.

PPS:
В комментариях descentspb дал более простое решение этой проблемы, рекомендую использовать его подход.
Поделиться публикацией

Похожие публикации

Комментарии 12

    +1
    на контейнере поднят весь необходимый мне софт

    Это противоречит идеологии докера «один контейнер — один сервис (служба, софтина)». Отсюда возникает вопрос — а чем вас не устраивает просто виртуальная машина, собранная по конфигам. Какой-нибудь Vagrant с шефом или с пачкой bash-скриптов. Тем более что boot2docker это и есть виртуальная машина и вы не получаете всех плюшек паравиртуализации контейнерами, которые есть на линуксе.
      +2
      Про софт: порой просто чтобы запустить простейший ресурс на nodejs необходимо весьма немалое количество софта — в моем случае обычно это и g++ и ruby (sass) и еще куча всего.

      Идеология докера не заключается в этой фразе — «одна служба один контейнер» порой для работы одной службы необходимо для ее обслуживания поднять еще 10 служб.
      Правильней рассматривать идеологию докера что это stateless виртуальная машина с очень быстрым стартом. (Я например часто использую докеры по аналогии с gnu утилитами — передаю в stdin контейнера данные — читаю из stdout)

      Теперь про плюшки паравиртуализации и тп.
      Так уж получилось, что моей рабочей машиной является macbook и boot2docker это необходимое зло, а весь софт который я пишу работает на linux серверах.
      Часто в режиме отработал по запросу и закрылся: отсюда каждый раз под запрос поднимать виртуальную машину не вариант — долго, держать пачку виртуальных машин включенными тоже не вариант — очень уж большая пачка получается.

      В интернете есть куча дискуссий на тему чем докер лучше-хуже виртуальной машины и решать что использовать вам.
      В моем конкретном случае докер удобнее и лучше.

      Решать использовать ли при разработке шефы вагранты и тп тоже вам — моя статья не об этом и никаким образом не запрещает этого делать.

      +3
      Не самое логичное и идейно-верное использование докера. Сама философия проекта как раз не в том чтобы сделать максимально укомплектованную виртуалку а в том чтобы сделать контейнер с минимально необходимым для его работы окружением. Фактически, проще было сделать полноценную виртуалку и потом при необходимости использовать ее образ.

      Нет, конечно задача решается, но приспосабливать один инструмент под задачи другого — не лучший подход.
        0
        В моей статье нет ни слова о том, что надо максимально докоумплектовывать докер контейнер, в ней вообще об этом не говорится,
        более того я не использую софт выходящий за рамки стандартного, либо уже установленного в системе, либо необходимого для билда проекта.
        netcat обычно уже стоит на любом linux,
        grunt и его плагины выступают как make для веб проектов

        Поэтому я не вижу каким образом мой пример использование докера нарушает идею сделать минимальное окружение для работы с контейнером.
          0
          В принципе вы ответили на комментарий выше уже более универсально.

          Вообще тема интересная, возможно некоторое ложное впечатление как раз и сложилось из-за недостатка информации по вашему пути использования. Пишите еще.
            0
            Да тема интересная, да и сами докеры вначале пути — пока они очень удобны для использования в рамках одного хоста, и требуют дополнительных усилий при работе на разных, (почти все имеющиеся технологии связи контейнеров в рамках разных машин нарушают основную по мне вещь — неизменность окружения в контейнере либо требуют дополнительных контейнеров)
            думаю в скором времени эти вопросы будут решены средствами самого докера и всем будет хорошо :-)
              0
              Вообще я дальнейшее развитие виртуализации вижу еще более интересным — автономные контейнеры, способные прозрачно использовать ресурсы других контейнеров по популярным протоколам. Ну либо еще интересная была бы концепция разделения ресурсов и приложений в рамках одной платформы, когда контейнеры по сути выступают потребителями общего ресурса, и вся эта радость прозрачно логируется.
        +1
        Решил абсолютно ту же самую проблему чуть проще:

        Локальный код монтируется через NFS в такую же папку внутри boot2docker, что и локально.

        Например, путь к коду получается /Users/user/projects/someproject и на OS X, и внутри boot2docker.
        Если у вас другие пути, то простым -v "$PWD":"$PWD" (описано ниже) будет не обойтись

        Когда меняется файл локально, мы делаем «touch» того-же файла удаленно, чтобы ядро подхватило. inotify не видит «чужие» изменения в NFS, но видит те, которые сделала система локально. Никаких http клиентов и серверов, а также суррогатного файла не нужно при таком подходе.

        function fswatch_propagate_pwd_changes_to_docker () {
            echo "Starting fswatch on $PWD"
            # tracking previous not to get into endless loop of changing the same file
            local previous=''
            fswatch -r "$PWD" | while read file; do
                if [[ previous != "$file" ]]; then
                    docker run --rm -v "$PWD":"$PWD" busybox touch "$file"
                fi
                previous="$file"
            done
        }
        
        


          0
          Да действительно проще, такой вопрос а после рестарта boot2docker заново перемонтируете nfs или он изменения в fstab запоминает?
            0
            Не решал еще эту проблему. Пока монтирую руками, но это делать нужно редко, лишь один раз при рестарте boot2docker. Системные файлы у него не поменять, т.к. образ системы read-only.

            Пока, к сожалению, мне видится что единственный способ это сделать — собирать образ boot2docker вручную с нужными изменениями. Надеюсь, есть что-то попроще и красивее.
              0
              В любом случае добавил в PPS статьи ссылку на ваше решение как на более простое и рекомендованное. Сам подправлю образ boot2docker тем более что он у меня всегда подправленный. Меня не устраивает размер dev/shm в докер контейнерах поэтому всегда пересобираю как сам докер так и boot2docker
            +1
            Кстати, чтобы touch не создавал файлы, которые удаляются, нужно использовать touch -c
            На всякий случай, если будут еще изменения в функции, вынес ее в gist, чтобы комментарии лишние вроде этого не плодить

            gist.github.com/ikatson/7c6a7e84339305090457

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое