Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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 чи метрики черги.

Докладніше в документації: Laravel: маршрут перевірки стану

  • Образ (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.

Докладніше в документації: Довідник Dockerfile

Файлова система контейнера - тимчасова: усе, що записано всередину, зникає разом з контейнером (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.

Кілька мереж дозволяють ізолювати сервіси: фронтенд-проксі бачить застосунок, але не базу.

Докладніше в документації: Мережа в Compose

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.