Learning Platform
Глоссарий Troubleshooting
Урок 15.04 · 20 мин
Начальный
dockerdebugmonitoring

docker stats и top

После logs, exec, inspect остаётся ещё один важный класс debug-команд — про ресурсы: сколько контейнер ест CPU, RAM, сети, дисковых операций. Это docker stats и docker top. Они отвечают на вопросы «почему хост тормозит» и «какой процесс внутри контейнера съел всё».

top и htop — что показывают и как читать top, htop, btop: живой мониторинг процессов

docker stats

Базовый вызов:

docker stats

Откроется realtime-таблица:

CONTAINER ID   NAME          CPU %    MEM USAGE / LIMIT   MEM %   NET I/O          BLOCK I/O      PIDS
5fa3d8e9b1c0   pg            0.05%    34.2MiB / 7.7GiB    0.43%   12.3MB / 5.2MB   0B / 8.2MB     12
ad34e2f5c6d8   minio         0.12%    158MiB / 7.7GiB     2.00%   2.4MB / 1.1MB    0B / 234kB     22
7f3e1a2b3c4d   app           5.67%    412MiB / 512MiB     80.5%   45MB / 23MB      0B / 0B        8

Обновляется раз в секунду. Ctrl+C — выход.

Колонки:

  • CPU % — процент использования CPU. Важно: 100% = одно ядро. Если у тебя 8 ядер и контейнер показывает 400% — он жрёт 4 ядра.
  • MEM USAGE / LIMIT — текущая память / лимит. Лимит = --memory или дефолт «вся RAM хоста».
  • MEM % — процент от лимита. 80.5% — близко к OOM, время бить тревогу.
  • NET I/O — байты RX / TX по сети (с момента старта контейнера, не дельта).
  • BLOCK I/O — байты read / write на блочные устройства (диск).
  • PIDS — количество процессов/тредов внутри.

Один shot, не realtime:

docker stats --no-stream

Полезно в скриптах — выдаст текущий snapshot и сразу вернётся.


CPU % > 100% и почему это нормально

docker stats показывает CPU% по той же логике, что и top на Linux:

  • 100% = одно ядро полностью загружено.
  • 200% = два ядра.
  • 800% на 8-core хосте = вся CPU.

Это не баг, это nginx/Postgres/etc используют столько ядер, сколько могут (потоки разлетаются по доступным CPU). Если хочешь ограничить — --cpus при запуске:

docker run -d --cpus=2 --name app my-image
# CPU% будет максимум 200%

--cpus=2 означает «контейнер получает 2 ядра эквивалентно». Это cgroup CPU quota, не affinity (не привязывает к конкретным ядрам).

Чтение docker stats
CPU 200%2 ядра загружено на 100% каждое. На 8-core хосте это 25% общей CPU. Если без лимита — норма
CPU 800% на 8 coresВсе 8 ядер заняты этим контейнером. Соседние контейнеры будут страдать. Ставь --cpus лимит
MEM 80% от LIMITКонтейнер близок к OOM. Если LIMIT задан --memory 512m и MEM=410MiB — скоро SIGKILL
MEM 95% от LIMITOOM произойдёт в ближайшие минуты. ExitCode будет 137. Срочно: профайлинг или увеличение лимита

Кастомизация формата

docker stats поддерживает --format с Go-шаблоном — полезно для скриптов:

docker stats --no-stream --format "{{.Name}}: CPU {{.CPUPerc}}, MEM {{.MemPerc}}"

# pg: CPU 0.05%, MEM 0.43%
# minio: CPU 0.12%, MEM 2.00%
# app: CPU 5.67%, MEM 80.50%

Все доступные поля: {{.Container}}, {{.Name}}, {{.ID}}, {{.CPUPerc}}, {{.MemUsage}}, {{.MemPerc}}, {{.NetIO}}, {{.BlockIO}}, {{.PIDs}}.

Для мониторинга — пихаешь в файл, парсишь в Python, шлёшь в Prometheus.


docker top — процессы внутри

Если хочется увидеть что именно работает внутри контейнера — docker top:

docker top pg

Вывод:

USER     PID    PPID   C  STIME  TTY   TIME    CMD
postgres 12345  12300  0  09:42  ?     00:00:01 postgres
postgres 12356  12345  0  09:42  ?     00:00:00 postgres: checkpointer
postgres 12357  12345  0  09:42  ?     00:00:00 postgres: background writer
postgres 12358  12345  0  09:42  ?     00:00:00 postgres: walwriter
postgres 12359  12345  0  09:42  ?     00:00:00 postgres: autovacuum launcher
postgres 12360  12345  0  09:42  ?     00:00:00 postgres: logical replication launcher

Важно: PID здесь — это PID на хосте, не PID внутри контейнера. Внутри контейнера postgres имеет PID 1, но на хосте — 12345 (зависит от того, какой process IDs выдал ядро).

docker top под капотом запускает ps -ef для PID контейнера на хосте через ps --pid или просмотр /proc. Альтернатива — ps aux | grep postgres на хосте, но docker top фильтрует только процессы этого контейнера.

В отличие от docker exec ps, docker top работает даже если внутри контейнера нет ps (distroless, scratch). Запускается на хосте.

docker top pg aux
# С дополнительными опциями ps (USER, %CPU, %MEM, STAT, etc.)

docker stats vs docker top

docker statsdocker top
Что показываетагрегированные метрики (CPU, MEM, IO)список процессов
Realtimeда (обновляется каждую секунду)snapshot
Уровеньконтейнер целикомпроцессы внутри
Нужен shell внутри?нетнет (запускается на хосте)
Работает с distroless?дада

Они дополняющие. stats отвечает «что-то жрёт CPU», top — «вот этот конкретный процесс».


Как поймать OOM

Когда контейнер падает по OOM (out of memory), это происходит так:

  1. Процесс внутри запросил больше памяти, чем доступно (лимит контейнера или RAM хоста).
  2. Ядро (или Docker daemon) шлёт SIGKILL.
  3. Процесс умирает с кодом 137 (= 128 + 9).
  4. Если PID 1 умер — контейнер останавливается.
  5. Если restart policy = always/on-failure — контейнер перезапускается, цикл повторяется.

Признаки в docker stats: MEM% растёт к 100%, потом контейнер исчезает из списка (или скачет с 100% обратно к 0% при рестарте).

В docker inspect.State.OOMKilled = true, .State.ExitCode = 137.

В dmesg (на Linux-хосте, с sudo):

Out of memory: Killed process 12345 (python) total-vm:1024MB, anon-rss:768MB, file-rss:0KB, shmem-rss:0KB, UID:1000 pgtables:1556kB oom_score_adj:0

Это и есть точный момент OOM. Дата, PID, имя, сколько памяти жрал.

WARNING

Без --memory лимита OOM Killer убьёт любой процесс на хосте — может быть Postgres из соседнего контейнера, или sshd. Хост окажется в недоступном состоянии. Поэтому всегда ставь --memory лимит на контейнеры с непредсказуемой памятью (Python ETL, Java apps).


Реальные кейсы

«Контейнер тормозит, но я не знаю почему»

docker stats --no-stream

Видим:

NAME   CPU %    MEM USAGE / LIMIT
app    180%     2.1GiB / 4GiB

CPU 180% при 8-core хосте — это 2 ядра целиком, не катастрофа. Но MEM 2.1GiB, и если это Python ETL с pandas — возможно, грузит весь датасет в RAM. Решение: смотреть код или включить профайлер.

«Хост тормозит, я подозреваю Docker»

docker stats --no-stream | sort -k 4 -h -r | head

Топ-5 контейнеров по CPU. Сразу видно виновника.

«Контейнер ничего не делает, но висит»

docker top app

Видим внутри процесс с CMD sleep infinity. Это значит, что приложение упало, но контейнер с restart policy запустил entrypoint, который вместо приложения держит контейнер живым (плохой паттерн в каких-нибудь debug-Dockerfile). Лезем в docker logs, смотрим, почему main process не стартанул.

Sanity check: stats + top + logs
docker statsRealtime метрики: CPU/MEM/Net/Block IO. Видишь общую картину что жрёт ресурсы
docker topСписок процессов внутри. Если ожидаешь postgres но видишь sh — что-то пошло не по плану
docker logs --tail 50Сообщения процесса в момент проблемы — обычно там traceback или OOM message
docker inspect State + OOMKilledТочный exit code и OOM flag. Подтверждение гипотезы

Альтернативные инструменты

docker stats — realtime в терминале. Для долгого мониторинга есть лучше:

  • ctop (brew install ctop / GitHub bcicen/ctop) — TUI на базе stats, удобнее отображает данные.
  • cAdvisor (Google) — экспортирует метрики контейнеров в Prometheus. Стандарт для production-мониторинга.
  • Portainer — Web UI для Docker, показывает то же самое в браузере.
  • OrbStack / Docker Desktop — встроенный мониторинг в Mac-приложении.

Для команды на пять серверов — ставь cAdvisor + Prometheus + Grafana. Для одного dev-ноута — docker stats или ctop.


Попробуй сам

  1. Запусти Postgres, открой docker stats в одном терминале. В другом — docker exec pg pgbench -i -U postgres (или просто N коннектов). Посмотри, как меняется CPU/MEM/IO.
  2. Запусти docker top pg и посмотри, какие процессы крутятся внутри (postgres + куча worker-процессов).
  3. Запусти контейнер с искусственно ограниченной памятью и проверь OOM:
    docker run --rm --memory 64m python:3.13-slim python -c "x = ' ' * 100_000_000; print(len(x))"
    Контейнер должен умереть. docker ps -a -> exit 137. Если этого не произошло (например, на macOS Docker Desktop), попробуй увеличить размер строки.
  4. Сравни docker top pg (показывает PID на хосте) с docker exec pg ps aux (показывает PID внутри namespaces). Разница в PID — это user namespace в действии.
  5. Поставь ctop (если есть brew), открой — увидишь то же docker stats в красивом TUI.

Проверка знанийKnowledge check
Что значит CPU 400% в docker stats и стоит ли паниковать, и как отличить нормальную загрузку от проблемы?
ОтветAnswer
CPU% в docker stats работает по той же логике, что top на Linux: 100% = одно ядро полностью загружено. 400% значит, что контейнер использует 4 ядра одновременно (например, многопоточное приложение, или Postgres с несколькими worker-процессами на параллельный запрос). Это НЕ значит «400% от общей CPU хоста». На 8-core машине 400% — это 50% общей мощности; на 4-core — это вся CPU. Стоит ли паниковать — зависит от контекста: - 400% на 16-core хосте + контейнер занимается тяжёлой работой (ETL, batch processing, recompile) — норма. - 400% на 4-core хосте + соседние контейнеры тормозят — проблема, надо ставить --cpus лимит. - 400% когда приложение должно быть idle — баг (busy loop, бесконечный retry, забытый thread). Что делать: docker top <c> покажет, какие именно процессы внутри жрут CPU. Если это рабочая нагрузка — увеличить ресурсы или оптимизировать. Если фоновый процесс не должен крутиться — пересмотреть код. Лимит через --cpus=2 — компромисс, который заставляет приложение деградировать (медленнее), но не съедает весь хост.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. В docker stats для контейнера app показывает CPU 400%. Это бьёт тревогу или нет?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5