Docker: Compose, мережі й томи
20 питань · ~20 хв · Версія v3.0
Увійдіть, щоб продовжити
Сервіси й залежності в Compose, томи й монтування, мережі, ресурси й політики перезапуску, сигнали, журнали й діагностика - питання всіх рівнів, від junior до senior.
- За спробу
- 20
- У пулі
- 55
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
28 питаньНові застосунки Laravel мають вбудований маршрут перевірки стану, оголошений у bootstrap/app.php:
->withRouting(
web: __DIR__.'/../routes/web.php',
health: '/up',
)
GET /up повертає 200, якщо застосунок завантажився без винятків, і 500 - якщо під час обробки сталася помилка. Під час запиту Laravel генерує подію DiagnosingHealth, на яку можна підписатися й додати власні перевірки (якщо слухач кидає виняток - відповідь 500).
Healthcheck у Compose:
services:
app:
image: myapp:1.4.0
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8000/up"]
interval: 10s
timeout: 3s
retries: 3
start_period: 30s
Docker позначає контейнер healthy чи unhealthy. На це спираються:
depends_on: { condition: service_healthy }у Compose;- балансувальники й платформи деплою (Dokploy, Coolify, Swarm), що не перемикають трафік на новий контейнер, поки він не здоровий;
- моніторинг і сповіщення.
Що варто перевіряти, а що ні:
- liveness («процес живий і відповідає») -
/upбез зовнішніх залежностей. Якщо в перевірку додати базу, то при короткому збої бази всі контейнери застосунку позначаться нездоровими й можуть бути перезапущені - збій бази перетвориться на збій сайту; - readiness («готовий приймати трафік») - тут доречно перевірити підключення до бази й Redis, бо без них застосунок не може обробити запит;
- у Kubernetes це окремі проби; у Compose - зазвичай одна, і краще тримати її легкою.
Практичні деталі:
curlмає бути в образі - для мінімальних образів замість ньогоphp -rзfile_get_contentsчи маленький скрипт;start_period- час на старт іconfig:cache, протягом якого невдалі перевірки не рахуються;- маршрут має працювати без сесії й автентифікації і не потрапляти під обмеження частоти запитів чи режим обслуговування, якщо його використовує балансувальник;
- для воркерів черги HTTP-перевірки немає - стан видно з того, що процес живий, а глибше - через
horizon:statusчи метрики черги.
- Образ (image) - незмінний шаблон: файлова система з усім потрібним (ОС-шар, PHP, розширення, код, залежності) і метадані - яку команду запускати, які порти слухати. Збирається з
Dockerfile, зберігається в реєстрі (Docker Hub, GHCR). - Контейнер - запущений екземпляр образу: ізольований процес з власною файловою системою, мережею й обмеженнями ресурсів.
Аналогія: образ - як клас, контейнер - як об'єкт. З одного образу можна запустити скільки завгодно контейнерів.
docker build -t myapp:1.4 . # зібрати образ
docker run -d --name web myapp:1.4 # запустити контейнер
docker ps # запущені контейнери
docker images # локальні образи
Шари. Образ складається з шарів тільки для читання - по одному на інструкцію Dockerfile, що змінює файли. Контейнер додає зверху тонкий записуваний шар. Тому:
- десять контейнерів з одного образу ділять його шари й не займають десятикратно більше місця;
- зміни, зроблені всередині контейнера, зникають разом із ним. Дані, що мають жити довше (база, завантажені файли), зберігають у томах (volumes).
Практичне правило: контейнер має бути «одноразовим» - його можна знищити й створити заново з того ж образу без втрати даних. Змінити застосунок - це зібрати новий образ, а не зайти в контейнер і поправити файл.
Обидві інструкції задають, що запускається в контейнері, але по-різному поводяться з аргументами docker run:
CMD- команда за замовчуванням. Якщо при запуску передати команду, вона повністю замінитьCMD.ENTRYPOINT- основний виконуваний файл. Аргументиdocker run(абоCMD) дописуються до нього як параметри.
ENTRYPOINT ["php", "artisan"]
CMD ["serve", "--host=0.0.0.0"]
docker run myapp # php artisan serve --host=0.0.0.0
docker run myapp queue:work # php artisan queue:work
docker run myapp migrate --force # php artisan migrate --force
Типовий сценарій - скрипт-обгортка як ENTRYPOINT, що готує середовище й передає керування команді:
#!/bin/sh
set -e
php artisan config:cache
exec "$@" # запустити CMD - і зробити його PID 1
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["php-fpm"]
Exec-форма проти shell-форми:
CMD ["php-fpm"](JSON-масив, exec-форма) - процес запускається напряму й отримує сигнали.CMD php-fpm(shell-форма) - запускається через/bin/sh -c, і PID 1 - це оболонка. СигналSIGTERMприdocker stopотримує shell, а не застосунок, тож контейнер не завершується коректно і через 10 секунд його вбиваютьSIGKILL.
Тому в обох інструкціях - exec-форма, а в скрипті-обгортці - exec "$@".
ENTRYPOINT можна перевизначити при запуску: docker run --entrypoint sh myapp.
Файлова система контейнера - тимчасова: усе, що записано всередину, зникає разом з контейнером (docker rm). Для даних, що мають жити довше, є два основні способи:
Том (volume) - сховище, яким керує Docker (зазвичай у /var/lib/docker/volumes). Живе незалежно від контейнерів.
services:
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Bind mount - каталог хоста, змонтований у контейнер напряму. Зміни видно з обох боків одразу.
services:
app:
volumes:
- ./:/var/www # код з машини розробника
Коли що:
- Томи - для даних сервісів: бази, Redis, завантажені файли. Портабельні, не залежать від структури каталогів хоста, з ними простіше робити бекапи. На macOS і Windows значно швидші за bind mount.
- Bind mount - у розробці: правите код в IDE, контейнер одразу бачить зміни. На проді - лише для конфігурацій, якщо взагалі.
Пастки:
docker compose down -vвидаляє томи разом з даними бази. Без-vтоми лишаються.- Права доступу в bind mount: файли, створені в контейнері від root, на хості теж належать root.
- Анонімні томи (без імені) легко загубити й накопичити -
docker volume pruneприбирає невикористані.
На проді для коду ні том, ні bind mount не потрібні: код запікають в образ, а новий реліз - це новий образ.
Compose створює для проєкту окрему мережу, і всі сервіси в ній доступні один одному за назвою сервісу - вбудований DNS Docker перетворює її на IP контейнера.
services:
app:
build: .
environment:
DB_HOST: db # не localhost і не IP
REDIS_HOST: redis
db:
image: postgres:17
redis:
image: redis:7
Типова помилка новачка - localhost. У контейнері app адреса localhost - це сам контейнер app, а не база. Бази там немає, тож DB_HOST=localhost дає «connection refused». Потрібно DB_HOST=db.
Порти: внутрішні й опубліковані.
- Усередині мережі сервіси звертаються один до одного за внутрішнім портом контейнера:
db:5432. ports: ["5433:5432"]публікує порт на хост - щоб підключитися з машини розробника чи з інтернету. Для зв'язку між контейнерами публікувати не потрібно.
db:
ports:
- "127.0.0.1:5433:5432" # доступно лише з цього комп'ютера
Безпека: ports: ["5432:5432"] на сервері відкриває базу на всі інтерфейси - і Docker при цьому обходить правила ufw, бо сам керує iptables. Базу на проді або не публікують узагалі, або прив'язують до 127.0.0.1.
Кілька мереж дозволяють ізолювати сервіси: фронтенд-проксі бачить застосунок, але не базу.
Прочитати - ще не значить знати
20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.