По работе потребовалось ставить на конвейер разработку модулей под разные CMS для команды разработчиков. Имея опыт работы с Docker, было решено конфигурировать среду разработки модуля для CMS в Docker.
При наличии достаточного пространства на диске, организованная среда на docker контейнерах имеет ряд преимуществ:
В данной статье развернем среду разработки в Docker для произвольной CMS на php с использованием Apache2, PHP-FPM, MySQL (5.7/8), phpMyAdmin. В конце подведем итог.
Когда-то мы уже пробовали развернуть локальный LAMP, теперь развернем его в Docker.
Файловая структура:
.docker
- директория с конфигами для организации среды разработки
mysql
php
webserver
(не apache чтобы в дальнейшем можно было заменить на любой другой web сервер)cms
- директория с исходниками CMS
src
- директория с исходниками плагина (если нужно)Каждый компонент среды будет помещен в отдельный контейнер - микросервисная архитектура.
При разработке плагина для CMS, директорию с исходниками CMS нужно добавить в
.gitignore
чтобы не коммитить в удаленный репозиторий.
Сначала сконфигурируем каждый сервис/контейнер по отдельности, а потом запишем это в docker-compose.yml
.
Возможно, проще для понимания будет параллельно поглядывать на
docker-compose.yml
.
Нужна отладка и тесты на phpunit, значит нужен xdebug и composer.
Создаем файл .docker/php/Dockerfile
и записываем в него конфигурацию образа на основе официального образа docker php:
FROM php:7.3-fpm
RUN apt update && apt install -y libzip-dev zip mc nano
# установка модулей php из репозитория ОС
RUN docker-php-ext-install pdo_mysql zip
# установка модулей php из репозитория pecl
RUN pecl install xdebug && docker-php-ext-enable xdebug
# установка composer
RUN cd ~ \
&& curl -sS https://getcomposer.org/installer -o composer-setup.php \
&& php composer-setup.php --install-dir=/usr/local/bin --filename=composer
# запуск PHP-FPM
CMD ["php-fpm"]
Внутрь каждого контейнера предназначенного для разработки, я устанавливаю mc (консольный файловый менеджер) и nano (консольный текстовый редактор), чтобы работать внутри контейнера с удобным для меня ПО.
Внутрь контейнера с PHP будем прокидывать следущие файлы:
.docker/php/php.ini
(документация про файл конфигурации php и про директивы php.ini:[PHP]
user_ini.filename = "php.ini"
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
log_errors = On
error_log = /var/www/html/php_errors.log
.docker/php/xdebug.ini
(документация xdebug по настройкам):xdebug.remote_handler = dbgp
xdebug.log=/var/www/html/xdebug.log
xdebug.client_host = 172.18.0.1
xdebug.client_port = 9003
xdebug.mode=coverage,debug
xdebug.start_with_request = yes
.docker/php/entrypoint.sh
:#!/bin/bash
# устанавливаем нужные библиотеки, если надо
composer install
# запускаем вечный цикл, чтобы контейнер не упал
while true; do sleep 10; done;
.docker/php/entrypoint.sh
будет запускаться каждый раз при старте контейнера!
За основу возьмем официальный Docker образ Apache2 от Canonical (Ubuntu) и внутри образа установим модуль для работы веб-сервера по FastCGI. Запишем все это в конфиг образа .docker/webserver/Dockerfile
:
FROM ubuntu/apache2:latest
RUN apt update && apt install -y libapache2-mod-fcgid nano mc
Запускать веб-сервер будем в отдельном скрипте .docker/webserver/entrypoint.sh
, имхо удобнее, там несколько команд на запуск:
#!/bin/bash
# включаем нужные модули
a2enmod proxy
a2enmod proxy_fcgi
a2enmod rewrite
# тестируем конфиги
apachectl configtest
# запускаем демона apache2
service apache2 start
# стартуем вечный цикл
while true; do sleep 10; done;
Сделаем конфиг .docker/webserver/host.conf
для нашего виртуального хоста:
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<FilesMatch \.php$>
# по дефолту PHP-FPM работает на 9000 порту
# имя хоста php берем из имени сервиса,
# которое присвоили контейнеру с php в docker-compose-dev.yml
SetHandler "proxy:fcgi://php:9000"
</FilesMatch>
ErrorLog /var/www/html/error.log
CustomLog /var/www/html/access.log combined
</VirtualHost>
Теперь нам необходим сам конфиг .docker/webserver/apache2.conf
. Оригинальный конфиг apache2 в контейнере можно просмотреть так:
# запускаем контейнер в фоне
$ docker run -d ubuntu/apache2
# смотрим список запущенных контейнеров
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2464a3b528d2 ubuntu/apache2 "apache2-foreground" 25 seconds ago Up 23 seconds 80/tcp heuristic_lederberg
# заходим в терминал в контейнере
$ docker exec -it 2464a3b528d2 /bin/bash
# просматрвиаем файл конфига
$ cat /etc/apache2/apache2.conf
# здесь будет вывод файла конфига
# закрываем сессию терминала в контейнере (выходим из контейнера)
$ exit
# останавливаем контейнер
$ docker stop 2464a3b528d2
Копируем то что в файле конфига, и заменяем некоторые настройки на это:
# ip адрес сервера
ServerName 172.18.0.1
# разрешение на децентрализованную конфигурацию (.htaccess) в диреткории /var/www/
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# название файла с децентрализованным конфигом
AccessFileName .htaccess
IP адрес директивы
ServerName
должен совпадать с IP адресом подсети создаваемой для композиции контейнеров вdocket-compose.yml
.
В простом варианте достаточно взять Docker образ MySQL и заюзать переменные из раздела Environment Variables
типа MYSQL_ROOT_PASSWORD
, MYSQL_USER
и MYSQL_PASSWORD
.
Однако, у меня это не заработало в момент когда появилась вторая композиция контейнеров, то есть была одна запущенная композиция контейнеров с MySQL и все работало, но при попытке поднять вторую композицию контейнеров с использованием MySQL начались проблемы, что-то вроде такого:
Lost connection to MySQL server at 'reading initial communication packet', system error: 104
# или
Can't open and lock privilege tables: Table 'mysql.user' doesn't exist
# или
Can't open the mysql.plugin table. Please run mysql_upgrade to create it
И да, я не пытался поднять второй инстанс одной и той же композиции контейнеров, нет.
Все сводилось к тому, что сервис MySQL не мог инициализироваться.
В одном случае могла успешно работать композиция контейнеров 1, но не работала 2-ая композиция, но после docker system prune -a
, магичеким образом переставала работаться 1-ая композиция, а 2-ая работала.
Решение: провести инициализацию самостоятельно при первом запуске сервиса.
Для этого нужно 2 файла.
.docker/mysql/entrypoint.sh
:
#!/bin/bash
# инициализировать mysql без root пароля
# в первый запуск сработает, во второй нет, потому что хранилище уже инициализировано
mysqld --initialize-insecure
# запустить демона mysql в фоне
mysqld --user=root --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password --daemonize
# зайти в mysql от root но без пароля и передать на выполнение содержимое файла
# в первый запуск сработает, во второй не сможет зайти без пароля
mysql -u root < /usr/bin/init.sql
# запустить вечный цикл
while true; do sleep 10; done;
.docker/mysql/init.sql
для MySQL 5.7:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';
GRANT ALL ON *.* to 'root'@'%' IDENTIFIED BY 'root';
FLUSH PRIVILEGES;
CREATE DATABASE IF NOT EXISTS db_name;
USE db_name;
.docker/mysql/init.sql
для MySQL 8.0:
CREATE USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
FLUSH PRIVILEGES;
CREATE DATABASE IF NOT EXISTS db_name;
USE db_name;
'root'@'%'
позволяет указать чтоroot
пользователь может заходить с любого хоста.localhost
будет доступен только в контейнере с MySQL, но к сервису MySQL в композиции контейнеров, будут обращаться другие контейнеры не поlocalhost
, а по имени сервиса MySQL, например'root'@'mysql'
.
А здесь ничего дополнительно конфигурировать не будем, просто возьмем Docker образ phpMyAdmin, прокинем порт контейнера на хост машину, и укажем доступы к сервису mysql прямо в docker-compose.yml
, и все будет работать :)
Сделаем docker-compose.yml
(документация):
version: '3.0'
services:
mysql:
image: mysql:5.7
container_name: cms-mysql-dev
restart: unless-stopped
volumes:
# хранилище mysql прокинем на хост машину
- ./docker-data/mysql/data:/var/lib/mysql
- ./.docker/mysql/init.sql:/usr/bin/init.sql
- ./.docker/mysql/entrypoint.sh:/usr/bin/entrypoint.sh
command: /bin/bash -c "/usr/bin/entrypoint.sh"
networks:
- app-network
php:
build:
context: ./.docker/php
container_name: cms-php-dev
restart: unless-stopped
depends_on:
- mysql
volumes:
# монтируем файлы cms (и плагина) внутрь контейнера
networks:
- app-network
webserver:
build:
context: ./.docker/webserver
container_name: cms-webserver-dev
restart: unless-stopped
depends_on:
- php
ports:
- "5011:80"
volumes:
- ./.docker/webserver/host.conf:/etc/apache2/sites-available/000-default.conf
- ./.docker/webserver/apache2.conf:/etc/apache2/apache2.conf
- ./.docker/webserver/entrypoint.sh:/usr/bin/entrypoint.sh
command: /bin/bash -c "/usr/bin/entrypoint.sh"
networks:
- app-network
pma:
image: phpmyadmin/phpmyadmin:latest
container_name: cms-pma-dev
restart: unless-stopped
depends_on:
- mysql
ports:
- "5012:80"
networks:
- app-network
networks:
app-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.18.0.1/24
В терминал введем команду:
$ docker-compose up --build
И наш пустой web-сервер заработает, можно проверить по адресу localhost:5011
.
Настало время разобраться как же сюда впихнуть CMS ...
Чтобы CMS заработала в Docker ее надо прокинуть/смонтировать - снаружи внутрь, и тогда можно будет редактировать файлы на хост машине, а изменения будут вступать в силу сразу при сохранении файлов на диск.
В случае когда работа осуществляется с целым инстансом CMS и этот ##инстанс CMS является продуктом##, то на этом развертывание среды заканчивается - монтируем CMS, запукаем docker-compose, заходим на localhost:5011
и производим установку CMS.
Если в качестве продукта выступает ##плагин##/##модуль##/##дополнение## для CMS, тогда, кроме прокидывания CMS нужно настроить прокидывание файлов модуля - сначала монтируются файлы CMS, затем файлы модуля.
Если говорить про плагины для Wordpress, 1С-Битрикс, Shop-Script и прочие CMS где плагин располагается в отдельной директории, то достаточно прокинуть только эту директорию.
Например, в корне есть такие директории:
shop-script
- исходники CMS
src
- исходники плагинаТогда монтирование CMS и исходников будет выглядеть так:
volumes:
- ./shop-script/:/var/www/html/
- ./src/:/var/www/html/wa-apps/shop/plugins/mymodule/
А если файлы плагина внедряются смешиваясь с основными файлами CMS, как например сделано в OpenCart, то прокидывать файлы плагина надо по отдельности каждый.
Например, в корне есть такие диреткории:
opencart
- исходники CMS
src
- исходники плагинаПри попытке такого монтирования:
volumes:
- ./opencart/:/var/www/html/
- ./src/:/var/www/html/
Получим такую ошибку:
ERROR: Duplicate mount points: [/home/byurrer/modules/opencart3.0/opencart:/var/www/html:rw, /home/byurrer/modules/opencart3.0/src:/var/www/html:rw]
А вот так, многословно но работает:
volumes:
- ./opencart/:/var/www/html/
- ./src/admin/controller/extension/module/mymodule.php:/var/www/html/admin/controller/extension/module/mymodule.php
- ./src/catalog/controller/extension/module/mymodule.php:/var/www/html/catalog/controller/extension/module/mymodule.php
- ./src/admin/language/ru-ru/extension/module/mymodule.php:/var/www/html/admin/language/ru-ru/extension/module/mymodule.php
В итоге мы получаем среду для работы с CMS в Docker контейнерах, организованных при помощи композиции контейнеров. При этом работа с CMS может осуществляться в двух вариантах:
По адресу localhost:5011
будет доступен сайт на CMS, а по адресу localhost:5012
будет доступен phpMyAdmin.