Docker-Compose PHP

Сделал видео о моем создании Docker-Compose для приложения Laravel

Также приглашаю на свой блог о Laravel и программировании https://maxyc.ru

Данная статья перевод в будущем будет удалена и переписана с нуля. Делалась для того, чтобы самому разобраться. Разобрался! Нашел много новых и более простых решений. По мере необходимости дополняю и исправшляю ошибки. В будущем хочу написать свою статью, со своими ошибками, трудностями, и решениями.

Многие последнее время сходят с ума по контейнеризации. Что это? Для чего? Полез я разбираться вместе с вами. Обещают, что это проще, чем кажется. Давайте попробуем вместе сделать полноценное PHP окружение для разработки.

Планируем контейнеры

Одно из преимуществ Docker это то, что мы можем определять разные контейнеры для различных задач. Это позволяет нам более правильно организовывать файлы.

В первую очередь, нам потребуется веб сервер nginx, php-fpm, mariaDB. Если нам в будущем потребуется еще какое либо ПО, мы с легкостью сможем добавить еще один контейнер. А пока создадим структуру папок, как показано на изображении ниже. Как видите, мы также добавили папку src для исходников.

Давайте теперь мы добавим файл Dockerfile в каждую служебную папку и index.php в папку src. У нас должно получиться следующая структура:

В index.php предлагаю разместить следующий код, который мы увидим, когда все заработает:

<?php $value = “World”; ?> 
<html>
<body>
<h1>Hello, <?= $value ?>!</h1>
</body>
</html>

Ну, а теперь повеселимся!

Создаем наши Dockerfiles

Одна из главных фишек Docker-Compose в том, что он сильно упрощает содержимое наших Dockerfiles, особенно в сочетании с предварительно созданными контейнерами служб (nginx, mysql, etc…). В результате, мы лишь ссылаемся на образы и указываем контейнерам какие либо необходимые параметры, например, на каком порту ему работать.

Вот пример настройки для нашей службы базы данных:

FROM mariadb:latest  
CMD ["mysqld"]
EXPOSE 3306

FROM сообщает Докеру какой образ использовать для нашего контейнера. Указывается в виде Repository:version

CMD сообщает Докеру какую команду необходимо выполнить после того, как контейнер будет запущен. В нашем случае команда запускает сервер.

EXPOSE сообщает контейнеру, какие порты следует использовать для внутренней сети Docker. Другие контейнеры могут использовать эту информацию для подключения к нему.

Это все, что нам нужно! Если вы запустите “docker up” в папке “database”, Docker сможет запустить эту службу.

Все что мы делаем, это ссылаемся на образ ОС с конкретной службой. Эти образы очень минималистичны. Ведь нам не нужны полнофункциональные операционные системы, потому мы используем Alpine OS. Это linux, который очень сильно урезан до минималистичной эффективной рабочей ОС.

Наш Dockerfiles теперь выглядит так:

# nginx 
FROM nginx:alpine
CMD ["nginx"]
EXPOSE 80 443
# php-fpm
FROM php:fpm-alpine
CMD ["php-fpm"]
EXPOSE 9000

Файл Docker-Compose

Для эффективного запуска контейнеров Docker нам нужен способ их организации. Создадим и откроем файл docker-compose.yml в корне.

version: '3'services:
php-fpm:
nginx: database:

В разделе services мы указываем названия наших служебных папок. Докер будет понимать, что это три наших контейнера. Настраивать их будем чуть позже, просто дополняя каждый из этих разделов параметрами.

The Build Context

Далее нам необходимо указать Докеру где найти Dockerfile для наших сервисов. Сделать это мы можем, добавив параметр context, с относительным путем к папке

services:
database:
build:
context: ./database

Наш первый запуск

В этом месте, технически мы можем выполнить docker-compose up и Докер запустит наши сервисы. Давайте попробуем и посмотрим, что получится?

Ну что ж… Вроде все заработало, правда база данных не запустилась, потому что мы не создали пользователя и базу данных. Давайте сделаем это.

База данных

Решение такое же простое, как и сама ошибка. Сервер БД просит пароль для root пользователя и базу данных. Так давай укажем это.

В разделе environment мы можем передавать переменные в Докер. В нашем случае это имя базы данных, пользователь, пароль и пароль для root пользователя.

Теперь наш раздел базы данных выглядит следующим образом:

database:
build:
context: ./database
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=myuser
- MYSQL_PASSWORD=secret
- MYSQL_ROOT_PASSWORD=docker

Давай попробуем снова

Уже лучше! Никаких ошибок!

Сервер

Давайте вернемся к нашему первому запуску. Там мы видели в конце такую строчку:

docker_nginx_1 exited with code 0

Почему-то наш сервер nginx не запустился. Нет идей почему? Думаю в том, что его нужно немного донастроить.

Исходники

Контейнер не видит наши исходники по умолчанию. Для этого нам необходимо указать ему место, где он может их найти.

nginx:
build:
context: ./nginx
volumes:
- ../src:/var/www

Как видите, мы указали где искать исходники нашего сайта в формате папокhost:container. Этот путь может содержать как файлы, так и папки, а также может иметь относительный или абсолютный пути.

Теперь, когда мы снова попробуем запуститьdocker-compose up, Nginx не выключается! Давайте перейдем на localhostи посмотрим, что у нас получилось.

Это не то, что мы ожидали…

Пробрасываем порты

Теперь нам необходимо в секции ports указать Докеру какие порты нам необходимо пробросить в формате host:container.

nginx:
build:
context: ./nginx
volumes:
- ../src:/var/www
ports:
- "80:80"
- "443:443"

Оно заработало! В самом деле не совсем… А как же php?

php-fpm

Nginx по умолчанию загружает только HTML файлы. Перейдем к настройке. Нам необходимо указать серверу открывать PHP файлы, но Nginx все еще не умеет их обрабатывать. Для этого у нас есть php-fpm. Нам необходимо настроить конфиг Nginx и создать новый виртуальный хост.

Для начала создадим конфиг в папке nginx:

Поскольку наш php-fpm сервис находится в своем контейнере, нам необходимо подружить его с nginx контейнером. Докер удобно настраивает внутреннюю сеть между контейнерами, потому мы можем довольно просто обращаться к контейнерам по их имени. Просто добавим следующую настройку в conf.d/default.conf:

upstream php-upstream {
server php-fpm:9000;
}

UPD: ВАЖНО!

для php-fpm так же нужно проложить доступ для /var/www

volumes:
- ../src:/var/www

Затем, настроим наш виртуальный хост вsites/default.conf:

server {    listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name localhost;
root /var/www;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_pass php-upstream;
fastcgi_index index.php;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#fixes timeouts
fastcgi_read_timeout 600;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt/;
log_not_found off;
}
}

Конфиг выше довольно стандартный для nginx. Наше внимание привлекает только строка fastcgi_pass php-upstream;. В этой строке мы ссылаемся на upstream из конфигаconf.d/default.conf.

Теперь, поскольку наши изменения хоста немного отличаются от дефолтного, нам необходимо немного поправить основной конфиг nginx.conf :

user  nginx;
worker_processes 4;
daemon off;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
# Switch logging to console out to view via Docker
#access_log /dev/stdout;
#error_log /dev/stderr;
sendfile on;
keepalive_timeout 65;

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*.conf;
}

Наиболее интересные параметры здесь, это подключение конфигов виртуальных хостов в конце файла и daemon off. daemon off является решением проблемы exited with status 0, потому что он держит nginx в фоновом режиме.

Ну и наконец, мы укажем контейнеру, где брать наши файлы конфигурации.

nginx:
build:
context: ./nginx
volumes:
- ../src:/var/www
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/sites/:/etc/nginx/sites-available
- ./nginx/conf.d/:/etc/nginx/conf.d
depends_on:
- php-fpm

Добавляем новый параметрdepends_on, который указывает Докеру, что контейнер nginx должен дождаться запуска php-fpm контейнера, т.к. зависит от его работы.

Пробуем, Работает!

Пробуем работать с базой данных

Наш прошлый index.php не позволял работать с БД. Давайте исправим это.

<?php$value = "World";$db = new PDO('mysql:host=database;dbname=mydb;charset=utf8mb4', 'myuser', 'secret');$databaseTest = ($db->query('SELECT * FROM dockerSample'))->fetchAll(PDO::FETCH_OBJ);?><html>
<body>
<h1>Hello, <?= $value ?>!</h1>
<?php foreach($databaseTest as $row): ?>
<p>Hello, <?= $row->name ?></p>
<?php endforeach; ?>
</body>
</html>

Мы также добавим файл данных в процесс запуска и используем специальную папку точки входа образа базы данных в качестве места назначения Тома в файле docker-compose:

database:
build:
context: ./database
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=myuser
- MYSQL_PASSWORD=secret
- MYSQL_ROOT_PASSWORD=docker
volumes:
- ./database/data.sql:/docker-entrypoint-initdb.d/data.sql

В заключении, мы установим PDO расширение для php-fpm. Для этого добавим команду RUN в наш файл Dockerfile.

FROM php:fpm-alpineRUN docker-php-ext-install pdo_mysqlCMD ["php-fpm"]EXPOSE 9000

Теперь, когда мы выполним docker-compose up, Докер импортирует данные из нашего файла данных.

Ну и финальный результат

Вот и все ребята

Выше мы попытались разобраться в основах докера на примере очень простой настройки веб-сервера. Но пройдя через те же процессы, описанные выше, мы сможем добавить в кластер любые сервисы, какие захотим.

Давайте экспериментировать вместе!

Окончательный рабочий вариант настройки

version: '2'
services:
database:
restart: always
build:
context: ./database
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=myuser
- MYSQL_PASSWORD=secret
- MYSQL_ROOT_PASSWORD=docker
php-fpm:
build:
context: ./php-fpm
volumes:
- ../src:/var/www
nginx:
ports:
- "80:80"
- "443:443"
build:
context: ./nginx
volumes:
- ../src:/var/www
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/sites/:/etc/nginx/sites-available
- ./nginx/conf.d/:/etc/nginx/conf.d
depends_on:
- php-fpm

--

--