В публикации опишу подход к использованию контейнеров docker и make который я практиковал последние несколько лет в своих рабочих командах и личных pet-проектах. Подход сформировался в процессе поиска минималистичного и унифицированного способа запуска проектов на php. Чтобы любой разработчик мог в пару простейших команд получить рабочую копию для разработки, располагая только доступом к репозиторию, без бубнов, обновляемых инструкций и тимлида на соседнем стуле.
Требования:
docker
make
debian-based distro
Файл .env
Файл .env
широко используется многими инструментами для определения переменных окружения. Преимущества файла и формата — его легко читать и модифицировать, в том числе программно, добавляя строки, переопределяя тем самым нужные переменные, например в рамках CI.
Чтобы сохранить свободу изменений локального окружения и гибкость версионирования, в репозитории хранится dev.env
с минимальным набором переменных для запуска проекта на машине разработчика, сам .env
добавлен в .gitignore
. При необходимости, помимо dev.env
можно добавить аналогичные шаблоны для других окружений: ci.env
, test.env
, stage.env
, etc. В рабочем флоу разработчика dev.env
копируется в .env
.
Makefile
Утилита make широко распространена, включена во множество дистрибутивов. Её легко использовать как основную точку входа в проект. В описанном подходе, после клонирования репозитория предполагается выполнение одной инструкции: make install
, после чего можно запускать тесты. Т.о. проект готов к разработке в два простых шага: git clone...
+ make install
.
Чтобы это было возможно, используется пара простых трюков в Makefile. Внимание на первые его строки:
PATH := $(shell pwd)/bin:$(PATH)
$(shell cp -n dev.env .env)
include .env
Первой строкой в путь для поиска исполняемых команд добавляет директория /bin
проекта. О ней отдельно будет ниже.
Вторая строка — упомянутое копирование файла .env
для окружения разработки.
Третья - включение файла, для использования переменных окружения в инструкциях make.
Сам рецепт install
содержит минимум необходимый для получения рабочего проекта локально:
install: build
composer install
cp -n phpunit.xml.dist phpunit.xml
build:
docker build -t $(PHP_DEV_IMAGE):$(REVISION) .
Директория /bin и контейнеризированные команды
Здесь располагаются скрипты, которые позваляют запускать необходимые разработке команды в контейнерах. В нашем случае это php и composer.
Листинг с пояснениями
/bin/php
#!/bin/bash
source .env # считываем переменные из файла, для их использования в сценарии
test -t 1 && USE_TTY="--tty" # проверяем наличие tty (для корректного запуска на CI-сервере)
docker run --rm --interactive ${USE_TTY} \ # запускаем одноразовый контейнер, который будет удалён после выполенения команды, с возможностью интерактивного ввода при необходимости
--init \
--user `id -u`:`id -g` \ # передаём текущего пользователя и его группу, для выставления корректных прав при работе с ФС
--volume $PWD:/var/www \ # прокидываем директорию проекта в том контейнера, являющейся рабочей директорией
--env-file .env \ # делаем доступными переменные окружения проекта внутри контейнера
${PHP_DEV_IMAGE}:${REVISION} php "$@" # используем образ и тэг заданные в переменных окружения, где собственно и вызываем интерпретатор с переданными аргументами
/bin/composer
Примерно тоже что и php, со спецификой необходимой composer.
#!/bin/bash
source .env
mkdir -p $HOME/.composer/cache/ # подготовка ФС для кэша composer, при необходимости
test -t 1 && USE_TTY="--tty"
docker run --rm --interactive ${USE_TTY} \
--init \
--user `id -u`:`id -g` \
--volume $PWD:/var/www \
--volume $HOME/.composer:/tmp/.composer \ # монтирование директории для кэширования
--env COMPOSER_HOME=/tmp/.composer \ # указание директории для использования composer
${PHP_DEV_IMAGE}:${REVISION} composer "$@"
Таким образом, находясь в корне репозитория, мы можем работать с нужной версией php и composer, обращаясь к ним как ./bin/php ...
, ./bin/composer ...
, либо напрямую, предварительно добавив в PATH, например на уровне сессии терминала. В Makefile это добавление релизовано, и мы можем легко добавлять необходимые рецепты, как будто это прямой вызов локального интерпретатора.
Всё описанное в статье собрано вместе в шаблоне-репозитории https://github.com/samizdam/php-project-skeleton. Этот минимум можно использовать для библиотек или простейших сервисов на php.
Что дальше
Описанные приёмы хорошо переиспользуются и дают базу для реализации запуска составных проектов из нескольких контенеров: docker-compose сам умеет работать с .env
. В сценариях для CI аналогично с Makefile, можно приготовить и импортировать .env
и расширить PATH. Также контейнеризация приложения на раннем этапе — полезный задел для построения дальнейших процессов CI/CD.
Ограничения - описанный стек и debian-like дистрибутивы, где все описанные инструкции будут работать. На универсальность не претендую. Для кроссплатформенного запуска — нужен напильник, либо описанный способ, как есть, может не работать ¯_(ツ)_/¯
Комментарии (6)
MadridianFox
13.09.2022 14:18Да, простая идея, что конкретные версии рантаймов и инструментов не должны засорять машину разработчика получает всё больше реализаций.
Аналогичную цель преследуют и laradock и laravel sail.
Однако bash и make недостаточно гибкие, а задачи постепенно становятся всё более сложными. В системах, состоящих из десятков сервисов, одно только клонирование всех их может порядком утомить, не говоря уже об установке зависимостей или ежедневном запуске той или иной группы сервисов.
Позанимавшись некоторое время скриптоводством я перешёл на ELC - обёртку над docker-compose, написанную на golang.
TonyKentnarEarth
13.09.2022 14:52Преследуя аналогичные цели, сделал свой мини-комбайн, не раз выручал.
А как приведенные
/bin/php
и/bin/composer
интегрируются с инструментами ОСи? Будет ли например phpstorm корректно понимать такой "интерпретатор"?samizdam Автор
13.09.2022 19:50+1Ваш комбайн напомнил мне про laradoc. Я тут скорее за минимализм -- усложнить простое проще, чем упростить сложное)
В phpstorm я не использую подобные интеграции, не могу сказать, мне терминал ближе. В теории может быть, если ему нужен именно путь с исполняемым файлом -- все аргументы прокидываются. С точки зрения ОСи разницы быть не должно.
MyraJKee
13.09.2022 20:20+1Используем в компании именно такой подход. Сам работаю на убунту based дистре, но большинство работают на маках. Все работает, есть какие-то различия в области докера, но их не очень много
OnYourLips
Подход мне нравится, я его сам использую, но реализация имхо у вас хромает.
В частности этот пункт не будет работать в таком виде: вам придется иметь на локальной машине PHP нужной версии с нужными расширениями. Который не будет совпадать с частью проектов, если у вас их много.
Для этого используют докер и ставят зависимости с помощью PHP из образа докера. И его придется собрать самостоятельно: в официальных образах нет композера.
При сборке придется пробрасывать внутрь ключи от ваших репозиториев зависимостей, чтобы подключать внутренние библиотеки, это самый простой способ.
Далее, make. Инструмент очень душный и дающий чувство ложного его понимания, готовый в любой момент отстрелить вам ноги, когда вы зазеваетесь и ошибётесь. Очень много подводных камней. Я бы посоветовал Taskfile, его значительно проще поддерживать.
Что касается кеша, то можно использовать как внешнюю директорию на докер-хосте, так и пользоваться внутренними средствами докера, которые недавно перестали быть экспериментальными. Это не отдельные layers, а полноценные директории, похожии на volume. Это значительно проще. При этом кеш не делится с хостом, что является недостатком, но совершенно незначительным.
samizdam Автор
Нет, требований к локальной машине, кроме docker+make нет. Вызовы интерпретатора и composer происходят в контейнерах. Посмотрите внимательней, этот момент раскрыт в статье.
make используется из-за его доступности из коробки и распространённости. Taskfile -- ещё одна зависимость, которую надо иметь для запуска. Не оспаривая её преимуществ, это усложнение решения поставленной задачи. Я за годы использования не припомню казусов с make. Да, синтаксис специфичен, и какие-то решения без мануала написать не получится, но работает исправно и достаточно прост для обсуждаемого кейса.