В публикации опишу подход к использованию контейнеров 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)


  1. OnYourLips
    13.09.2022 13:56
    +1

    Подход мне нравится, я его сам использую, но реализация имхо у вас хромает.

    install:
    composer install

    В частности этот пункт не будет работать в таком виде: вам придется иметь на локальной машине PHP нужной версии с нужными расширениями. Который не будет совпадать с частью проектов, если у вас их много.
    Для этого используют докер и ставят зависимости с помощью PHP из образа докера. И его придется собрать самостоятельно: в официальных образах нет композера.
    При сборке придется пробрасывать внутрь ключи от ваших репозиториев зависимостей, чтобы подключать внутренние библиотеки, это самый простой способ.

    Далее, make. Инструмент очень душный и дающий чувство ложного его понимания, готовый в любой момент отстрелить вам ноги, когда вы зазеваетесь и ошибётесь. Очень много подводных камней. Я бы посоветовал Taskfile, его значительно проще поддерживать.

    Что касается кеша, то можно использовать как внешнюю директорию на докер-хосте, так и пользоваться внутренними средствами докера, которые недавно перестали быть экспериментальными. Это не отдельные layers, а полноценные директории, похожии на volume. Это значительно проще. При этом кеш не делится с хостом, что является недостатком, но совершенно незначительным.


    1. samizdam Автор
      13.09.2022 14:05

      Нет, требований к локальной машине, кроме docker+make нет. Вызовы интерпретатора и composer происходят в контейнерах. Посмотрите внимательней, этот момент раскрыт в статье.

      make используется из-за его доступности из коробки и распространённости. Taskfile -- ещё одна зависимость, которую надо иметь для запуска. Не оспаривая её преимуществ, это усложнение решения поставленной задачи. Я за годы использования не припомню казусов с make. Да, синтаксис специфичен, и какие-то решения без мануала написать не получится, но работает исправно и достаточно прост для обсуждаемого кейса.


  1. MadridianFox
    13.09.2022 14:18

    Да, простая идея, что конкретные версии рантаймов и инструментов не должны засорять машину разработчика получает всё больше реализаций.

    Аналогичную цель преследуют и laradock и laravel sail.

    Однако bash и make недостаточно гибкие, а задачи постепенно становятся всё более сложными. В системах, состоящих из десятков сервисов, одно только клонирование всех их может порядком утомить, не говоря уже об установке зависимостей или ежедневном запуске той или иной группы сервисов.

    Позанимавшись некоторое время скриптоводством я перешёл на ELC - обёртку над docker-compose, написанную на golang.


  1. TonyKentnarEarth
    13.09.2022 14:52

    Преследуя аналогичные цели, сделал свой мини-комбайн, не раз выручал.

    А как приведенные /bin/php и /bin/composer интегрируются с инструментами ОСи? Будет ли например phpstorm корректно понимать такой "интерпретатор"?


    1. samizdam Автор
      13.09.2022 19:50
      +1

      Ваш комбайн напомнил мне про laradoc. Я тут скорее за минимализм -- усложнить простое проще, чем упростить сложное)

      В phpstorm я не использую подобные интеграции, не могу сказать, мне терминал ближе. В теории может быть, если ему нужен именно путь с исполняемым файлом -- все аргументы прокидываются. С точки зрения ОСи разницы быть не должно.


  1. MyraJKee
    13.09.2022 20:20
    +1

    Используем в компании именно такой подход. Сам работаю на убунту based дистре, но большинство работают на маках. Все работает, есть какие-то различия в области докера, но их не очень много