Как поднять проект на PHP в Docker под Windows

habr.png

Чем является статья


Статья является набором простых, понятных инструкций и советов для пользователей Docker под Windows. Статья поможет разработчикам на PHP быстро поднять проект. Описываются проблемы и их решения. Статья полезна тем, кто не обладает бесконечным ресурсом времени, чтобы глубоко копаться в проблемах докера под Windows. Автор был бы бесконечно признателен, если бы ему ранее встретилась подобная статья и автор бы съэкономил бы много сил и времени. Текст может содержать ошибки и неточности.


Чем не является статья


Статья не является полным и исчерпывающим руководством по Docker for Windows. В статье не описывается ничего нового и не разглашаются ранее неизвестные факты — все это вы можете самостоятельно найти в разных источниках. Статья также не отвечает на вопрос — перешел ли цыпленок дорогу.



Первый шаг — добавить нового администратора в систему


Это важный шаг, если вы его пропустите или не выполните, то возможно нижеследующие инструкции вообще не имеют смысла для вас. Выполните Windows+R, lusrmgr.msc, откроется «Локальные пользователи и группы». Далее пользователи, контекстное меню, в нем «Новый пользователь…». Добавьте нового пользователя (например dockerhost) с обязательным паролем. Пароль является обязательным! Добавьте членство в группе Администраторы. Других групп не добавляйте.


Далее в Docker settings, Shared drivers отметьте нужные вам диски и введите данные (логин и пароль) нового пользователя. Сохраните настройки. Если вы ранее вводили логин и пароль пользователя, под которым работаете, то введите данные нового пользователя. Хотите огрести проблем в неожиданном месте — используйте рабочий аккаунт и дальше статью не читайте.



С какой конфигурации начать?


На просторах интернета встречаются всякие конфиги docker-compose.yml для PHP вэбсервера. Какая то их часть не запуститься под Windows. Я рекомендую посмотреть генератор docker-compose.yml. У меня конфиг из генератора завелся сразу и поэтому далее я редактировал конфиг именно оттуда. Окончательный результат выложил в github. Вариант из генератора плох несколькими вещами. Описание проблемы и ее решения приводятся ниже.



Настройка постоянного хранилища БД


Проблема: невозможно хранить файлы БД на локальном диске. Это следует принять как аксиому под Windows и попытаться найти приемлемое решение, чтобы данные хранились вне контейнера. Для Windows это — named volume. Всего пара строк решает эту проблему


    postgres:
      volumes:
        - db:/var/lib/postgresql/data

volumes:
  db:


Named volume находятся в папке /var/lib/docker/volumes/ виртуальной машины докера MobiLinuxVM. Прямого доступа к этим файлам нет, только через контейнер посредник. Мне неизвестно решение, как сделать доступной эту папку из под Windows. Для управления named томами воспользуйтесь коммандой docker volume. Например удалить не используемые тома docker volume prune.


Вам не нужно парится с правами и пользователями для файлов в named volume — за вас все делает докер, за что ему большое спасибо. В некоторых руководствах, по настройке постоянного хранилища БД в докере, приводятся танцы с назначением прав. Под Windows без named volume у вас ничего не выйдет. Ну первый раз может и запуститься, а вот при повторном запуске загнется. Вы даже не представляете, какое было облегчение когда заработало постоянное хранилище с named volume.



Добавление SSH ключей


SSh ключи также добавим через named volume. Причина — для приватных ключей нужны особые права, а эту возножность не дают обычные тома под Windows.Процитирую требуемый кусок из docker-compose.yml


services:
    php:
      volumes:
        - ssh:/root/.ssh

volumes:
  ssh:


Чтобы это заработало необходимо в named volume скопировать ключи, изменить права на приватные ключи, протестировать связь. Все эти действия можно оформить в виде одного bat файла


docker run -v first_ssh:/root/.ssh --name helper busybox true
docker cp ./.ssh/. helper:/root/.ssh/
docker rm helper
docker-compose exec php ls /root/.ssh
docker-compose exec php chmod 600 /root/.ssh/id_rsa
docker-compose exec php ssh git@github.com


В скрипте, естественно, вместо first_ssh подставить свое имя тома. Напомню, что название named volume будет сформировано докером как COMPOSE_PROJECTNAME + + ssh в нашем случае. Вместо ./.ssh поставить путь к вашим ключам (ну или временно скопируйте папку с ключами туда, где запускаете этот скрипт). Если вы добавляли свой ключ на github, то в самом конце github поприветствует. Скрипт является одноразовым и его следует запустить сразу после успешного первого старта ваших контейнеров (docker-compose up -d). Повторные запуски не имеют никакого смысла, разве что если вы удалили named volume.



Сетевое взаимодействие между контейнерами


Чтобы соеденятся между контейнерами, описанными в одном docker-compose.yml файле, ничего ненужно. Достаточно указать имя сервиса или имя контейнера как имя хоста. Номера портов менять не нужно — работают порты по умолчанию. На этом можно было бы закончить, если бы не требовалось получать и отправлять запросы в контенйеры из другого docker-compose.yml. С этим тоже никаких проблем, достаточно указать название сетей и контейнеров в сети. Процитирую нужный участок docker-compose.yml


services:
    php:
      networks:
          # this network
          - default
          # external network
          - second_default
      external_links:
        - ${EXTERNAL_NGINX}

networks:
  default:
    driver: bridge
  second_default:
    external: true


Обратите внимание, что имя сети second_default не помещено в переменные окружения потому, что глобальная секция networks не позволяет использовать переменные в принципе. Тогда теряется смысл использовать переменные в секции php, где это возможно. например поместить туда имя контейнера из внешней сети. Если в .env файле зададим EXTERNAL_NGINX=second_nginx, то в коде на PHP достаточно будет использовать имя хоста second_nginx, чтобы сделать http запрос. Порт по прежнему 80 и специально его прописывать не надо. В репозитории github я добавил скрипты проверки связи между контейнерами из разных docker-compose.yml. Достаточно выполнить docker-compose exec php php get.php чтобы удостовериться в работоспособности.


При первом запуске контейнеров докер может ругнуться. ERROR: Network second_network declared as external, but could not be found. Please create the network manually using docker network create second_default and try again. Собственно, все верно, воспользуйтесь подсказкой докера и вручную создайте сеть.



Настройка логов


Хотелось бы иметь логи от всех сервисов в одной папке, доступной локально. Делается это несложно при помощи обычных томов. Главное — включить логирование в самом сервисе. Для php — это опции в файле php.ini, в nginx — в его конфиге, в postgres — тоже. С php и nginx все просто — имеются соответствующие файлы в нашем конфиге. Для postgres придется воспользоваться опциями коммандной строки (существует еще путь через файл postgresql.conf, но это будет несколько сложнее)


services:
    postgres:
      command: postgres -c logging_collector=on -c log_destination=stderr -c log_directory=/logs -c client_min_messages=notice -c log_min_messages=warning -c log_min_error_statement=warning -c log_min_duration_statement=0 -c log_statement=all -c log_error_verbosity=default

      volumes:
        - ${LOGS_DIR}:/logs


Полный текст docker-compose.yml в репе и в конце статьи. Надо сказать, что я до сих пор не доволен настройкой логов. Пока устраивает как есть. Желающие более тонкой настройки логирования, могут воспользоваться документацией соответствующих сервисов.



Переменные в .env


Отличный вариант сделать docker-compose.yml почти универсальным конфигом является файл .env. Полной универсальности не получится из за глобальной секции networks, где невозможно указать переменные среды. Из особенностей моего файла — это переменные для postgress, которые не нужно прописывать в самом docker-compose.yml. В php для коннекта к БД можно воспользоваться


'dsn' => sprintf('pgsql:host=%s;dbname=%s', getenv('POSTGRES_HOST'), getenv('POSTGRES_DB')),



Образы контейнеров


Если обратили внимание, то я для php и nginx использовал свои собственные образы. Мне это экономило время при тестировании. Вам ничего не мешает использовать другие образы контейнеров — мой конфиг всего лишь демо. Свои образы легко построить — в посмотите в репе папку build, где создаются использованные образы.



Заключение


Приведу здесь финальный вариант docker-compose.yml если кому то лень лезь в реп:


version: "3.5"
services:

    php:
      image: litepubl/php70:latest
      container_name: ${FPM_CONTAINER_NAME}
      env_file: .env
      working_dir: /var/www/html
      volumes:
        - ..:/var/www/html
        - ./php/php-ini-overrides.ini:/etc/php/7.2/fpm/conf.d/99-overrides.ini
        - ${LOGS_DIR}:/logs
        - ssh:/root/.ssh
      depends_on:
        - postgres
        - mongo
      networks:
          # this network
          - default 
          # external network
          - second_default
      external_links:
        - ${EXTERNAL_NGINX}

    postgres:
      image: postgres:9.5
      container_name: ${POSTGRES_CONTAINER_NAME}
      env_file: .env
      ports:
        - ${POSTGRES_EXT_PORT}:5432
      working_dir: /var/www/html
      command: postgres -c logging_collector=on -c log_destination=stderr -c log_directory=/logs -c client_min_messages=notice -c log_min_messages=warning -c log_min_error_statement=warning -c log_min_duration_statement=0 -c log_statement=all -c log_error_verbosity=default
      volumes:
        - ..:/var/www/html
        - db:/var/lib/postgresql/data
        - ${LOGS_DIR}:/logs

    mongo:
      image: mongo:latest
      container_name: ${MONGO_CONTAINER_NAME}
      ports:
      - ${MONGO_EXTERNAL_PORT}:27017
      volumes:
        - mongo:/data/db

    webserver:
      image: litepubl/nginx
      container_name: ${NGINX_CONTAINER_NAME}
      working_dir: /var/www/html
      volumes:
          - ..:/var/www/html
          - ${LOGS_DIR}:/var/log/nginx/
          - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      ports:
        - ${NGINX_EXT_PORT}:80
      depends_on:
        - php

volumes:
  ssh:
  db:
  mongo:

networks:
  default:
    driver: bridge
  second_default:
    external: true


Как видите, ничего сложного и все работает. Также приведу несколько полезных комманд, которые я оформил в виде bat файлов. Запуск тестов codeception


del tests\_output\*.* /f /q
del tests\_output\debug\*.* /f /q
del logs\debug.log
cd docker
@cls
docker-compose exec php bash test.sh
cd ..


и сам test.sh


vendor/bin/codecept run unit  --steps --html --debug>testlog.txt


После этой статьи, возникшие вопросы в состоянии решить официальная документация по докеру или другим использованным компонентам. На этом все, удачи в разработке.

© Habrahabr.ru