Docker + php-fpm + PhpStorm + Xdebug

в 8:08, , рубрики: debug, docker, nginx, php, php-fpm, phpstorm

Не так давно тимлид нашей команды сказал: ребята я хочу, чтобы у всех была одинаковая среда разработки для наших боевых проектов + мы должны уметь дебажить всё — и web приложения, и api запросы, и консольные скрипты, чтобы экономить свои нервы и время. И поможет нам в этом docker.
Сказано — сделано. Подробности под катом.

В сети есть много мануалов по контейнеризации, но как их применить к реальной боевой разработке? Для каждого проекта написать свой docker-compose.yml? Но все наши проекты общаются между собой через апи, они все используют стандартный стек технологий: nginx + php-fpm + mysql.

Поэтому, давайте уточним условия задачи:

1. Мы работаем в компании, в команде, сопровождаем несколько боевых проектов. Все работаем под Ubuntu + PspStorm

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

3. Мы хотим разрабатывать с комфортом, мы хотим дебажить всё: и web приложения, и консольные скрипты, и api запросы.

Еще раз: мы хотим завести в докер несколько рабочих проектов.

На боевых серверах используется стандартная связка nginx + php-fpm + mysql. И, в чем проблема?

Разворачиваем на локальной машине точно такое же окружение + Xdebug, настраиваем наши проекты в PhpStorm, работаем. Для дебага включаем «трубку» в PhpStorm, всё работает из коробки, всё замечательно.

Docker + php-fpm + PhpStorm + Xdebug - 1

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

Nginx + php-fpm общаются через сокет, xdebug слушает порт 9000, PhpStorm тоже, по умолчанию, слушает порт 9000 для дебага и всё вроде бы замечательно. А если у нас открыто несколько приложений в PhpStorm, и включена прослушка («трубка)» для нескольких приложений? Что сделает PhpStorm? Он начнет ругаться, что обнаружено новое подключение для Xdebug, вы хотите его игнорировать, или нет?

Т.е., при настройках по умолчанию в PhpStorm, в конкретный момент времени, я могу дебажить только одно приложение. У всех других открытых приложений дебаг должен быть выключен. Блин, но это же неудобно. Я хочу слушать для дебага все приложения, и если в каком-то из них стоит точка останова, то я хочу, чтобы PhpStorm остановился в этом приложении, на той строке, где мне нужно.

А что для этого нужно? А нужно, чтобы каждое приложение запускалось со своими настройками для Xdebug. Чтобы каждое приложение слушало свой порт, искало свой сервер, а не так, как у нас всё общее, всё в одной куче.

А для этого есть замечательный докер! Мы можем запустить каждое наше боевое приложение в отдельном контейнере, на основе одного общего образа, например, php:7.1-fpm. Благодаря технологии докера мы можем изолировать наши приложения, при минимальных накладных расходах.

Ок, давайте заведем наши боевые проекты под докер, запустим каждый проект в отдельном контейнере, настроим каждый проект в PhpStorm для дебага индивидуально, всё должно быть замечательно.

И, упс, первая проблема: контейнеры в докере запускаются от имени суперпользователя root, а локально мы работаем, обычно, от пользователя с uid 1000, gid 1000. Приложения боевые, и давать каждому приложению права 777 на всё — это не выход. Наши же приложения под гитом, и если мы дадим права 777 локально, то гит всё это запишет, и передаст на боевой сервер.

Костылим, вот пример образа php:7.1-fpm, который будет собираться:

FROM php:7.1-fpm

RUN apt-get update && apt-get install -y 
        git 
        curl 
        wget 
        libfreetype6-dev 
        libjpeg62-turbo-dev 
        libmcrypt-dev 
        libpng-dev zlib1g-dev libicu-dev g++ libmagickwand-dev libxml2-dev 
    && docker-php-ext-configure intl 
    && docker-php-ext-install intl 
    && docker-php-ext-install mbstring zip xml gd mcrypt pdo_mysql 
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ 
    && docker-php-ext-install -j$(nproc) gd 
    && pecl install imagick 
    && docker-php-ext-enable imagick 
    && pecl install xdebug 
    && docker-php-ext-enable xdebug

ADD ./php.ini /usr/local/etc/php/php.ini

RUN wget https://getcomposer.org/installer -O - -q 
    | php -- --install-dir=/bin --filename=composer --quiet

RUN usermod -u 1000 www-data && groupmod -g 1000 www-data

WORKDIR /var/www

USER 1000:1000

CMD ["php-fpm"]

При запуске контейнера php:7.1-fpm, мы назначаем пользователю www-data uid=1000, gid=1000. Обычно такие права у первого созданного пользователя в операционной системе на базе Линукс. Буду очень благодарен, если кто-то из представителей уважаемого хабросообщества объяснит, как можно работать с докером, и не заморачиваться с правами доступа. В нашем же случае контейнер php:7.1-fpm запускается с правами 1000:1000, и боевой рабочий проект не ломается.

Вторая проблема, которая у нас возникла, при переходе на докер: при локальной разработке, для настройки Xdebug, необходимо указать верный адрес хоста, который и будет слушать это расширение. А он у всех разный. У каждой локальной машины, у каждого члена команды.
127.0.0.1 тут не катит. Но нам приходит на помощь опять же докер — мы можем явно сконфигурировать сеть, например 192.168.220.0/28, и тогда наша машина всегда получит адрес 192.168.220.1
Именно его мы будет использовать для настройки наших приложений на каждом компьютере члена нашей команды. И это не обязательно PhpStorm.

Ну и собственно вот он наш docker-compose.yml

version: '3'
services:
    php71-first:
      build: ./images/php71
      volumes:
        - ./www:/var/www
        - ./aliases/php71/bash.bashrc:/etc/bash.bashrc
      environment:
        XDEBUG_CONFIG: "remote_host=192.168.220.1 remote_enable=1 remote_autostart=off remote_handler=dbgp idekey=PHPSTORM remote_port=9008"
        PHP_IDE_CONFIG: "serverName=first"
      networks:
        - test-network
    php71-two:
      build: ./images/php71
      volumes:
        - ./www:/var/www
        - ./aliases/php71/bash.bashrc:/etc/bash.bashrc
      environment:
        XDEBUG_CONFIG: "remote_host=192.168.220.1 remote_enable=1 remote_autostart=off remote_handler=dbgp idekey=PHPSTORM remote_port=9009"
        PHP_IDE_CONFIG: "serverName=two"
      networks:
        - test-network
    nginx-test:
      image: nginx
      volumes:
        - ./hosts:/etc/nginx/conf.d
        - ./www:/var/www
        - ./logs:/var/log/nginx
      ports:
        - "8080:80"
      depends_on:
        - php71-first
        - php71-two
      networks:
        test-network:
          aliases: # алиасы нужны если нужно общаться внутри сети между хостами. Например, если вы используете api
            - hello-first.loc
            - hello-two.loc
#    mysql:
#      image: mysql:5.7
#      ports:
#        - "3306:3306"
#      volumes:
#        - ./mysql/data:/var/lib/mysql
#      environment:
#        MYSQL_ROOT_PASSWORD: secret
#      networks:
#        - test-network
networks:
  test-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.220.0/28

Мы видим, что в данном конфиге создаются два контейнера php71-first и php71-two, на основе одного образа php:7.1-fpm. У каждого контейнера свои настройки для Xdebug. Каждый отдельно взятый контейнер будет слушать, для дебага, свой порт и свой сервер.

Полный код для демо версии выложен на гитхабе

Листинг демо проекта:
Docker + php-fpm + PhpStorm + Xdebug - 2

Пробежимся, кратко, по листингу проекта

Каталог aliases -> php71 -> bash.bashrc. Спорный момент. Я предпочитаю общаться с php-fpm контейнерами через алиасы.
Данный файл пробрасывается в docker-compose.yml: — ./aliases/php71/bash.bashrc:/etc/bash.bashrc
Стандартный инструмент Линукса.

Каталог hosts — конфигурационные файлы для Nginx. В каждом конфиге прописан свой контейнер php-fpm. Пример:

server {
    listen 80;
    index index.php;
    server_name first.loc;
    error_log  /var/log/nginx/first_error.log;
    root /var/www/first.loc;

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

    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        # контейнер php-fpm
        fastcgi_pass php71-first:9000;
        fastcgi_index index.php;
        fastcgi_read_timeout 1000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

Каталог images — инструкции для сборки образов php-fpm, каталог mysql — храним базы, каталог www — все наши web проекты, в нашем примере first.loc и two.loc.

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

Осталось корректно настроить PhpStorm для каждого из проектов. При настройке мы должны прописать порт для дебага и имя сервера в нескольких местах.

Создаем проект в PhpStorm

Docker + php-fpm + PhpStorm + Xdebug - 3

Docker + php-fpm + PhpStorm + Xdebug - 4

Docker + php-fpm + PhpStorm + Xdebug - 5

Настраивать будем разделы меню
— PHP (необходимо верно прописать CLI Interpreter),
— Debug (меняем порт на 9008, как в файле docker-compose.yml),
— DBGp proxy (IDE key, Host, Port),
— Servers (необходимо верно указать имя сервера, как в файле docker-compose.yml, и use path mappings)

Docker + php-fpm + PhpStorm + Xdebug - 6

Все дальнейшие скрины буду прятать под спойлер.

Настраиваем CLI Interpreter из docker-compose.yml файла

Хитрого ничего нет — важно, при настройке выбрать нужный образ, и верно прописать имя сервера. По умолчанию имя сервера Docker, у нас оно своё.

Docker + php-fpm + PhpStorm + Xdebug - 7

Docker + php-fpm + PhpStorm + Xdebug - 8

Docker + php-fpm + PhpStorm + Xdebug - 9

Docker + php-fpm + PhpStorm + Xdebug - 10

Docker + php-fpm + PhpStorm + Xdebug - 11

Docker + php-fpm + PhpStorm + Xdebug - 12

Docker + php-fpm + PhpStorm + Xdebug - 13

Docker + php-fpm + PhpStorm + Xdebug - 14

Docker + php-fpm + PhpStorm + Xdebug - 15

Настраиваем DBGp proxy и Debug

Опять же всё прописываем из настроек docker-compose.yml для конкретного контейнера. На этом же шаге валидируем как работает над дебаг.

Docker + php-fpm + PhpStorm + Xdebug - 16

Docker + php-fpm + PhpStorm + Xdebug - 17

Docker + php-fpm + PhpStorm + Xdebug - 18

Настраиваем раздел меню Servers

Важно правильно прописать use path mappings, имя сервера опять же берем из настроек

Docker + php-fpm + PhpStorm + Xdebug - 19

Docker + php-fpm + PhpStorm + Xdebug - 20

Уходим из раздела меню File -> Settings, идем в раздел меню Run -> Edit Configuration, создаем Php Web Page

Сервер выбираем наш, созданный на предыдущем шаге.

Docker + php-fpm + PhpStorm + Xdebug - 21

Docker + php-fpm + PhpStorm + Xdebug - 22

Docker + php-fpm + PhpStorm + Xdebug - 23

Ну, собственно и всё. Написано много букв, вроде бы всё непросто

На самом деле — главное понять очень простую вещь. Благодаря технологии докера мы можем запустить все наши рабочие приложения в едином пространстве, но с разными настройками для Xdebug. Каждое приложение работает в своем контейнере, и нам остаётся аккуратно прописать настройки для каждого приложения в PhpStorm.

И на выходе мы получаем чудесную картину.

1. Клонируем репозиторий на гитхабе

2. Прописываем узлы first.loc и two.loc в файле /etc/hosts
127.0.0.1 first.loc
127.0.0.1 two.loc

3. В папке с гитом запускаем команду docker-compose up -d

4. Настраиваем оба проекта first.loc и two.loc в PhpStorm, так как описано выше, и запускаем оба проекта в PhpStorm. Т.е. у нас открыто два окна PhpStorm, с двумя проектами, каждый из них слушает входящие соединения (трубка включена).

5. В проекте two.loc ставим точку останова на второй, например, строке. В первом проекте first.loc запускаем http запрос из файла http.http
И о чудо! Нас перекидывает во второй проект, на нашу точку останова.

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

Что-то вроде:

alex@alex-Aspire-ES1-572 ~ $ php71first 
www-data@a0e771cfac72:~$ cdf
www-data@a0e771cfac72:~/first.loc$ php index.php 
I'am first host
www-data@a0e771cfac72:~/first.loc$

Где php71first — алиас на машине хоста

alias php71first="cd ~/docker_git && docker-compose exec php71-first bash"

cdf — алиас, который работает в контейнере. Выше я писал о том, что для общения с контейнерами предпочитаю использовать алиасы.

На этом всё, конструктивная критика, замечания приветствуются.

P.S. Хочется выразить огромную благодарность Денису Бондарю за его статью PhpStorm + Docker + Xdebug, которая была отправной точкой для написания данного туториала.

Автор: Алексей Кулагин

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js