Пришла задача WEB-12982
Создаешь ветку web-12982 в репозитории
Пока ветка собирается, читаешь тз и пьешь кофе
Приступаешь непосредственно к разработке
git commit, git push
Пока ветка пересобирается листаешь хабр
git commit, git push
Пока ветка пересобирается листаешь твиттер
git commit, git push
…
Сдаешь на ревью ветку с 50 коммитами
Понимаешь, что 50 коммитов — это ровно 50 минут чистого времени, которое собрано урывками, потому что отрезки по 1 минуте слишком малы, чтобы заниматься чем-то, кроме прокрастинации и элементарных потребностей.
Знакома ситуация? В моей компании инфраструктура разработки организована таким образом:
- В гитлабе есть множество репозиториев по проектам
- Чтобы обеспечить удобство разработки при создании новой ветки автоматически докерами создается своя песочница по уникальному адресу, полная копия родительской ветки со всем необходимым окружением.
- Все, что нужно, уже готово — просто пиши код и тестируй-смотри-оценивай результат после каждого коммита, очень удобно!
Но, медленно... Если тебе близка эта ситуация, добро пожаловать под кат.
Суть проблемы
tldr: Внесенные в код правки требуют пересборки контейнеров и тратят время (более минуты, сильно зависит от проекта, особенно если настроено CI\CD), которое по факту не получается потратить с пользой, но которое суммарно отнимает приличный кусок от рабочего времени программиста.
Например, тот же liveReload для фронтенда придумали явно не просто так
Ранее я уже публиковал статью на смежную тему, но связанную с процессом отладки (Кстати, спасибо за информативные комментарии и положительные отзывы). Однако, проблема над катом по сути никуда не делась, мы также продолжаем ждать, пока ветка пересобирается.
Даже если пропускать дополнительные стадии и оставлять только build-dev и deploy-dev, время ожидания незначительно для каких-либо полезных действий, но значительно в суммарном потраченном времени, особенно если речь идет про CI\CD например от Gitlab.
Конечно, можно ускорить процесс, собирая проект локально, при условии, что у программиста относительно мощный компьютер, настроен gitlab-runner, настроено такое же окружение, как и на удаленном сервере, и добавлены соответствующие теги в gitlab-ci.yml, но сомневаюсь, что скорость сборки будет такая же, как в свое время автоматический деплой кода по FTP после клавиш ctrl+s.
Особенно подгорает бесит выматывает, когда в процессе разработки допускаешь опечатки\ошибки, которые в целом не влияют на работу приложения, но которые нельзя оставить просто так и замечаешь их только, когда смотришь на результат после сборки.
Что требуется и варианты решения
tldr: Найти способ, чтобы максимально просто и быстро видеть результат внесенных правок. Чтобы не коммитить каждый раз и не ждать пересборки ветки. Я выбрал rsync с локального компьютера в удаленную папку сервера, смонтированную с папкой контейнера.
Я не нашел в интернетах полностью готовое решение, но на самом деле вариантов несколько:
- Настроить ftp\ssh непосредственно в сам контейнер с кодовой базой, включить его в образ, подключаться по FTP непосредственно к самому контейнеру
- Лично мне этот вариант показался несколько сложным, и слишком "костыльным", хотя здесь все варианты являются костылями
- (для локального использования) использовать docker cp для загрузки кода непосредственно в контейнер
- Вариант не подходит для работы с удаленным сервером
- docker cp имеет крайне ограниченный функционал, учитывая вендорные папки, которые не следует копировать каждый раз, и сам алгоритм копирования, это будет достаточно медленно.
- (для удаленного\локального использования) Смонтировать нужную папку контейнера к внешней папке хоста. Загружать локальные файлы непосредственно в монтированную папку хоста. Тут уже масса вариантов реализации:
- Использовать docker-machine и конкретно docker-machine scp
- Опять же, нужно настраивать окружение, настраивать docker-machine, возможно это имеет смысл, когда постоянно работаешь с разными серверами
- В IDE настроить FTP-соединение с нужной папкой хоста
- Каждая новая ветка требует создания нового соединения или изменения маппингов
- Использовать scp или rsync для загрузки файлов в нужную папку хоста, для этого использовать небольшой bash-скрип и повесить его на горячие клавиши
- По началу кажется, что излишне сложно, но на самом деле это не так. Сам скрипт максимально простой и нужен для автоматизации процесса, чтобы каждый раз не приходилось перенастраивать маппинги
- Использовать docker-machine и конкретно docker-machine scp
Само решение: rsync + volumes
tldr:
- Нужен ssh доступ к удаленному серверу и rsync на локальной машине
- На удаленном сервере должна быть свободная для записи папка
- Монтируем папку проекта в контейнере к внешней папке хоста
- Добавляем небольшой bash-скрипт в корень проекта для синхронизации файлов с удаленным сервером
- Настраиваем горячую клавишу на выполнения скрипта синхронизации
Стоит отметить, что решение предусмотрено исключено для разработки и тестовой среды
В данном случае я рассмотрю окружение docker-compose и gitlab-ci, при этом docker-compose использует переменные окружения из gitlab-ci.
Формируем путь к конечной папке в gitlab-ci и экспортируем этот путь в docker-compose.yml:
before_script:
- export SHARED_DIR_BASE='/var/www/builds' # папка на удаленном сервере, в которой будем хранить проекты
- export SHARED_BRANCH_DIR=${SHARED_DIR_BASE}/${PROJECT_GROUP}/${PROJECT_NAME}/${CI_COMMIT_REF_NAME} # допустим мы создаем ветку web-123 в проекте my_group/my_project, конечный путь для записи будет /var/shared/my_group/my_project/web-123
Deploy dev:
stage: deploy_dev
script:
# непосредственно создаем папку, переносим туда проект и настраиваем права на запись
- mkdir -p ${SHARED_BRANCH_DIR}
- rsync -r --exclude-from=.gitignore --exclude-from=.dockerignore . ${SHARED_BRANCH_DIR}
- find ${SHARED_BRANCH_DIR} -type d -exec setfacl -d -m o:rwx {} \;
- find ${SHARED_BRANCH_DIR} -type d -exec setfacl -m o:rwx {} \;
- find ${SHARED_BRANCH_DIR} -type f -exec setfacl -m o:rwx {} \;
- envsubst < docker-compose.tmpl > docker-compose.yml # Переносим переменные окружения из gitlab-ci.yml в docker-compose.yml, шаблоном является docker-compose.tmpl
- docker-compose up -d
Далее нам нужно смонтировать папки проекта к внешней папке хоста в docker-compose, так как мы используем переменные в docker-compose, нам требуется шаблон docker-compose.tmpl, в котором мы будем использовать эти переменные.
version: '2.3'
services:
web:
...
volumes:
- ${SHARED_BRANCH_DIR}:/app/:rw # Непосредственная привязка внешней папки хоста к внутренней папке контейнера с самим приложением
# Строки ниже уже индивидуальны в зависимости от проекта. Общая суть в том, что нам нужно смонтировать общую папку, но при этом НЕ МОНТИРОВАТЬ вендорные или динамические файлы и папки. Иначе при загрузке файлов из локальной машины в смонтированную нужно переносить и эти файлы, которые как правило довольно громоздкие, либо они могут быть удалены при синхронизации
- /app/protected/vendor/
Уже текущей конфигурации достаточно, теперь при сборке ветки на хост-сервере будет создана папка /var/www/builds/ИМЯ_ГРУППЫ/ИМЯ_ПРОЕКТА/НАЗВАНИЕ_ВЕТКИ и туда перенесен сам проект за исключением тех файлов и папок, которые указаны в .gitignore и .dockerignore, далее можно просто настроить FTP-маппинги, но мы пойдем немного дальше и сделаем процесс чуть более автоматизированным.
По сути, чтобы синхронизировать файлы, нам нужно запускать примерно такую команду:
rsync -r -u \
--delete-after \
--exclude-from=.gitignore \
--exclude-from=.dockerignore \
. $sshUserName@$sshHost:$sharedBaseDir
Фактически, на небольших-средних проектах, эта команда выполнится быстрее, чем ты успеешь закоммитить и запушить правки в репозиторий. Осталось привести этот скрипт к более законченному виду и забиндить его выполнение на горячие клавиши.
#!/usr/bin/env bash
# Присваиваем переданные данные
while [ -n "$1" ]
do
case "$1" in
--sshUserName=*)
sshUserName=${1#*=}
;;
--sshHost=*)
sshHost=${1#*=}
;;
esac
shift
done
# Определяем shared папку для ветки
gitBranch=$(git branch | grep \* | cut -d ' ' -f2)
gitProject=$(git config --local remote.origin.url|sed -n 's#.*/\([^.]*\)\.git#\1#p')
gitGroup=$(git config --local remote.origin.url|sed -n 's#.*/\([^.]*\)/.*\.git#\1#p')
sharedBaseDir=/var/www/builds/$gitGroup/$gitProject/$gitBranch
# Синхронизируем папку проекта с папкой на удаленном хосте
rsync -r -u \
--delete-after \
--exclude-from=.gitignore \
--exclude-from=.dockerignore \
. $sshUserName@$sshHost:$sharedBaseDir
echo "done"
Стоит учесть, что скрипт должен находиться в корне проектной папки репозитория или указывать в какой директории он должен сработать.
Осталось только прикрутить выполнение данного скрипта к конкретным хоткеям и задать параметры sshUserName и sshHost (подразумевается, что уже есть доступ к удаленному серверу по ssh). Как это сделать я приведу на примере PHPstorm.
- Переходим в File -> Settings
- В окне настроек в левом меню разворачиваем Tools, выбираем External Tools
- Прописываем путь к скрипту и указываем в аргументах реальный sshUserName и sshHost
- Далее переходим в Keymap и ищем название нашего External Tools, устанавливаем необходимую комбинацию
На этом все. Теперь при нажатии нужной комбинации все файлы проекта синхронизируется с удаленной папкой, которая смонтирована с папкой проекта внутри контейнера. То есть любые изменения будут видны практически сразу.
Я не претендую на "идеальность" этого решения, наверняка есть варианты лучше, буду рад, если узнаю о них в комментариях. Спасибо!