Pull to refresh

Деплоим код напрямую в docker-контейнер. Или как не прокрастинировать после каждого коммита

Reading time6 min
Views12K
Пришла задача 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-скрип и повесить его на горячие клавиши
      • По началу кажется, что излишне сложно, но на самом деле это не так. Сам скрипт максимально простой и нужен для автоматизации процесса, чтобы каждый раз не приходилось перенастраивать маппинги

Само решение: 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

Фактически, на небольших-средних проектах, эта команда выполнится быстрее, чем ты успеешь закоммитить и запушить правки в репозиторий. Осталось привести этот скрипт к более законченному виду и забиндить его выполнение на горячие клавиши.


Полный код скрипта: deploy.sh
#!/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, устанавливаем необходимую комбинацию


На этом все. Теперь при нажатии нужной комбинации все файлы проекта синхронизируется с удаленной папкой, которая смонтирована с папкой проекта внутри контейнера. То есть любые изменения будут видны практически сразу.


Я не претендую на "идеальность" этого решения, наверняка есть варианты лучше, буду рад, если узнаю о них в комментариях. Спасибо!

Tags:
Hubs:
Total votes 14: ↑11 and ↓3+8
Comments15

Articles