Promtail: Log Collection Agent สำหรับ Loki

Promtail เป็น log collection agent ที่ Grafana Labs พัฒนาเพื่อเก็บ log จาก server หรือ container แล้วส่งต่อไปยัง Loki สำหรับจัดเก็บและค้นหา ด้วยโครงสร้างที่เน้น label discovery คล้าย Prometheus แต่ทำงานกับไฟล์ log แทน metric บทความนี้ครอบคลุมวิธีติดตั้ง, การกำหนด scrape target, pipeline แปลง log, relabeling และ best practice ที่ทีมใช้จริงเวลา deploy บน server หลากหลายประเภท

Promtail ทำอะไรและเหมาะกับกรณีไหน

หน้าที่หลักมี 3 อย่าง: ค้นหา log file ที่ต้องเก็บ (discovery), อ่านและตาม position (tail), และ push log ไป Loki พร้อม label agent ทำงานเป็น stateless process เบา กิน RAM ประมาณ 50-200 MB และสามารถ deploy เป็น sidecar, DaemonSet บน Kubernetes หรือ systemd service บน bare metal ก็ได้ จุดแข็งคือ integration กับ service discovery แบบ Prometheus — ดึง label ของ Kubernetes pod, Docker container, หรือ Consul service มาใส่ใน log stream อัตโนมัติ

กรณีที่ agent ตัวนี้ทำได้ดี: Kubernetes log collection (DaemonSet 1 pod/node), log file server แบบ traditional, systemd journal, และ Windows Event Log กรณีที่ควรใช้ Fluent Bit หรือ Vector แทน: pipeline ที่ต้องแปลง log ก่อนส่งออกหลาย destination (Loki + S3 + Kafka), หรือทีมที่มี standard log agent เดิมอยู่แล้ว

Architecture และ Data Flow

Promtail มี component ย่อย 3 ส่วน: Targets Discovery — หาว่าต้องอ่าน log จากที่ไหน (static paths, Kubernetes API, journal, syslog), Scraper + Pipeline — อ่าน log แล้ว parse, filter, เพิ่ม label ผ่าน stages, และ Client — push log ไป Loki ด้วย HTTP batch API พร้อม retry และ backpressure handling state ของ position file (positions.yaml) ถูกเก็บไว้ local เพื่อให้ start ใหม่แล้วอ่านต่อจากจุดเดิม ไม่อ่านซ้ำ

ตัวอย่าง Config พื้นฐาน

# promtail-config.yaml
server:
  http_listen_port: 9080

positions:
  filename: /var/lib/promtail/positions.yaml

clients:
  - url: http://loki.monitoring.svc:3100/loki/api/v1/push
    tenant_id: team-platform

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          host: web-01
          __path__: /var/log/*.log

  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx
          app: api
          __path__: /var/log/nginx/*.log
    pipeline_stages:
      - regex:
          expression: '^(?P<remote>[\w\.]+).*"(?P<method>\w+) (?P<path>\S+).*" (?P<status>\d+)'
      - labels:
          method:
          status:

ส่วน __path__ เป็น meta-label พิเศษที่บอกว่าต้องตาม log file ไหน รองรับ glob pattern และ symlink การตั้ง label เช่น job, host, app ควรมี cardinality ต่ำเพื่อไม่ให้ระเบิด index ของ Loki

Pipeline Stages — แปลง Log ก่อนส่ง

Pipeline คือชุด stage ที่รันตามลำดับ แต่ละ stage เปลี่ยน log line หรือ label ได้ stage ที่ใช้บ่อย:

  • regex / json / logfmt — parse structure ของ log line ไปเก็บใน extracted map เพื่อใช้ใน stage ถัดไป
  • timestamp — ดึง field timestamp จาก log มาใช้แทนเวลา ingest (สำคัญเวลา replay log เก่า)
  • labels — promote field ใน extracted map เป็น label (ระวัง cardinality)
  • output — เปลี่ยน content ของ log line (เช่น เอาเฉพาะ message, ตัด prefix)
  • match — branch pipeline ตามเงื่อนไข label หรือ selector
  • drop — ทิ้ง log ที่ไม่ต้องการส่ง (noise filter)
  • multiline — รวม log หลายบรรทัดเป็น entry เดียว (stack trace)

ตัวอย่าง Kubernetes DaemonSet Config

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    pipeline_stages:
      - cri: {}
    relabel_configs:
      - source_labels:
          - __meta_kubernetes_pod_node_name
        target_label: node
      - source_labels:
          - __meta_kubernetes_namespace
        target_label: namespace
      - source_labels:
          - __meta_kubernetes_pod_label_app
        target_label: app
      - source_labels:
          - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        target_label: __path__
        source_labels:
          - __meta_kubernetes_pod_uid
          - __meta_kubernetes_pod_container_name
        separator: /
        replacement: /var/log/pods/*$1/*.log

Config นี้ดึง label จาก Kubernetes pod meta แล้ว promote เป็น label log stream ด้วย relabel_configs ทำให้ Loki รู้ว่า log line ไหนมาจาก namespace, app, pod ใด — query ได้ทันที

Relabeling — ปรับ Label ก่อน Scrape

Relabel ทำงานบน meta-label (prefix __meta_) ที่ discovery ให้มา — เช่น namespace, pod name, container name, label ของ pod เทคนิคที่ใช้บ่อย: drop log ของบาง container (sidecar, istio-proxy), merge label หลายตัวเป็น label เดียว, rename label เพื่อให้ match กับ convention ของทีม

Action ที่ใช้ได้: replace, keep, drop, hashmod, labelmap, labeldrop, labelkeep — ตรงกันกับของ Prometheus แต่ทำงานบน log stream แทน metric target

Systemd Journal และ Syslog

สำหรับ server ที่ใช้ systemd ใช้ journal source ได้โดยตรง — ไม่ต้องตาม file path ตัวแทนอ่านจาก journal API แล้ว extract field เช่น _SYSTEMD_UNIT, _HOSTNAME, PRIORITY เป็น label นี่เป็นทางเลือกที่ดีสำหรับ log ของ service เช่น nginx.service, postgresql.service ที่ใช้ journal เป็นปลายทางมาตรฐาน

ส่วน syslog source รองรับ RFC5424 ผ่าน TCP/UDP port 1514 (default) เหมาะกับอุปกรณ์ network gear ที่ส่ง syslog ออกมา เช่น firewall, switch

Troubleshooting ที่พบบ่อย

  • Log ไม่ส่งไป Loki — ตรวจ endpoint ใน clients.url, network connectivity, tenant ID, และ /metrics ของ Promtail ดู promtail_sent_bytes_total
  • Position file หาย — start แล้วอ่าน log เก่าซ้ำทั้งหมด เหตุจาก volume ที่ mount ไม่ persistent — ต้อง bind path ของ positions.filename ไป volume
  • Label cardinality สูง — เช็ค loki_distributor_ingester_append_failures_total ถ้า active stream ต่อ tenant สูง ต้องลด label ที่ unique ต่อ request
  • Log ล่าช้า — ดู promtail_stream_lag_seconds ถ้าค่าสูง ตรวจว่า Loki push rate limit หรือ Promtail CPU ไม่พอ

สรุป

Agent ตัวนี้เหมาะกับทีมที่ตั้งต้น log aggregation บน Loki เพราะติดตั้งง่าย รู้จัก Kubernetes discovery ได้ดี และ pipeline stage เพียงพอสำหรับ parse log รูปแบบส่วนใหญ่ — ทั้ง JSON, Nginx access log, application log การวาง label design ให้ low cardinality และเข้าใจ relabel ตั้งแต่แรกช่วยให้ระบบ scale ได้ราบรื่น

สำหรับ workload ที่ต้องการ feature ขั้นสูง เช่น multiple output, advanced enrichment, หรือ buffer ขนาดใหญ่ ทีมควรพิจารณา Vector หรือ Fluent Bit แทน — ทั้งสองทำงานร่วมกับ Loki ได้เช่นกันและไม่ผูกกับ Grafana Labs ecosystem โดยตรง