Часто перед разработчиками PHP встаёт задача проверить работу веб-приложения под несколько версий интерпретатора. Решить её можно разными способами. Можно банально установить разные версии PHP на один хост, но это чревато конфликтами библиотек и другими сложностями. Вторая крайность — сделать несколько изолированных виртуальных машин с разным окружением, но здесь не обойтись без чрезмерного использования аппаратных ресурсов и излишней траты времени на разворачивание рабочего окружения. На настоящий момент наиболее просто решить данную задачу можно с помощью Docker.

image

Ниже я опишу рабочее решение под Ubuntu 18, где в качестве стека используется связка Nginx + PHP-FPM. Данное решение легко масштабируется: контейнер с PHP-FPM занимает всего 300 Мб в памяти, а добавить контейнеры с другими версиями интерпретатора можно тремя командами (или в зависимости от предпочтений даже одной — run). Второй плюс этого решения в том, что разработчику не нужно переключать веб-сервер между интерпретаторами, так как они уже разнесены в разные контейнеры (код приложения при этом используется один и тот же).

Итак, начнём…

1. Устанавливаем Docker


sudo apt update

sudo apt install ca-certificates curl software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

sudo apt update

sudo apt install docker-ce

2. Устанавливаем контейнеры с нужными версиями PHP


В качестве примера создания рабочего окружения использованы PHP версий 7.1 и 7.2 из официального Docker-репозитория PHP. По аналогии при наличии образа можно установить какие угодно версии PHP:

sudo docker pull php:7.1.25-fpm-stretch

sudo docker create --name=fpm71 -p 127.0.0.1:9071:9000 -v /var/www:/var/www php:7.1.25-fpm-stretch

sudo docker start fpm71

sudo docker pull php:7.2.13-fpm-stretch

sudo docker create --name=fpm72 -p 127.0.0.1:9072:9000 -v /var/www:/var/www php:7.2.13-fpm-stretch

sudo docker start fpm72

PHP-FPM по умолчанию работает на 9000 порту. При создании образов мы опубликовали 9000-е порты контейнеров на свободные 9071 и 9072 порты хост-машины (номера взяты произвольно из непривилегированного диапазона). Далее на эти порты мы будем проксировать запросы на обработку PHP (параметр fastcgi_pass в конфигурации виртуальных хостов Nginx).

Также понадобилось пробросить внутрь контейнеров каталог с проектами (/var/www), иначе PHP-FPM ругается, что не видит файлов (если знаете, как сделать этот момент лучше/правильней, то пишите в комментарии).

Проверяем, что контейнеры запущены, а порты опубликованы правильно:

sudo docker ps -a

sudo netstat -lpn


3. Настраиваем окружение для виртуальных хостов


Добавляем в /etc/hosts строки:
127.0.0.1    project.local.php71  ### php 7.1
127.0.0.1    project.local.php72  ### php 7.2

Создаём каталог для проекта:

sudo mkdir -p /var/www/project.local

echo '<?php phpinfo(); ?>' | sudo tee /var/www/project.local/index.php

Название для проекта (project.local) и виртуальных хостов (project.local.php71/72) я взял произвольно, но вы можете использовать удобные для вас названия (только не забудьте при этом поменять настройки виртуальных хостов).

Изначально в индексный файл была положена только одна команда phpinfo, после настройки и проверки работоспособности системы, index.php нужно будет заменить на тот, что используется в проекте.

4. Устанавливаем nginx и настраиваем виртуальные хосты


sudo apt install nginx

Создаём файл /etc/nginx/sites-available/project.local.php71 с описанием первого виртуального хоста (он будет использоваться для проверки работы проекта под PHP v.7.1):

server {
  listen 80;
  server_name project.local.php71;
  index index.php;
  root /var/www/project.local;

  location / {
    try_files $uri $uri/ =404;
  }

  location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9071;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}

Точно так же файл /etc/nginx/sites-available/project.local.php72 для второго виртуального хоста:

server {
  listen 80;
  server_name project.local.php72;
  index index.php;
  root /var/www/project.local;

  location / {
    try_files $uri $uri/ =404;
  }

  location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9072;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}

Теперь делаем симлинки на вышесозданные конфигурации виртуальных хостов и перегружаем Nginx:

cd /etc/nginx/sites-enabled

sudo ln -s ../sites-available/project.local.php71

sudo ln -s ../sites-available/project.local.php72

sudo systemctl reload nginx

5. Проверяем


curl --silent http://project.local.php71/index.php | grep -o "PHP Version [0-9\.]\{1,\}"

curl --silent http://project.local.php72/index.php | grep -o "PHP Version [0-9\.]\{1,\}"

В качестве результата мы должны получить версию PHP (как результат обработки команды phpinfo интерпретаторами разных версий).

Теперь осталось лишь залить свой проект в папку /var/www/project.local и можно проверять его работу в интерпретаторе PHP 7.1 по адресу http://project.local.php71 и PHP 7.2 по http://project.local.php71.

Дополнительные материалы
1. Полное практическое руководство по Docker

Комментарии (28)


  1. oxidmod
    14.01.2019 14:52
    +1

    Подобным образом можно установить несколько версий fpm и без докера, коль уж вы всеравно локальный nginx используете


    1. lleo_aha
      14.01.2019 15:00
      +1

      особенно с учётом того что условно-стандартный ondrej репозиторий как раз так и сделан чтобы несколько версий php друг другу не мешали; плюс будет чуть быстрее из за того что для fastcgi_pass будет использоваться не сетевой а файловый socket


    1. psycho-coder
      14.01.2019 15:45

      Например: у TimeWeb в /opt установлены версии от 5.3 до 7.2 и вполне нормально живут себе.


  1. inkvizitor68sl
    14.01.2019 15:17

    > без чрезмерного использования аппаратных ресурсов и излишней траты времени на разворачивание рабочего окружения.
    Познакомьтесь уже с lxc и чем-то вроде ansible.

    Но вообще справедливо говорят, что ondrej-евские пакеты (а он, если что, мейнтейнер php-пакетов в debian) ставятся скопом всех версий, а потом (даже если говорить про самый сложный вариант — апачевый модуль) переключаются в одну команду. Ну а fpm просто вешается на разные порты.

    Единственный вариант, когда есть смысл морочиться с описанной вами схемой — когда нужно тестить код под, например, php5.2 и php7+. Зачем — другой вопрос, но банально понадобятся разные версии дистрибутивов, если не хочется морочиться со сборкой руками.


    1. o-pod
      14.01.2019 15:30

      Познакомьтесь уже с lxc и чем-то вроде ansible.


      Разве конфигурирование этой связки сравнится с элегантностью решения, которое предоставляет Docker: одна консольная команда в Docker — run (ну или 3 что у меня) против N команд связки lxc+ansible?


      1. inkvizitor68sl
        14.01.2019 15:40

        Это только при условии, что есть готовые image в registry (и при условии, что задача позволяет доверять им). Такое случается крайне редко, а писать докерфайлы — не шибко быстрее, чем дернуть пару команд (создать контейнер, запустить на нём ansible — вот уж для него-то плейбуков есть на любой случай жизни).
        А ещё и остаются вопросы с различным statefull-содержимым (а у PHP его исторически много — от сессий, до привычки на любой чих писать в локальные каталоги).

        Более того, конкретно php-ные image в публичном registry — дерьмо. Люди там больше выпендривались «смотрите как я могу!», чем создавали production-ready образ.


        1. o-pod Автор
          15.01.2019 22:31

          Очень красиво заминусовали ВОПРОС!!!
          А вам не кажется, что минусовать вопрос глупо, при этом даже не приведя ссылки на какой-нибудь мануал в пользу других решений хотя бы с минимальным анализом затраченных ресурсов (как железных, так и временных на разворачивание)?


          1. inkvizitor68sl
            15.01.2019 22:42

            > А вам не кажется, что минусовать вопрос глупо
            Мне? Мне вообще лениво в эти стрелочки целиться.

            > ри этом даже не приведя ссылки на какой-нибудь мануал
            мануал к чему? Поставить 2 мета-пакета и написать 4 конфига?

            И вы вообще смотрели в dockerfile, который вы советуете всем использовать?


      1. Arris
        15.01.2019 04:19

        Более того, оно превосходит по элегатности решение с докером :)

        Не говоря уже о том, что можно и даже нужно использовать разные апстримы (/etc/nginx/conf.d), хотя вот разные пулы, на мой взгляд, подключаются неудобно (или я не умею их правильно и удобно подключать?)


    1. vanxant
      14.01.2019 15:32

      Под 5.2 наверняка еще особо древние вебсерверы потребуются для тестов, и там скорее всего будет полноценная виртуалка с ядром типа 2.6.27


      1. inkvizitor68sl
        14.01.2019 15:38

        debian 6 отлично работает на ядре 3.x, php 5.2 туда ставится. Да и в докере эта связка в целом терпимо (ну… для тестов) работает.
        На 4.х не тестировал, уже не нужно было, но суть, думаю, не поменяется.


  1. Akuma
    14.01.2019 15:19

    Nginx тогда уж тоже в Докер положить. А то какая-то мешанина.

    И важный минус, такая конфигурация при перезапуске сервера перестанет работать. Докер не поднимаем простые контейнеры автоматически.


    1. Framework
      14.01.2019 16:44

      docker run --restart=always ...
      После перезапуска всё продолжит работать. Не то чтобы это best practices, но docker daemon может стартовать контейнеры автоматически.


  1. ONEGiN
    14.01.2019 17:04

    В такой конфигурации все вируальные хосты будут работать под одним пользователем, что небезопасно.


    1. dok2d
      14.01.2019 17:20

      От www-data? o.O
      Что в этом небезопасного?


      1. vanxant
        14.01.2019 18:11

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


        1. o-pod
          14.01.2019 21:07

          Откуда юзеры хостинга на рабочей станции разработчика? Поясните, из какой фразы можно сделать вывод, что речь в статье про продакшн?


          1. vanxant
            14.01.2019 21:54

            Не вижу в статье ничего про личный комп разработчика.
            А если мы говорим про некий тестовый/отладочный/staging сервер, то там почти наверняка больше 1 проекта, в этих проектах наверняка есть баги и этот сервер почти наверняка смотрит в инет. Найдя уязвимость каком-нибудь в одном всеми забытом проекте, кулхацкер получит доступ ко всем проектам через файловую систему. Так что такую конфигурацию однозначно фтопку.


            1. Framework
              15.01.2019 10:48

              Описанное в статье как раз и годиться только для машины разработчика или тестировщика. Тащить это на сервер (любой) плохая идея и не только из-за возможных проблем с безопасностью. Описаное в принципе не является хорошим примером работы с контейнерами.


              1. vanxant
                15.01.2019 11:55

                Описанное в статье вообще ни для чего не годится. dev-окружение в принципе сильно отличается от продакшена. Как минимум будет отладчик с логгером, какой-нибудь упаковщик с хотрелоадом, будут другие настройки (memory limit и т.п.), будет куча dev-пакетов типа того же бабеля. Тащить туда ещё и докер ради докера, а потом скакать с ним по граблям как минимум глупо.


                1. Framework
                  15.01.2019 12:43

                  Так Docker как раз и решает проблему разницы между продом и дев окружением. И не только эту. Но взамен требует изменить подход к разработке достаточно сильно, что судя по всему часто вызывает непонимание того, зачем вообще он нужен разработчикам, если и без него всё работает.


                  1. vanxant
                    15.01.2019 23:41

                    Нет такой проблемы как «разница между продом и девом», это не проблема, а возможность. Возможность юзать отладчики, сборщики и прочие тулы для разработки, а не для прода.
                    Да, перед выкатыванием на прод код нужно проверить в «обстановке, максимально приближенной к боевой». Для этого есть staging сервера, самые разные тесты от юнит до интеграционных и прочее прочее. И это должен быть именно отдельный сервер, а не так что разработчик сообщает что «мамой клянусь, я в докере протестил».
                    Я понимаю, зачем нужен докер девопсам и тестировщикам. Я понимаю, как получить от него пользу разработчику — ну, хочешь там попробовать какой-нибудь эластик, установил, попробовал, снёс. Но для описанной в статье задачи докеру места нет.


        1. dok2d
          15.01.2019 11:09

          Ну, если в один из проектов получится залить эксплоит. Но это уже разговор о другом будет.
          А так там всё разграничено локациями в nginx и сокетами в php-fpm.


  1. lryzhik
    14.01.2019 17:28

    отмечу, что еще не рассмотрен вопрос изоляции разных проектов друг от друга. а часто это имеет значение. :-)


    1. o-pod
      14.01.2019 17:40

      lryzhik, поясните пожалуйста, какое значение имеет изоляция проектов друг от друга на рабочей станции разработчика?

      Надеюсь, все правильно поняли, что фраза «перед разработчиками PHP встаёт задача» имеет отношение именно к фазе разработки, а не к продакшн?


      1. lryzhik
        14.01.2019 17:42

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


  1. dok2d
    15.01.2019 11:12

    Благодаря статьям подобного рода, среди новичков распространяется мнение, что решение всех задач — это docker.
    Потом, прочитавшего эту статью новичка другой новичок спросит «А как поставить php7.1?», а тот скажет «А разверни docker-контейнер!» и кинет ссылку на эту статью.
    По сути, докер нужен для весьма специфичных задач, не надо его пихать куда попало. Пожалуйста.
    Лучше изучите внимательней документацию.


  1. akimdi
    15.01.2019 22:18

    почему нельзя через nix установить несколько версий PHP, без всяких виртуальных машин и конфликтов библиотек?