Foto arsip docker inspect pada Chapter Docker Inspect membeku keadaan pada satu momen: State.Running bernilai running, batas memori tercatat 268435456 byte — tetapi berapa banyak CPU dan memori yang benar-benar dipakai proses node di dalamnya saat ini, tidak pernah dijawab arsip itu. Siti sedang menjalankan ulang laporan rekap di backend setelah membetulkan bug yang kemarin kita baca jejaknya di log, dan pertanyaan “berapa berat toko-api bekerja sekarang” butuh jawaban yang berdenyut. Chapter ini membuka jendela angka hidup tersebut lewat docker stats — delapan kolom yang menyegar tiap detik, dibaca tanpa memasang satu alat pun ke dalam container.

Kamera yang Dipasang dari Luar #

Sistem monitoring klasik menuntut agen — program kecil yang dipasang di setiap mesin yang ingin dipantau. docker stats tidak memakai pola itu, dan alurnya layak dipahami sebelum kita membaca satu angka pun. Kernel Linux telah menghitung sendiri pemakaian setiap container lewat cgroups — penghitung CPU, memori, blok, dan jumlah proses tertulis di berkas-berkas cgroup milik masing-masing container, dan dialah yang menegakkan batas --memory yang kita pasang di Chapter CPU dan Memory Limit. Daemon Docker tinggal membaca berkas-berkas itu dari luar, ditambah penghitung antarmuka jaringan milik network namespace setiap container, lalu menyajikannya sebagai tabel. Konsekuensinya luas: image apa pun — alpine ramping, distroless, bahkan scratch — otomatis terpantau penuh tanpa satu paket monitoring di dalamnya, dan biaya pengukurannya nyaris nol karena kernel memang sudah menulis angka-angka itu untuk kepentingannya sendiri.

Aliran Langsung Delapan Kolom #

Perintah telanjang docker stats membuka aliran langsung — tabel yang disegarkan berkala selama terminal kita terbuka:

bash
$ docker stats

Output yang Dihasilkan (baris toko-frontend-1, toko-cache-1, dan toko-db-1 dipotong agar tabel tetap muat):

text
CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O      PIDS
7389c56ce533   toko-proxy-1     0.07%     5.312MiB / 7.766GiB   0.07%     1.46MB / 2.31MB   12.6MB / 0B    3
20269502f039   toko-backend-1   152.40%   96.84MiB / 256MiB     37.83%    9.87MB / 11.2MB   3.14MB / 0B    11
...

Mari kita bedah maknanya kolom demi kolom.

CONTAINER ID dan NAME #

Dua kolom pembuka adalah identitas baris: bentuk pendek ID 12 heksadesimal dan nama container, persis pasangan yang kita kenal dari docker ps. Tanpa argumen, docker stats memantau seluruh container yang sedang berjalan — sama seperti docker ps tanpa -a.

CPU % #

Persentase pemakaian CPU terhadap satu core penuh, dan di sinilah angka paling sering mengejutkan pendatang baru: 152.40% pada toko-backend-1 bukan galat tampilan. Node berjalan sebagai satu proses, tetapi event loop beserta thread pool-nya dijadwalkan lintas core; 152,40 persen berarti kira-kira satu setengah core bekerja penuh untuk rekap Siti. Mesin latihan kita punya 8 core, sehingga plafon teoretis kolom ini adalah 800% — angka di atas 100% adalah keadaan yang sepenuhnya sah pada mesin multi-core, dan makin dekat angka ke 800%, makin sibuk host kita secara keseluruhan.

MEM USAGE / LIMIT dan MEM % #

Pasangan kolom ini membandingkan pemakaian memori saat ini dengan langit-langitnya, lalu menyimpulkannya sebagai persen. Dua baris kita sengaja memperagakan dua wajah kolom LIMIT. Baris toko-proxy-1 menampilkan 7.766GiB — bukan karena nginx diberi batas sebesar itu, melainkan karena container tanpa --memory tidak punya pagar cgroup sama sekali, dan satu-satunya langit-langit yang tersisa adalah seluruh RAM host. Baris toko-backend-1 menampilkan 256MiB — pagar 256 MiB yang tim pasang pada service backend setelah kejadian rekap yang tumbang OOM di Chapter Debugging Container; pemakaian 96,84 MiB berarti proses laporan masih menyisakan ruang aman sekitar 62 persen.

Pagar cgroup juga bisa dipasang kilat untuk melihat wujudnya di tabel:

bash
$ docker run -d --name uji-mem -m 256m alpine:3.20 sleep 600

Output yang Dihasilkan:

text
4e843c9cd556a898b7b02786b81231f4a79fc900a7f608576da4bfb83f6d4bb8
bash
$ docker stats --no-stream uji-mem

Output yang Dihasilkan:

text
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O   PIDS
4e843c9cd556   uji-mem   0.00%     1.109MiB / 256MiB     0.43%     0B / 0B     0B / 0B     1

Flag -m 256m menulis 268435456 byte ke cgroup — angka yang sama yang kita baca lewat {{.HostConfig.Memory}} di chapter sebelumnya — dan kolom LIMIT langsung menyajikannya sebagai 256MiB. Container tidur ini hanya memakai 1,109 MiB, cukup untuk hari ini; kita bereskan bekasnya:

bash
$ docker rm -f uji-mem

Output yang Dihasilkan:

text
uji-mem

NET I/O dan BLOCK I/O #

Dua kolom berpasangan ini memuat lalu lintas jaringan dan perangkat blok dalam format kumulatif sejak container menyala — angka total yang terus bertambah, bukan laju sesaat. Tanda pemisahnya adalah arah: NET I/O membaca terima / kirim, demikian pula BLOCK I/O untuk baca / tulis. Backend menunjukkan 9.87MB / 11.2MB — wajar untuk laporan rekap yang menarik ribuan baris dari db lalu menuliskan hasilnya; proxy menunjukkan 12.6MB / 0B — banyak membaca berkas statis frontend, nyaris tidak menulis apa pun ke disk. Konsekuensi kumulatifnya penting dicatat: angka yang besar tidak selalu berarti container sedang sibuk — ia bisa sekadar berumur panjang; penghitung baru kembali nol ketika container dinyalakan ulang.

PIDS #

Kolom terakhir menghitung jumlah proses — lebih tepatnya task, termasuk thread — yang hidup di dalam cgroup container. Proxy menampung 3: master nginx beserta dua worker-nya. Backend menampung 11: satu proses node beserta thread pool-nya. Kolom mungil ini adalah pintu menuju kelas kecelakaan yang khas: fork bomb di dalam container bisa meledakkan jumlah task hingga host tumbang, dan cgroups menyediakan pagarnya — pembatasan --pids-limit yang kita bedah nanti pada Chapter PIDs Limit dan Ulimit di Section Manajemen Resource.

Batas Aplikasi Bukan Batas Cgroup #

Satu kebingungan paling umum saat membaca kolom MEM layak diberikan panggung sendiri. Mari kita periksa cache yang kita kenal:

bash
$ docker stats --no-stream toko-cache-1

Output yang Dihasilkan:

text
CONTAINER ID   NAME           CPU %     MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O   PIDS
69bebb2487a6   toko-cache-1   0.19%     9.84MiB / 7.766GiB    0.13%     1.02MB / 0B   0B / 0B     4

Padahal kita ingat betul konfigurasinya: service cache menjalankan redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru sejak Chapter Cache dan Queue. Mengapa LIMIT tetap menampilkan 7,766 GiB?

Potret Sekali dan Flag Lainnya #

Aliran langsung sempurna untuk mata manusia, tetapi skrip dan cron butuh potret sekejap yang selesai lalu keluar:

bash
$ docker stats --no-stream

Flag ini memotret satu bingkai lalu mengembalikan prompt — bentuk yang sama dengan yang kita pakai untuk uji-mem dan cache di atas. Seluruh flag pendukungnya dirangkum di tabel berikut:

FlagFungsi
--no-streammemotret sekali lalu keluar, alih-alih mengalir terus
--all / -asertakan juga container yang sedang tidak berjalan
--formatsusun sendiri kolom dan isinya lewat Go template
argumen nama containerpantau hanya container yang disebut

Keluaran untuk Skrip dengan –format #

Ketika angka stats ingin diolah lebih lanjut — dicatat ke berkas, dikirim ke dashboard sederhana, atau dibandingkan antar waktu — Go template memangkas tabel menjadi persis yang kita butuhkan, memakai kosakata yang sama dengan --format milik docker inspect:

bash
$ docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}'

Output yang Dihasilkan (dipotong):

text
NAME             CPU %     MEM USAGE / LIMIT
toko-proxy-1     0.07%     5.312MiB / 7.766GiB
toko-backend-1   152.40%   96.84MiB / 256MiB
...

Kata table di depan template menginstruksikan CLI menyusun header secara otomatis, sementara \t memisahkan kolom. Tanpa kata itu, keluaran menjadi baris data polos — bentuk yang paling ramah bagi awk dan pipeline lain.

Satu Container dan Seluruh Project #

Dua pintu terakhir menyesuaikan cakupan pantauan. Pintu pertama: sebut nama containernya, dan aliran langsung menyempit pada satu baris:

bash
$ docker stats toko-backend-1

Output yang Dihasilkan:

text
CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O      PIDS
20269502f039   toko-backend-1   148.02%   97.11MiB / 256MiB     37.94%    10.1MB / 11.5MB  3.14MB / 0B    11

Perhatikan bahwa angkanya tidak lagi persis sama dengan tabel sebelumnya: 148.02% berbanding 152.40% — itulah sifat angka yang berdenyut; setiap penyegaran adalah pengukuran jendela waktu yang baru. Pintu kedua memantau dari sisi Compose:

bash
$ docker compose stats

Output yang Dihasilkan (dipotong):

text
NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O      PIDS
toko-proxy-1     0.07%     5.312MiB / 7.766GiB   0.07%     1.46MB / 2.31MB   12.6MB / 0B    3
toko-backend-1   152.40%   96.84MiB / 256MiB     37.83%    9.87MB / 11.2MB   3.14MB / 0B    11
...

Dua bedah untuk keluaran Compose itu: kolom NAME hadir tanpa CONTAINER ID — Compose memang memanggil penghuninya dengan nama service, bukan ID mentah; dan yang tampil hanya service milik project aktif, sehingga mesin yang menampung banyak project dari banyak orang tetap terbaca rapi tanpa penyaringan manual.

Kolom-kolom tadi menjawab pertanyaan “berapa banyak” dengan sangat baik — CPU, memori, jaringan, blok, jumlah proses, semuanya terukur dan terbarui. Yang tidak pernah dijawab adalah pertanyaan “apa yang barusan terjadi”: kapan cache terakhir dibunuh, siapa yang memulai ulang worker tadi malam, dan kejadian apa persisnya yang mendahului lonjakan CPU itu. Jawaban atas kelas pertanyaan itu hidup di kronik daemon — aliran kejadian yang bertitel docker events, dan itulah chapter berikutnya.

Referensi #