JavaRush /Курси /Docker for Spring /Smoke-check API і логи контейнера

Smoke-check API і логи контейнера

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

1. «Контейнер запущено» ≠ «сервіс працює»

Контейнер у docker ps — ще не доказ того, що сервіс справді працює. Контейнер — це лише «коробка» навколо процесу. Він може бути запущений, а застосунок усередині ще завантажується, аварійно завершитися під час ініціалізації контексту, слухати на іншому порту або взагалі зависнути на старті. Тому вводимо просте правило: успіх — це не docker run, успіх — це HTTP-відповідь + зрозумілі стартові логи.

Давайте зафіксуємо мінімальну модель перевірки, щоб вона стала не разовою магією, а рефлексом.

На першому запуску сервіс вважається «живим», коли виконуються дві умови. По-перше, службова кінцева точка відповідає — це швидкий індикатор, що застосунок піднявся і хоча б базова інфраструктура Spring Boot працює. По-друге, прикладна кінцева точка теж відповідає — це підтвердження, що піднявся не лише HTTP-шар, а й справді працюють ваш контролер і ваш код, а не просто «заглушка здоров’я». Паралельно ми читаємо логи, щоб розуміти, що відбувалося під час старту, а не вгадувати за симптомами.

Ось як це виглядає у вигляді маленької схеми:

flowchart TD
    %% Спочатку перевіряємо сам факт HTTP-відповіді, а вже потім заглиблюємося в деталі
    A[Контейнер запущено] --> B{"HTTP відповідає?"}
    B -->|Ні| C[Перевіряємо docker ps + порти + docker logs]
    B -->|Так| D{"Health у нормі? /actuator/health"}
    D -->|Ні| E[Читаємо логи: що не піднялося]
    D -->|Так| F{"Бізнесова кінцева точка у нормі? /api/catalog/items"}
    F -->|Ні| G[Логи + перевірка правильного URL]
    %% Якщо дійшли сюди — для поточного кроку вважаємо сервіс живим
    F -->|Так| H[Сервіс вважаємо живим для поточного кроку]

Зверніть увагу на важливу психологічну річ: ця схема спеціально влаштована так, щоб ви не «тикалися» випадковими командами. Спочатку спостерігаємо, потім перевіряємо, потім читаємо симптоми.

2. Smoke-check № 1: перевіряємо /actuator/health

Перший smoke-check має бути швидким, передбачуваним і не залежати від демо-даних або складної бізнес-логіки. Тому починаємо з endpoint Actuator /actuator/health. У нашому навчальному сервісі він уже є як частина операційного базового рівня, і його задача проста: сказати «застосунок живий» у зрозумілому JSON-форматі. Це не про красу, а про діагностику.

Спочатку переконаємося, що контейнер узагалі запущено і в нього саме те імʼя, з яким ми його підіймали: catalog-service.


# Перевіряємо, що контейнер справді запущено і порт проброшено назовні
docker ps
# CONTAINER ID   NAMES           STATUS          PORTS
# 4c2a...         catalog-service Up 15 seconds  0.0.0.0:8080->8080/tcp

Тепер робимо запит на health. Якщо у вас є curl, використовуємо його:


# Smoke-check: перевіряємо, що Actuator відповідає і застосунок узагалі піднявся
curl http://localhost:8080/actuator/health
# {"status":"UP"}

Якщо ви на Windows і curl поводиться неочікувано, тому що PowerShell іноді підставляє свій псевдонім, можна використати Invoke-RestMethod:


# Для PowerShell це зручний «рідний» спосіб зробити HTTP-запит
Invoke-RestMethod http://localhost:8080/actuator/health
# status
# ------
# UP

Сенс відповіді на сьогодні максимально конкретний. Нас не цікавить «чи все ідеально» — це буде пізніше. Нас цікавить: чи є відповідь узагалі і чи є в ній UP.

Трохи розширимо перевірку: іноді корисно одразу бачити HTTP-статус. Для цього можна додати прапорець -i, який покаже заголовки:


# -i показує статус і заголовки — зручно одразу побачити 200/401/503 тощо
curl -i http://localhost:8080/actuator/health
# HTTP/1.1 200
# Content-Type: application/vnd.spring-boot.actuator.v3+json
#
# {"status":"UP"}

Якщо ви отримали 200 і UP, це означає, що застосунок уже підняв вебшар і базова інфраструктура Spring Boot усередині контейнера жива. Якщо ви отримали Connection refused, це не «Actuator зламано», а сигнал, що ваш компʼютер не зміг підключитися до порту: або контейнер не запущено, або порт не опубліковано, або застосунок ще не піднявся, або ви перевіряєте не той порт.

І так, на старті буває цілком нормальна ситуація: контейнер уже запущено, а застосунок ще завантажується. Тоді curl може не встигнути з першого разу. Це не привід перевстановлювати Docker або йти в філософію. Просто зачекайте кілька секунд і повторіть запит. Spring Boot — штука жвава, але не телепатична: йому потрібен час, щоб розгорнути контекст.

3. Smoke-check № 2: перевіряємо /api/catalog/items

Після health робимо другу перевірку — прикладну кінцеву точку. У нашому проєкті це GET /api/catalog/items. Вона хороша тим, що одразу перевіряє кілька речей: працює маршрутизація, піднявся контролер, сервісний шар не падає на старті, а відповідь серіалізується в JSON. Для першого запуску цього більш ніж достатньо: ми не будуємо тестову піраміду, а надаємо першу допомогу на місці.

Виконаємо запит:


# Прикладний smoke-check: перевіряємо, що працює ваш контролер і серіалізація відповіді
curl http://localhost:8080/api/catalog/items
# [{"id":1,"sku":"SKU-001","title":"Demo item","price":10.00,"status":"ACTIVE"}]

Точний JSON і кількість елементів можуть відрізнятися залежно від того, як підготовлено стартовий репозиторій, але логіка та сама. Вам важливо побачити дві речі: відповідь приходить і вона схожа на JSON — не на HTML, не на «whitelabel error page» і не на тишу.

Якщо хочете перевіряти акуратніше й привчати себе мислити кодами статусу, можна зробити так:


curl -i http://localhost:8080/api/catalog/items
# HTTP/1.1 200
# Content-Type: application/json
#
# [...]

Тепер давайте розберемо типові варіанти «що пішло не так».

Якщо замість JSON ви бачите 404, це часто означає банальну річ: ви помилилися в URL або застосунок стартував, але endpoint не зареєстровано, наприклад якщо ви випадково смикаєте /api/catalog/item замість /api/catalog/items. І тут важливо не перетворюватися на «мага, який читає долю за 404». Ми не ворожимо — ми відкриваємо логи й дивимося, чи взагалі піднявся контролер і чи дійшов застосунок до кінця старту.

Якщо ви бачите 500, контейнер і застосунок, найімовірніше, живі, раз уже відповідь узагалі прийшла. Але всередині сталася помилка під час обробки запиту. На цьому рівні ми не йдемо глибоко у виправлення бізнес-логіки, але зобов’язані вміти побачити цю помилку в логах, інакше Docker швидко перетвориться для вас на «чорну скриньку».

На цьому кроці вже має зʼявитися корисне відчуття: health — це «пульс є», а /api/catalog/items — це «пацієнт уже може назвати своє імʼя та дату народження». Не медична аналогія століття, але для першого запуску працює.

4. Читаємо стартові логи

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

Найпростіша команда, і найкорисніша на старті, виглядає так:


docker logs catalog-service

Але на практиці ви швидко помітите: логів може бути багато, і найчастіше потрібен саме хвіст старту. Тому зручно обмежувати вивід:


# Беремо «хвіст» логів, щоб швидко побачити старт, помилку або рядок Started ...
docker logs --tail 80 catalog-service
# ... Starting CatalogApplication ...
# ... Tomcat started on port 8080 (http) ...
# ... Started CatalogApplication in 2.4 seconds ...

Що саме ми шукаємо у стартових логах? Не потрібно читати кожен рядок як художній роман — Spring Boot не завжди пише сюжетно. Нам потрібні кілька сигнальних моментів.

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

По-друге, має зʼявитися сигнал, що вебсервер піднявся і слухає порт. У класичному випадку це щось на кшталт Tomcat started on port 8080 (http).

По-третє, ми шукаємо фінальний рядок, або близький до нього за змістом, про те, що застосунок закінчив старт. Як правило, це Started ... in ... seconds. Це один із найцінніших рядків на цьому етапі: він перетворює старт із «здається, працює» на «застосунок дійшов до кінця ініціалізації».

Якщо вам потрібно спостерігати старт у реальному часі, можна відкрити другий термінал і ввімкнути «підписку» на логи:


# -f = follow, логи підуть потоково (зупинка виводу — Ctrl+C)
docker logs -f catalog-service

Тепер важливий момент: логи та HTTP-перевірка мають жити разом у вашій голові. Наприклад, ситуація: curl /actuator/health дає Connection refused. Що ви робите? Не переписуєте Dockerfile в паніці. Ви відкриваєте docker logs і дивитеся: застосунок узагалі стартує? Може, він упав на старті. І якщо так, то в логах буде конкретна причина, а не відчуття, що «Всесвіт проти вас».

Щоб зробити це ще більш механічним і менше залежати від настрою, можна тримати у себе такий міні-набір команд, буквально як чек:


docker ps
curl -i http://localhost:8080/actuator/health
curl -i http://localhost:8080/api/catalog/items
docker logs --tail 80 catalog-service

Це не «скрипт», а звичка. Коли така звичка зʼявляється, Docker перестає бути страшним: він стає передбачуваним.

5. Склеюємо картину: HTTP-симптоми + логи = швидкий діагноз

Найчастіша проблема новачка в Docker звучить так: «У мене не працює». Це невдале формулювання не тому, що ви погана людина, а тому, що воно не допомагає мозку обрати наступний крок. Нам потрібно навчитися перетворювати «не працює» на спостережувану картину: що показує curl, що показує docker ps, що показують логи.

У цього першого контейнера є корисний ритуал, який краще проганяти цілком, а не згадувати шматками:

  1. ./gradlew bootJar — збираємо артефакт.
  2. java -jar build/libs/docker-java-catalog-service-0.0.1-SNAPSHOT.jar — один раз переконуємося, що сам JAR живий без Docker.
  3. docker build -t docker-java-catalog-service:day3 . — збираємо образ із кореня проєкту.
  4. docker run --name catalog-service -p 8080:8080 docker-java-catalog-service:day3 — запускаємо контейнер.
  5. curl http://localhost:8080/actuator/health — перевіряємо базову ознаку життя.
  6. curl http://localhost:8080/api/catalog/items — перевіряємо, що працює й прикладний шлях.
  7. docker logs --tail 80 catalog-service — читаємо стартову картину, а не вгадуємо за одним симптомом.

Якщо після docker build і docker run картина лишається мутною, не лікуйте все як «суто Docker-проблему». Поверніться до локального java -jar: цей крок швидко відділяє зламаний артефакт від зламаної контейнеризації.

Коли такий мінімум повторюється без сюрпризів, Dockerfile перестає бути магічним файлом і перетворюється на звичайний спосіб керувати запуском.

Нижче невелика таблиця-шпаргалка, яка допомагає не метатися. Це не енциклопедія, а мінімальний «перекладач симптомів» для першого запуску.

Що ви бачите у curl Що це найчастіше означає Що перевірити насамперед
Failed to connect / Connection refused До порту ніхто не слухає: контейнер не запущено, порт не опубліковано, застосунок не підняв вебсервер docker ps, секція PORTS, потім docker logs --tail 80
HTTP 200 на /actuator/health, але 404 на /api/catalog/items Застосунок піднявся, але ви смикаєте не той URL або endpoint справді відсутній Звірити шлях, потім docker logs на наявність помилок мапінгу або старту
HTTP 500 на /api/catalog/items Endpoint знайдено, але обробка запиту падає всередині застосунку docker logs (там буде stack trace / причина)
Довго висить без відповіді (timeout) Контейнер живий, але застосунок може зависнути на старті або «підвиснути» під час обробки docker logs -f, подивитися, чи дійшов старт до Started ...

Зверніть увагу: майже в кожному рядку таблиці з’являються логи. Це нормально. У контейнерному світі логи — це «чорна скринька», яка раптом стає прозорою, тому що ви вмієте користуватися docker logs.

Ще один практичний прийом: коли ви бачите проблему, спробуйте сформулювати її через одне з двох питань. Або «процес живий?» — це видно по docker ps і логах, — або «HTTP відповідає?» — це видно по curl. Коли ви навчитеся чесно відповідати на ці питання, половина типових проблем перестане бути загадкою.

Коли образ уже зібрано і контейнер запущено, допомагає ще коротка перевірка під час виконання — її навіть можна зберегти собі в нотатки:


1) Чи є контейнер у docker ps?
2) Чи є опублікований порт 8080?
3) Чи відповідає /actuator/health?
4) Чи відповідає /api/catalog/items?
5) Якщо чогось немає — відкрити docker logs і шукати момент, де все пішло не так

Так, це майже інструкція зі збирання шафи. Але шафу хоча б можна збирати мовчки. А Spring Boot, якщо його не слухати, починає «мститися» stack traceʼами.

6. Типові помилки під час smoke-check і логів

Помилка № 1: зупинитися на docker ps і оголосити перемогу.
Дуже хочеться: контейнер у списку — значить, усе ок. Але контейнер може бути “Up”, а застосунок усередині ще стартує або вже впав і перезапустився — у складніших сценаріях. Навіть на цьому ранньому кроці дисципліна проста: після запуску контейнера ви робите хоча б один HTTP-запит і дивитеся стартові логи. Це не параноя, а гігієна.

Помилка № 2: перевіряти лише /api/catalog/items і ігнорувати /actuator/health.
Якщо прикладна кінцева точка не відповідає, ви одразу провалюєтеся у світ «занадто багато причин». Health endpoint потрібен як швидкий фільтр: застосунок узагалі піднявся чи ви стукаєте в порожнечу? Це економить хвилини, а іноді й години, і допомагає не сваритися на контролери, коли проблема насправді у старті застосунку.

Помилка № 3: плутати Connection refused і 404, називаючи це однаково «не працює».
Connection refused означає, що з’єднання не встановилося: на цьому порту ніхто не слухає або ви підключаєтеся не туди. 404 означає, що ви підключилися до застосунку, але він не знайшов маршрут. Це два різні всесвіти. У першому ви дивитеся на контейнер, порти й старт. У другому — на правильність URL, мапінги і, звісно, логи.

Помилка № 4: читати лише останній рядок логів і намагатися за ним зрозуміти все.
Spring Boot уміє красиво падати, але причина майже завжди лежить вище по логу. Якщо застосунок не стартував, у логах зазвичай є момент «ось тут усе пішло не туди»: виняток, повідомлення про порт, відсутність конфігурації. Тому на старті краще дивитися хоча б останні 50–100 рядків. --tail 80 — хороший робочий мінімум. Шукайте зв’язний шматок: від Starting до Started або до явної помилки.

Помилка № 5: шукати логи за випадковим container ID, тому що контейнер запускали без імені.
Технічно так жити можна, але це морока. Коли у контейнера є імʼя catalog-service, ви просто пишете docker logs catalog-service і не витрачаєте ресурси мозку на копіювання ID. У реальній розробці це дрібниця, яка заощаджує десятки мікростресів на день. А мікростреси, як відомо, чудово складаються в стан «чому я втомився, я ж нічого не робив».

1
Опитування
Spring Docker, рівень 3, лекція 4
Недоступний
Spring Docker
Збирання й запуск сервісу
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ