- PVSM.RU - https://www.pvsm.ru -

Данный пост написан по заявкам трудящихся, которые с завидной периодичностью спрашивают о том "Как запустить Illuminate / Symfony / MyOwnPsr7 [1] приложение в докере". Давать ссылку на ранее написанный пост [2] уже не хочется, так как взгляды относительно того, как следует решать поставленную задачу, довольно сильно изменились.
Всё, что будет написано ниже, является субъективным опытом, который (как и всегда) не претендует на право считаться единственно верным решением, но некоторые подходы и решения, возможно, тебе покажутся интересными и полезными.
В качестве приложения так же буду использовать Laravel, так как он мне наиболее знаком и довольно широко распространен. Адаптировать под другие PSR-7 [1]-based фреймворки/компоненты возможно, но этот рассказ не про это.
Хотелось бы начать с того, что оказалось "не лучшими практиками" в контексте предыдущей статьи [2]:
Теперь, зная какие проблемы необходимо устранить и с пониманием как это сделать — предлагаю приступить к их устранению. Набор "инструментов разработчика" у нас не изменился — это всё тот-же docker-ce, docker-compose и могучий Makefile.
В результате мы получим:
git pull автоматически и запретим пушить код, если тесты не проходят (хуки будут храниться под гитом, естественно)dd(..) и dump(..) для отладки, и при этом ничего не будет крашиться в их браузереCHANGELOG.md и ENVIRONMENT.mdДля наглядной демонстрации — весь процесс разобью на несколько этапов, изменения в рамках которых будут оформлены в виде отдельных MR (после слияния все бранчи останутся на своих местах; ссылки на MR в заголовках "шагов"). Отправная точка — это скелетон Laravel приложения созданный с помощью composer create-project latavel/laravel:
$ docker run
--rm -i
-v "$(pwd):/src"
-u "$(id -u):$(id -g)"
composer composer create-project --prefer-dist laravel/laravel
/src/laravel-in-docker-with-rr "5.8.*"
Первым делом необходимо научить приложение запускаться в контейнере. Для этого нам нужны Dockerfile, docker-compose.yml для описания "как поднимать и линковать контейнеры", и Makefile для того, чтобы свести и без того упрощенный процесс к одной-двум командам.
Базовый образ использую php:X.X.X-alpine как наиболее легкий и содержащий то, что надо для запуска. Более того — все последующие обновления интерпретатора сводятся к тому, чтобы просто изменить значение в этой строчке (обновить PHP теперь проще некуда).
Composer и бинарный файл RoadRunner доставляются в контейнер с помощью multistage и COPY --from=... — это очень удобно, да и все значения связанные с версиями не "разбросаны", а находятся в начале файла. Работает это быстро, и без зависимостей от curl / git clone / make build. Образы 512k/roadrunner [5] поддерживаются мною, если хотите — можете собирать бинарный файл самостоятельно.
Интересная история приключилась с переменной окружения
PS1(отвечает за prompt в шелле) — оказывается, использовать в ней emoji можно, и всё локально работает, но стоит попытаться запустить образ с переменной содержащей emoji в, скажем, rancher — он будет крашиться (в swarm всё работает без проблем).
В Dockerfile я запускаю генерацию самоподписанного SSL сертификата для того, что бы его использовать для входящих HTTPS запросов. Естественно — ничего не мешает использовать "нормальный" сертификат.
Отдельно хочется сказать про:
COPY ./composer.* /app/
RUN set -xe
&& composer install --no-interaction --no-ansi --no-suggest --prefer-dist
--no-autoloader --no-scripts
&& composer install --no-dev --no-interaction --no-ansi --no-suggest
--prefer-dist --no-autoloader --no-scripts
Тут смысл следующий — отдельным слоем в образ доставляются файлы composer.lock и composer.json, после чего выполняется установка всех зависимостей, описанных в них. Делается это для того, чтобы при последующих сборках образа с использованием --cache-from, если состав и версии установленных зависимостей не изменились, то composer install не выполнялся, взяв этот слой из кэша, тем самым экономя время сборки и трафик (за идею спасибо jetexe [6]).
composer install выполняется дважды (второй раз с --no-dev) для "прогрева" кэша dev-зависимостей, чтобы когда мы на CI для запуска тестов поставили все зависимости, они ставились из кэша composer-а что уже есть в образе, а не тянулись из далеких галактик.
Последний инструкцией RUN мы выводим версии установленного ПО и состав модулей PHP как для истории в логах сборки, так и для того, чтобы убедиться, что "оно как минимум есть и как-то запускается".
Entrypoint использую тоже свой, так как перед тем как запустить приложение где-то в кластере очень хочется проверить доступность зависимых сервисов — БД, redis, rabbit и прочих.
Для интеграции RoadRunner с Laravel-приложением был написан пакет [7], который сводит всю интеграцию к паре команд в шелле (выполнив docker-compose run app sh):
$ composer require avto-dev/roadrunner-laravel "^2.0"
$ ./artisan vendor:publish --provider='AvtoDevRoadRunnerLaravelServiceProvider' --tag=rr-config
Добавляем APP_FORCE_HTTPS=true в файл ./docker/docker-compose.env, и указываем путь до SSL сертификата в контейнере в файлах .rr*.yaml.
Для того, чтобы была возможность использовать
dump(..)иdd(..)и всё при этом работало, есть другой пакет —avto-dev/stacked-dumper-laravel[8]. Всё, что потребуется — это добавлять пефикс к этим хэлперам, а именноdevdd(..)иdevdump(..)соответственно. Без этого будете наблюдать ошибку вида:worker error: invalid data found in the buffer (possible echo)
После всех манипуляций выполняем docker-compose up -d и вуа-ля:

База данных PostgeSQL, redis и воркеры RoadRunner успешно запущены в контейнерах.
Как уже писал ранее, Makefile — очень недооцененная штука. Зависимые цели, свой синтаксический сахар, 99% вероятность того, что на linux/mac машине разработчика он уже стоит, автокомплит "из коробки" — малый список его преимуществ.
Добавив его [10] в наш проект и выполнив make без параметров, мы можем наблюдать:

Для запуска юнит-тестов мы можем как выполнить make test, так и получив шелл внутрь контейнера с приложением (make shell) выполнить composer phpunit. Для получения coverage отчета достаточно выполнить make test-cover, и перед запуском тестов в контейнер доставится xdebug с его зависимостями, и запустятся тесты (так как эта процедура выполняется не часто и не силами CI — это решение кажется лучшим, чем держать отдельный образ со всеми dev-примочками).
Хуки в нашем случае будут выполнять 2 важные роли — не позволять пушить в origin код, тесты которого не выполняются успешно; и автоматически ставить все необходимые зависимости, если стянув изменения себе на машину окажется, что composer.lock изменился. В Makefile для этого существует отдельный target:
cwd = $(shell pwd)
git-hooks: ## Install (reinstall) git hooks (required after repository cloning)
-rm -f "$(cwd)/.git/hooks/pre-push" "$(cwd)/.git/hooks/pre-commit" "$(cwd)/.git/hooks/post-merge"
ln -s "$(cwd)/.gitlab/git-hooks/pre-push.sh" "$(cwd)/.git/hooks/pre-push"
ln -s "$(cwd)/.gitlab/git-hooks/pre-commit.sh" "$(cwd)/.git/hooks/pre-commit"
ln -s "$(cwd)/.gitlab/git-hooks/post-merge.sh" "$(cwd)/.git/hooks/post-merge"
Выполнение make git-hooks просто сносит имеющиеся хуки, и ставит на их место те, что находятся в директории .gitlab/git-hooks. Их исходники можно посмотреть по этой ссылке [11].
Не смотря на то, что это довольно просто и удобно — сам довольно долго пользовался ./vendor/bin/phpunit --group=foo вместо того, чтоб просто нажимать хоткей прямо во время написания теста или кода, с ним связанного.
Нажимаем File > Settings > Languages & Frameworks > PHP > CLI interpreter > [...] > [+] > From Docker, Vargant, VM, Remote. Выбираем Docker compose, и имя сервиса app.

Второй шаг — это указание phpunit-у необходимость использовать интерпретатор из контейнера: File > Settings > Test frameworks > [+] > PHPUnit by remote interpreter и выбрать ранее созданный удаленный интерпретатор. В поле Path to script указываем /app/vendor/autoload.php, а в Path mappings указываем корневую директорию проекта как монтируемую в /app.

И теперь мы можем запускать тесты прямо из IDE используя интерпретатор внутри образа с приложением, нажимая (по дефолту, Linux) Ctrl + Shift + F10.
Всё, что нам остается сделать — это автоматизировать процесс запуска тестов и сборки образа. Для этого создаем файл .gitlab-ci.yml в корневой директории приложения, наполняя его примерно следующим содержанием [13]. Основная идея данной конфигурации — быть максимально простой, но не терять в функциональности при этом.
Сборка образа производится на каждом бранче, на каждом коммите. Используя --cache-from сборка образа при повторном коммите производится очень быстро. Необходимость пересборки обусловлена тем, что на каждом бранче у нас есть образ с теми изменениями, которые были в рамках этого бранча сделаны, а как следствие — ничего нам не мешает его раскатать на swarm/k8s/etc для того, что бы "вживую" убедиться в том, что всё работает, и работает как надо ещё до мерджа с master-веткой.
После сборки — запускаем unit-тесты и проверяем запуск приложения в контейнере, отправляя на health-check endpoint запросы curl-ом (данное действие опционально, но несколько раз данный тест меня очень выручал).
Для "выпуска релиза" — просто публикуем тег вида vX.X.X (если вы ещё и будете придерживаться семантического версионирования — будет очень круто) — CI соберет образ, прогонит тесты, и выполнит действия, что вы укажете в deploy to somewhere.
Не забудьте в настройках проекта (если это возможно) ограничить возможность публикации тегов только лицам, которым разрешено "выпускать релизы".
CHANGELOG.md и ENVIRONMENT.mdПеред тем, как принять тот или иной MR — проверяющий должен в обязательном порядке проверить на соответствие файлы CHANGELOG.md и ENVIRONMENT.md. Если с первым всё более и менее понятно [14], то вот относительного второго дам пояснения. Данный файл [15] служит для описания всех переменных окружения, на которые реагирует контейнер с приложением. Т.е. если разработчик добавляет или удаляет поддержку той или иной переменной окружения — это обязательно должно быть отражено в этом файле. И в момент, когда возникает вопрос "Нам нужно срочно переопределить то-то и то-то" — никто судорожно не начинает копаться в документации или исходниках — а смотрит в одном-единственном файле. Очень удобно.
А данной статье мы рассмотрели довольно безболезненный процесс переноса разработки и запуска приложения в Docker-окружение, интегрировали RoadRunner и используя простой CI сценарий автоматизировали сборку и тестирование образа с нашим приложением.
Разработчикам остается после клонирования репозитория выполнить make git-hooks && make install && make up и начать писать полезный код. Товарищам *ops-ам — брать образ с нужным тегом и раскатывать его на своих кластерах.
Естественно — данная схема тоже является упрощенной, и на "боевых" проектах накручиваю ещё много всего, но если изложенный в статье подход поможет кому-то — я буду знать, что потратил время не зря.
Автор: paramtamtam
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/325543
Ссылки в тексте:
[1] Psr7: https://www.php-fig.org/psr/psr-7/
[2] ранее написанный пост: https://habr.com/ru/post/425101/
[3] lachezis: https://habr.com/ru/users/lachezis/
[4] Докеризация + RR: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/merge_requests/1/diffs
[5] 512k/roadrunner: https://github.com/512k/roadrunner-docker
[6] jetexe: https://habr.com/ru/users/jetexe/
[7] пакет: https://github.com/avto-dev/roadrunner-laravel
[8] avto-dev/stacked-dumper-laravel: https://github.com/avto-dev/stacked-dumper-laravel
[9] Makefile и тесты: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/merge_requests/2/diffs
[10] его: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/blob/master/Makefile
[11] по этой ссылке: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/tree/master/.gitlab/git-hooks
[12] Автоматизация: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/merge_requests/3/diffs
[13] примерно следующим содержанием: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/blob/master/.gitlab-ci.yml
[14] более и менее понятно: https://keepachangelog.com/en/1.0.0/
[15] Данный файл: https://gitlab.com/tarampampam/laravel-in-docker-with-rr/blob/master/ENVIRONMENT.md
[16] Источник: https://habr.com/ru/post/461687/?utm_campaign=461687&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.