Настройка рабочего окружения в Docker для yii-framework приложения

  • Tutorial

Для того чтобы быстро поднять рабочее окружение существует много способов. Один из них — поднять все необходимые сервисы в Docker-контейнерах. Чтобы ускорить создание новых проектов на Yii-framework я написал такую небольшую инструкцию, которую используют разработчики в нашей команде.


На старте у вас должны стоять docker, docker-compose, php и php-composer.
Создаем папку с проектом и в ней папку docker.


mkdir project-dir 
cd project-dir && mkdir docker

В папке docker создаем файл конфигурации нашего контейнера Dockerfile.


# Базовый образ с nginx и php
FROM richarvey/nginx-php-fpm

# Добавляем наше веб приложение
ADD app /var/www/app

# Удаляем конфиги сайтов которые там есть
RUN rm -Rf /etc/nginx/sites-enabled/*

# Добавляем наш конфиг
ADD docker/conf/nginx/site.conf /etc/nginx/sites-available/site.conf
# Включаем его
RUN ln -s /etc/nginx/sites-available/site.conf /etc/nginx/sites-enabled/site.conf

В этой же папке docker создаем docker-compose.yml для поднятия окружения разработки.


# Последняя версия docker-compose
version: '3'

# Создаем общую сеть deafult для всех контейнеров
networks:
  default:
    driver: bridge

# Создаем отдельные контейнеры
services:
  # Контейнер с веб-приложением
  app:
    # Собираем из Dockerfile 
    build: 
      # Корнем указываем корень основного проекта
      context: ../
      dockerfile: ./docker/Dockerfile
    # Показываем наружу 80 порт
    ports: 
      - "80:80"
    # Подключаем к общей сети с другими контейнерами
    networks: 
      - default
    # Запускаем только после db
    depends_on: 
      - db    
    # Линкуем внешнюю папку с исходниками внутрь
    volumes:
      - "../app:/var/www/app"
      # Так же линкуем конфиг для nginx
      - "./conf/nginx:/etc/nginx/sites-available"      
  # Контейнер с базой данных
  db:
    image: mysql:latest
    # Подключаем к общей сети с другими контейнерами
    networks: 
      - default
    # Показываем наружу порт
    ports:
      - "3336:3306"
    # Задаем параметры для инициализации БД
    environment:
      # Пароль к БД
      MYSQL_ROOT_PASSWORD: root
      # Создаваемая по умолчанию бд
      MYSQL_DATABASE: yii-template-db
    # Линкуем внешнюю папку для хранения БД
    volumes:
      - "./database:/var/lib/mysql"

Для nginx создаем папку docker/conf/nginx и файл site.conf в ней. Файлик может изменяться, в зависимости от того, как вы хотите настроить nginx на своем проекте. Его можно менять локально, т.к. он подключается через volume. Но надо не забывать внутри контейнера перезагружать nginx: nginx -s reload


server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4

    root        /var/www/app/frontend/web/;
    index       index.php;

    access_log  /var/www/app/log/frontend-access.log;
    error_log   /var/www/app/log/frontend-error.log;

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

    # uncomment to avoid processing of calls to non-existing static files by Yii
    #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
    #    try_files $uri =404;
    #}
    #error_page 404 /404.html;

    # deny accessing php files for the /assets directory
    location ~ ^/assets/.*\.php$ {
        deny all;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/var/run/php-fpm.sock;
        try_files $uri =404;
    }

    location ~* /\. {
        deny all;
    }
}

Все команды выполняются из корневой папки.


  • Выполняем команду создания проекта composer create-project --prefer-dist yiisoft/yii2-app-advanced app.
  • Запускаем окружение docker-compose -f docker/docker-compose.yml up -d
  • Инициализируем проект app/init --env=Development --overwrite=All
  • Открываем в редакторе файл app/common/config/main-local.php и заполняем его данными для подключения к БД. В примере у нас пароль root — root, хост БД — db, имя БД — yii-template-db.
  • Подключаемся к контейнеру docker exec -it docker_app_1 bash
  • Выполняем команду миграции БД php /var/www/app/yii migrate
  • Создаем папку для логов mkdir /var/www/app/log
  • И выходим exit
  • Тормозим сервис docker-compose -f docker/docker-compose.yml down
  • Запускаем его заново docker-compose -f docker/docker-compose.yml up -d
  • Открываем localhost в браузере и смотрим на новый сайт.

Upd: Обязательно стоит упомянуть, что всегда есть официальный Docker-образ Yii2.

Поделиться публикацией
Комментарии 21
    –1
    Думаю логично тут оставить ссылку на официальный yii2 docker образ.
      +1
      Спасибо, ссылку добавил. Просто цель статьи была показать как создавать свои окружения в docker'e. Кроме того, в официальном докер образе все собрано в один контейнер (приложение и база), что не всегда удобно
      +2

      Я ни разу не являюсь специалистом по Docker, однако даже мне известно, что размещение более одного контейнера внутри докера, это нарушение самой сути контейнеров. Внутри контейнера supervisord забирает на себя все функции Docker'a. А процессами должен управлять сам Docker, следить за ними, показывать их адекватные метрики. Если очень хочется сделать такой бандл "все в одном", то пожалуйста — создавайте контейнер, внутрь которого пробрасываете сокет Docker'a, и внутри этого контейнера обычным docker-compose или без него запускайте сколько угодно контейнеров, связанных между собой, но только непосредственно через Docker! И когда будет нужно, можно делать так же docker exec на какой-то entrypoint, а от него пробрасывать команды на целевые контейнеры. Даже такой Docker внутри Docker это гораздо лучше, чем один контейнер с несколькими процессами, у которых Docker даже pid не знает, тогда зачем Docker нужен?


      Описанный в статье инструмент можно использовать только для хоумпейджей или для девелопинга. Но кому оно надо, если Docker используются только в более менее крупных проектах, или просто нормальными системщиками. Все прочие используют "сPanel" или максимум sftp. А те, кто хотят войти в мир Docker'а начитаются таких статей, и делают неправильно с самого начала.

        0
        1. А где именно тут «все в одном»?
        2. Да, статья именно про окружение для девелопинга
          0

          Все в одном здесь вот где:


          FROM richarvey/nginx-php-fp

          Ну даже учитывая, что это окружение для разработки, то я все равно не вижу в этом смысла. Смысл докера в том, чтобы использовать для разработки то же самое окружение для разработки, что и для продакшена. То есть php должен быть скомпилирован точно так же как для дев окружения, как и для прода. Один и тот же, чтобы не было никаких нюансов. Например, для дева запросы по https работают нормально, а на проде нет, потому что для прода php скомпилирован с другой версией OpenSSL. Вот и ломай потом голову, что не так.

            +1

            Я правильно понимаю, что разделив nginx и php в разные контейнеры мы получим относительно верный вариант?
            И что тогда делать с php-fpm? Его тоже отдельно, чтобы все было по канону?

              –1

              Правило простое: 1 процесс на 1 контейнер. Если нужно определять свой entrypoint, то внутри обязательно делать:


              #!/usr/bin/env sh
              # ...
              exec daemon-process

              Или если ваш демон не умеет нормально обрабатывать сигналы прерывания(SIGTERM, например), а такое случается, то можно использовать tini. Он и pid передаст куда следует, и за сигналами следить будет, а потом убивать запущенный им процесс. Так как tini добавили в Docker с версии 1.13, можно вообще просто добавить init: true для сервиса в docker-compose.yml, но тогда придется даунгрейднуть формат файла до версии 2.x, или наоборот проапргрейдить до 3.7. (Опцию --init запуска Docker'a в связи с поддержкой Swarm для docker-compose сначала удалили, а потом вернули) Между опцией init: true или модификацией параметров запуска контейнера фактически разницы нет, но в первом варианте вам вообще ничего с имеджем делать не нужно. Для нормальных имеджей ничего дополнительно делать не стоит, там уже все в порядке. Проверить же нормальность можно легко: выполняете docker-compose up и после завершайте по Ctrl + C. Если процесс умирает сразу, а не после задержки в несколько секунд, то внутри все правильно, если нет, попробуйте добавить init: true и процесс будет завершаться сразу, так как им уже будет управлять tini.


              Даже если вам нужно использовать cron, то, по-хорошему, его нужно запускать не на хосте, а в отдельном контейнере, в него пробрасывать сокет Docker'а, и им же что-то делать через docker-compose / docker. Естественно такой имедж нужно будет создавать с установленным внутри docker.

                +4
                И это говорит человек который вначале декларирует:
                Я ни разу не являюсь специалистом по Docker
                . Страшно подумать что мог бы написать нам в ответ специалист Docker
                  +2
                  Правило простое: 1 процесс на 1 контейнер.
                  Одна задача на контейнер. Не процесс. В документации докера этот пункт best practice недавно обновили и расширили.

                  Тот же php-fpm — это набор процессов, но это не нарушает правило.
                  Или apache + mod_php, хотя процессов много.
                    0
                    Да, согласен что по канону стоит вынести nginx в отдельный контейнер.
                    Но вопрос в том, что стоит ли это делать, если есть гарантии что в продакшене будет точно такая же связка, которая точно не будет менять на протяжении всего проекта?
                      0

                      Дело даже не в том, чтобы именно по канону. Кроме прочего, просто так удобнее. В случае с разделенными php-fpm и nginx вы будете использовать официальные образы с поддержкой и актуальной документацией.

                0
                Ну как минимум у вас в одном контейнере одновременно находится и nginx и php. А докер подход состоит в том, что бы каждый контейнер выполнял всего одну конкретную задачу, тоесть как минимум nginx и php должны разбиты на отдельные контейнеры. Если подразумевается использование каких то второстепенных сервисов — типа кэшируеющего или почтового сервера — то на них так же предлагается заводить свой отдельный сервис. К примеру на одном из поддерживаемых мной проектов вот такой список сервисов на текущий момент

                1. webserver (nginx)
                2. php
                3. events (nodejs)
                4. db
                5. cache (redis)

                При этом я честно говоря не смог с наскока вынести почтовый сервер в отдельный контейнер, поэтому подсадил его рядом с php (каюсь). Но обещаю исправиться =)
                  0
                  А как вы решаете вопрос с доступам к файлам статики из контейнера с nginx?
                  Этот вопрос легко решается пока все находится на машине разработчика, просто монтируем папку с проектом в контейнеры с nginx и php как volume.
                  Сложность начинается тогда, когда это всё надо перенести с машины разработчика, скажем в Kubernetes. Поэтому я для себя пока остановился на решении с nginx и php в одном контейнере. Т. к. этот вариант показался меньшим злом, по сравнению с теми решениями, что пришли в голову.
                    0
                    На данный момент этот вопрос решается монтированием как volume, вы правы. До k8s пока не дорос, и проблемы, связанные с этим еще не осмыслил. Я так понимаю в общем проблема в том, что я могу разнести свои контейнеры на разные физические машины и возможности монтирование одних и тех же разделов в этом случае не будет, верно? Первая возникшая мысль как их подружить почему то — распределенные фс, типа гластерфс, монтирующиесе в оба контейнера. Или я не понял суть проблемы?
                      0
                      Да, вы правильно поняли проблему.
                      Только не из пушки по воробьям получится, для отдачи нескольких файлов разворачивать кластерную ФС? Лишний расход вычислительных ресурсов, снижение надежности и скорости доступа, расходы на администрирование.
                      Вариант 2. Копировать файлы приложения в оба контейнера (и в nginx и в php), мне как-то тоже не понравился из-за перерасхода дискового пространства.
                      В итоге я пришел к мысли, что запихать nginx в контейнер к приложению будет наименьшим злом.
                        0
                        Ну на самом деле в одном из моих проектов размер /upload/ директории растет очень активно, и идея прикрутить GFS в таком случае вполне имеет смысл. На счет второго варианта не очень понял в чем проблема, данные же не копируются, а монтируются, никакого перерасхода дискового пространства и не будет. Но в таком случае php и nginx должны жить на одной физической машине, да
              0
              Сколько не встречаю обзоров, но никак пока не понял как развертывать приложение с существующим проектом из гита? В каждом руководстве create-project.
              Я вообще клонирую мастер и делаю composer install и т.к.
                0
                Вместо create-project, просто качаем исходники в папку app
                0
                Подскажите, пожалуйста, что и где необходимо сконфигурировать если необходимо чтобы приложение было доступно не на основном домене (http://example.ru), а например по определенному URL пути, например вот так: example.ru/my_yii_app. Если не сконфигурировать, то приложение автоматически устанавливает абсолютные пути к ресурсам приложения от доменного (корневого) имени.
                  0
                  Нужно указать в конфигурации nginx location для этого адреса, где уже внутри прописать что там будет наше приложение
                  location /my_yii_app { 
                  ...
                  }
                    0
                    Или как вариант сделать это роутингом в самом yii

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое