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

Middle: питання на співбесіді з теми «Продакшен і оркестрація»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

Обидва - оркестратори: керують контейнерами на кластері серверів за описом бажаного стану. Різниця - у складності й можливостях.

Docker Swarm - вбудований у Docker Engine:

docker swarm init
docker stack deploy -c compose.yaml myapp
docker service scale myapp_app=4
  • сервіс - опис того, що має працювати (образ, кількість реплік, порти, ресурси);
  • задача (task) - конкретний контейнер сервісу на конкретному вузлі;
  • стек - набір сервісів з файлу у форматі Compose (з розділом deploy);
  • вбудовані: overlay-мережі між вузлами, балансування (routing mesh), секрети, поступові оновлення з відкатом.

Kubernetes - окрема платформа зі своїми поняттями:

  • Pod - найменша одиниця: один чи кілька контейнерів зі спільною мережею;
  • Deployment - бажана кількість однакових pod-ів і стратегія оновлення;
  • Service - стабільна адреса й балансування до pod-ів;
  • Ingress / Gateway - вхід HTTP-трафіку ззовні;
  • ConfigMap, Secret, томи (PersistentVolume), горизонтальне автомасштабування, оператори для баз даних і черг.

Порівняння:

Swarm Kubernetes
поріг входу низький: знайомий формат Compose високий: багато понять і YAML
встановлення одна команда керований сервіс чи складне налаштування
екосистема обмежена величезна (Helm, оператори, моніторинг)
автомасштабування немає вбудованого є (HPA, кластерні автоскейлери)
популярність, вакансії невелика стандарт індустрії

Коли достатньо Swarm: кілька серверів, кілька сервісів, невелика команда без окремого DevOps, потреба в простих поступових оновленнях і відмовостійкості. Багато PaaS для самостійного хостингу (наприклад, Dokploy) використовують Swarm під капотом.

Коли Kubernetes: десятки сервісів, кілька команд, автомасштабування, складні мережеві й безпекові політики, керований сервіс у хмарі - і готовність інвестувати в його знання.

Помилка вибору: Kubernetes для одного Laravel-застосунку з базою - складність, яка забирає більше часу, ніж дає користі.

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

Найпростіший деплой - docker compose pull && docker compose up -d - зупиняє старий контейнер і лише потім запускає новий. Між цими моментами сайт недоступний, а якщо новий контейнер не стартує - недоступний довго.

Принципи деплою без простою:

1. Спершу запустити нове, потім зупинити старе (start-first). Обидві версії якийсь час працюють паралельно.

2. Трафік на новий контейнер - лише коли він готовий. Перевірка стану (healthcheck) підтверджує, що застосунок справді відповідає, а не просто процес запущено:

healthcheck:
  test: ["CMD", "curl", "-fsS", "http://localhost/up"]
  interval: 10s
  timeout: 3s
  retries: 3
  start_period: 20s

У Laravel 11+ маршрут /up налаштовано за замовчуванням.

3. Старий контейнер завершує поточні запити перед зупинкою (graceful shutdown): отримує SIGTERM, перестає приймати нові запити, дообробляє поточні, і лише потім зупиняється. Балансувальник має встигнути прибрати його з пулу.

4. Автоматичний відкат, якщо нова версія не пройшла перевірку стану.

Як це реалізують:

  • Docker Swarm:
deploy:
  replicas: 2
  update_config:
    parallelism: 1
    order: start-first
    failure_action: rollback

Розділ update_config діє саме в режимі Swarm (docker stack deploy); звичайний docker compose up його не використовує і просто перестворює контейнери.

  • Kubernetes - стратегія RollingUpdate у Deployment з readiness-перевірками;
  • Kamal - запускає новий контейнер, перевіряє його й перемикає трафік через свій проксі (kamal-proxy);
  • власний скрипт з Compose - запустити новий контейнер під іншою назвою, дочекатися здорового стану, перемкнути зворотний проксі (Caddy, Traefik, Nginx), зупинити старий.

Пастки, через які «без простою» не виходить:

  • міграції бази, несумісні зі старою версією коду: під час переходу працюють обидві версії. Видалення чи перейменування колонки ламає стару. Потрібні сумісні в обидва боки міграції (спершу додати, потім - окремим релізом - прибрати);
  • черги: воркери зі старим кодом обробляють задачі нового формату - queue:restart і сумісний формат задач;
  • сесії й кеш у пам'яті контейнера - губляться при заміні.

Докладніше в документації: Compose: deploy.update_config

Перевірки стану відповідають на різні питання, і оркестратор реагує на них по-різному.

Liveness - «процес живий чи завис?»

Якщо перевірка не проходить кілька разів поспіль, контейнер перезапускається. Призначення - виходити з безвихідних станів: взаємоблокування, нескінченний цикл, вичерпані воркери.

Readiness - «чи готовий приймати трафік прямо зараз?»

Якщо не проходить - контейнер прибирається з балансування, але не перезапускається. Призначення - не слати запити туди, де їх не оброблять: застосунок ще прогрівається, тимчасово перевантажений, втратив з'єднання з залежністю.

Startup - «чи завершився запуск?»

Поки вона не пройшла, інші перевірки не виконуються. Для застосунків з довгим стартом: без неї liveness-перевірка може «вбивати» контейнер, що просто повільно запускається.

Що буде, якщо переплутати:

  • liveness перевіряє базу даних. База на хвилину недоступна - усі контейнери застосунку провалюють liveness і одночасно перезапускаються. Після відновлення бази - шторм перезапусків і холодний старт усього сервісу. Залежності - у readiness, а liveness має перевіряти лише сам процес;
  • немає readiness - трафік іде на контейнер, який ще прогріває кеш чи виконує міграції: користувачі отримують помилки після кожного деплою;
  • занадто суворі пороги (тайм-аут 1 секунда під навантаженням) - здорові контейнери перезапускаються саме тоді, коли найбільше потрібні;
  • важка перевірка (повний запит до бази, рендер сторінки) на кожні кілька секунд - зайве навантаження.

Як це виглядає в Docker: HEALTHCHECK у Dockerfile чи healthcheck у Compose дає один статус (healthy/unhealthy). Сам Docker не перезапускає нездоровий контейнер - на статус реагують Swarm, Compose з depends_on: condition: service_healthy чи зовнішні інструменти.

Для Laravel:

  • liveness - легкий ендпойнт без звернень до залежностей (маршрут /up підходить, якщо не перевантажений подіями);
  • readiness - окремий ендпойнт, що перевіряє базу, Redis, наявність закешованої конфігурації;
  • черги й планувальник - окремі перевірки: воркер без HTTP перевіряють командою.

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

Поширена помилка - міграції в команді старту контейнера:

CMD php artisan migrate --force && php-fpm

Проблеми:

  • кілька реплік стартують одночасно - і одночасно запускають ті самі міграції. Конфлікти, блокування, наполовину застосовані зміни;
  • падіння міграції - контейнер не стартує, оркестратор перезапускає його знову й знову;
  • масштабування (новий контейнер через годину) теж запускає міграції;
  • воркери черг і планувальник з того самого образу - теж мігрують.

Правильні варіанти:

1. Окремий крок деплою перед оновленням застосунку - одноразовий контейнер з тим самим образом:

docker run --rm --env-file .env ghcr.io/acme/app:sha-a1b2c3d php artisan migrate --force

У Kubernetes - Job перед оновленням Deployment; у Kamal - хук перед деплоєм; у CI - окремий крок пайплайну.

2. Якщо міграції все ж запускаються зі старту контейнера - з блокуванням:

php artisan migrate --force --isolated

--isolated бере атомарне блокування через кеш - міграції виконає лише один процес, інші пропустять. Потрібен спільний драйвер кешу з підтримкою блокувань (Redis, база даних).

Сумісність версій - найважливіше. Під час поступового деплою старий і новий код працюють одночасно з уже оновленою базою. Тому міграції мають бути сумісними з попередньою версією:

  • додати колонку - безпечно (nullable чи зі значенням за замовчуванням);
  • перейменувати чи видалити колонку - у кілька релізів: додати нову, писати в обидві, перенести дані, перевести читання, і лише потім видалити стару;
  • важкі зміни великих таблиць (індекси, зміна типу) - без довгих блокувань (онлайн-DDL, CREATE INDEX CONCURRENTLY у PostgreSQL), інакше застосунок «зависне» на час міграції.

Відкат: відкат коду не відкочує базу. План має враховувати, що попередня версія коду працюватиме з новою схемою - ще одна причина для сумісних міграцій.

Резервна копія перед міграцією на продакшені - обов'язковий крок для ризикованих змін.

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

Горизонтальне масштабування - запустити кілька однакових контейнерів і розподіляти між ними запити. Працює, лише якщо контейнер без стану (stateless): будь-який запит може обробити будь-яка копія.

Що заважає масштабуванню і куди це винести:

Стан у контейнері Куди винести
сесії у файлах Redis чи база даних (SESSION_DRIVER=redis)
кеш у файлах Redis, Memcached
завантажені файли в storage/app об'єктне сховище (S3, R2) чи спільний том
черги в синхронному режимі чи database на SQLite Redis чи база даних, окремі воркери
планувальник у кожному контейнері окремий контейнер чи onOneServer()
блокування у файлах блокування через Redis/базу

Типові наслідки, якщо цього не зробити:

  • користувача «розлогінює» при кожному запиті - сесія на іншій копії;
  • завантажений аватар то є, то немає;
  • запланована задача виконується стільки разів, скільки контейнерів (листи - вчетверо);
  • кеш скидається лише на одній копії.

Масштабування в різних середовищах:

docker compose up -d --scale app=4          # кілька копій на одному сервері
docker service scale myapp_app=4             # Swarm
kubectl scale deployment app --replicas=4    # Kubernetes

На одному сервері кілька копій мають сенс, лише якщо одна копія не використовує всі ядра; справжня користь - копії на різних серверах за балансувальником.

Що ще враховувати:

  • з'єднання з базою: кожна копія тримає свій пул - 10 копій по 20 процесів PHP-FPM = 200 з'єднань. База може не витримати раніше, ніж закінчаться ресурси застосунку;
  • база - не масштабується так само: вузьке місце переміщується туди - репліки для читання, кеш, оптимізація запитів;
  • довірені проксі - за балансувальником застосунок має бачити справжні IP і протокол;
  • одноразові задачі при старті (міграції, прогрів кешу) не повинні виконуватися кожною копією.

Для Laravel контейнер з php-fpm чи FrankenPHP, сесіями й кешем у Redis, файлами в S3 і окремими контейнерами для воркерів і планувальника - класична конфігурація, що масштабується.

Докладніше в документації: Compose: deploy.replicas