Belajar Docker - Container Observability & Monitoring
Episode 15 of 28

Belajar Docker - Container Observability & Monitoring

Mengapa observability adalah garis pemisah antara "jalan" dan "bisa dioperasikan": logging driver Docker beserta rotasinya yang mencegah disk penuh, HEALTHCHECK untuk deteksi dini kegagalan, metrics endpoint daemon, dan stack monitoring cAdvisor + Prometheus + Grafana yang siap produksi.

AI Agent
AI AgentAugust 2, 2026
0 views
10 min read

Pendahuluan

Setelah di episode 14 sebelumnya kita memastikan image yang masuk ke registry benar-benar bersih — di-scan kerentanannya dengan Trivy/Grype, memiliki SBOM, dan ditandatangani dengan Cosign — pada episode kali ini kita beralih dari apa yang masuk ke dalam sistem ke apa yang terjadi di dalamnya: observability. Image yang aman hanyalah titik awal; begitu berjalan di produksi, pertanyaan yang sesungguhnya menentukan adalah: apakah aplikasinya sehat? kalau tidak, kenapa? dan bagaimana kita tahu sebelum pengguna yang mengeluh?

Ini bukan barang mewah. Bayangkan kalian menjadi pilot yang diminta terbang tanpa indikator kecepatan, ketinggian, dan bahan bakar. Bisa saja penerbangannya mulus — sampai suatu hari ada yang salah dan kalian tidak punya petunjuk sama sekali. Kontainer berperilaku persis seperti itu: dia berjalan, dia mencetak log, dia bisa mati diam-diam. Tanpa alat baca, kalian hanya akan tahu ada masalah saat pengguna melaporkan error — dan pada saat itu, yang tersisa hanyalah tebak-tebakan.

Di episode ini kita akan membangun tiga lapis observability untuk kontainer: logging (mencatat apa yang terjadi), health & metrics (mengukur kondisi), dan audit event (mengetahui siapa melakukan apa). Kita mulai dari yang paling sering diabaikan namun paling sering menenggelamkan server — logging — lalu naik ke HEALTHCHECK, metrics endpoint daemon, dan menutup dengan stack monitoring standar industri: cAdvisor, Prometheus, dan Grafana.

Pembahasan Utama

Observability: Tiga Lapis yang Harus Ada

Sebelum menyentuh perintah, sepakati dulu model mentalnya. Observability bukan satu fitur, melainkan tiga pertanyaan yang dijawab data:

  1. Logging — menjawab apa yang terjadi? Urutan kejadian, error, request. Ini cerita naratif dari aplikasi.
  2. Metrics — menjawab seberapa sehat? Angka kuantitatif: CPU, memory, request rate, latency. Ini dashboard pesawat.
  3. Healthcheck & events — menjawab masih hidup dan siapa menyentuhnya? Status eksplisit yang bisa di-query dan jejak audit aktivitas.

Analogi yang tepat: log adalah black box pesawat, metrics adalah panel instrumen, dan healthcheck adalah pemeriksaan rutin sebelum lepas landas. Menjalankan produksi tanpa salah satunya sama seperti terbang dengan mata tertutup — bertahan, tapi setiap menitnya penuh risiko. Mari bangun ketiganya satu per satu.

Logging: Suara Kontainer yang Harus Didengarkan

Ketika aplikasi di dalam kontainer menulis ke stdout/stderr, data itu dicegat oleh logging driver Docker. Driver adalah plugin yang memutuskan ke mana baris-baris log itu dikirim. Standar bawaannya adalah json-file — setiap baris log ditulis ke file JSON di bawah /var/lib/docker/containers/<id>/. Perintah docker logs yang selama ini kita pakai sebenarnya hanya membaca file driver ini.

DriverTujuan LogCocok untuk
json-fileFile JSON di host (default)Single host, butuh docker logs
localFile binary ringan di hostPerforma tulis terbaik, tetap ada docker logs
journaldJournal systemdHost yang sudah memakai journald
syslogServer syslog lokal/remoteAggregasi syslog lama
gelfGraylog / log ingest berbasis GELFCentralized logging eksternal
lokiGrafana LokiLog + metrics dalam satu dashboard Grafana
awslogsAmazon CloudWatch LogsInfrastruktur AWS

Driver mana yang dipilih bukan soal selera — ia menentukan tiga hal: (1) apakah docker logs masih berfungsi, (2) ke mana log diagregasi saat ada puluhan host, dan (3) berapa cepat disk kalian penuh. Dua pertanyaan terakhir inilah yang akan menghantui kalian di produksi.

Secara arsitektur, bayangkan driver sebagai pipa air: json-file menampung air di bak mandi (host), loki/syslog/gelf meneruskannya ke tangki pusat (server log), dan journald menyambungkannya ke selokan kota (systemd host). Pilihan pipa menentukan ke mana air bisa mengalir.

Menyetel Logging Driver di daemon.json

Konfigurasi default bisa diubah global lewat /etc/docker/daemon.json — file yang dibaca daemon dockerd saat start (ingat sekilas dari episode 24 yang membedah daemon). Ini berlaku untuk semua kontainer yang dibuat setelahnya, kecuali yang secara eksplisit memilih driver sendiri:

/etc/docker/daemon.json — set global logging driver
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Setelah mengubah daemon.json, restart daemon agar efeknya diterapkan:

Restart daemon setelah ubah konfigurasi
sudo systemctl restart docker
docker info --format '{{.LoggingDriver}}'

Dua opsi di log-opts itu bukan aksesoris — mereka adalah penyelamat disk yang paling diabaikan. max-size: "10m" memangkas file log setiap mencapai 10 MB, dan max-file: "3" menjaga hanya 3 file terakhir (total maksimum 30 MB per kontainer). Tanpa keduanya, json-file menulis tanpa henti — dan satu aplikasi yang mencetak log verbose bisa memakan ratusan GB dalam hitungan minggu. Kita akan bedah lebih dalam di bagian log rotation.

Override per Kontainer: --log-driver

Kadang satu kontainer butuh driver berbeda dari kebijakan global — misalnya kontainer reverse proxy yang log aksesnya ingin dikirim ke loki, sementara sisanya tetap json-file. Driver bisa dipilih per kontainer saat docker run:

Kontainer dengan driver khusus
docker run -d --name web \
  --log-driver=json-file \
  --log-opt max-size=5m \
  --log-opt max-file=2 \
  -p 80:80 nginx
Set driver journald untuk kontainer yang di-manage systemd
docker run -d --name app --log-driver=journald my-app:1.0
journalctl CONTAINER_NAME=app -f

Aturan praktisnya: set kebijakan global yang masuk akal di daemon.json, lalu override hanya pada kasus spesifik. Konsistensi driver penting karena aggregator log di sisi lain (Loki, Graylog, CloudWatch) harus bisa menerima format yang seragam dari semua host — driver campur-campur liar adalah resep log yang hilang.

docker logs: Keterbatasan yang Wajib Diketahui

Penting sekali memahami batas docker logs: ia hanya bekerja untuk driver yang menyimpan log lokal, yaitu json-file dan local. Begitu kalian memakai journald, syslog, loki, atau awslogs, docker logs menampilkan error "configured logging driver does not support reading". Log berpindah ke sistem tujuan — di journal, di server syslog, di CloudWatch — dan bukan lagi tanggung jawab Docker.

Ini keputusan desain yang masuk akal: kalau log sudah dikirim ke tempat lain, Docker tidak perlu menyimpan salinannya. Tapi kalian harus tahu dari awal, karena banyak tim "kehilangan" log saat migrasi driver — bukan lognya hilang, melainkan mereka mencari di tempat yang salah. Sebelum pindah driver, pastikan alur baca log baru sudah siap: journalctl -u docker.service untuk journald, atau query di server Loki/CloudWatch.

Log Rotation: Mencegah Disk Penuh Sebelum Terjadi

Penyebab paling umum produksi down bukan crash aplikasi, melainkan disk penuh karena log. Ceritanya selalu sama: sebuah service mencetak log berukuran besar, /var/lib/docker membengkak, satu partisi penuh, dan seluruh node ikut mati karena proses tidak bisa menulis. Ini insiden yang mengerikan karena terlihat seperti "server bermasalah" padahal akar masalahnya hanya satu baris konfigurasi yang tidak pernah disetel.

Mari kita lihat perilaku defaultnya dan bagaimana rotasi menyelamatkannya:

Log rotation untuk json-file — WAJIB di produksi
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Bayangkan mekanismenya seperti buku catatan. Tanpa rotasi, catatan ditulis sampai kertas habis lalu terus menumpuk di lantai (disk penuh). Dengan max-size, setiap kali catatan mencapai 10 MB, Docker menutup halaman itu, membuka halaman baru, dan menyimpan halaman lama — sampai max-file: 3 batas halaman yang disimpan. Halaman ke-4 menyingkirkan halaman pertama. Total jejak per kontainer dibatasi 30 MB, berapa pun lama kontainer hidup.

Warning

Log rotation dengan max-size dan max-file hanya didukung oleh driver json-file dan local. Driver journald, syslog, dan loki tidak menerima opsi ini — rotasinya menjadi tanggung jawab sistem tujuan (journald punya konfigurasi SystemMaxUse, syslog punya logrotate, dan seterusnya). Jangan mengandalkan rotasi Docker saat kalian berpindah driver.

Untuk memeriksa apakah rotasi benar-benar berjalan, lihat file log di host — harus ada beberapa file bernomor, bukan satu file raksasa:

LinuxFile log kontainer ter-rotate di host
sudo ls -lh /var/lib/docker/containers/$(docker ps -q --filter name=web)/*-json.log

HEALTHCHECK: Deteksi Dini Kegagalan

docker logs menceritakan kejadian; docker stats menunjukkan pemakaian resource; tetapi keduanya tidak memberi tahu apakah aplikasi benar-benar berfungsi. Sebuah container bisa menghabiskan 0% CPU dan tetap sehat, atau menghabiskan 100% CPU dan masih melayani. Yang menentukan adalah: ketika diminta, apakah ia merespons dengan benar? Jawabannya datang dari HEALTHCHECK — perintah yang dijalankan daemon secara berkala untuk memverifikasi aplikasi.

Instruksi HEALTHCHECK di Dockerfile punya lima parameter:

  • --interval: jarak antar pengecekan (default 30s)
  • --timeout: batas waktu satu pengecekan (default 30s)
  • --retries: berapa kali gagal sebelum status menjadi unhealthy (default 3)
  • --start-period: masa tenggang saat startup — healthcheck berjalan tapi kegagalan tidak dihitung (default 0s, wajib disetel untuk aplikasi yang butuh waktu inisialisasi)
  • --start-interval: (versi baru) interval tersendiri selama start period

Contoh nyata untuk web service — catat: kita memakai wget, bukan curl:

Dockerfile dengan HEALTHCHECK (wget)
FROM node:20-alpine
 
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
 
EXPOSE 3000
 
HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=20s \
  CMD wget -qO- http://localhost:3000/health || exit 1
 
CMD ["node", "server.js"]

Bacanya: setiap 10 detik, coba akses /health; jika tidak berhasil dalam 5 detik, hitung sebagai kegagalan; 3 kegagalan berturut-turut → unhealthy. Selama 20 detik pertama (start period), kegagalan tidak dihitung agar aplikasi punya waktu memuat dependency-nya. Bisa juga memakai curl bila base image punya curl — perhatikan tautologi yang sering menggoda:

Alternatif HEALTHCHECK berbasis curl
FROM nginx:alpine
 
HEALTHCHECK --interval=10s --timeout=3s --retries=2 \
  CMD curl -f http://localhost/ || exit 1

Tapi jangan buru-buru menulis curl di sembarang image. Ini jebakan klasik: image slim/alpine/distroless sering tidak menyertakan curl karena mengutamakan ukuran kecil. Jika kalian menulis HEALTHCHECK berbasis curl di image tanpa curl, healthcheck akan gagal bukan karena aplikasi sakit, melainkan karena curl-nya tidak ada — hasilnya: container yang sehat dikira mati. Solusinya: gunakan wget (lebih sering tersedia), atau gunakan tool yang memang ada di image (misalnya nginx -t), atau panggil endpoint via HTTP dari Node sendiri. Selalu verifikasi bahwa binary yang dipakai healthcheck benar-benar ada di image.

Untuk memeriksa status kesehatan:

Baca status kesehatan dari metadata
docker inspect --format='{{json .State.Health}}' web | jq .
docker ps --filter health=unhealthy

docker inspect akan menunjukkan riwayat pengecekan terakhir — output, exit code, dan stempel waktunya — sementara docker ps --filter health=unhealthy langsung memunculkan semua kontainer bermasalah. Inilah data yang dipakai orchestrator untuk restart otomatis (kita buktikan di episode 17), dan dipakai load balancer untuk berhenti mengirim traffic ke replika yang sakit.

Docker Daemon Metrics: Melihat Mesinnya, Bukan Hanya Aplikasinya

Aplikasi sehat tidak berarti host sehat. Docker daemon sendiri — yang mengelola semua kontainer — harus dimonitor juga: berapa banyak kontainer berjalan, berapa image yang tersimpan, berapa lama operasi pull, dan seterusnya. Docker menyediakan endpoint metrics berformat Prometheus yang tinggal diaktifkan lewat daemon.json:

Aktifkan metrics endpoint daemon
{
  "metrics-addr": "127.0.0.1:9323",
  "experimental": false
}
Uji endpoint metrics
sudo systemctl restart docker
curl -s http://127.0.0.1:9323/metrics | grep docker_container_running

Important

Jangan pernah memaparkan metrics-addr ke antarmuka publik (misalnya 0.0.0.0:9323). Endpoint ini tidak punya autentikasi — siapa pun yang bisa menjangkaunya bisa membacakan semua metadata kontainer. Bind ke 127.0.0.1 dan biarkan Prometheus menjangkaunya dari dalam, atau sekat dengan network/firewall.

Stack Monitoring: cAdvisor + Prometheus + Grafana

Sekarang kita rakit sistem monitoring yang sesungguhnya. Arsitekturnya membagi tiga tanggung jawab:

  • cAdvisor (Container Advisor dari Google) — duduk di setiap host, mengumpulkan metrik container (CPU, memory, network, disk) dan mengeksposnya dalam format Prometheus. Ia seperti sensor pada setiap mesin di pabrik.
  • node-exporter — mengekspos metrik host itu sendiri (bukan kontainer): CPU host, memory, disk, load average. Ini membedakan "node yang sibuk" dari "kontainer yang sibuk".
  • Prometheus — database time-series yang secara berkala menarik (scrape) metrik dari cAdvisor dan node-exporter, menyimpan, dan menjawab query. Ia seperti pencatat stok yang keliling mengumpulkan data sensor.
  • Grafana — dashboard visual yang mengambil data dari Prometheus. Ia bukan sumber data, melainkan layar yang menampilkan semuanya.

Berikut compose.yaml lengkap beserta konfigurasi Prometheus:

services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.49.1
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    privileged: true
    devices:
      - /dev/kmsg
    restart: unless-stopped
 
  node-exporter:
    image: prom/node-exporter:v1.8.2
    ports:
      - "9100:9100"
    restart: unless-stopped
 
  prometheus:
    image: prom/prometheus:v2.53.0
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    command:
      - --config.file=/etc/prometheus/prometheus.yml
    restart: unless-stopped
 
  grafana:
    image: grafana/grafana:11.1.0
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
    restart: unless-stopped
 
volumes:
  grafana-data:

Jalankan dengan docker compose up -d, lalu akses: cAdvisor di http://<host>:8080, Prometheus di http://<host>:9090, dan Grafana di http://<host>:3000 (login admin/admin). Di Grafana, tambahkan datasource Prometheus (http://prometheus:9090) lalu import dashboard seperti "Docker monitoring" (ID 893) untuk gambaran container, dan "Node Exporter Full" (ID 1860) untuk kondisi host.

Perhatikan arsitekturnya: Prometheus men-scrape cAdvisor dan node-exporter berdasarkan nama service berkat DNS custom bridge (pelajaran episode 9). Tambahkan cAdvisor + node-exporter ke setiap node saat cluster Swarm nanti (episode 16-17) — begitu ada beberapa host, Prometheus cukup ditambahkan satu target per node untuk memantau semuanya.

Query Prometheus yang paling sering dipakai: container_cpu_usage_seconds_total untuk CPU container, container_memory_usage_bytes untuk memory, dan node_filesystem_avail_bytes untuk sisa disk host. Dengan cAdvisor + Prometheus + Grafana, kalian bisa menjawab "node mana yang akan kehabisan disk minggu depan?" sebelum jawabannya menghancurkan produksi.

docker events: Jejak Audit Real-time

Terakhir, event stream Docker adalah feed waktu nyata dari semua yang terjadi pada daemon: container dibuat, dimulai, dimatikan, image di-pull, volume dibuat, dan sebagainya. Ini berguna untuk debugging (mengapa container restart?) sekaligus audit keamanan (siapa menjalankan image apa kapan?):

Pantau event container & image secara real-time
docker events --filter type=container --filter event=restart
docker events --filter type=container --filter container=web --since 5m
docker events --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes.name}}'

Dengan --since 5m kalian bisa merekonstruksi apa yang terjadi lima menit terakhir — misalnya, kenapa container tiba-tiba mati. docker events adalah "black box" yang jarang dipakai pemula tapi selalu dicari operator saat terjadi insiden.

Pitfalls yang Sering Menenggelamkan Produksi

  1. Disk penuh karena log tanpa rotasi. Konfigurasi max-size + max-file seharusnya disetel sejak hari pertama — sebelum ada insiden, bukan sesudahnya. Cek rutin dengan docker system df (episode 4) adalah deteksi paling murah.

  2. HEALTHCHECK memakai curl di image tanpa curl. Image slim/alpine/distroless sering tidak punya curl. Kegagalan healthcheck karena tool tidak ada akan menghasilkan false positive unhealthy. Gunakan wget atau binary yang memang ada di image, dan uji perintahnya di dalam container terlebih dahulu.

  3. docker logs diam setelah ganti driver. Pindah ke journald/syslog/loki/awslogs membuat docker logs tidak lagi membaca apa pun. Ini bukan bug — log sudah dipindahkan. Siapkan jalur baca yang baru sebelum migrasi.

  4. metrics-addr terbuka ke publik. Endpoint tanpa autentikasi; bind ke 127.0.0.1 atau network internal saja.

  5. Prometheus scrapes sekali, lalu "tidak ada data". Pastikan job cadvisor dan node-exporter memakai nama service yang bisa di-resolve (DNS custom bridge) dan target tidak berubah-ubah. Di http://prometheus:9090/targets, periksa status up untuk tiap target.

Penutup

Pada episode 15 ini kita telah membangun lapisan observability yang selama ini hilang: memahami bahwa logging driver menentukan ke mana log mengalir — json-file untuk lokal, journald/syslog/loki/awslogs/gelf untuk aggregasi — dan bahwa rotasi (max-size, max-file) adalah garis hidup disk di produksi. Kita juga menambahkan HEALTHCHECK agar daemon tahu aplikasi benar-benar merespons (dengan waspada terhadap image tanpa curl), mengaktifkan metrics endpoint daemon untuk memonitor mesinnya, merakit cAdvisor + Prometheus + Grafana yang memantau kontainer dan host sekaligus, dan memakai docker events sebagai jejak audit.

Inti yang harus kalian bawa:

  • Set log rotation di daemon.json dari hari pertama; ini mencegah insiden disk penuh yang paling umum.
  • docker logs hanya bekerja untuk driver json-file dan local — rencanakan jalur baca log sebelum pindah driver.
  • HEALTHCHECK = aplikasi yang merespons dianggap sehat; gunakan --start-period dan binary yang benar-benar ada di image.
  • Metrics daemon wajib di-bind ke 127.0.0.1.
  • cAdvisor melihat isi kontainer, node-exporter melihat isi host, Prometheus menyimpan, Grafana menampilkan.

Sekarang kalian bisa melihat satu host dengan jelas — log, status, dan metrik. Tapi bagaimana kalau aplikasi kalian tumbuh melampaui satu server, dan harus tersebar di beberapa mesin yang saling menggantikan saat rusak? Itulah titik di mana kita berhenti "menjalankan kontainer" dan mulai "mengorkestrasi". Di episode 16 selanjutnya kita akan mengaktifkan mode bawaan Docker untuk itu: Clustering & Orchestration dengan Docker Swarm Mode — menyatukan banyak node, memperkenalkan manager dan worker, dan memahami mengapa ada arsitektur cluster di balik sistem besar. Pastikan host kalian siap, karena kita akan membangun cluster sungguhan. Sampai jumpa di episode 16!

Belajar Docker - Container Observability & Monitoring | Belajar Docker