JavaRush /Курси /Docker for Spring /Діагностика: симптом → перевірка

Діагностика: симптом → перевірка

Docker for Spring
Рівень 22 , Лекція 4
Відкрита

1. Роль playbook і логів

Якби логи завжди були короткими, помилки — чесними, а конфіги — незмінними, ми б на цьому курсі вивчали лише команду docker logs і йшли пити чай. Але реальність інша: у вас є кілька сервісів, кілька профілів Spring Boot і кілька Compose-файлів, причому кожен фрагмент може «перевизначити» інший. У такій системі перемогти можна тільки порядком дій, а не героїзмом.

Troubleshooting playbook — це не «бюрократія заради бюрократії», а спосіб зробити діагностику повторюваною. Причому не лише для вас сьогоднішнього, який ще пам’ятає, що чіпляв, а й для вас завтрашнього, який уже не пам’ятає взагалі нічого, бо завтрашній ви — це трохи інша особистість. Playbook потрібен, щоб будь-яке «контейнер не стартує» перетворювалося на зрозумілий маршрут: зафіксували симптом → перевірили одну гіпотезу → внесли одну зміну → повторно перевірили вихідну перевірку.

Тепер у нас уже є вся база для складання маршруту: ми вміємо називати симптом, знаходити першу точку відмови, обирати дешеву команду й не плутати помилку застосунку з помилкою Compose. Залишилося зібрати з цього короткий playbook, який однаково корисний і для банального broken host, і для історій, де симптом уже пов’язаний із самим поводженням процесу під навантаженням.

2. Скелет playbook: симптом → клас → гіпотеза → перевірка

Зараз ми зробимо головний трюк дня: перетворимо розрізнені команди та спостереження на алгоритм, який можна повторювати майже без творчості. І це добре. Творчість залишимо для написання коду та назв змінних. У діагностиці творчість зазвичай закінчується фразою «ну я вже все спробував».

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

Ось його зручно уявити як блок-схему:

flowchart TD
    A["Симптом: що саме не працює?"] --> B["Перша точка відмови: build / start / init / call"]
    B --> C["Клас проблеми: build / startup / config / network / permission"]
    C --> D["Одна гіпотеза: думаю, причина саме тут"]
    D --> E["Одна перевірка: logs / inspect / config / exec"]
    E --> F{"Гіпотеза підтвердилася?"}
    F -- так --> G["Одна зміна"]
    G --> H["Повторна перевірка вихідної failing-check"]
    F -- ні --> I["Нова гіпотеза без накопичення випадкових правок"]

Щоб це не було абстракцією, давайте закріпимо терміни в маленькій таблиці. Вона замінить нам десять булетів і дасть змогу не плутатися.

Термін Що це означає «по-людськи» Приклад із нашого світу Boot+Compose
Симптом Перший спостережуваний факт, без пояснень curl http://localhost:8080/actuator/health не відповідає
Перша точка відмови Де вперше стало погано «збірка впала на COPY», «контейнер стартував і відразу помер», «застосунок живий, але БД недоступна»
Клас проблеми Тип поломки, який допомагає обрати інструмент network і config дуже часто плутають
Гіпотеза Одне конкретне припущення «порт проброшено не туди» або «в SPRING_PROFILES_ACTIVE є помилка»
Підтверджувальна перевірка Дешева команда, яка підтверджує або знімає гіпотезу docker compose config,
docker logs
,
docker inspect --format ...
Правило однієї зміни Між перевірками змінюємо рівно одну річ тільки порт або тільки host, але не «все підряд»

Зверніть увагу: ми спеціально тримаємося близько до спостережуваної поведінки. Це важлива навичка для backend-розробника: не вигадувати пояснення раніше, ніж є факт. Інакше мозок починає «дотягувати реальність» — приблизно так само, як Spring Boot дотягує вам автоконфігурацію, тільки мозок робить це без документації.

3. Міні-playbook для одного контейнера: коли «воно просто не працює»

В одиночному контейнері все простіше, але саме тому там особливо легко впасти в самовпевненість. Здається: «ну це ж один контейнер, зараз за 30 секунд полагоджу». А потім минає година, і ви вже читаєте про шаманські танці навколо ENTRYPOINT. Тому навіть для single-container запуску корисно мати короткий маршрут, який ви виконуєте майже на автопілоті.

Починати варто з питання: контейнер узагалі запускався і що з ним сталося? Тут ідеально підходить «трійка» команд: побачити список контейнерів, прочитати логи, підтвердити фактичну конфігурацію.

Нижче <container-name> — це умовна назва контейнера. Якщо сервіс піднято через Compose, контейнер сервісу app зручно отримувати через $(docker compose ps -q app) і взагалі не залежати від generated names.

docker ps -a                      # бачимо: контейнер узагалі запускався? який статус (Exited/Up)?
docker logs <container-name>      # читаємо: чому впав / що відбувалося під час старту
docker inspect <container-name>   # підтверджуємо: як реально запущено (env, ports, mounts, entrypoint)

Сенс такої послідовності в тому, що logs часто одразу показує першопричину (наприклад, застосунок не знайшов jar, не зміг розпарсити параметр або впав через відсутній файл). inspect потрібен, коли логів недостатньо і треба підтвердити, чим контейнер реально запущений: який Entrypoint, які env vars, які порти опубліковано, які mounts підключено.

Якщо ви відчуваєте спокусу відразу зробити docker exec -it <container-name> sh, зупиніться на секунду. exec — це як розкрити системний блок і почати пальцем мацати дроти. Іноді це потрібно, але спочатку все ж логічніше подивитися, що система сама про себе каже. Як правило, exec потрібен тоді, коли у вас уже є конкретна гіпотеза на кшталт «змінна середовища не дійшла» або «файл реально відсутній».

Наприклад, якщо гіпотеза про env vars, перевірка має бути короткою і прицільною:

docker exec <container-name> printenv | grep SPRING  # перевіряємо, що потрібні змінні реально всередині контейнера
# SPRING_PROFILES_ACTIVE=postgres,cache

А якщо гіпотеза про файл (умовно, jar не там), то перевірка має бути саме про файл, а не «давайте просто відкриємо shell і подивимося»:

docker exec <container-name> ls -la /app  # перевіряємо, що потрібний файл узагалі існує в очікуваному шляху
# ... app.jar

Ця точковість заощаджує час: ви не перетворюєте діагностику на туризм файловою системою контейнера. Бо контейнер — не музей, там нема що роздивлятися, доки ви не знаєте, що шукаєте.

4. Compose-playbook: config, фактичний стан і логи кількох сервісів

Compose — це місце, де помилка особливо любить маскуватися під «зламався Spring Boot». Але в multi-container середовищі у вас з’являється ще один шар: потрібно окремо побачити зібрану модель запуску, фактичний стан контейнерів і ланцюжок логів кількох сервісів. Інакше дуже легко лагодити застосунок там, де насправді зламано YAML, override або залежність readiness.

Стартовий маршрут у Compose зазвичай виглядає так:

# Якщо стек піднімається кількома файлами, тут використовуйте той самий набір `-f`, що й у `up`
docker compose config
docker compose ps
docker compose logs app postgres

docker compose config показує бажану модель: що Compose збере з файлів, override і .env. docker compose ps і docker inspect показують фактичний стан: які контейнери вже створено, у якому вони стані та що в них реально застосовано. Якщо ви поправили YAML, config уже правильний, а контейнер продовжує жити зі старим env або mount, це не магія — сервіс треба пересоздати і лише потім повторити вихідну failing-check.

# Якщо змінювали конфігурацію app і хочете застосувати її до вже створеного контейнера
docker compose up -d --force-recreate app

Логи кількох сервісів потрібні не заради кількості тексту. app зазвичай показує симптом, а залежність — причину, тому в Compose корисно одразу читати хоча б пару «клієнт + залежність».

docker compose logs --tail=50 app postgres
docker compose logs -f app postgres

docker compose exec залишається корисним, але тільки тоді, коли контейнер стабільно живе і ви перевіряєте конкретну гіпотезу.

# Для працюючого контейнера
docker compose exec app env | grep SPRING

# Для сценарію швидкого падіння, коли `exec` уже не встигає
docker inspect --format '{{json .Config.Env}}' $(docker compose ps -q app)

5. Правило однієї зміни

Зараз буде найменш технічна, але найрятівніша частина. Коли щось не працює, руки починають свербіти зробити відразу три правки: змінити порт, переписати SPRING_DATASOURCE_URL, додати depends_on і ще про всяк випадок docker compose down -v, щоб «усе точно чисто». Знайомо? Вітаю, ви людина.

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

Практично це виглядає так: у вас є failing-check, наприклад curl на /actuator/health не відповідає. Ви змінюєте лише публікацію порту і знову повторюєте той самий curl. Якщо почало відповідати — ви знаєте, що причина була в портах. Якщо ні — портова гіпотеза знята, і ви не «засмітили» систему ще двома випадковими змінами.

6. Короткі кейси: симптом → повторна перевірка

У реальній роботі зручніше тримати поруч не три довгі історії, а одну lookup-матрицю. Довгі розбори корисні вперше, а поруч із терміналом зазвичай потрібна коротка версія, яка швидко повертає в playbook.

Симптом Перша точка відмови / клас Перша команда Де перевіряти далі Одна типова правка
docker build
падає на COPY або RUN
build вивід самого docker build шлях до файлу, build context, .dockerignore, Dockerfile виправити шлях / контекст, а не лізти в мережу та порти
Контейнер одразу Exited startup або config
docker logs <container-name>
docker inspect по State, Entrypoint, env vars виправити шлях до jar, команду запуску або невалідне значення env var
app живий, але HTTP не відкривається з host network / ports
docker compose ps
docker inspect ... Ports + логи зі рядком про реальний порт Boot привести ports: і SERVER_PORT до однієї реальності
app не достукався до PostgreSQL / Redis / RabbitMQ network, readiness або config
docker compose logs app postgres
docker compose config, env vars, health залежностей виправити service name / профіль / модель readiness
Застосунок стартував «не в тому режимі» config
docker compose config
docker compose exec app printenv або docker inspect .Config.Env виправити ім’я або значення env var / профілю
Експорт пише з помилкою або файл не з’являється на host permission / mount / config
docker compose config
docker inspect .Mounts, APP_EXPORT_DIR, права на target dir вирівняти mount target і шлях у застосунку, за потреби пересоздати сервіс

6.1. Один повний прогін: YAML уже правильний, а контейнер іще старий

Уявіть: експорт має писати файли в ./data/exports. Ви вже поправили APP_EXPORT_DIR у compose.dev.yaml, але на host усе одно порожньо. Це той самий момент, де треба розвести бажану модель і фактичний стан, а не переписувати YAML ще раз.

  1. Симптом: endpoint експорту відпрацював, але файла на host немає.

  2. Спочатку дивимося бажану модель тими самими файлами, з якими реально піднімаємо стек:

    docker compose -f compose.yaml -f compose.dev.yaml config
  3. Якщо зібрана конфігурація вже показує правильні APP_EXPORT_DIR і volumes, порівнюємо її з фактичним станом контейнера:

    docker inspect --format '{{json .Mounts}}' $(docker compose -f compose.yaml -f compose.dev.yaml ps -q app)
    docker inspect --format '{{json .Config.Env}}' $(docker compose -f compose.yaml -f compose.dev.yaml ps -q app)
  4. Якщо inspect усе ще показує старий target path або старе значення env, проблема вже не в YAML. Контейнер просто не пересоздано.

  5. Робимо одну зміну — явно пересоздаємо лише сервіс app:

    docker compose -f compose.yaml -f compose.dev.yaml up -d --force-recreate app
  6. Повторюємо вихідну перевірку: знову запускаємо експорт і дивимося, чи з’явився файл у ./data/exports.

6.2. Оформлення playbook у репозиторії

Зараз ми не будемо перетворювати playbook на роман, бо в нас уже є один роман — compose.yaml після кількох місяців життя. Playbook — це короткий документ, який допомагає швидко відтворити діагностику й не забути важливі перевірки. Ідеально, якщо він живе або в README.md як розділ Troubleshooting, або в окремому файлі на кшталт docs/troubleshooting-playbook.md, і на нього є посилання з README.

Корисний формат — не список команд, а шаблон «картки інциденту». Він змушує вас фіксувати симптом, гіпотезу та повторну перевірку, тобто утримує в межах алгоритму. Приклад шаблону — мінімальний і читабельний:

### Симптом
`curl http://localhost:8080/actuator/health` не відповідає.

### Клас проблеми
Network / Ports.

### Гіпотеза
У Compose опубліковано не той порт.

### Перевірка
`docker compose config` → дивлюся секцію `ports`.

### Виправлення (одне)
Змінюю `8080:8081` на `8080:8080`.

### Повторна перевірка
Повторюю `curl` на `/actuator/health`.

Зверніть увагу, що тут немає «магії» й немає зайвих розгалужень. Це і є «короткий, але робочий» playbook. Він не замінює мислення, але не дає цьому мисленню перетворитися на хаотичне перемикання між вкладками та командами.

Для Compose-спірних випадків зручно дописати ще дві короткі строки: Resolved config (docker compose config) і Actual container state (docker inspect / docker compose exec printenv). Це допомагає відразу побачити, де ви сперечаєтеся з YAML, а де — зі старим контейнером.

Якщо ви використовуєте кілька Compose-файлів, окремим рядком варто зафіксувати, що перед будь-якою діагностикою ви дивитеся зібрану конфігурацію через docker compose config. Docker прямо рекомендує цей прийом, щоб бачити підсумкову конфігурацію та уникати проблем зі шляхами й overrides.

7. Типові помилки під час роботи за troubleshooting playbook

Помилка №1: playbook перетворюють на енциклопедію «про всяк випадок».
Найчастіша помилка — перетворювати playbook на енциклопедію команд «про всяк випадок». У результаті документ стає занадто довгим, його ніхто не читає, а ви знову повертаєтеся до хаосу. Playbook має залишатися коротким: симптом, гіпотеза, одна перевірка, одна зміна, повторна перевірка.

Помилка №2: пропускають повторну перевірку.
Друга типова помилка — пропускати повторну перевірку. Виправлення без повторної перевірки — це як git commit без запуску тестів: наче ви щось зробили, але впевненості немає. Повторна перевірка має повторювати саме вихідний failing-check, інакше ви легко «полагодите не те» та отримаєте фальшиве відчуття перемоги.

Помилка №3: роблять кілька змін підряд.
Третя помилка — робити кілька змін підряд. Це ламає діагностику навіть тоді, коли ви дуже досвідчені, а для новачка перетворює процес на лотерею. Якщо дуже хочеться змінити три речі, це добрий сигнал, що ви не сформулювали гіпотезу достатньо конкретно.

Помилка №4: довіряють фрагменту YAML замість зібраної моделі.
Четверта помилка — довіряти фрагменту YAML замість зібраної моделі. У Compose-світі, особливо за кількох файлів, правду показує не те, що ви «пам’ятаєте», і не те, що «написано в одному файлі», а те, що Compose реально зібрав і застосував. Тому docker compose config — не прикраса, а системна звичка.

1
Задача
Docker for Spring, 22 рівень, 4 лекція
Недоступна
Bash-плейбук для single-container сценарію
Bash-плейбук для single-container сценарію
1
Задача
Docker for Spring, 22 рівень, 4 лекція
Недоступна
Bash-playbook для Compose-стеку
Bash-playbook для Compose-стеку
1
Опитування
Контейнерна діагностика, рівень 22, лекція 4
Недоступний
Контейнерна діагностика
Пошук проблем у контейнерах
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ