03. Локальный стек
Как поднять локальное окружение для работы над конкретным сервисом и как прогонять кросс-сервисные сценарии.
Подход
Каждый сервис-репо имеет свой docker-compose.yml, который поднимает
его собственную инфру (postgres + redis + kafka + сам сервис). Когда ты
работаешь над сервисом — ты работаешь с одним компоузом.
Для тестов кросс-сервисных сценариев со всеми сервисами одновременно
будет отдельный full-stack compose в infra-репозитории (TBD). Пока его
нет — поднимай per-service compose’ы рядом, при необходимости объединяй
через host.docker.internal (подробности ниже).
Запуск
Первый раз:
cd ~/projects/<service>
make bootstrapmake bootstrap:
- Копирует
.env.exampleв.env, если.envотсутствует. - Скачивает Go-модули (
go mod download && go mod tidy). docker compose up -d— поднимает инфру и сам сервис.- Ждёт healthcheck’и postgres/redis/kafka.
- Применяет миграции (либо через встроенный runner в
main.go, либо через отдельный migrate-контейнер — зависит от сервиса). - Сервис стартует и начинает слушать порт.
Последующие запуски:
make up # docker compose up -d без rebuild, если образ уже собранRebuild после правок кода:
make rebuildПорты
Текущие порты сервисов (см. docker-compose.yml и internal/config/
каждого репо):
| Сервис | HTTP | Метрики |
|---|---|---|
| user | 8001 | через /metrics на 8001 |
| review | 8007 | через /metrics на 8007 |
| media | 8008 | через /metrics на 8008 |
| notification | 8013 | через /metrics на 8013 |
Общие инфра-порты, пробрасываемые наружу контейнера. Это host-порты
по умолчанию; конкретное значение фиксируется в docker-compose.yml
сервиса и может быть переопределено через .env. Внутри compose-сети
контейнеры всё равно общаются по именам (postgres:5432,
kafka:9092) независимо от host-проброса.
| Компонент | Host-порт (default) | Переопределяется в .env |
|---|---|---|
| Postgres | 5432 | <SVC>_DB_HOST_PORT (например, USER_DB_HOST_PORT=3431) |
| Redis | 6379 | <SVC>_REDIS_HOST_PORT |
| Kafka (external) | 9092 | <SVC>_KAFKA_HOST_PORT |
| Kafka UI | 8090 | — |
| MinIO (media) | 9000 (API), 9001 (console) | — |
| Prometheus | 9090 | — |
| Grafana | 3000 | — |
| Loki | 3100 | — |
| Tempo | 3200 | — |
Если у тебя локально уже занят 5432 (системный Postgres) или запускаешь
несколько сервис-compose’ов одновременно — переопредели host-порт
через .env перед make up, проброс внутри контейнера не меняется.
Сверяйся с docker-compose.yml своего сервиса: именно он — источник
истины, какой host-порт открыт именно у тебя.
Проверка здоровья
curl -sf http://localhost:<port>/healthz
# {"status":"ok"}
curl -sf http://localhost:<port>/readyz
# {"status":"ready"}/healthz — liveness, отвечает 200, пока процесс жив.
/readyz — readiness, проверяет pool/redis/kafka. Если вернул 503 —
какая-то зависимость не отвечает, смотри логи.
Postgres
Открыть psql внутри контейнера:
make psql(Реализовано как docker compose exec postgres psql -U <user> -d <db>.)
С машины (host-порт смотри в docker-compose.yml своего сервиса /
в .env; 5432 — дефолт, но может отличаться):
psql -h localhost -p "${DB_HOST_PORT:-5432}" -U <service>_user -d <service>_dbУ каждого сервиса — своя отдельная БД (user_db, review_db,
media_db, notification_db). Локально допустимо поднимать их в одном
Postgres-инстансе (в рамках одного docker-compose.yml), в проде
каждая БД живёт в своём инстансе. Общей БД (типа kazmaps) со
схемами-per-service у нас нет.
Пароль — из .env (локальный dev).
Полезные команды внутри psql (таблицы сервиса живут в схеме public
его собственной БД, без префикса с именем сервиса):
\dn — список схем
\dt — таблицы текущей БД (в схеме public)
\d <table> — структура таблицы
SELECT * FROM outbox WHERE offset_acked IS NULL;Kafka
Быстрый просмотр — redpanda/kafdrop UI в compose (если подключён):
http://localhost:8090Либо через CLI:
docker compose exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --list
docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic kazmaps.review.review \
--from-beginningЕсли в Makefile есть make kafka-console — используй его.
Redis
make redis-cli # внутри контейнера
redis-cli -h localhost -p 6379 # с машиныПолезные команды:
KEYS idempotency:*
TTL rl:login:127.0.0.1
MONITOR # поток всех команд (выключи Ctrl+C)MinIO (media-сервис)
- API:
http://localhost:9000(S3-совместимый endpoint для SDK). - Console:
http://localhost:9001(веб-UI).
Логин/пароль — из .env (MINIO_ROOT_USER/MINIO_ROOT_PASSWORD).
Логи
Follow логи самого сервиса:
make logsДругие контейнеры:
docker compose logs -f postgres
docker compose logs -f kafka
docker compose logs -f redisВсе контейнеры одновременно:
docker compose logs -fОстановка
make down # stop, volumes сохраняются — данные на месте
make clean # down -v, volumes удаляются — чистый стартVolumes
Postgres-данные живут в named volume <service>_postgres_data (точное
имя зависит от compose project). make down их не удаляет — в
следующий раз поднимется та же БД с теми же записями.
Чистый старт:
make clean # или
docker compose down -v && docker compose up -dПосле make clean Postgres стартует пустым, миграции применяются заново.
Troubleshooting
Порт занят
Error: ports are not available: 0.0.0.0:5432 -> listen tcp 0.0.0.0:5432: bind: address already in useНайти, кто держит порт:
- macOS / Linux:
lsof -i :5432 - Windows:
netstat -ano | findstr 5432
Обычно это уже запущенный Postgres другого сервиса (или системный
Postgres на 5432). Либо останови его (make down в том проекте),
либо переопредели host-порт в .env для текущего compose’а
(например, <SVC>_DB_HOST_PORT=5433) и сделай make rebuild.
Миграции не применяются
См. ../troubleshooting/migration-fails.
Healthcheck висит
Смотри docker compose ps — кто unhealthy? Обычно Kafka дольше всех
поднимается (до 30 секунд в первый раз).
«Connection refused» от сервиса в pool/redis
Сервис стартанул раньше инфры. Должен быть depends_on: condition: service_healthy в compose; если отсутствует — добавь и сделай make rebuild.
Cross-service сценарии
Два варианта, когда нужно проверить «сервис A зовёт сервис B»:
Вариант 1: оба compose’а рядом
# терминал 1
cd ~/projects/user && make up
# терминал 2
cd ~/projects/review && make upЧтобы review дозвонился до user внутри контейнера, используй
host.docker.internal:8001 (на macOS/Windows) или 172.17.0.1:8001 (на
Linux) в env-переменной USER_SERVICE_URL:
USER_SERVICE_URL=http://host.docker.internal:8001 make upПортовые конфликты разруливай через .env per-compose.
Вариант 2: mock downstream в integration-тесте
Когда нужен не живой сервис, а просто детерминированный ответ — подними
httptest.NewServer в тесте и подставь его URL в конфиг тестируемого
сервиса. Подробнее — ../conventions/testing.
Переменные окружения
.env— не коммитится, живёт локально. Сюда ты кладёшь свои dev-значения..env.example— коммитится, содержит placeholder’ы. При смене переменных вinternal/config/обновляй оба: пример и документацию вREADME.mdсервиса.- docker-compose читает
.envавтоматически (с корня compose-файла).
Проверь, что всё ок
cd ~/projects/<service>
make bootstrap
curl -sf http://localhost:<port>/healthz
curl -sf http://localhost:<port>/readyz
make logs # смотри, что сервис пишет "starting ..." и без ERRORЕсли до этого момента всё проходит — инфра поднята корректно. Переходи к
02-first-pr, если ещё не.