Comments 80
Зачем настраивать DBGp Proxy если 1) вы его нигде не используете и 2) он вам попросту не нужен здесь?
Мне кажется таких статей тысяча и одна и все с одним посылом — как запустить hello-world.php.
Было бы круто описать работу с k8s, какие-нибудь траблы по пути познания докера и способы решения.
Месье знает толк в извращениях! Но вообще классно что даже такую задачу можно решить докером и настроить рабочее окружение на свой вкус. Другое дело что в принципе такой дебагинг — занятие крайне сомнительно и лучше покрывать тестами узкие места. На то приложения и находятся отдельно для разделения логики. А так выходит логику разнесли, а дебажить все равно будем в едином флоу привязывая их друг к другу?! Почему тогда не слить вместе в одну репу и усложнять?
Недавно решал похожую задачу — органзиовать идентичную среду разработки нескольких проектов для всей команды, используюя Docker.
С правами на файлы закостылил точно так же, как и Вы. Лучше решения тоже не смог найти, буду очень рад, если кто-то подскажет.
Правда под каждый отдельный проект я решил поднимать свои контейнеры (nginx + php-fpm + mysql). Да, в этом случае появляется необходимость использования разных портов, но за счет этого появляется больше гибкости и изолированности. Долго размышлял над этим вопросов, и решил, что при использовании разных конфигов уменьшается связанность проектов между собой.
Например, в вашем варианте, если допустить ошибку в конфиге nginx (например, в файле first.conf), то ляжет весь контейнер с nginx, а вместе с ним и все проекты, которые зависят от него.
Тоже самое касается и контейнера с mysql. Например, разные проекты могут использовать разные версии MySQL (5.7 или 8.0). Имея отдельный контейнеры можно более гибко управлять отдельными проектами, не нарушая работу остальных.
В docker-compose.yml вы передаете "XDEBUG_CONFIG", но в Dockerfile я не вижу использования этого. А как тогда это попадает внутрь контейнера? Специальный проверил базовый образ php:7.1-fpm, он тоже не ожидает увидеть такую перменную окружения.
А как попадает XDEBUG_CONFIG внутрь контейнера? Точно так же как все другие переменные окружения (environment). Как это происходит на уровне приложения я не знаю. Я знаю только то, что докер нам позволяет настраивать контейнеры на лету, за что ему огромное спасибо. Изначально Xdebug в наших контейнерах не настроен вообще никак. Если бы мы развернули рабочее окружение без докера, нам мы пришлось прописывать настройки для Xdebug явно в файле php.ini, например. А тут докер нам позволяет всё это сделать на лету.
А как попадает XDEBUG_CONFIG внутрь контейнера? Точно так же как все другие переменные окружения (environment). Как это происходит на уровне приложения я не знаю. Я знаю только то, что докер нам позволяет настраивать контейнеры на лету, за что ему огромное спасибо. Изначально Xdebug в наших контейнерах не настроен вообще никак. Если бы мы развернули рабочее окружение без докера, нам мы пришлось прописывать настройки для Xdebug явно в файле php.ini, например. А тут докер нам позволяет всё это сделать на лету.
Все, я понял почему это работает. Это сам xdebug поддерживает передачу параметров через переменную окружения XDEBUG_CONFIG.
Из оф. документации xdebug:
All settings that you can set through the XDEBUG_CONFIG setting can also be set with normal php.ini settings.
— в Dockerfile: ARG USERID=1000
— в docker-compose.yml USERID: $USERID
— В папке с докером надо сделать export USERID=$UID (баш подставляет в UID юзерайди. direnv у меня подхватывает конфиг с env)
— Дальше в Dockerfile useradd с нужным UID, USER название_юзера
И контейнер начинает делать файлы с тем же uid что и хостовой пользователь. Если не хочется заморачиваться то в Dockerfile ручками ARG менять. В такой схеме еще и контейнер не от рута работает, что поидее безопаснее (при подтягивании зависимостей, например).
Дело в том, что мне не понравился подход, в котором каждый член команды должен локально билдить образы. А в случае обновления Dockerfile (который можно положить под git) еще и потом перебилдивать заново. Для упрощения, я поднял свой приватный реестр для образов, один раз сбилдил свои образы и запушил туда, а членам команды предоставил docker-compose.yml конфиги, в которых уже просто указывается ссылка на наш реестр.
При таком подходе, тиммейтам не нужно делать никаких лишних телодвижений, достаточно просто выполнить одну команду "docker-compose up" и готовые образы просто выкачаются с нашего реестра. Это на много быстрее и проще для всех. А в случае обновления, я просто сам ребилдю образы, снова пушу в реестр и обновляю версии образов в docker-compose.yml (который находится под git). Таким образом, у всех членов команды автоматически обновятся их контейнеры (после git pull и перезапуска контейнеров, естественно).
Так что, на этапе сборки образов мне просто неизвестны UID будущих пользователей. Сейчас просто надеюсь, что там будут 1000, в инном случае решаю проблему по ходу их появления.
Кстати, хорошая идея. Я и так использую ENTRYPOINT и скрипты-хелперы для своих нужд (автоматизирую установку composer зависимостей и т.п. вещи). На этапе старта контейнеров можно чекать UID пользователя и если что апдейтить пользователя в контейнере. Спасибо за наводку.
Anton_Zh, у меня сейчас руки дошли до апдейта своих конфигов Docker. Решил попробовать заменить свой хардкод в Dockerfile (где я просто делал usermod -u 1000 www-data
) на обновлении UID и GID при запуске контейнера.
Но тут у меня возник вопрос, ты пишешь, что можно без ARG, но каким образом тогда прокидывать UID и GID хостового пользователя внутрь ENTRYPOINT скрипта? Этот же скрипт не имеет возможности доставать хост ENV переменные, если их не указать в args (в docker-compose.yml).
UPD: все, вопрос снят, нашел ответ в твоем "шаблончике". Просто создаешь кастомный скрипт, где перед docker-compose up делается export ENV переменных с UID и GID запускающего пользователя.
Да, в этом случае появляется необходимость использования разных портов
В нашей фирме мы делаем огромное количество проектов и запоминать для каждого порт было бы ужасно, еще одиним решением было держать запущенным только один проект. И оба эти решения имеют свои минусы.
На помощь нам пришел Traefik, мы назначили каждому контейнеру домен вида project.domain.localhost и теперь когда нам надо работать над отдельным проектом достаточно написать его домен в браузере + .localhost что бы обратиться к бегущему локально контейнеру этого проекта.
Для того что бы не было заморочек с настройками сети, в контейнер с php прописываем XDEBUG_CONFIG: "remote_host= host.docker.internal remote_enable=1", а в конфиг Xdebug, в секции IP адреса так же указываем host.docker.internal. Это полностью решает проблему с разными IP
А оно работает в Linux? А то тут (https://stackoverflow.com/q/58539029/783119 — в комментариях) вот пишут что только в Mac & Windows
Это понятно. Я спрашивал о host.docker.internal
, работает ли оно в Linux (так как на Mac & Windows работает).
В Линуксе не работает.
Я сделал костыль в виде промежуточного контейнера, который пытается отрезолвить host.docker.internal, если не выходит — берет ip default gw, и nat-ит на него весь диапазон портов.
Выше скинул ссылку, вполне рабочее решение. По крайне мере на первый взгляд, пока протестировать нет возможности.
environment:
PHP_IDE_CONFIG: serverName=${XDEBUG_SERVER_NAME:-some_project.docker}
XDEBUG_CONFIG: remote_host=${XDEBUG_HOST:-192.168.99.1}
192.168.99.1 — это стандартный айпишник у коллег под маком для докера в virtualbox
Если айпишник отличается, то в .env файле можно задать свой локальный айпишник, на котором ваш шторм (или другая IDE) будет слушать дебаг
XDEBUG_HOST=10.22.33.44
Докер в виртуалбоксе? OMG! а зачем?
Сижу на маке, поэтому не знал что работает не везде. Вот здесь вроде предложили решение, что бы на Linux заработало — https://github.com/docker/for-linux/issues/264
Пока не поправят скорость работы файловой системы рассматривать докер для разработки я бы не стал.
Это в какой ОС? На линуксе все прекрасно. На маке — ну, да, есть такое, но вполне терпимо с opcache. Насчёт винды я не в курсе, может, действительно все совсем плохо.
Тут даже не проблема php, nginx отдает статические файлы по 30-40 секунд.
Статические?.. Надо разбираться, во что упирается. У меня медленнее, чем с хостноды, раза в 2-3, но чтобы по 30 секунд… Наблюдал такое только в проекте, где scss на лету компилировался php-библиотекой, но это из разряда «не делайте так».
docs.docker.com/docker-for-mac/osxfs-caching
Уже сказали на счет Мака, я скажу на счет Винды. Реально первую загрузку страницы когда еще нет кэшей можно ждать минутами на системе 9900k samsung nvme 970 pro. На условном i3 и hdd наверное можно успеть пообедать пока загрузится страница.
С кэшами и прочими оптимизациями получится так: в Линуксе 0,5 сек, в Винде 10 сек. И это не преувеличение. Работать с таким невозможно.
Проблема глобальная, про нее всем известно, но пока решений нет. Возможно, вопрос решится с wsl 2.0.
Проблема известная и довольно старая. Тоже от неё страдаем. https://github.com/docker/for-win/issues/188
WSL 2 уже зарелизили, но мне ещё не довелось опробовать. https://devblogs.microsoft.com/commandline/wsl-2-is-now-available-in-windows-insiders/
* Есть файл из репозитория, например, composer.json.
* Получаем uid/gid файла
* Создаем пользователя user с нужными uid/gid
* Стартовый скрипт запускается от рута, но тут же делает su user -c <скрипт для старта проекта>
Тоже неудобно местами, иногда определяет неправильно права, но лучше решения не нашли
И все будет читаться и писаться.
Но композер, например, ругается на работу под рутом. Все миграции, или иные файлы, которые вы создадите, когда зайдете в контейнер, будут принадлежать не вам, что очень неудобно. Так, что я пока работаю с костылем для решения проблемы прав доступа.
CURRENT_ID=$([[ $(id -u) -gt 9999 ]] && echo "root" || id -u)
CURRENT_GROUP=$([[ $(id -g) -gt 9999 ]] && echo "root" || id -g)
DC := CURRENT_USER=${CURRENT_ID}:${CURRENT_GROUP} docker-compose
build:
@$(DC) build
start:
@$(DC) up -d
stop:
@$(DC) down
Данная конструкция работает как на Linux так и на Windows — правильно подставляя id пользователя и группы от имени которого идет работа.
Если Windwos то uid и gid будут выше 9999, и там подставляем рута — с Windows это вполне себе ок, если нет — используем uid и gid текущего пользователя.
концепция контейнеризации нам говорит: один контейнер — один процесс.
один контейнер на nginx
, один на php-fpm
. Вроде все логично и хорошо.
Но что первому что второму надо доступ к одним и тем же ресурсам (файлам) и мы подключаем том с ними
volumes:
- ./www:/var/www
Но контейнеризация нам может быть нужна не только для того чтоб все собиралось у всех одинаково, но и для того чтоб можно было удобно делать выпуски релизов, автоматического тестирования или накатывания тестовых стендов.
И тут приходишь к мысли что ну никак не выйдет "12 factor app". Его не прикрутишь к условному куберу легко, без костылей.
Может лучше плюнуть и запустить nginx и fpm в одном контейнере, а в замен получить артефакт какому хоть и требуются другие сервисы, но минимально он самодостаточен?
если что сорри, я ненастоящий сварщик, просто не так давно пытался разобраться в теме php-fpm и так остались открытие вопросы по контейнеризации этого стека
Если статика и php-код разнесены по разным репозиториям, то вполне себе выйдет.
Если нет, то, наверное, логичнее использовать вместо php-fpm nginx unit (да, он недавно научился раздавать статику), а nginx-у оставить маршрутизацию, балансировку, терминирование SSL и вот это все (для разработки, кстати, не факт что это все вообще нужно).
# docker-compose.yml
web:
image: ruby:2.4
user: "${UID}:${GID}"
Где переменные берутся из окружения
RUN wget https://getcomposer.org/installer -O - -q \ | php -- --install-dir=/bin --filename=composer --quiet
вы можете использовать зависимость из другого образа:
FROM composer:latest AS composer
FROM php:7.3.9-alpine3.9
COPY --from=composer /usr/bin/composer /usr/bin/composer
......
docker-php-entrypoint:
#!/usr/bin/env bash
set -e
if [ -z "${UID}" ]; then
echo "Необходимо задать значение переменной окружения UID"
exit 1
fi
if [ -z "${GID}" ]; then
echo "Необходимо задать значение переменной окружения GID"
exit 1
fi
groupmod -g ${GID} hostuser
usermod -u ${UID} hostuser > /dev/null 2>&1
exec runuser -u hostuser -- "$@"
Dockerfile:
COPY docker-php-entrypoint /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-php-entrypoint
RUN useradd -m -U -s /bin/bash hostuser
ENTRYPOINT ["docker-php-entrypoint"]
CMD ["php", "-a"]
Команды будут выполняться внутри контейнера под hostuser, uid и gid которого настраиваются при запуске контейнера. Если не указать их — не запустится. С консольными командами помогает, с веб-сервером, возможно, тоже поможет. Можно попробовать в Entrypoint модифицировать uid и gid www-data (я пробовал, но не помню чем кончилось)))).
Юзаю один контейнер — один процесс (группа схожих процессов). Даже под консоль и веб-сервер разные контейнеры). docker-compose хорош.
Если кто-такое пробовал, или есть какие-то замечения, пишите, указывайте на недостатки. Свежий взгляд со стороны всегда полезен.
А чем 777 на ассеты и кеш не устраивают? По моему это хуже чем когда владельцем файлов с кодом является пользователь для запуска веб-сервера. Это гораздо более опасно. Из каталогов с ассетами и кешем просто выполнение php запретить и всё.
А чем 777 на ассеты и кеш не устраивают? По моему это хуже чем когда владельцем файлов с кодом является пользователь для запуска веб-сервера. Это гораздо более опасно. Из каталогов с ассетами и кешем просто выполнение php запретить и всё.
Почему не устраивает. Все устраивает кроме одного: вот мы зашли в контейнер, и создали миграцию, или иной файл, находясь в контейнере. И от того, что на нужные папки у нас стоят права 777, нам легче не станет. Если не шаманить с правами, то владельцем этого файла будет суперпользователь root, и когда мы вернемся на машину хоста, то нам придется совершать определенные действия, чтобы работать с такими файлами.
Для контейнеров на образе php-fpm необходимо изменить права доступа таким образом, чтобы мы могли заходить в эти контейнеры, и создавать файлы с правами, которые должны совпадать с правами текущего пользователя.
Важно: в таком контейнере уже есть пользователь www-data, от имени которого и работает демон php-fpm.
Что произойдет, если использовать ваш рецепт в данном конкретном случае? Контейнер запустится от имени суперпользователя root, после запуска отработает docker-php-entrypoint, будет создан пользователь hostuser c нужными правами, и php-fpm попытается запуститься от имени этого пользователя, и вылетит с ошибкой. Данный процесс, php-fpm, может запустить только пользователь www-data. Можно конечно как-то подшаманить, но зачем?
Проще просто передать uid и gid текущего пользователя в докерфайл, и использовать
RUN usermod -u ${USER_ID} www-data && groupmod -g ${GROUP_ID} www-data
при сборке. Т.е. не жестко приколачивать гвоздями uid и gid в докерфайле, как у меня, сейчас в статье, а передать их извне.exec runuser -u hostuser -- "$@"
Чем не устраивает простое
su hostuser
У меня баш ругался на нее. Хотя и работал.
Реализация с адресацией выглядит слегка костыльной, рассматривали ли вы traefik если да то почему он вам не подошёл?
У меня стояла подобная задача и мне показалось ваше решение слегка усложненным и недостаточно автоматическим. Поэтому хочу написать отличия от вашего решения и спросить что вы о них думаете.
1.
Например, мы можем явно сконфигурировать сеть — 192.168.220.0/28.
Можно избежать конфигурации подсети, если вы собираетесь использовать makefile. Тогда достаточно на команду поднятия контейнеров передать что-то подобное: XDEBUG_REMOTE_HOST := $(shell hostname -I | grep -o "^\S*") и заменить в компоузе на remote_host=${XDEBUG_REMOTE_HOST}
Минусы: Не будет работать на windows.
2.
2. Прописываем узлы first.loc и two.loc в файле /etc/hosts
Если использовать трафик и домены типа your.domain.localhost, то можно избежать пункта с изменением файла хостс.
Плюсы: При добавлении/удалении сервисов не надо будет заставлять всех менять файл хостс т.к. вся конфигурация трафика будет прямо в репозитории.
а Вы пытались настроить xdebug используя xdebug.remote_connect_back, а не вот это вот всё с ip?
как-то так:
xdebug.remote_connect_back = 1 # включаем обратное подключение
xdebug.remote_enable = 1
xdebug.remote_port = 9000 # изменяем на нужный порт
не пробовал в такой конфигурации как у Вас, но для одного сервиса работает хорошо и не надо выдумывать ничего с сетью внутри контейнера и биндить ip адрес
Может глупый вопрос. А чем плох запуск nginx и php-fpm от рута? Просто у меня 3 сервиса nginx не помню с каким юзером, php-fpm c www-data и один консольный воркер php-cli который от рута запускается.
Второе: вы заходите в php-cli контейнер, и создаете из консоли, какой-либо файл, например, файл миграции. Кто будет его владельцем? Суперпользователь root.
Сможете вы с этим файлом просто так работать, когда вернетесь на машину хоста? Нет. Вам придется делать лишние движения
Docker + php-fpm + PhpStorm + Xdebug