JavaRush /Курсы /Docker for Spring /Совместное чтение логов: ap...

Совместное чтение логов: app + postgres

Docker for Spring
16 уровень , 4 лекция
Открыта

1. Сигналы работоспособности стека

Когда вы впервые поднимаете стек app + postgres, очень хочется выдохнуть на моменте «вижу два контейнера в статусе running — значит всё ок». Это нормальная человеческая реакция, но плохая инженерная привычка. В контейнерном мире “процесс жив” и “сервис готов” — разные вещи, и мы будем собирать доказательства через несколько независимых сигналов: статус контейнеров, логи и HTTP-ответы.

Named volume тоже не даёт автоматической гарантии: база может честно переживать пересоздание контейнера, а приложение всё равно не достучаться до неё из-за профиля, host или пароля.

Если упростить, у нас есть три уровня реальности. Самый нижний — Docker: контейнеры стартуют и не падают сразу. Средний — приложение и база “видят” друг друга: есть сетевое соединение, PostgreSQL принимает подключения, приложение прошло инициализацию persistence слоя. Верхний — бизнесовый: сервис отвечает по HTTP на нужных endpoint’ах, а /actuator/health честно говорит UP.

Чтобы эта логика не осталась абстрактной, держите в голове маленькую таблицу «что доказывает что». Это не чек-лист на жизнь, а удобная линейка, чтобы измерять реальность, а не настроение:

Сигнал Чем смотрим Что он доказывает
Контейнеры живы
docker compose ps
Процессы app и postgres запущены, но не факт, что они «связаны»
Сервисы общаются
docker compose logs app postgres
Приложение пытается подключиться к БД, а БД либо принимает, либо ругается конкретно
API реально работает
GET /api/catalog/items
Наш HTTP-контракт жив, web-слой не просто стартанул «для красоты»
Операционная “живость”
GET /actuator/health
Приложение сообщает, что оно в рабочем состоянии (минимальный operational сигнал)

Пока достаточно руками увидеть эти сигналы и научиться читать их согласованно. Иначе любой healthcheck превратится в магию: флаг готовности появится, а что именно он подтверждает, будет непонятно.

В этой лекции мы будем действовать ровно в такой последовательности: сначала смотрим оба лога вместе, потом подтверждаем работу двумя HTTP-запросами. И да, это тот редкий случай, когда “проверить два урла” — не формальность, а экономия времени на будущие «почему оно не работает».

2. Читаем логи app и postgres вместе

Логи в multi-container мире — это как переписка в мессенджере: если читать только сообщения одной стороны, вы будете постоянно додумывать вторую. Поэтому самое полезное действие сегодня — научиться смотреть на логи app и postgres одновременно, одним потоком. Docker Compose для этого как раз и придуман: он аккуратно помечает строки префиксом сервиса и не заставляет вас угадывать, кто сказал «ой».

Базовая команда выглядит так:

# Читаем логи сразу двух сервисов одним потоком (видно, кто и на что реагирует)
docker compose logs app postgres

Она берёт логи двух сервисов из вашего compose.yaml и печатает их в одном выводе. Это удобно уже потому, что на типичные ошибки (не тот пароль, не та база, не тот хост) часто ругаются оба участника разговора.

Часто полезно “подписаться” на логи, чтобы они текли в реальном времени. Тогда добавляют -f (follow). Я не буду превращать лекцию в каталог флагов, но этот один реально стоит знать:

# То же самое, но в режиме "живой ленты" — удобно во время старта контейнеров
docker compose logs -f app postgres

Типичный фрагмент вывода выглядит примерно так (обратите внимание на префиксы слева):

# Compose помечает каждую строку именем сервиса: так легче сопоставлять "вопрос" и "ответ"
postgres  | database system is ready to accept connections
app       | The following 1 profile is active: "postgres"
app       | HikariPool-1 - Start completed.
app       | Tomcat started on port 8080 (http) with context path '/'

Если вы видите одновременно “PostgreSQL готова принимать соединения” и “приложение стартовало на порту 8080”, это уже хороший знак. Но самый вкусный сигнал обычно чуть позже: когда приложение реально делает запросы к БД (например, во время инициализации или миграций) и база отвечает без ошибок.

Есть важный нюанс. docker compose up --build тоже печатает логи, и студенты часто читают только их. Проблема в том, что как только вы закрыли терминал или у вас прокрутилось 200 строк, вы начинаете жить “по памяти”. А память у нас, как и кэш, иногда врёт. docker compose logs ... позволяет в любой момент вернуться и пересмотреть симптомы спокойно, без паники и без прокрутки на скорости света.

3. Логи PostgreSQL: признаки готовности

С PostgreSQL в контейнере есть одна хорошая новость: она обычно довольно честно пишет в логах, что с ней происходит. Плохая новость — на старте она любит много “служебного шума”, который новичка пугает, хотя это просто нормальная жизнь базы. Поэтому сегодня нам важно научиться вылавливать смысловые маркеры: база инициализировалась, база поднялась, база готова принимать соединения и, если повезёт, база уже видит подключения приложения.

Самая желанная строка в логах PostgreSQL обычно звучит примерно так:

# Ключевой маркер: база поднялась и реально готова принимать подключения
postgres  | database system is ready to accept connections

По-человечески это означает: «я поднялась настолько, что могу принимать TCP-соединения и обрабатывать запросы». Без этого приложение может быть каким угодно красивым, но его попытки подключиться превратятся в знакомую песню “Connection refused”.

Чуть выше или рядом вы можете увидеть строки инициализации базы (особенно при самом первом запуске, когда named volume ещё пустой). Они могут выглядеть по-разному в зависимости от образа и версии, но смысл один: создаётся кластер данных, заводится пользователь, создаётся база с именем из POSTGRES_DB. И тут важно не перепутать: эти переменные окружения — про инициализацию контейнера PostgreSQL, они не настраивают приложение.

Напомню минимальный кусок окружения у сервиса postgres (как идея, не как «единственно правильный YAML»):

services:
  postgres:
    environment:
      # Эти переменные используются PostgreSQL на старте, чтобы создать БД/пользователя
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Есть тонкий момент, который часто выстреливает именно после введения named volume. Если данные PostgreSQL уже созданы и лежат в volume, то изменение POSTGRES_DB/USER/PASSWORD в compose.yaml или .env может не привести к ожидаемому “пересозданию мира”. Named volume как раз и делает данные устойчивыми, а значит инициализация не обязана запускаться заново. На этом месте люди обычно искренне удивляются: «Я же поменял пароль в переменной, почему он не поменялся в базе?» Это не магия, это persistence.

И ещё одна важная логическая подсказка для диагностики: если вы видите ошибку подключения в логах приложения, но в логах PostgreSQL вообще нет никакой реакции, это часто означает, что приложение до базы даже не дотянулось. Например, вы указали неправильный host (localhost) или неправильный порт. Если бы запрос долетел до PostgreSQL, она почти всегда оставила бы след в логах, хотя бы в виде отказа.

4. Логи приложения: профиль, БД, старт

Логи Spring Boot приложения в контейнере — это ваш “голос” со стороны app. И тут важно помнить, что мы не читаем их ради красоты. Мы читаем их, чтобы ответить на три практических вопроса: действительно ли активировался профиль postgres, получилось ли подключиться к базе, и стартанул ли web-сервер так, чтобы принимать HTTP-запросы снаружи (через опубликованный порт).

Первый маркер — активные профили. Обычно Boot пишет что-то вроде:

# Проверяем, что приложение запущено в нужном режиме (профиль влияет на конфигурацию datasource)
app | The following 1 profile is active: "postgres"

Если вместо этого вы видите пусто или какой-то другой профиль, не спешите обвинять PostgreSQL во всех грехах. Очень часто база тут ни при чём: приложение просто стартует не в том режиме и живёт в standalone-мире, где никакого datasource вообще не нужно. Поэтому в этой лекции мы воспринимаем строку про профили как “паспорт режима”. Сначала паспорт — потом всё остальное.

Второй маркер — признаки того, что datasource поднялся. В Spring Boot приложении с JDBC пулом вы часто увидите строки от HikariCP (или аналогичного пула), например:

# Эти строки означают, что пул соединений стартовал (и обычно уже смог подключиться к БД)
app | HikariPool-1 - Starting...
app | HikariPool-1 - Start completed.

Не обязательно запоминать именно эти слова как заклинание. Важно уловить идею: приложение не просто “запустилось”, оно ещё и смогло подготовить соединение с БД. Если пароль неверный или хост неправильный, эти строки либо не появятся, либо рядом появится жирная ошибка.

Третий маркер — старт web-сервера и финальная строка “Started … in … seconds”. Она сообщает, что приложение дошло до состояния, в котором оно вообще способно отвечать по HTTP:

# Маркер готовности web-слоя: есть порт, есть контекст, приложение дошло до финального "Started ..."
app | Tomcat started on port 8080 (http) with context path '/'
app | Started CatalogApplication in 3.421 seconds

Ещё один маленький, но важный принцип. Хорошие логи не должны печатать секреты. Если вы вдруг увидели, что приложение логирует пароль к базе — это плохая практика. В нашем учебном проекте мы держим курс на “логи пригодны для docker compose logs”, а это означает: максимум полезных событий, минимум чувствительных данных.

И финальная мысль, которая прямо помогает в диагностике. Ошибка подключения к базе — это не одна “ошибка подключения”, а несколько разных классов проблем, и они выглядят по-разному в логах. UnknownHostException почти всегда говорит про неправильный host. Connection refused часто про порт или недоступность. password authentication failed — это уже не сеть, это аутентификация. И именно поэтому мы читаем логи вместе: приложение сообщает, что оно пыталось сделать, база подтверждает (или отрицает) факт попытки.

5. HTTP smoke-check: health и items

Даже если логи выглядят красиво, финальное доказательство для backend-сервиса — это HTTP-ответ. Это тот момент, когда можно перестать верить глазам и начать верить протоколу. Плюс это привычка, которая спасает в командах: вместо “у меня вроде всё поднялось” вы говорите “/actuator/health вернул UP, /api/catalog/items отдаёт список”. Звучит скучно, но скука — лучший друг инженера.

Минимальный набор проверок сегодня — два endpoint’а. Первый — операционный: /actuator/health. Второй — бизнесовый: /api/catalog/items.

Если вам удобны .http файлы (IDEA, VS Code plugins и т.д.), можно держать рядом такой фрагмент:

### catalog items
# Бизнесовый эндпоинт: проверяем, что работает "реальный" контроллер
GET http://localhost:8080/api/catalog/items

### health
# Операционный эндпоинт: быстрый сигнал живости приложения
GET http://localhost:8080/actuator/health

Если вы предпочитаете curl, то вот тот же смысл в терминале:

# Операционная проверка: должен вернуться JSON со статусом UP
curl http://localhost:8080/actuator/health
# {"status":"UP"}

# Бизнесовая проверка: должен вернуться JSON со списком элементов (структура зависит от вашего проекта)
curl http://localhost:8080/api/catalog/items
# ... JSON со списком элементов ...

Обратите внимание на адрес: с host-машины мы ходим на localhost:8080, потому что порт опубликован наружу (например, 8080:8080). Это другой “тип адреса”, чем тот, что использует приложение внутри Compose-сети. Внутри Compose приложение должно говорить с БД как postgres:5432. Снаружи вы говорите с приложением как localhost:8080. Это не противоречие, это два разных мира: “контейнер ↔ контейнер” и “хост ↔ контейнер”.

Почему именно эти два endpoint’а? /actuator/health хорош тем, что он короткий, быстрый и почти всегда показывает базовую живость приложения. /api/catalog/items хорош тем, что заставляет сервис пройти путь чуть дальше: это уже ваш “настоящий” контроллер, который трогает бизнесовую часть. Если /actuator/health UP, а /api/catalog/items падает, это часто подсказка: web-сервер поднялся, но внутри что-то не так (например, persistence слой не смог нормально стартовать). И снова: это не повод менять код “наугад”, это повод вернуться к логам и сопоставить симптомы.

6. Переводчик симптомов по логам

Когда что-то не работает, мозг новичка обычно делает две вещи. Во-первых, начинает хаотично менять настройки. Во-вторых, читает только одну сторону логов — обычно приложения — и пытается угадать, что думает PostgreSQL. Мы вместо этого соберём маленький “переводчик”: типовая проблема → что видно в логах приложения → что видно в логах базы → что обычно проверяют в compose.yaml.

Вот несколько самых частых сценариев для первого стенда app + postgres:

Ситуация Что обычно видно в app Что обычно видно в postgres Что чаще всего неверно в конфиге
В SPRING_DATASOURCE_URL указан localhost Connection refused или попытка подключиться к 127.0.0.1:5432 Часто тишина (подключения не доходят) Host в JDBC URL должен быть postgres, а не localhost
Перепутан порт (указали host-port вместо container-port) Connection refused на “не тот” порт Тишина или редкие следы Внутри Compose нужно ходить на 5432, не на опубликованный наружу порт
Неверный пользователь/пароль Ошибка аутентификации, часто с password authentication failed FATAL: password authentication failed for user ... Несовпадение SPRING_DATASOURCE_USERNAME/PASSWORD с POSTGRES_USER/PASSWORD
Неверное имя базы Ошибка вида database "...” does not exist Похожая FATAL: database "...” does not exist SPRING_DATASOURCE_URL собран с одним db, а PostgreSQL инициализирован с другим
Забыли включить профиль postgres В логах нет postgres среди активных профилей; приложение может “успешно” стартовать PostgreSQL стартует нормально, но к ней никто не подключается Нет SPRING_PROFILES_ACTIVE=postgres (или оно переопределено)

Заметьте, насколько “симптомы” разные. Если Postgres пишет “password authentication failed”, то сеть, адресация и порты скорее всего уже нормальные: приложение до базы дошло. Если Postgres молчит, а приложение кричит про connection refused — проблема часто на уровне host/port/service name. Это очень полезная логика: вы не лечите всё одним лекарством, вы по симптомам определяете класс проблемы.

И ещё один практический вывод, который редко проговаривают вслух. Если вы видите ошибку, не бегите сразу редактировать Java-код. В нашем проекте переход на PostgreSQL — это конфигурация, а не переписывание контроллеров. Поэтому первый кандидат на исправление почти всегда живёт в compose.yaml (или .env), а не в src/main/java.

7. Типичные ошибки

На этом этапе вы уже знаете, как выглядит “хороший” сценарий: подняли стек, посмотрели два лога вместе, убедились в профиле postgres, увидели, что база готова, и проверили два HTTP endpoint’а. Теперь полезно закрепить самые частые промахи — не как “стыдно”, а как “нормально, все через это проходят”. Эти ошибки как раз и отличают хаотичную отладку от спокойной.

Ошибка №1: считать, что docker compose up --build — это проверка.
up — это запуск, а не доказательство работоспособности. Он может завершиться красивыми строками, а через секунду приложение упадёт из-за конфигурации. Дисциплина здесь простая: запуск → логи → HTTP. Если пропускать середину и конец, вы постоянно будете “верить в удачу”.

Ошибка №2: смотреть только логи приложения и игнорировать логи PostgreSQL.
Когда приложение пишет “не подключился”, это как человек, который говорит “мне не ответили”. Возможно, вы правда не дозвонились. А возможно, вы дозвонились, но вас вежливо послали из-за пароля. PostgreSQL почти всегда оставляет честный след: отказ по паролю, отсутствие базы, или наоборот подтверждение готовности. Читайте обе стороны, иначе диагностика превращается в угадайку.

Ошибка №3: путать адреса и порты «для хоста» и «для контейнеров».
Host-port нужен вам, чтобы открыть psql или дернуть API с localhost. Контейнерный порт нужен контейнеру app, чтобы достучаться до контейнера postgres. Если в JDBC URL вы случайно используете опубликованный наружу порт вместо внутреннего 5432, вы строите схему “контейнер ходит в хост”, что в Compose почти всегда лишняя и хрупкая петля.

Ошибка №4: смешивать переменные POSTGRES_* и SPRING_DATASOURCE_* в одну кашу.
POSTGRES_DB/USER/PASSWORD — это то, как инициализируется PostgreSQL. SPRING_DATASOURCE_URL/USERNAME/PASSWORD — это то, как приложение подключается к уже запущенной базе. Да, значения часто совпадают, но смысл разный. Когда смысл смешан, вы начинаете “чинить” не ту сторону.

Ошибка №5: не проверять, какой профиль реально активировался.
Приложение может стартовать и отвечать по HTTP даже без PostgreSQL — например, в standalone режиме. И тогда возникает коварная ситуация: “API работает, значит postgres подключился”… а на самом деле база живёт отдельно, в гордом одиночестве. Поэтому строка про активные профили в логах — это ваш быстрый способ не обмануть самого себя.

1
Задача
Docker for Spring, 16 уровень, 4 лекция
Недоступна
Совместные логи и smoke-check рабочего стека
Совместные логи и smoke-check рабочего стека
1
Задача
Docker for Spring, 16 уровень, 4 лекция
Недоступна
Ошибка пароля и два журнала одного сбоя
Ошибка пароля и два журнала одного сбоя
1
Опрос
Docker Compose, 16 уровень, 4 лекция
Недоступен
Docker Compose
Связь приложения и БД
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ