Ниже я опишу рабочее решение под 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)
inkvizitor68sl
14.01.2019 15:17> без чрезмерного использования аппаратных ресурсов и излишней траты времени на разворачивание рабочего окружения.
Познакомьтесь уже с lxc и чем-то вроде ansible.
Но вообще справедливо говорят, что ondrej-евские пакеты (а он, если что, мейнтейнер php-пакетов в debian) ставятся скопом всех версий, а потом (даже если говорить про самый сложный вариант — апачевый модуль) переключаются в одну команду. Ну а fpm просто вешается на разные порты.
Единственный вариант, когда есть смысл морочиться с описанной вами схемой — когда нужно тестить код под, например, php5.2 и php7+. Зачем — другой вопрос, но банально понадобятся разные версии дистрибутивов, если не хочется морочиться со сборкой руками.o-pod
14.01.2019 15:30Познакомьтесь уже с lxc и чем-то вроде ansible.
Разве конфигурирование этой связки сравнится с элегантностью решения, которое предоставляет Docker: одна консольная команда в Docker — run (ну или 3 что у меня) против N команд связки lxc+ansible?inkvizitor68sl
14.01.2019 15:40Это только при условии, что есть готовые image в registry (и при условии, что задача позволяет доверять им). Такое случается крайне редко, а писать докерфайлы — не шибко быстрее, чем дернуть пару команд (создать контейнер, запустить на нём ansible — вот уж для него-то плейбуков есть на любой случай жизни).
А ещё и остаются вопросы с различным statefull-содержимым (а у PHP его исторически много — от сессий, до привычки на любой чих писать в локальные каталоги).
Более того, конкретно php-ные image в публичном registry — дерьмо. Люди там больше выпендривались «смотрите как я могу!», чем создавали production-ready образ.o-pod Автор
15.01.2019 22:31Очень красиво заминусовали ВОПРОС!!!
А вам не кажется, что минусовать вопрос глупо, при этом даже не приведя ссылки на какой-нибудь мануал в пользу других решений хотя бы с минимальным анализом затраченных ресурсов (как железных, так и временных на разворачивание)?inkvizitor68sl
15.01.2019 22:42> А вам не кажется, что минусовать вопрос глупо
Мне? Мне вообще лениво в эти стрелочки целиться.
> ри этом даже не приведя ссылки на какой-нибудь мануал
мануал к чему? Поставить 2 мета-пакета и написать 4 конфига?
И вы вообще смотрели в dockerfile, который вы советуете всем использовать?
Arris
15.01.2019 04:19Более того, оно превосходит по элегатности решение с докером :)
Не говоря уже о том, что можно и даже нужно использовать разные апстримы (/etc/nginx/conf.d), хотя вот разные пулы, на мой взгляд, подключаются неудобно (или я не умею их правильно и удобно подключать?)
vanxant
14.01.2019 15:32Под 5.2 наверняка еще особо древние вебсерверы потребуются для тестов, и там скорее всего будет полноценная виртуалка с ядром типа 2.6.27
inkvizitor68sl
14.01.2019 15:38debian 6 отлично работает на ядре 3.x, php 5.2 туда ставится. Да и в докере эта связка в целом терпимо (ну… для тестов) работает.
На 4.х не тестировал, уже не нужно было, но суть, думаю, не поменяется.
Akuma
14.01.2019 15:19Nginx тогда уж тоже в Докер положить. А то какая-то мешанина.
И важный минус, такая конфигурация при перезапуске сервера перестанет работать. Докер не поднимаем простые контейнеры автоматически.Framework
14.01.2019 16:44docker run --restart=always ...
После перезапуска всё продолжит работать. Не то чтобы это best practices, но docker daemon может стартовать контейнеры автоматически.
ONEGiN
14.01.2019 17:04В такой конфигурации все вируальные хосты будут работать под одним пользователем, что небезопасно.
dok2d
14.01.2019 17:20От www-data? o.O
Что в этом небезопасного?vanxant
14.01.2019 18:11Один юзер хостинга сможет лазить по файлам другого юзера хостинга. Действительно, что там небезопасного то.
o-pod
14.01.2019 21:07Откуда юзеры хостинга на рабочей станции разработчика? Поясните, из какой фразы можно сделать вывод, что речь в статье про продакшн?
vanxant
14.01.2019 21:54Не вижу в статье ничего про личный комп разработчика.
А если мы говорим про некий тестовый/отладочный/staging сервер, то там почти наверняка больше 1 проекта, в этих проектах наверняка есть баги и этот сервер почти наверняка смотрит в инет. Найдя уязвимость каком-нибудь в одном всеми забытом проекте, кулхацкер получит доступ ко всем проектам через файловую систему. Так что такую конфигурацию однозначно фтопку.Framework
15.01.2019 10:48Описанное в статье как раз и годиться только для машины разработчика или тестировщика. Тащить это на сервер (любой) плохая идея и не только из-за возможных проблем с безопасностью. Описаное в принципе не является хорошим примером работы с контейнерами.
vanxant
15.01.2019 11:55Описанное в статье вообще ни для чего не годится. dev-окружение в принципе сильно отличается от продакшена. Как минимум будет отладчик с логгером, какой-нибудь упаковщик с хотрелоадом, будут другие настройки (memory limit и т.п.), будет куча dev-пакетов типа того же бабеля. Тащить туда ещё и докер ради докера, а потом скакать с ним по граблям как минимум глупо.
Framework
15.01.2019 12:43Так Docker как раз и решает проблему разницы между продом и дев окружением. И не только эту. Но взамен требует изменить подход к разработке достаточно сильно, что судя по всему часто вызывает непонимание того, зачем вообще он нужен разработчикам, если и без него всё работает.
vanxant
15.01.2019 23:41Нет такой проблемы как «разница между продом и девом», это не проблема, а возможность. Возможность юзать отладчики, сборщики и прочие тулы для разработки, а не для прода.
Да, перед выкатыванием на прод код нужно проверить в «обстановке, максимально приближенной к боевой». Для этого есть staging сервера, самые разные тесты от юнит до интеграционных и прочее прочее. И это должен быть именно отдельный сервер, а не так что разработчик сообщает что «мамой клянусь, я в докере протестил».
Я понимаю, зачем нужен докер девопсам и тестировщикам. Я понимаю, как получить от него пользу разработчику — ну, хочешь там попробовать какой-нибудь эластик, установил, попробовал, снёс. Но для описанной в статье задачи докеру места нет.
dok2d
15.01.2019 11:09Ну, если в один из проектов получится залить эксплоит. Но это уже разговор о другом будет.
А так там всё разграничено локациями в nginx и сокетами в php-fpm.
lryzhik
14.01.2019 17:28отмечу, что еще не рассмотрен вопрос изоляции разных проектов друг от друга. а часто это имеет значение. :-)
o-pod
14.01.2019 17:40lryzhik, поясните пожалуйста, какое значение имеет изоляция проектов друг от друга на рабочей станции разработчика?
Надеюсь, все правильно поняли, что фраза «перед разработчиками PHP встаёт задача» имеет отношение именно к фазе разработки, а не к продакшн?lryzhik
14.01.2019 17:42честно говоря, я не раз встречал у разработчиков активное стремление перенести практику с локалхоста на сервера. с искренним удивлением, почему НЕТ. и было бы хорошо понимание, что все будут с одинаковыми правами в рамках контейнера — сразу отметить.
dok2d
15.01.2019 11:12Благодаря статьям подобного рода, среди новичков распространяется мнение, что решение всех задач — это docker.
Потом, прочитавшего эту статью новичка другой новичок спросит «А как поставить php7.1?», а тот скажет «А разверни docker-контейнер!» и кинет ссылку на эту статью.
По сути, докер нужен для весьма специфичных задач, не надо его пихать куда попало. Пожалуйста.
Лучше изучите внимательней документацию.
akimdi
15.01.2019 22:18почему нельзя через nix установить несколько версий PHP, без всяких виртуальных машин и конфликтов библиотек?
oxidmod
Подобным образом можно установить несколько версий fpm и без докера, коль уж вы всеравно локальный nginx используете
lleo_aha
особенно с учётом того что условно-стандартный ondrej репозиторий как раз так и сделан чтобы несколько версий php друг другу не мешали; плюс будет чуть быстрее из за того что для fastcgi_pass будет использоваться не сетевой а файловый socket
psycho-coder
Например: у TimeWeb в /opt установлены версии от 5.3 до 7.2 и вполне нормально живут себе.