Як налаштувати проект Symfony для роботи з субдоменами Docker

Symfony with Docker

Існують ситуації, коли Symfony потребує маршрутизації на основі субдоменів. Наприклад, коли ми маємо справу з різними функціональними кластерами в межах одного проекту чи одного репозиторію. Скажімо, коли, окрім API, є адміністративна частина, написана в пакетах Symfony, або деяка функція для URL-адрес публічного доступу з боку клієнта.

Ось кілька варіантів:

  • api.project-name.work —  хост API для клієнтських програм (веб- і мобільних клієнтів).
  • hub.project-name.work —  хост, який використовується для зворотного з’єднання з мобільним клієнтом, коли для певної функції потрібна URL-адреса, доступна з браузера. Наприклад, у випадку функції скидання пароля, функції підтвердження електронної пошти, функції скасування підписки на електронну пошту, обробки веб-перехоплень із платіжних систем, обробки переспрямувань платіжних систем і таких сторінок, як угода користувача або положення та умови, які мають відкриватися у веб-переглядачі або з веб-перегляду в мобільній програмі. Ви можете розділити всі ці функції на субдомени, якщо вам потрібно. Але нерозумно зберігати весь цей матеріал у субдомене API, оскільки він не’пов'язаний із запитами клієнтів і в більшості випадків має метод автентифікації, відмінний від API. Субдомени з ‘хабом’ назви дуже популярні в різних службах для функцій одного типу.
  • admin.project-name.work —  хост, де зберігається адміністративна частина. Це зручно, коли панель адміністратора реалізовано в написаній у Symfony в тому самому сховищі, що й код API. Використовуються звичайні об’єкти, і можливі реалізації як чистого Symfony, так і деякого доступного пакета адміністратора (наприклад, SonataAdminBundle, EasyAdminBundle тощо). Це зручно, коли ви маєте справу з малими або середніми стартапами з обмеженим бюджетом і невеликим запланованим завантаженням адміністративної частини.
  • Залежно від тематики проекту, можуть використовуватися інші субдомени, як-от ‘мій’, ‘статистика’, ‘застаріла’ тощо

Якщо вам потрібні окремі субдомени:

Маючи окремі субдомени для різних функцій, легше маршрутизувати та балансувати трафік, коли вам потрібно збільшити пропускну здатність послуги. Це також дозволяє впроваджувати зовнішній інтерфейс за допомогою будь-якої іншої технології чи фреймворку та розміщувати його в кореневому домені project-name.work. Наприклад, коли клієнт хоче розмістити лендінг для мобільної програми в кореневому домені. Лендінг може розробити інша команда без вашої участі. І тоді ваш проект Symfony просто буде відокремлено від маршрутизації кореневого домену та працюватиме лише з відповідними субдоменами.

Як налаштувати проект із субдоменами для Docker:

Щоб налаштувати проект у Symfony для Docker, вам потрібна додаткова конфігурація. У Docker ви можете створити маршрутизацію на основі різних портів, але ви не можете застосувати маршрутизацію з пакета до субдоменів, оскільки за замовчуванням Docker працює в контексті одного хосту.

З Symfony є ще одна проблема: він не підтримує маршрутизацію за портами, але підтримує маршрутизацію субдоменів. Тобто поки що ви не можете налаштувати Symfony так, щоб він викликав той чи інший контролер залежно від порту. І цей варіант підтримки не очікується від https://github.com/symfony/symfony/issues/7592#issuecomment-16278784 найближчим часом (ви можете підписатися на цю тему, щоб відстежувати всі зміни: можливо, вони повернуться до цієї функції в якийсь момент).

З двох варіантів: або налаштувати компонент Symfony Router, або додати підтримку субдоменів у Docker, останній є простішим, і він вже має готову реалізацію.

Примітка. Його можна застосовувати лише в локальному середовищі розробки для Docker, і це жодним чином не є універсальним рішенням, яке можна використовувати у виробництві.

Щоб додати підтримку субдоменів у Docker, потрібно додати інший контейнер на основі jwilder/nginx-proxy зображення та конфігурацію для нього у ваш docker-compose.yml. Давайте розглянемо приклад частини конфігурації docker-compose.yml для двох контейнерів: nginx і nginx-proxy. Очевидно, у вашому проекті будуть інші контейнери, крім цих. У цьому прикладі jekakm/nginx-core:201802261 це образ нашої студії, який ми використовуємо для розробки.

docker-compose.yml

version: '2'
services:
    nginx_proxy:
        image: jwilder/nginx-proxy
        ports:
            - "80:80"
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
 
    nginx:
        image: jekakm/nginx-core:201802261
         environment:
            - "VIRTUAL_HOST=hub.project-name.work,admin.project-name.work,api.project-name.work"
        volumes:
            - "./docker-configs/nginx.conf:/etc/nginx/sites-enabled/default"
            - ".:/app:cached"
        expose:
            - 80
        depends_on:
            - "php"

nginx_proxy слухатиме локальний порт 80 і передаватиме його до Docker. Крім того, змінну середовища VIRTUAL_HOST додано до контейнера nginx. У ньому через кому потрібно згадати всі хости (з субдоменами та без), які мають бути проксі-сервером до контейнера nginx. Через цей проксі Symfony отримуватиме запити від згаданих хостів без змін і зможе відповідно до правил маршрутизації визначати, який контролер має обробити запит на основі субдоменів.

Швидкий приклад маршрутизації Symfony на основі субдоменів:

easy_admin_bundle:
    resource: "@EasyAdminBundle/Controller/AdminController.php"
    type: annotation
    host: "%admin_host%" # <-- admin panel host
 
api_fos_oauth_server_token:
    resource: "@FOSOAuthServerBundle/Resources/config/routing/token.xml"
    host: "%api_host%" <--  API host
    methods: POST #
 
api:
    resource: ../src/Controller/API
    type: annotation
    host: "%api_host%" <-- API host
    prefix: /v1.0
 
hub:
    resource: ../src/Controller/Hub
    type: annotation
    host: "%hub_host%" <-- host for all "frontend features"
 
admin:
    resource: ../src/Controller/Admin
    type: annotation
    host: "%admin_host%" <-- host for admin panel, the processing of all custom actions which were implemented from the admin panel  
admin_logout:
    path: /logout
    host: "%admin_host%"
 

У конфігурації для контейнера nginx є конфігурація хоста ./docker-configs/nginx.conf, яка підставляється в контейнер. У цій конфігурації параметр SERVER_NAME має значення project-name-docker. Це важливо, оскільки пізніше це значення буде використано для налаштування XDebug у PhpStorm (ми’розглянемо цю проблему трохи пізніше ). Я також наводжу приклад повної конфігурації для хосту nginx, щоб за потреби можна було перевірити відмінності.

Увага! Представлена конфігурація призначена для Symfony 4 та пізніших версій, оскільки вона використовує шлях public/index.php до інтерфейсного контролера:

server {
        gzip            	on;
        gzip_types      	text/plain text/css application/x-javascript text/xml application/xml application/rss+xml text/javascript image/x-icon application/json;
        gzip_min_length     1000;
        gzip_comp_level     6;
        gzip_http_version   1.0;
        gzip_vary       	on;
        gzip_proxied    	expired no-cache no-store private auth;
        gzip_disable    	msie6;
 
        listen 80;
 
        client_max_body_size 50M;
 
        root /app/public;
 
        rewrite ^/index\.php/?(.*)$ /$1 permanent;
 
        location / {
                index index.php;
                try_files $uri @rewriteapp;
        }
 
        location @rewriteapp {
                rewrite ^(.*)$ /index.php/$1 last;
        }
 
        location ~ ^/(index|config)\.php(/|$) {
                fastcgi_pass   php:9001;
                fastcgi_split_path_info ^(.+\.php)(/.*)$;
                include fastcgi_params;
                fastcgi_param  SERVER_NAME    	project-name-docker;
                fastcgi_param  SCRIPT_FILENAME	$document_root$fastcgi_script_name;
                fastcgi_param  HTTPS          	off;
        }
 
        location ~* ^.+\.(jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|wav|bmp|rtf|htc)$ {
                expires 	31d;
                add_header  Cache-Control private;
 
                error_page 404 = @rewriteapp;
        }
 
        location ~* \.(css|js)$ {
                expires 	7d;
                add_header  Cache-Control private;
        }
}

Після того як ви додали контейнер nginx-proxy до файлу docker-compose.yml, не забудьте створити нові контейнери та перезапустити запущені контейнери.

$ docker-compose build
$ docker-compose down && docker-compose up -d

Додавання правил перенаправлення:

Останнє, що вам потрібно зробити це додати правила переспрямування для ваших локальних хостів, щоб використовувані вами хости (або хости з субдоменами) переспрямовували на localhost. Ви можете це зробити, відредагувавши файл /etc/hosts і додавши до його такі рядки:

127.0.0.1   api.project-name.work
127.0.0.1   hub.project-name.work
127.0.0.1   admin.project-name.work

Або ви можете скористатися утилітою dnsmasq і налаштувати глобальне правило в ній. Наприклад, якщо ви використовуєте домен .work для локальних хостів, він налаштовується за таким правилом:

address=/.work/127.0.0.1

Додавання переспрямувань для локальних віртуальних хостів є далеким від того, що вважається найкращою практикою Docker (а саме, коли ви розгортаєте робоче середовище без додаткових команд на локальній машині). Але, на жаль, іншого вирішення цієї проблеми для субдоменів Symfony в Docker не було. І це компромісне рішення. Усе ж краще використовувати утиліту dnsmasq, щоб налаштувати загальне правило для локальної машини.

Щоб PhpStorm встановити підключення XDebug, вам потрібно додати локальний сервер до PHP -> Конфігурація серверів:

Symfony project on Docker handling

Ім’я хоста має збігатися зі значенням параметра SERVER_NAME у конфігураціях nginx. У нашому випадку це project-name-docker.

Отже,

Ми перевірили, як налаштувати проект у Symfony для роботи з субдоменами Docker. Сподіваємось, стаття буде корисною для вас. Запрошуємо до обговорення та співпраці!