Отладка: логи, exec, healthcheck, размер образа
Отладка: логи, exec, healthcheck, размер образа
Когда контейнер не работает, алгоритм простой: логи → внутрь → проверка сети/переменных.
Логи и exec
docker compose logs -f backend
docker compose exec backend sh
env | sort
Healthcheck: HTTP / TCP / exec
# HTTP - для веб-сервисов с /health endpoint
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30s # grace-период на старт (миграции, прогрев кэша)
# TCP - для БД/брокеров, где нет HTTP-эндпоинта
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d app || exit 1"]
interval: 5s
timeout: 5s
retries: 10
# exec - кастомный скрипт, если сервис экзотический
healthcheck:
test: ["CMD", "/app/healthcheck.sh"]
Тип Когда выбирать Подводный камень
──── ────────────────────────────────── ─────────────────────────────────
HTTP обычный REST/gRPC-сервис /health должен быть «лёгким» -
не дёргать БД на каждый probe
TCP БД, очереди, кеши «порт открыт» ≠ «сервис готов»;
пиши проверку через клиент сервиса
exec нестандартные сервисы скрипт должен работать в образе
(нет `bash` в scratch/distroless!)
# Зависимый сервис стартует только когда БД зелёная
services:
backend:
depends_on:
db:
condition: service_healthy
Ограничения docker logs и log rotation
docker compose logs -f --tail=100 backend # последние 100 строк + хвост
Стандартный драйвер json-file пишет stdout/stderr контейнера в файл /var/lib/docker/containers/<id>/<id>-json.log. Без настройки этот файл растёт бесконечно - на проде типичный аварийный сценарий: за месяц диск забит логами одного болтливого сервиса.
services:
backend:
logging:
driver: "json-file"
options:
max-size: "10m" # ротация по 10MB
max-file: "5" # хранить максимум 5 файлов
Альтернативы - journald (системный journal), syslog, fluentd, gelf (Graylog). На проде логи обычно отправляют в централизованный сборщик, а локальный драйвер - только аварийный буфер.
Размер образа
docker images | head
docker history <image>
.dockerignore
.git
node_modules
dist
*.md
.env
.vscode
__pycache__
Без .dockerignore Docker копирует всё - включая .git (может быть сотни МБ).
Уменьшение размера образа
# Проверь размер
docker images | grep backend
# Сравни:
# golang:1.22 → ~800MB
# golang:1.22-alpine → ~250MB
# scratch → 0MB (только бинарник)
# distroless → ~20MB (минимум для Go)
Рецепт для Go:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /backend ./cmd/server
FROM gcr.io/distroless/static-debian12
COPY --from=builder /backend /backend
CMD ["/backend"]
scratch vs distroless vs alpine - что выбрать
База Размер Что внутри Когда выбирать
───────── ───── ──────────────────────── ────────────────────────────────
scratch 0 ничего (даже /tmp нет) статический бинарь Go без CGO,
нет шанса нужен shell в проде
distroless ~20MB libc, CA-certs, tzdata Go-сервисы, которым нужны HTTPS
(CA-сертификаты) и timezone
alpine ~5MB musl libc + sh + apk нужен shell для скрипта-обёртки,
база base curl/wget для дебага, CGO=1
debian-slim ~80MB debian минимум CGO с зависимостями glibc;
совместимость > размер
Debugging без shell (scratch / distroless)
В distroless и scratch нет sh, поэтому привычное docker exec backend sh упадёт. Что делать:
# Вариант 1: ephemeral debug container
docker run -it --rm \
--pid=container:my-backend \
--net=container:my-backend \
alpine sh
# Теперь у тебя alpine-shell в namespaces (PID, network) backend-контейнера.
# Видны его процессы, доступна его сеть.
# Вариант 2 (Kubernetes): kubectl debug
kubectl debug -it pod/backend --image=busybox --target=backend
# Вариант 3: «отладочный» tag образа со shell на тот же бинарь
FROM gcr.io/distroless/static-debian12:debug
# или
FROM busybox AS debug-runtime
distroless/static:debug включает busybox-shell - образ можно поднять для дебага, прод-образ оставить чистым.
docker stats и docker inspect для прода
docker stats --no-stream
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT NET I/O BLOCK I/O
# a1b2c3d4 backend 12.3% 120MiB / 512MiB 5MB / 2MB 1MB / 0B
Быстрый ответ на «кто жрёт ресурсы?». На проде это полезно при разовых инцидентах - для постоянного мониторинга всё равно нужны Prometheus + cadvisor.
docker inspect backend | jq '.[0].State'
# {
# "Status": "running",
# "Running": true,
# "ExitCode": 0,
# "Health": { "Status": "healthy", "FailingStreak": 0, "Log": [...] }
# }
docker inspect backend --format '{{.NetworkSettings.IPAddress}}'
docker inspect backend --format '{{json .Config.Env}}' # без хитростей светит секреты
Полезные точки в inspect: State.Health (история health-проб), Mounts (что и куда смонтировано), NetworkSettings.Networks (в каких сетях контейнер), HostConfig.RestartPolicy.
Чек-лист отладки контейнера
docker compose logs -f service- что пишет сервис?docker compose exec service sh- зайти внутрь, проверить env, файлыdocker compose ps- статус и порты всех сервисовdocker inspect container_id- полная информация (сеть, volumes, env)docker stats- CPU/RAM в реальном времени
Мини-задание
- Добавь
.dockerignore(node_modules, dist, .git, .env) - Собери образ, проверь размер через
docker images - Добавь healthcheck в Dockerfile или compose
- Зайди в работающий контейнер через
execи проверь переменные (env | sort)
Итог
- Логи - первое, на что смотришь.
exec- второе.inspect- когда совсем непонятно. .dockerignoreи multi-stage build - два главных способа уменьшить образ.- Healthcheck - не декорация: compose и оркестраторы используют его для перезапуска.
Типичная ошибка
Не добавлять .dockerignore и удивляться, почему образ весит 2GB. Или использовать golang:latest как runtime-образ вместо distroless или scratch.
Мини-практика (10 минут)
Возьми любой свой Dockerfile. Собери образ, запомни размер (docker images). Добавь .dockerignore. Пересобери - сравни размер. Если образ не multi-stage - сделай multi-stage (builder + runtime). Сравни ещё раз. Цель - увидеть разницу своими глазами.