Docker с нуля для приложений PHP 8.1 в 2022 году [Учебник Часть 2]

Эта статья впервые появилась на сайте 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
        • ENV против ARG
      • Соглашение об именовании изображений
      • Среды и цели сборки
  • 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 – поскольку есть только один базовый образ, нет необходимости в специальном скрипте.
  • Очистка
    • не имело смысла, поскольку “очищенные файлы” уже были частью предыдущего слоя
    • мы можем “вернуть их” позже, когда оптимизируем размер образа, чтобы ускорить загрузку/выгрузку образов в/из реестра.
  • Предоставление 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
    • контейнер приложений, который мы используем для выполнения команд.

Мы сохраним каталог .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 различных типа переменных:

  1. переменные, которые зависят от локальной настройки отдельного разработчика, например, 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. Мы используем ее в двух местах:

  1. как часть имени образа, как суффикс имени сервиса (см. соглашение об именовании образов) 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
      • http://app.local?queue
        • показывает содержимое очереди
      • http://app.local?db
        • показывает содержимое базы данных
          • => гарантирует, что php-fpm может разговаривать с mysql
    • запускается как процесс-демон в контейнере 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 канал или по электронной почте, чтобы получать автоматические уведомления о выходе следующей части 🙂

Оцените статью
Procodings.ru
Добавить комментарий