Эта статья впервые появилась на сайте https://www.pascallandau.com/ в разделе Docker с нуля для PHP 8.1 в 2022 году [Учебник, часть 2].
В этой части серии уроков по разработке PHP на Docker мы вернемся к предыдущим урокам и обновим некоторые вещи, чтобы быть актуальными в 2022 году.
Все примеры кода находятся в открытом доступе в моем репозитории Docker PHP Tutorial на Github. Вы найдете ветку для этого руководства по адресу part-4-1-docker-from-scratch-for-php-applications-in-2022.
Все опубликованные части учебника по Docker PHP собраны на специальной странице Docker PHP Tutorial. Предыдущая часть была “Структурирование установки Docker для PHP-проектов”, а следующая – “PhpStorm, Docker и Xdebug 3 на PHP 8.1 в 2022 году”.
Если вы хотите следить за развитием событий, пожалуйста, подпишитесь на RSS канал или по электронной почте, чтобы получать автоматические уведомления о выходе следующей части 🙂
- Оглавление
- Введение
- Локальная настройка docker
- Docker
- docker-compose
- .docker/.env файл и необходимые переменные ENV
- Изображения
- Образы PHP
- Соглашение об именовании изображений
- Окружения и цели сборки
- Makefile
- .make/*.mk includes
- Общие переменные: .make/.env
- Ручные модификации
- Принудительное использование необходимых параметров
- Make + Docker = <3
- Обеспечение порядка сборки
- Выполнение команд в контейнерах docker
- Решение проблем с разрешениями
- PHP POC
- Завершение
Оглавление
- Введение
- Локальная настройка docker
- Docker
- docker-compose
- Файл
.docker/.env
и необходимые переменные ENV
- Файл
- Изображения
- Изображения PHP
- ENV против ARG
- Соглашение об именовании изображений
- Среды и цели сборки
- Изображения PHP
- docker-compose
- Makefile
.make/*.mk
включает в себя- Общие переменные:
.make/.env
- Ручные модификации
- Усиление необходимых параметров
- Make + Docker = <3
- Обеспечение порядка сборки
- Выполнение команд в контейнерах docker
- Решение проблем с разрешениями
- PHP POC
- Подведение итогов
Введение
Если вы читали предыдущее руководство “Структурирование установки Docker для PHP-проектов”, вы можете столкнуться с некоторыми существенными изменениями. Учебник был опубликован более 2 лет назад, Docker развивался, и я узнал о нем больше. Кроме того, я накопил практический опыт (хороший и плохой) с предыдущей установкой. Сейчас я бы посчитал большинство пунктов раздела “Основы”, посвященных созданию контейнеров, либо “необязательными”, либо просто “излишне продуманными / слишком сложными”. Если быть конкретным:
- Установка часового пояса
- не требуется, если по умолчанию уже стоит UTC (что почти всегда так).
- Синхронизация прав собственности на файлы и папки на общих томах
- это проблема только в том случае, если файлы должны быть изменены контейнерами и хост-системой – что действительно актуально только для контейнеров PHP
- Кроме того, я бы рекомендовал добавить совершенно нового пользователя (например,
application
) вместо того, чтобы использовать существующего, напримерwww-data
– это значительно упрощает всю настройку пользователей - с этого момента мы будем использовать
application
в качестве имени пользователя (APP_USER_NAME
) и10000
в качестве идентификатора пользователя (APP_USER_ID
; следуя лучшей практике не использовать UID ниже 10,000).
- Изменение конфигурационных файлов
- просто используйте
sed
– нет необходимости в специальном скрипте
- просто используйте
- Установка расширений php
- см. изображения PHP – теперь будет выполняться через
apk add
.
- см. изображения PHP – теперь будет выполняться через
- Установка общего программного обеспечения
- см. изображения PHP – поскольку есть только один базовый образ, нет необходимости в специальном скрипте.
- Очистка
- не имело смысла, поскольку “очищенные файлы” уже были частью предыдущего слоя
- мы можем “вернуть их” позже, когда оптимизируем размер образа, чтобы ускорить загрузку/выгрузку образов в/из реестра.
-
Предоставление host.docker.internal для хост-систем linux
- теперь может быть сделано с помощью магической ссылки
host-gateway
services: myservice: extra_hosts: - host.docker.internal:host-gateway
- таким образом, больше не требуется пользовательская точка входа
- теперь может быть сделано с помощью магической ссылки
Локальная настройка docker
Целью этой части является создание рабочей локальной установки без инструментов разработки. Другими словами: Мы хотим иметь минимум, чтобы что-то работало локально.
Основными компонентами являются:
- установка
make
вMakefile
и в каталоге.make/
- установка docker в каталоге
.docker/
- несколько PHP-файлов, которые выступают в качестве POC для функциональности end2end установки docker.
Проверьте код через
git checkout part_4_section_1_docker_from_scratch_for_php_applications_in_2022_code_structure
инициализируйте его через
make make-init
make docker-build
и запустите его через
make docker-up
Теперь вы можете получить доступ к веб-интерфейсу через http://127.0.0.1. На следующей схеме показано, как подключены контейнеры.
Смотрите также PHP POC для полного тестирования настройки.
Docker
Установка docker состоит из
- контейнер nginx в качестве веб-сервера
- контейнер базы данных MySQL
- контейнера Redis, который работает как очередь
- базовый образ php, который используется
- контейнер php worker, который порождает несколько рабочих процессов PHP через
supervisor
- контейнер php-fpm в качестве бэкенда для контейнера nginx
- контейнер приложений, который мы используем для выполнения команд.
- контейнер php worker, который порождает несколько рабочих процессов PHP через
Мы сохраним каталог .docker/
из предыдущего руководства, хотя он будет разделен на docker-compose/
и images/
, как показано ниже:
.
└── .docker/
├── docker-compose/
| ├── docker-compose.yml
| └── <other docker-compose files>
├── images/
| ├── nginx/
| | ├── Dockerfile
| | └── <other files for the nginx image>
| └── <other folders for docker images>
├── .env
└── .env.example
docker-compose
Все образы собираются с помощью docker-compose
, поскольку файл(ы) docker-compose.yml
предоставляет хороший уровень абстракции для конфигурации сборки. Кроме того, мы можем использовать его для оркестровки контейнеров, т.е. управления томами, сопоставлениями портов, сетевым взаимодействием и т.д. – а также запускать и останавливать их с помощью docker-compose up
и docker-compose down
.
К вашему сведению: Несмотря на то, что удобно использовать docker-compose
для обеих вещей, я обнаружил, что это также делает настройку более сложной, чем нужно, когда мы запускаем вещи позже в производстве (когда мы больше не используем docker-compose
). Я думаю, что проблема здесь в том, что некоторые модификации требуются ТОЛЬКО для сборки, а другие ТОЛЬКО для запуска – и объединение обоих в одном файле дает определенное количество шума. Но: Это то, что есть.
Мы используем три отдельных файла docker-compose.yml
:
- docker-compose.yml
- содержит всю информацию, действительную для всех окружений
- docker-compose.local.yml
- содержит информацию, специфичную для среды
local
, см. раздел Среды и цели сборки
- содержит информацию, специфичную для среды
- docker-compose-php-base.yml
- содержит информацию для сборки базового образа php, см. раздел Образы PHP
.docker/.env
файл и необходимые переменные ENV
В нашей установке docker у нас в основном есть 3 различных типа переменных:
- переменные, которые зависят от локальной настройки отдельного разработчика, например,
NGINX_HOST_HTTP_PORT
на хост-машине (потому что стандартный может уже использоваться) 2. переменные, которые используются в нескольких образах, например, расположение кодовой базы в файловой системе acontainer 3. переменные, которые хранят информацию, которая “может измениться”, например, точная версия базового образа.
Поскольку – опять же – мы стремимся сохранить единый источник истины, мы извлекаем информацию в виде переменных и помещаем их в файл .docker/.env
. В идеальном мире я бы хотел разделить эти различные типы в разных файлах – но docker-compose
позволяет только один файл .env
, см., например, этот комментарий. Если файл не существует, он копируется из .docker/.env.example
.
Затем переменные используются в файле(ах) docker-compose.yml
. Мне показалось “наименее болезненным” всегда использовать модификатор ?
для переменных, чтобы docker-compose
немедленно завершался, если переменная отсутствует.
Примечание: Некоторые переменные должны передаваться через переменные окружения при вызове docker-compose
(т.е. они требуются, но не определены в файле .env
; см. также Общие переменные: .make/.env
.
Изображения
Для MySQL и redis мы не используем созданные на заказ образы, вместо этого мы используем официальные образы напрямую и настраиваем их через переменные окружения при запуске контейнеров. В production мы не будем использовать docker в любом случае для этих сервисов, а вместо этого будем полагаться на управляемые версии, например.
- redis => Memorystore for Redis (GCP) илиElastiCache für Redis (AWS)
- mysql => Cloud SQL для MySQL (GCP) илиRDS для MySQL (AWS).
Остальные контейнеры определяются в соответствующих подкаталогах в каталоге .docker/images/
, например, образ для контейнера nginx
собирается через Dockerfile
, расположенный в .docker/images/nginx/Dockerfile
.
Образы PHP
Нам нужно 3 разных образа PHP (fpm, workers, application), и мы используем немного другой подход, чем в разделе “Структурирование установки Docker для PHP-проектов”:
Вместо использования официальных базовых образов PHP (т.е. cli или fpm) мы используем “простой” базовый образ alpine и устанавливаем в него PHP и необходимые расширения вручную. Это позволяет нам создать общий базовый образ для всех образов PHP. Преимущества:
- центральное место для общих инструментов и конфигурации (больше нет необходимости в каталоге
.shared/
) - уменьшение размера изображения при проталкивании отдельных образов (базовый образ распознается как слой и, следовательно, “уже существует”)
- установка расширений через
apk add
происходит намного быстрее, чем черезdocker-php-ext-install
.
У этого нового подхода есть два основных недостатка:
- мы зависим от цикла выпуска PHP (и расширений PHP) в альпийском стиле
- процесс сборки образов усложняется, так как мы должны сначала собрать базовый образ, прежде чем мы сможем собрать окончательные образы.
К счастью, обе проблемы могут быть решены довольно легко:
- codecasts/php-alpine поддерживает репозиторий
apk
с последними версиями PHP для alpine - мы используем специальную цель
make
для сборки образов вместо прямого вызоваdocker-compose
– это позволяет нам определить “порядок сборки” (сначала база, потом все остальное), при этом разработчику нужно выполнить всего одну команду (см. Обеспечение порядка сборки).
ENV против ARG
Я заметил, что некоторые аргументы сборки требуются в нескольких контейнерах PHP, например, имя пользователя приложения, определенное в переменной APP_USER_NAME
ENV. Имя пользователя необходимо
- в базовом образе для создания пользователя
- в образе fpm для определения пользователя, запускающего процессы fpm (см.
php-fpm.d/www.conf
) - в образе worker для определения пользователя, запускающего рабочие процессы (см.
supervisor/supervisord.conf
).
Вместо того, чтобы передавать имя всем образам через аргумент build, т.е.
- определить его явно в
services.*.build.args
в файлеdocker-compose.yml
- “получить” его в Dockerfile через
ARG APP_USER_NAME
.
Я решил сделать имя пользователя доступным как переменную ENV
в базовом образе через
ARG APP_USER_NAME
ENV APP_USER_NAME=${APP_USER_NAME}
и таким образом иметь возможность обращаться к нему в дочерних изображениях напрямую, теперь я могу написать
RUN echo ${APP_USER_NAME}
вместо
ARG APP_USER_NAME
RUN echo ${APP_USER_NAME}
Я не уверен на 100%, что мне нравится такой подход, поскольку я более или менее “злоупотребляю” переменными ENV так, как они, вероятно, не предназначены (“Зачем имя пользователя хранить как переменную ENV?”) – но я также не вижу других практических возможностей.
но я также пока не вижу никаких других практических недостатков.
Соглашение об именовании изображений
Определение полностью определенного имени для образов значительно упростит последующие ссылки на них, например, при переносе их в реестр.
Соглашение об именовании изображений имеет вид $(DOCKER_REGISTRY)/$(DOCKER_NAMESPACE)/$(DOCKER_SERVICE_NAME)-$(ENV)
, например.
docker.io/dofroscra/nginx-local
$(DOCKER_REGISTRY)---^ ^ ^ ^ docker.io
$(DOCKER_NAMESPACE)-------------^ ^ ^ dofroscra
$(DOCKER_SERVICE_NAME)-------------------^ ^ nginx
$(ENV)-----------------------------------------^ local
и используется как значение для services.*.image
, например, для nginx
services:
nginx:
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/nginx-${ENV?}:${TAG?}
Если вам интересно: dofroscra
взята из Docker From Scratch.
Окружения и цели сборки
Наша конечная цель – это установка, которую мы можем использовать для
- локальная разработка
- в конвейере CI/CD
- в производстве
И хотя мы стремимся к паритету между этими различными окружениями, будут различия из-за принципиально разных требований. Например.
- на продакшене мне нужен контейнер, включающий исходный код без каких-либо тестовых зависимостей
- на CI мне нужен контейнер, включающий исходный код С тестовыми зависимостями
- на локальном я хочу контейнер, который монтирует исходный код с моего хоста (включая зависимости).
Это отражается через переменную окружения ENV
. Мы используем ее в двух местах:
- как часть имени образа, как суффикс имени сервиса (см. соглашение об именовании образов) 2. для указания целевой стадии сборки.
Для примера смотрите файл docker-compose-php-base.yml
:
services:
php-base:
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-base-${ENV?}:${TAG?}
build:
dockerfile: images/php/base/Dockerfile
target: ${ENV?}
Использование нескольких целей в одном Dockerfile позволяет нам сохранить общую базу, но также включить инструкции, специфичные для среды. Смотрите Dockerfile образа php-base
для примера
ARG ALPINE_VERSION
FROM composer:${COMPOSER_VERSION} as composer
FROM alpine:${ALPINE_VERSION} as base
RUN apk add --update --no-cache
bash
WORKDIR $APP_CODE_PATH
FROM base as local
RUN apk add --no-cache --update
mysql-client
- сначала определяется этап
base
, который включает программное обеспечение, необходимое во всех средах - а затем определяет этап
local
, который добавляет дополнительноmysql-client
, который помогает нам отлаживать проблемы с подключением.
После завершения сборки для local
мы получим образ под названием php-base-local
, который использовал этап сборки local
в качестве целевого этапа сборки.
Makefile
В следующем разделе я представлю несколько команд, например, для сборки и запуска контейнеров. И, честно говоря, я нахожу довольно сложным держать их в уме, не подыскивая точные опции и аргументы. Обычно в такой ситуации я бы создал вспомогательную функцию или псевдоним в локальном файле .bashrc
– но тогда это было бы недоступно другим членам команды и было бы очень специфично для данного проекта.
Вместо этого мы будем использовать самодокументирующийся Makefile, который действует как центральная точка входа в приложение. Поскольку Make-файлы имеют тенденцию расти со временем, я принял некоторые стратегии, чтобы сохранить их “вменяемыми” с помощью включений, общих переменных и лучшей обработки ошибок.
.make/*.mk
includes
Со временем набор make
существенно разрастается, поэтому мы разбиваем его на несколько файлов .mk
в каталоге .make/
. Отдельные файлы имеют префикс с номером, чтобы обеспечить их порядок, когда мы будем включать их в основной Makefile
через
include .make/*.mk
.
└── .make/
├── 01-00-application-setup.mk
├── 01-01-application-commands.mk
└── 02-00-docker.mk
Общие переменные: .make/.env
Мы стараемся сделать общие переменные доступными здесь, потому что затем мы можем передавать их отдельным командам в качестве префикса, например.
.PHONY: some-target
some-target: ## Run some target
ENV_FOO=BAR some_command --baz
Это сделает ENV_FOO
доступным в качестве переменной окружения для some_command
.
Общие переменные используются различными компонентами, и мы всегда стараемся поддерживать только один источник истины. Примером может служить переменная DOCKER_REGISTRY
, которая нужна нам для определения имен образов наших докеров в файлах docker-compose.yml
, а также при последующих push/pulling/deploying образов через make targets. В этом случае переменная требуется как make
, так и docker-compose
.
Чтобы четко разделить переменные и “код”, мы используем файл .env
, расположенный по адресу . make/.env
. Он может быть инициализирован через
make make-init
скопировав файл .make/.env.example
в .make/.env
.
.
└── .make/
├── .make/.env.example
└── .make/.env
Файл включается в основной Makefile
через
-include .make/.env
Префикс -
гарантирует, что make не откажет, если файл не существует (пока), см. GNU make: Including Other Makefiles.
Ручные модификации
При необходимости вы всегда можете изменить файл .make/.env
вручную. Это может произойти, когда вы запускаете docker
на Linux и вам нужно сопоставить user id
вашей хост-системы с user id
контейнера docker. Обычно ваш локальный пользователь и группа имеют идентификатор 1000
. В этом случае вы должны добавить записи вручную в файл .make/.env
.
APP_USER_ID=1000
APP_GROUP_ID=1000
См. также раздел Решение проблем с разрешениями.
Принудительное использование необходимых параметров
Мы как бы “злоупотребляем” make для выполнения произвольных команд (вместо сборки артефактов), и некоторые из этих команд требуют параметров, которые могут быть переданы в качестве аргументов команды в виде
make some-target FOO=bar
Нет возможности “определить” эти параметры, как в сигнатуре метода, но мы все равно можем гарантировать, что отказ произойдет как можно раньше, если параметр отсутствует в форме
@$(if $(FOO),,$(error FOO is empty or undefined))
См. также SO: Как прервать makefile, если переменная не установлена?
Мы используем эту технику, например, чтобы убедиться, что все необходимые переменные определены при выполнении целей docker с помощью цели предварительного условия validate-docker-variables
:
.PHONY: validate-docker-variables
validate-docker-variables:
@$(if $(TAG),,$(error TAG is undefined))
@$(if $(ENV),,$(error ENV is undefined))
@$(if $(DOCKER_REGISTRY),,$(error DOCKER_REGISTRY is undefined - Did you run make-init?))
@$(if $(DOCKER_NAMESPACE),,$(error DOCKER_NAMESPACE is undefined - Did you run make-init?))
@$(if $(APP_USER_NAME),,$(error APP_USER_NAME is undefined - Did you run make-init?))
@$(if $(APP_GROUP_NAME),,$(error APP_GROUP_NAME is undefined - Did you run make-init?))
.PHONY:docker-build-image
docker-build-image: validate-docker-variables
$(DOCKER_COMPOSE) build $(DOCKER_SERVICE_NAME)
Make + Docker = <3
Мы уже внесли некоторую сложность в нашу установку:
- “глобальные” переменные (общие для
make
иdocker
) - несколько файлов
docker-compose.yml
- зависимости сборки
Собрать все это вместе “вручную” – довольно трудоемкое занятие, чреватое ошибками. Но мы можем красиво спрятать все сложности в .make/02-00-docker.mk
, определив две переменные DOCKER_COMPOSE
и DOCKER_COMPOSE_PHP_BASE
.
DOCKER_DIR:=./.docker
DOCKER_ENV_FILE:=$(DOCKER_DIR)/.env
DOCKER_COMPOSE_DIR:=$(DOCKER_DIR)/docker-compose
DOCKER_COMPOSE_FILE:=$(DOCKER_COMPOSE_DIR)/docker-compose.yml
DOCKER_COMPOSE_FILE_LOCAL:=$(DOCKER_COMPOSE_DIR)/docker-compose.local.yml
DOCKER_COMPOSE_FILE_PHP_BASE:=$(DOCKER_COMPOSE_DIR)/docker-compose-php-base.yml
DOCKER_COMPOSE_PROJECT_NAME:=dofroscra_$(ENV)
DOCKER_COMPOSE_COMMAND:=ENV=$(ENV)
TAG=$(TAG)
DOCKER_REGISTRY=$(DOCKER_REGISTRY)
DOCKER_NAMESPACE=$(DOCKER_NAMESPACE)
APP_USER_NAME=$(APP_USER_NAME)
APP_GROUP_NAME=$(APP_GROUP_NAME)
docker-compose -p $(DOCKER_COMPOSE_PROJECT_NAME) --env-file $(DOCKER_ENV_FILE)
DOCKER_COMPOSE:=$(DOCKER_COMPOSE_COMMAND) -f $(DOCKER_COMPOSE_FILE) -f $(DOCKER_COMPOSE_FILE_LOCAL)
DOCKER_COMPOSE_PHP_BASE:=$(DOCKER_COMPOSE_COMMAND) -f $(DOCKER_COMPOSE_FILE_PHP_BASE)
Эти переменные могут быть использованы в дальнейшем в рецептах make.
Обеспечение порядка сборки
Как уже упоминалось в разделе PHP images, нам нужно собирать изображения в определенном порядке и использовать следующие цели make:
.PHONY: docker-build-image
docker-build-image: ## Build all docker images OR a specific image by providing the service name via: make docker-build DOCKER_SERVICE_NAME=<service>
$(DOCKER_COMPOSE) build $(DOCKER_SERVICE_NAME)
.PHONY: docker-build-php
docker-build-php: ## Build the php base image
$(DOCKER_COMPOSE_PHP_BASE) build $(DOCKER_SERVICE_NAME_PHP_BASE)
.PHONY: docker-build
docker-build: docker-build-php docker-build-image ## Build the php image and then all other docker images
Как разработчик, я теперь могу просто запустить make docker-build
– который сначала соберет образ php-base
через docker-build-php
, а затем соберет все остальные образы через docker-build-image
(не указывая переменную DOCKER_SERVICE_NAME
, docker-compose
будет собирать все сервисы, перечисленные в docker-compose. yml
файлах).
Я бы утверждал, что сами рецепты make вполне читабельны и просты для понимания, но когда мы запускаем их с опцией -n
, чтобы только “Вывести рецепт, который будет выполнен, но не выполнять его”, мы получаем представление о сложности:
$ make docker-build -n
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose-php-base.yml build php-base
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml build
Выполнение команд в контейнерах docker
Инструментарий является важной частью рабочего процесса разработки. Сюда входят такие вещи, как линтеры, статические анализаторы и инструменты тестирования, а также “пользовательские” инструменты, ориентированные на ваш конкретный рабочий процесс. Для этих инструментов обычно требуется среда выполнения PHP. На данный момент у нас есть только один “инструмент”, определенный в файле setup.php
. Он обеспечивает создание таблицы jobs
.
Чтобы запустить этот инструмент, мы должны сначала запустить docker setup через make docker-up
, а затем выполнить скрипт в контейнере application
. Соответствующая цель определена в .make/01-00-application-setup.mk
:
.PHONY: setup-db
setup-db: ## Setup the DB tables
$(EXECUTE_IN_APPLICATION_CONTAINER) php setup.php $(ARGS);
что по сути переводится как
docker-compose exec -T --user application application php setup.php
если мы находимся вне контейнера, и чтобы
php setup.php
если мы находимся внутри контейнера. Это довольно удобно, поскольку мы можем запускать инструментарий непосредственно с хост-системы без необходимости входа в контейнер.
Магия” происходит в переменной EXECUTE_IN_APPLICATION_CONTAINER
, которая определена в .make/02-00-docker.mk
как
EXECUTE_IN_WORKER_CONTAINER?=
EXECUTE_IN_APPLICATION_CONTAINER?=
EXECUTE_IN_CONTAINER?=
ifndef EXECUTE_IN_CONTAINER
# check if 'make' is executed in a docker container,
# see https://stackoverflow.com/a/25518538/413531
# `wildcard $file` checks if $file exists,
# see https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html
# i.e. if the result is "empty" then $file does NOT exist
# => we are NOT in a container
ifeq ("$(wildcard /.dockerenv)","")
EXECUTE_IN_CONTAINER=true
endif
endif
ifeq ($(EXECUTE_IN_CONTAINER),true)
EXECUTE_IN_APPLICATION_CONTAINER:=$(DOCKER_COMPOSE) exec -T --user $(APP_USER_NAME) $(DOCKER_SERVICE_NAME_APPLICATION)
EXECUTE_IN_WORKER_CONTAINER:=$(DOCKER_COMPOSE) exec -T --user $(APP_USER_NAME) $(DOCKER_SERVICE_NAME_PHP_WORKER)
endif
Мы можем снова посмотреть через -n
, чтобы увидеть разрешенный рецепт на хост-системе
pascal.landau:/c/_codebase/dofroscra# make setup-db ARGS=--drop -n
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml exec -T --user application application php setup.php --drop
Внутри контейнера это выглядит следующим образом:
root:/var/www/app# make setup-db ARGS=--drop -n
php setup.php --drop;
Решение проблем с разрешениями
Если вы используете Linux, вы можете столкнуться с проблемами с правами доступа при изменении файлов, которые являются общими для хост-системы и контейнеров docker, когда идентификатор пользователя не совпадает, как объяснялось в разделе Синхронизация прав собственности на файлы и папки на общих томах предыдущего руководства.
В этом случае вам необходимо вручную изменить .make/.env
и добавить переменные APP_USER_ID
и APP_GROUP_ID
в соответствии с вашей локальной настройкой. Это необходимо сделать до сборки образов, чтобы убедиться, что в образах используется правильный user id
.
В очень редких случаях это может привести к проблемам, поскольку ваши локальные идентификаторы уже будут существовать в контейнерах docker. Лично я никогда не сталкивался с этой проблемой, но вы можете прочитать о ней более подробно в Docker and the host filesystem owner matching problem. Автор даже предлагает общее решение через проект Github “FooBarWidget/matchhosthostfsowner”.
PHP POC
Чтобы убедиться, что все работает так, как ожидается, репозиторий содержит минимальный пробный вариант PHP. По умолчанию порт 80 с хоста перенаправляется на порт 80 контейнера nginx
.
FYI: Я бы также рекомендовал добавить следующую запись в файл hosts на хост-машине
127.0.0.1 app.local
чтобы мы могли получить доступ к приложению через http://app.local вместо http://127.0.0.1.
Файлы POC в основном гарантируют, что соединения контейнеров, описанные в Local docker setup, работают так, как ожидается:
-
- возвращает настроенные объекты
Redis
иPDO
для общения с очередью и базой данных
- возвращает настроенные объекты
-
- => гарантирует, что
application
может общаться сmysql
- => гарантирует, что
-
- корневой файл веб-страницы, доступ к которому можно получить через http://app.local
- => гарантирует, что
nginx
иphp-fpm
работают
- => гарантирует, что
- содержит 3 различных “маршрута”:
- http://app.local?dispatch=some-job-id
- отправляет новое “задание” с id
some-job-id
в очередь, чтобы его забрал рабочий- => гарантирует, что
php-fpm
может общаться сredis
- => гарантирует, что
- отправляет новое “задание” с id
- http://app.local?queue
- показывает содержимое очереди
- http://app.local?db
- показывает содержимое базы данных
- => гарантирует, что
php-fpm
может разговаривать сmysql
- => гарантирует, что
- показывает содержимое базы данных
- http://app.local?dispatch=some-job-id
- корневой файл веб-страницы, доступ к которому можно получить через http://app.local
-
- запускается как процесс-демон в контейнере
php-worker
- проверяет базу данных redis
0
на наличие ключа"queue"
каждую секунду - если значение найдено, оно сохраняется в таблице
jobs
базы данных- => гарантирует, что
php-worker
может общаться сredis
иmysql
.
- => гарантирует, что
- запускается как процесс-демон в контейнере
Полный сценарий тестирования определен в test.sh
и выглядит следующим образом:
$ bash test.sh
Building the docker setup
//...
Starting the docker setup
//...
Clearing DB
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml exec -T --user application application php setup.php --drop;
Dropping table 'jobs'
Done
Creating table 'jobs'
Done
Stopping workers
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml exec -T --user application php-worker supervisorctl stop worker:*;
worker:worker_00: stopped
worker:worker_01: stopped
worker:worker_02: stopped
worker:worker_03: stopped
Ensuring that queue and db are empty
Items in queue
array(0) {
}
Items in db
array(0) {
}
Dispatching a job 'foo'
Adding item 'foo' to queue
Asserting the job 'foo' is on the queue
Items in queue
array(1) {
[0]=>
string(3) "foo"
}
Starting the workers
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml exec -T --user application php-worker supervisorctl start worker:*;
worker:worker_00: started
worker:worker_01: started
worker:worker_02: started
worker:worker_03: started
Asserting the queue is now empty
Items in queue
array(0) {
}
Asserting the db now contains the job 'foo'
Items in db
array(1) {
[0]=>
string(3) "foo"
}
Завершение
Поздравляем, у вас все получилось! Если какие-то вещи не совсем понятны, не стесняйтесь оставить комментарий. Кроме того, теперь у вас должна быть запущенная установка docker и средства для удобного “управления” ею через make
.
В следующей части этого руководства мы настроим PhpStorm как нашу IDE для использования docker setup.
Пожалуйста, подпишитесь на RSS канал или по электронной почте, чтобы получать автоматические уведомления о выходе следующей части 🙂