Туториал: Разворачиваем Nginx+PHP+MySQL+phpMyAdmin в виртуалке Docker

Туториал: Разворачиваем Nginx+PHP+MySQL+phpMyAdmin в виртуалке Docker

https://habr.com/ru/post/459972/

Возможно, одна из самых основных причин почему мне нравится докер это то, что он позволяет избавиться от необходимости установки на компьютер различных сервисов. К их числу можно отнести и сам веб-сервер Apache или Nginx, базы данных и прочие компоненты инфраструктуры приложения. Вся инфраструктура прописана в конфигурационном файле docker-compose.yml и запускается одной командой вместе с вашим приложением. Все что нужно разработчику работающему с докером, это по сути сам докер и любимая среда разработки и ВСЕ!


Для полноты дальнейшего повествования все-таки придется бегло рассказать, что такое докер и его основные понятия.


Итак, докер следует рассматривать, как некоторую систему виртуализации и контейнеризации.

Одно из базовых понятий докера — это образ. Образ можно сравнить с файлом (может быть даже с исполняемым файлом программы), в котором содержится некоторая информация. Докер может выполнить запуск образа. Запущенный образ называется контейнером. Может быть запущено несколько контейнеров одного и того же образа. 


Так что же содержится в образе? 


Может быть образ операционной системы. К примеру, образ ubuntu. Может быть образ с базой данных, веб-сервером и php и практически с чем угодно. Для начала этих знаний нам будет достаточно.


Предполагается, что у читателя уже установлен сам docker и утилита docker-compose.


Начнем развертывание нашего окружения от простого к более сложному.


Урок №1. Установка Nginx


Попробуем для начала установить один лишь Nginx. Создадим docker-compose.yml следующего содержания:


version: '3.0'

services:

  nginx:
    image: nginx
    ports:
      - 80:80


Далее выполняем команду docker-compose up -d в ответ должно появиться примерно следующее:


Creating network "lesson1_default" with the default driver
Creating lesson1_nginx_1 ... done


Вводим в адресной строке браузера http://localhost/ и нашему взору должно открыться приветствие «Welcome to nginx!». Если все так, вы на верном пути.


Что тут вообще происходит?


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


Разберем представленный файл:


  • version — В начале объявляется версия compose-файла. Вместо 3.0, можно было бы указать и 2.0. Меньше уже не поддерживается. От версии зависят опции в compose-файле, которые доступны для использования.
  • services — объявление сервисов
  • nginx — имя нашего сервиса, задается произвольно по своему усмотрению. В данном примере совпадает с названием образа.
  • image: nginx — имя используемого образа.
  • ports — директива объявляет «проброс» портов. 8080:80 — данная запись означает, что открытому порту 80 контейнера будет соответствовать порт 8080 на хостовой машине, на который запущен докер. В примере из файла, 80 порт в контейнере связан с 80 портом на вашем компьютере.


Более детально разберем объявление образа, директива image: nginx.


Главным хранилищем всех образов является Docker Hub там представлено множество различных готовых образов (Можно собирать и свои собственные, но об этом позже). Объявленный образ nginx является одним из них.


Что касается «проброса» портов. Если вы указываете соответствие 80:80, как и в указанном примере, то nginx будет доступен по адресу localhost:80 или просто localhost. Если же 80 порт уже занят, то можно указать 8080:80. Тогда сайт будет доступен по адресу localhost:8080. И соответственно, если вы вовсе забыли указать данную директиву ports, то порт будет доступен только внутри контейнера и nginx через браузер уже будет недоступен.


Контейнер запущен. А как собственно с ним работать?


Установка Веб-сервера предполагает, что мы хотим с его помощью получать и просматривать html-страницы сайта. Появляется вопрос. Каким образом можно передать в контейнер какие-либо html-файлы? В этом нам помогут volumes


volumes


Приведем наш docker-compose.yml к следующему виду:


version: '3.0'

services:

  nginx:
    image: nginx
    ports:
      - 80:80
    volumes:
      - ./html:/usr/share/nginx/html


Из нового тут появилась директива volumes, которая говорит, что происходит монтирование локальной папки ./html в контейнер по адресу /usr/share/nginx/html. 


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


Чтобы все заработало, создадим папку html на одном уровне с файлом docker-compose.yml и добавим в нее файл index.html с произвольным текстом. Например, Hello from Docker!


И выполняем пересоздание контейнера той же командой docker-compose up -d

Докер выполняет пересоздание контейнера.


Recreating lesson1_nginx_1 ... done


Проверяем в браузере получившийся результат. И видим: Hello from Docker! Все получилось.


Важно заметить, что монтированная папка доступна для изменений в реальном времени. Т.е. если мы изменим текст в файле index.html или добавим новый файл в папку, то все эти изменения сразу же будут доступны внутри контейнера. Эта важная функция и позволяет вести разработку через докер. Мы изменения вносим на своем компьютере в примонтированные файлы, папки и они сразу же отображаются и в контейнере докера. 


Рассмотрим пример развертки локального окружения состоящего из связки Nginx+PHP+MySql+phpMyAdmin. Данная связка очень популярна и может удовлетворить ряд стандартных потребностей рядового разработчика.


Как и в прошлом посте акцент будет смещен в сторону утилиты docker-compose, чем докера в чистом виде. 


Итак, поехали!


Начнем вот с такого docker-compose.yml, который лежит в отдельной папке proxy:

version: '3.0'

services:

  proxy:
    image: jwilder/nginx-proxy
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - proxy


networks:
  proxy:
    driver: bridge



В представленном файле описана конфигурация для создания одного контейнера с именем proxy на базе образа image: jwilder/nginx-proxy и создание сети с одноименным именем. Директива networksуказывает к каким сетям подключен контейнер, в данном примере, это наша сеть proxy. 


При создание сети директиву driver: bridge можно было бы и не указывать. Драйвер типа «мост» является драйвером по умолчанию. Данный контейнер будет связываться по сети с прочими контейнерами.


Образ jwilder/nginx-proxy является базовым и взят и Docker Hub там же представлено довольно обширное и подробное описание по его использованию. Принцип работы nginx-proxy довольно простой, он через пробрасываемый сокет докера получает доступ к информации о запущенных контейнерах, анализирует наличие переменной окружения с именем VIRTUAL_HOST и перенаправляет запросы с указанного хоста на контейнер, у которого данная переменная окружения задана.


Запускаем прокси уже известной командой docker-compose up -d наблюдаем следующий вывод:


Creating network "proxy_proxy" with driver "bridge"
Creating proxy_proxy_1 ... done


Данный вывод информирует нас о том, что в начале была создана сеть proxy_proxy, а затем был создан контейнер proxy_proxy_1. Имя сети получилось из названия папки, в которой размещался файл docker-compose.yml, у меня это proxy и одноименного имени сети.


Если ввести команду docker network ls, то мы увидим список сетей докера в нашей системе и одна из них должна быть proxy_proxy.


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


Создаем второй docker-compose.yml следующего содержания:

version: '3.0'

services:

  nginx:
    image: nginx
    environment:
      - VIRTUAL_HOST=site.local
    depends_on:
      - php
    volumes:
      - ./docker/nginx/conf.d/default.nginx:/etc/nginx/conf.d/default.conf
      - ./html/:/var/www/html/
    networks:
      - frontend
      - backend

  php:
    build:
      context: ./docker/php
    volumes:
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
      - ./html/:/var/www/html/
    networks:
      - backend

  mysql:
    image: mysql:5.7
    volumes:
      - ./docker/mysql/data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
    networks:
      - backend


  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    environment:
      - VIRTUAL_HOST=phpmyadmin.local
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=root
    networks:
      - frontend
      - backend


networks:
  frontend:
    external:
      name: proxy_proxy
  backend:



Что тут у нас объявлено?


Перечислены четыре сервиса: nginx, php, mysql и phpmyadmin. И две сети. Одна сеть прокси с именем frontend, объявлена как внешняя сеть и новая внутренняя сеть backend. Драйвер для нее не указан, как и писал ранее, будет использоваться драйвер по умолчанию типа bridge.


nginx


Тут примерно должно быть все понятно. Используем базовый образ с докер хаб. Переменная окружения необходима для работы прокси и сообщает ему, по какому адресу должен быть доступен контейнер. Опция depends_on указывает, на зависимость данного контейнера от контейнера php. Это означает, что вперед будет запущен контейнер php, а после него будет выполнен запуск зависимого от него контейнера nginx. Далее пробрасываем конфигурацию для нашего nginx. Она будет чуть ниже и монтируем папку с html. Так же замечаем, что контейнер имеет доступ сразу к двум сетям. Он должен связываться и прокси из сети frontend и с php из сети backend. В принципе, можно было бы все контейнеры и в одну сеть frontend попихать, но я придерживаюсь, что подобное разделение более верное. 


default.nginx

server {
    listen 80;
    server_name_in_redirect off;
    access_log  /var/log/nginx/host.access.log  main;

    root /var/www/html/;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location ~ /\.ht {
        deny  all;
    }
}



default.nginx — это конфиг для nginx, который пробрасывается в контейнер. Ключевой момент тут директива fastcgi_pass php:9000. Она задает адрес FastCGI-сервера. Адрес может быть указан в виде доменного имени или IP-адреса, и порта.


php:9000 — имя сервиса это и есть адрес FastCGI-сервера. Nginx обращаясь по адресу php будет получать IP-адрес контейнера, в котором работает php. Порт 9000 это стандартный порт, он объявлен при создание базового контейнера. Данный порт доступен для nginx по сети, но не доступен на хостовой машине, так как не был проброшен.


php


Тут необычно то, что не указан образ. Вместо этого происходит сборка собственного образа прямо из compose-файла. Директива context указывает на папку, в которой находится Dockerfile.


Dockerfile

FROM php:7.3.2-fpm

RUN apt-get update && apt-get install -y \
        libzip-dev \
        zip \
 && docker-php-ext-configure zip --with-libzip \
 && docker-php-ext-install zip \
 && docker-php-ext-install mysqli

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html



В Dockerfile указано, что для сборки используется базовый образ php:7.3.2-fpm, далее выполняется запуск команд для установки php-расширений. Далее копируется composer из другого базового образа и устанавливается рабочая директория для проекта. Детальнее вопросы сборки рассмотрю в других постах.


Также во внутрь контейнера пробрасывается файл php.ini и папка html с нашим проектом.


Заметим, что php находится в сети backend и к примеру прокси к нему доступ получить уже не может.


mysql


Берется базовый образ mysql с тегом 5.7, который отвечает за версию mysql. Папка ./docker/mysql/data используется для хранения файлов базы данных (ее даже создавать не надо, сама создасться при запуске). И через переменные окружения задается пароль для пользователя root, тоже root.


База находится в сети backend, что позволяет ей держать связь с php. В базовом образе используется стандартный порт 3306. Он доступен по сети докера для php, но не доступен на хостовой машине. Если выполнить проброс для данного порта, то можно к нему коннектиться к примеру из того же PHPSTORM. Но если вам достаточно интерфейса phpmyadmin, то этого можно и не делать.


phpmyadmin


Официальный образ phpmyadmin. В переменных окружения используется VIRTUAL_HOST для взаимодействия с прокси, аналогично nginx. PMA_USER и PMA_PASSWORD доступ к базе. И PMA_HOST сам хост базы. Но это не localhost, как обычно бывает, а mysql. Т.е. связь с базой доступна по имени ее сервиса, т.е. mysql. Контейнер phpmyadmin может связаться с базой, т.к имеет подключение к сети frontend.


Запускаем сервисы привычной командой: docker-compose -d.


Видим следующий вывод:

Creating network "lesson2_backend" with the default driver
Building php
Step 1/4 : FROM php:7.3.2-fpm
 ---> 9343626a0f09
Step 2/4 : RUN apt-get update && apt-get install -y         libzip-dev         zip      && docker-php-ext-configure zip --with-libzip   && docker-php-ext-install zip   && docker-php-ext-install mysqli
 ---> Using cache
 ---> 5e4687b5381f
Step 3/4 : COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
 ---> Using cache
 ---> 81b9c665be08
Step 4/4 : WORKDIR /var/www/html
 ---> Using cache
 ---> 3fe8397e92e6
Successfully built 3fe8397e92e6
Successfully tagged lesson2_php:latest
Pulling mysql (mysql:5.7)...
5.7: Pulling from library/mysql
fc7181108d40: Already exists
787a24c80112: Already exists
a08cb039d3cd: Already exists
4f7d35eb5394: Already exists
5aa21f895d95: Already exists
a742e211b7a2: Already exists
0163805ad937: Already exists
62d0ebcbfc71: Pull complete
559856d01c93: Pull complete
c849d5f46e83: Pull complete
f114c210789a: Pull complete
Digest: sha256:c3594c6528b31c6222ba426d836600abd45f554d078ef661d3c882604c70ad0a
Status: Downloaded newer image for mysql:5.7
Creating lesson2_php_1        ... done
Creating lesson2_mysql_1      ... done
Creating lesson2_phpmyadmin_1 ... done
Creating lesson2_nginx_1      ... done



Видим, что в начале происходит создание сети lesson2_backend, затем сборка образа php, потом может происходить скачивание образов, которых еще нет в системе (pull) и собственно запуск описанных сервисов.


Последний штрих, чтобы все заработало это добавление в hosts или доменов site.local и phpmyadmin.local.


Содержимое index.php может быть следующим:

<?php

//phpinfo();

$link = mysqli_connect('mysql', 'root', 'root');
if (!$link) {
    die('Ошибка соединения: ' . mysqli_error());
}
echo 'Успешно соединились';
mysqli_close($link);



Тут мы проверяем корректность подключения расширения php — mysqli, которое было добавлено при сборке Dockerfile.


И заметим, что для связи с контейнером используется название сервиса — mysql.


Структура всего проекта получилась следующей:

habr/lesson2$ tree
.
├── docker
│   ├── mysql
│   │   └── data
│   ├── nginx
│   │   └── conf.d
│   │       └── default.nginx
│   └── php
│       ├── Dockerfile
│       └── php.ini
├── docker-compose.yml
├── html
│   └── index.php
└── proxy
    └── docker-compose.yml




Report Page