Senior: питання на співбесіді з теми «Compose для розробки»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Контейнери Linux потребують ядра Linux. На macOS (і Windows) Docker Desktop запускає легку віртуальну машину, і контейнери працюють усередині неї. Файли проєкту лежать на диску macOS, а процес у контейнері читає їх через межу віртуальної машини - механізм спільного доступу до файлів.
Чому це повільно для PHP і Node-проєктів: проблема не в розмірі файлів, а в їх кількості. Автозавантажувач Composer, node_modules, кеші фреймворку - кожен запит до Laravel відкриває й перевіряє сотні файлів; збирання фронтенду - тисячі. Кожна операція з файлом через межу ВМ коштує набагато дорожче, ніж на локальному диску.
Механізми й еволюція:
- gRPC FUSE, osxfs - старі механізми, дуже повільні на великих проєктах;
- VirtioFS - сучасний механізм за замовчуванням на macOS, значно швидший, але на великих репозиторіях усе ще відчутно повільніший за нативну файлову систему;
- Synchronized file shares - Docker Desktop тримає синхронізовану копію каталогу всередині ВМ (з файловим кешем), і контейнери читають її з нативною швидкістю. Розрахований на великі репозиторії (сотні тисяч файлів). Доступний у платних підписках Docker (Pro, Team, Business).
Практичні прийоми, що працюють незалежно від механізму:
- не монтувати залежності:
vendorіnode_modules- в іменованих томах (лишаються всередині ВМ) або встановлювати в образі; - монтувати лише потрібне: не весь проєкт з
.git, логами й кешем, а каталоги з кодом; - кеші й скомпільовані файли (
storage/framework,bootstrap/cache) - у томі чи tmpfs, а не на bind mount; - OPcache з
validate_timestampsі розумною частотою перевірки - менше звернень до файлової системи; docker compose watchзsync- копіювати змінені файли в контейнер замість спільного доступу;- ресурси ВМ: достатньо пам'яті й процесорів у налаштуваннях Docker Desktop.
Альтернативи Docker Desktop: OrbStack (швидший доступ до файлів і менше споживання ресурсів на macOS), Colima. Або відмовитися від Docker для самого PHP у розробці - Laravel Herd запускає PHP нативно, а в Docker лишаються лише бази й допоміжні сервіси.
На Linux проблеми немає: контейнери працюють на тому самому ядрі, і bind mount - це звичайне монтування без накладних витрат.
Докладніше в документації: Синхронізовані файлові спільні каталоги
Коли compose.yaml росте до десятків сервісів, з'являються дублювання й конфлікти. Compose має кілька механізмів структурування.
1. include (Compose 2.20+) - підключити інший файл Compose як окремий підпроєкт зі своїми відносними шляхами:
# compose.yaml
include:
- infra/compose.yaml # бази, Redis, пошук
- path: tools/compose.yaml # інструменти розробки
env_file: tools/.env
services:
app:
build: .
depends_on: [postgres, redis] # сервіси з підключених файлів доступні
На відміну від злиття через -f, кожен підключений файл розв'язує шляхи відносно свого розташування, тож команда, що відповідає за інфраструктуру, може тримати свій файл окремо. Конфлікт імен сервісів - помилка, а не тихе злиття.
2. extends - успадкувати конфігурацію сервісу з того самого чи іншого файлу:
services:
php-base:
build: .
environment:
APP_ENV: local
volumes:
- .:/var/www/html
app:
extends: php-base
ports: ["127.0.0.1:8000:8000"]
worker:
extends: php-base
command: php artisan queue:work
scheduler:
extends: php-base
command: php artisan schedule:work
3. Якорі YAML і поля розширення x-:
x-php: &php
build: .
env_file: .env
volumes: [".:/var/www/html"]
services:
app:
<<: *php
ports: ["127.0.0.1:8000:8000"]
worker:
<<: *php
command: php artisan queue:work
Поля верхнього рівня з префіксом x- Compose ігнорує, тож туди зручно класти спільні фрагменти. Злиття <<: - поверхневе: вкладені словники заміняються повністю, а не зливаються.
Як обрати:
- кілька незалежних частин (інфраструктура, застосунок, інструменти), різні власники -
include; - кілька ролей одного образу (web, worker, scheduler) -
extendsчи якорі YAML; - різні оточення для тих самих сервісів - кілька файлів через
-fчи профілі.
Перевірка результату - завжди docker compose config: усі три механізми розгортаються в підсумкову конфігурацію, і саме її варто перевіряти, а не покладатися на уявлення про злиття.
Xdebug - налагоджувач PHP: точки зупинки, покрокове виконання, перегляд змінних. У Docker головна складність - мережева: Xdebug сам ініціює з'єднання з IDE (порт 9003), а IDE працює на хості, поза контейнером.
Мінімальна конфігурація PHP у контейнері:
[xdebug]
xdebug.mode=debug,develop
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.start_with_request=trigger ; лише за запитом, а не на кожен
host.docker.internal - ім'я, що вказує на хост із контейнера. На Docker Desktop (macOS, Windows) воно працює автоматично; на Linux його треба додати явно:
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
У Laravel Sail це вже налаштовано: досить змінної в .env - SAIL_XDEBUG_MODE=develop,debug,coverage - і перезапуску контейнерів. Для CLI-команд - sail debug artisan ....
Налаштування IDE (PhpStorm):
- слухати вхідні з'єднання налагодження на порту 9003;
- відображення шляхів (path mappings): код у контейнері лежить у
/var/www/html, а на хості - у~/projects/app. Без відображення IDE отримує з'єднання, але не може зіставити файли й не зупиняється на точках; - ім'я сервера (
PHP_IDE_CONFIG=serverName=...) збігається з налаштуваннями сервера в IDE.
Чому з'єднання не приходить - чек-лист:
- Xdebug не ввімкнено:
php -vу контейнері має показувати Xdebug,php -i | grep xdebug.mode; - немає тригера при
start_with_request=trigger- потрібне розширення браузера Xdebug Helper чи параметрXDEBUG_TRIGGER; - неправильний
client_host- особливо на Linux безhost-gateway; - фаєрвол хоста блокує вхідний порт 9003;
- IDE не слухає чи слухає інший порт (старий Xdebug 2 використовував 9000);
- журнал Xdebug показує причину:
xdebug.log=/tmp/xdebug.log- там видно спробу з'єднання і помилку.
Продуктивність: Xdebug помітно сповільнює PHP навіть без активного налагодження. Тому start_with_request=trigger чи окремий образ/профіль з Xdebug, а не постійно ввімкнений режим. У продакшені Xdebug не встановлюють узагалі.
Докладніше в документації: Laravel Sail: налагодження з Xdebug
Ресурси Compose (контейнери, мережі, томи) належать проєкту. Назва проєкту - префікс усіх ресурсів: myapp-app-1, myapp_default, myapp_pgdata. За замовчуванням назва - ім'я каталогу з compose.yaml; явно задається полем name: у файлі, змінною COMPOSE_PROJECT_NAME чи прапорцем -p.
Рівні «скидання» - від м'якого до радикального:
docker compose restart app # перезапустити процес, контейнер той самий
docker compose up -d --force-recreate # перестворити контейнери (записуваний шар скинуто)
docker compose up -d --build # перезібрати образи й перестворити
docker compose down # видалити контейнери й мережі проєкту, ТОМИ ЛИШАЮТЬСЯ
docker compose down -v # + іменовані томи проєкту: дані бази ЗНИКНУТЬ
docker compose down --rmi local # + образи, зібрані для проєкту
Головне - розуміти, що де живе:
- записуваний шар контейнера зникає при перестворенні;
- іменовані томи (
pgdata) переживаютьdown, але неdown -v; - bind mount - це файли хоста, їх Docker не видаляє ніколи;
- анонімні томи накопичуються, якщо не видаляти їх разом з контейнерами (
down -vчиdocker compose rm -v).
Небезпечні звички:
docker system prune -a --volumes- чистить усе невикористовуване на машині: томи, образи, мережі всіх проєктів, включно з базами сусідніх проєктів, які зараз просто не запущені. Для чистки одного проєкту - командиdocker composeз його назвою;- однакова назва проєкту для двох різних каталогів (обидва
app/) - вони ділять мережі й томи, іdown -vв одному зносить дані іншого. Явнеname:уcompose.yamlзнімає проблему; down -v«за звичкою» при кожному перезапуску - щоразу порожня база й повторні міграції з сидерами.
Безпечне скидання бази розробки:
docker compose exec app php artisan migrate:fresh --seed # схема й тестові дані
# або точково видалити один том
docker compose down
docker volume rm myapp_pgdata
docker compose up -d
Перед видаленням томів із цінними даними - бекап: docker compose exec -T postgres pg_dump -U postgres app > backup.sql.
Перелік ресурсів проєкту: docker compose ps -a, docker volume ls --filter label=com.docker.compose.project=myapp.