Logging #

Log adalah jendela pertama yang dibuka saat ada masalah di produksi. Di Kubernetes, logging lebih kompleks dari server tunggal: Pod bisa berjalan di node mana saja, bisa di-restart kapan saja, dan satu permintaan bisa menghasilkan log di puluhan Pod berbeda. Memahami arsitektur logging di Kubernetes dan menyiapkan pipeline yang tepat adalah bagian penting dari observability yang matang.

Arsitektur Logging di Kubernetes #

Tiga lapisan logging di Kubernetes:

Lapisan 1 — Application log:
  Aplikasi menulis log ke stdout/stderr
  Container runtime menangkap dan menyimpan di node

Lapisan 2 — Node-level log:
  kubelet dan container runtime menyimpan log di /var/log/containers/
  Format: <pod-name>_<namespace>_<container-name>-<container-id>.log

Lapisan 3 — Cluster-level log:
  Log aggregator (Fluentd/Fluent Bit) berjalan sebagai DaemonSet
  Mengambil log dari semua node dan kirim ke central storage
  (Elasticsearch, Loki, CloudWatch, dll)
# Melihat log Pod secara langsung
kubectl logs <pod-name> -n production

# Follow log secara real-time
kubectl logs -f <pod-name> -n production

# Log dari semua Pod dengan label tertentu (agregasi sederhana)
kubectl logs -l app=api -n production --max-log-requests=10

# Log container sebelumnya (setelah restart)
kubectl logs <pod-name> -n production --previous

# Log dengan timestamp
kubectl logs <pod-name> -n production --timestamps=true

# Batasi jumlah baris
kubectl logs <pod-name> -n production --tail=100

Structured Logging: Kenapa JSON #

Log berbentuk teks biasa sulit di-parse dan di-query. JSON memungkinkan filtering dan aggregation yang jauh lebih powerful.

Log teks biasa (tidak ideal):
  2024-01-15 14:23:45 ERROR Failed to connect to database after 3 retries
  2024-01-15 14:23:46 INFO Request completed user_id=12345 duration=234ms status=200

Log JSON (ideal untuk aggregation):
  {"time":"2024-01-15T14:23:45Z","level":"ERROR","msg":"Failed to connect to database","retries":3,"service":"api","pod":"api-abc123","namespace":"production"}
  {"time":"2024-01-15T14:23:46Z","level":"INFO","msg":"Request completed","user_id":12345,"duration_ms":234,"status":200,"method":"GET","path":"/api/users"}
# Python: menggunakan structlog
import structlog

log = structlog.get_logger()
log = log.bind(service="api", version="v2.1.0")

# Log dengan context
log.info("request_completed",
    user_id=user.id,
    duration_ms=elapsed,
    status_code=response.status_code,
    path=request.path)

log.error("database_connection_failed",
    retries=3,
    host=db_host,
    error=str(e))
// Go: menggunakan zerolog atau zap
import "go.uber.org/zap"

logger, _ := zap.NewProduction()

logger.Info("request_completed",
    zap.Int("user_id", userID),
    zap.Int64("duration_ms", elapsed.Milliseconds()),
    zap.Int("status_code", statusCode),
    zap.String("path", path),
)

Field Standar yang Harus Ada #

Untuk log yang berguna saat debugging di cluster:

{
  "time": "2024-01-15T14:23:46Z",    // ISO 8601, bukan epoch
  "level": "INFO",                    // DEBUG|INFO|WARN|ERROR|FATAL
  "msg": "request_completed",        // deskripsi singkat, snake_case
  "service": "api",                  // nama service
  "version": "v2.1.0",              // versi aplikasi
  "pod": "api-7d4b9c-abc12",         // nama Pod (dari Downward API)
  "namespace": "production",         // namespace (dari Downward API)
  "node": "worker-node-1",           // nama node (dari Downward API)
  "trace_id": "4bf92f3577b34da6",   // trace ID untuk distributed tracing
  "span_id": "00f067aa0ba902b7",    // span ID
  // ... field spesifik request
}
# Inject Pod metadata via Downward API untuk digunakan di log
env:
- name: POD_NAME
  valueFrom:
    fieldRef:
      fieldPath: metadata.name
- name: POD_NAMESPACE
  valueFrom:
    fieldRef:
      fieldPath: metadata.namespace
- name: NODE_NAME
  valueFrom:
    fieldRef:
      fieldPath: spec.nodeName

Stack EFK: Elasticsearch, Fluentd/Fluent Bit, Kibana #

EFK adalah stack logging Kubernetes yang paling umum digunakan:

Arsitektur EFK:

  [Pod A logs] [Pod B logs] [Pod C logs]
       │             │             │
       └─────────────┴─────────────┘
                     │
              Fluent Bit DaemonSet
              (berjalan di setiap node)
              → parse log
              → tambah metadata Pod
              → kirim ke Elasticsearch
                     │
              Elasticsearch
              (simpan dan index log)
                     │
              Kibana
              (UI untuk search dan visualisasi)
# Fluent Bit DaemonSet (konfigurasi sederhana)
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    spec:
      serviceAccountName: fluent-bit
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:2.2
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: config
          mountPath: /fluent-bit/etc/
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: config
        configMap:
          name: fluent-bit-config
# fluent-bit.conf
[INPUT]
    Name              tail
    Path              /var/log/containers/*.log
    Parser            cri        # atau docker tergantung runtime
    Tag               kube.*
    Refresh_Interval  5

[FILTER]
    Name                kubernetes
    Match               kube.*
    Kube_URL            https://kubernetes.default.svc:443
    Merge_Log           On       # parse JSON log ke field terpisah
    Keep_Log            Off

[OUTPUT]
    Name  es
    Match *
    Host  elasticsearch.logging.svc.cluster.local
    Port  9200
    Index kubernetes-logs
    Type  _doc

Stack Grafana Loki: Alternatif yang Lebih Ringan #

Loki adalah log aggregation yang dirancang khusus untuk Kubernetes, terinspirasi dari Prometheus. Ia tidak mengindeks konten log (seperti Elasticsearch), hanya label — sehingga jauh lebih hemat storage dan biaya.

Loki Architecture:

  Promtail DaemonSet → kirim log ke Loki → query via Grafana (LogQL)

  Label yang diindeks: {namespace="production", app="api", pod="api-abc"}
  Konten log: disimpan sebagai plaintext chunks, tidak diindeks
  → Query cepat berdasarkan label
  → Query konten (full-text search) lebih lambat tapi storage lebih hemat
# Contoh query LogQL di Grafana:
# Semua log ERROR dari service api di production
{namespace="production", app="api"} |= "ERROR"

# Parse JSON dan filter berdasarkan field
{namespace="production", app="api"}
  | json
  | level="ERROR"
  | duration_ms > 1000

# Rate log error per menit
rate({namespace="production", app="api"} |= "ERROR" [1m])

Retensi dan Manajemen Log #

Kebijakan retensi yang masuk akal:

  Hot storage (Elasticsearch/Loki di cluster):
    → 7-14 hari untuk akses cepat selama debugging
    → Setelah itu: archive atau hapus

  Warm storage (S3, GCS dengan lifecycle policy):
    → 30-90 hari untuk audit dan compliance
    → Biaya jauh lebih murah dari hot storage

  Cold storage (Glacier, Nearline):
    → 1-7 tahun tergantung compliance requirement
    → Untuk financial services: biasanya 7 tahun

Estimasi volume log:
  100 Pod × 1KB log/detik × 86400 detik/hari = ~8.6GB/hari raw
  Setelah kompresi (~10x): ~860MB/hari
  Per bulan: ~26GB compressed

Ringkasan #

  • Tulis ke stdout/stderr, bukan ke file — Kubernetes dan container runtime sudah menangani log dari stdout; file di dalam container hilang saat Pod restart dan sulit dikumpulkan.
  • JSON structured logging wajib untuk produksi — log teks biasa sulit di-filter dan di-aggregate; JSON memungkinkan query yang powerful di Elasticsearch atau Loki.
  • Sertakan trace_id di setiap log entry — satu request bisa menghasilkan log di puluhan Pod; trace_id menghubungkan semua log itu menjadi satu alur yang bisa di-trace.
  • Fluent Bit lebih ringan dari Fluentd — untuk Kubernetes, Fluent Bit (ditulis dalam C) butuh resource jauh lebih sedikit dibanding Fluentd (Ruby); pilihan default yang lebih baik.
  • Loki untuk biaya lebih rendah, Elasticsearch untuk search yang powerful — Loki tidak mengindeks konten sehingga storage lebih hemat; Elasticsearch full-text search lebih cepat tapi lebih mahal.
  • Downward API untuk inject Pod metadata ke log — nama Pod, namespace, dan node sangat berguna saat debugging di Kibana/Grafana tapi hanya tersedia jika diinject via Downward API.

← Sebelumnya: Anti-Pattern Security   Berikutnya: Metrics dan Prometheus →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact