Разработка под Docker. Локальное окружение. Часть 2 — Nginx+PHP+MySql+phpMyAdmin

    Для лучшего понимания нижеследующего материяла сначала рекомендуется ознакомится с Предыдушим постом

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

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

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

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

    docker-compose.yml для nginx-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 следующего содержания:

    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 может быть следующим:

    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
    

    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      0

      А почему именно PMA? Можно взять Adminer, который будет пошустрее и полегче. Да и в установке попроще (поставляется одним файлом)

        –1
        phpmyadmin — личные предпочтения. В докере как раз сложность установки нивелируется. Куда уж проще пару строк в композ-файл добавить.
        +1
        Ну теперь пора писать про докер в проде
          0
          COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
          Далее копируется composer из другого базового образа

          просто скопировать достаточно? в дальнейшем можно спокойно делать composer selfupdate? как-то в composer/docker более интересные вещи происходят. Интересно куда кэш пойдет писать, зависимости в целом, php.ini для php-cli, к тому же можно выбрать php 7.1, а composer тянет последний PHP и т.д. со всем этим не будет проблем?
          Интересно в целом работа с php-cli под докером, допустим тот же artisan.php от Лары и yii.php от Yii. Можно отдельный поднять контейнер от php:*-cli, но тогда как все будет или все в один контейнер закинуть?) npm и в целом сборку фронта если нужно поднять, то как лучше? или я рано начал про это все?)
            0
            composer:latest — это последний образ. Есть сомнения, что его потребуется обновить, но полагаю ничего не помешает ему обновиться при необходимости.
            В указанном докер файле все сводится к установке лишь одного файла composer.phar, который в дальнейшем переименовывается в /usr/bin/composer. Копирование вполне корректно. Набором исходников он точно не поставляется, если разговор про это. Кеш писать? Что при копирование мешает это делать? Зависимости? Опять же он поставляется одним файлом, для выполнения, которого нужен php.
            В образе с php-fpm ничего не мешает выполнить любой скрипт в консольном режиме. Для докера есть образы php-cli, но такой образ можно использовать, если нет потребности в php в роли fastcgi.
            Касаемо сборки фронта и бекенда. В бекенде должен быть в основном php код и образ основывается на php-fpm, а фронт как правило всегда базируется на nginx. В этом образе должен быть публичный код, т.е. всякие js, css и например публичный index.php запрос к которому будет перенаправляться на бекенд. Если для сборки фронта требуется node, то проект можно собрать в образе нода и перекопировать скомпилированные js и css в образ фронта. Это речь больше про сборку для прода. Для дева тоже ничего особо не мешает использовать для сборки образ с нод или чем-то еще. Ну или на худой конец собрать свой.

            В дальнейшем будут посты на эту тему.
              0

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


              Для выполнения cli набираешь простую команду


              docker-compose exec php

              Проваливаешься внутрь запущенного контейнера и там уже выполняешь все необходимые тебе команды: composer install(update) yii и тд.

                0
                про php.ini и версии php верно подметили. меня тоже очень интересует cli, это стоит осветить.
                В сетях уже я думаю стоило бы упомянуть ipam, да и про make файлы можно тоже было бы сказать, часть программистов незаслуженно игнорируют этот удобный функционал.
                  0
                  И установка composer путем копирования не единственный вариант, скорее как одно из интересных решений. Если вдруг не сработает, то можно и по официальной инструкции пойти.
                  Про последнюю версию php, что-то в первые слышу. В системных требованиях getcomposer.org/doc/00-intro.md#system-requirements сказано, что достаточно и PHP 5.3.2
                    +1
                    Про последнюю версию php, что-то в первые слышу. В системных требованиях getcomposer.org/doc/00-intro.md#system-requirements сказано, что достаточно и PHP 5.3.2

                    я тут про образ в котором «собирается»(?) композер, сейчас в нем FROM php:7-alpine сегодня может проблем нет, но что будет завтра? Как пример composer начал собираться FROM php:8-*, используется новая мажорная версия самого композера, а мы перетаскиваем его в контейнер, где стоит php7?) да и джуны могут начать юзать такой подход там где совсем нельзя. Я к тому, что разные компоненты собираются в разных средах/окружениях: fpm собирается от одного (docker FROM), php-cli под другим (docker FROM), а composer может совсем под другим. Выходит кто-то собрался от php7.1-alpine, кто-то от php7.3-debian, а кто-то умудрился от php7.4-windows(?). Вот и думал, может все что связано с php собрать в один образ-контейнер, где всегда одна версия php?

                    по зависимостям имел виду, что composer/docker собирается со своими зависимостями (it subversion openssh-client mercurial tini bash patch make zip unzip coreutils), модули php (zip opcache), со своими переменными окружениями композера и настройкой php-cli в контейнере образа composer/docker! А мы просто вытащили из этой среды сам composer, который если не ошибаюсь юзает системный php, а он скорее всего настроен совсем по другому. Выходит при таком подходе composer работает по дефолтным настройкам, а не от composer:latest?
                  +3
                  Вместо nginx-proxy рекомендую посмотреть в сторону traefik.io
                    0
                    А nginx-proxy чем конкретно не нравится?
                      0
                      нравится, но у траефика немного больше плюшек (например вебморда) и мне их синтаксис чуть больше нравится
                        –1
                        Что-то аналогичное как траефик видел, но вот что-то память на название подводит
                    +1
                    Я для подобных целей использую Devilbox. Может кому полезно будет :)
                      0
                      Вставьте, пожалуйста, ссылку на первый пост в начало.

                    Only users with full accounts can post comments. Log in, please.