📡 Obs · Zero→Hero
หน้าแรก/📈 2 · Metrics/Exporters & Instrumentation
📖 บทเรียน⏱ ~13 นาที

Exporters & Instrumentation

Prometheus จะ scrape ได้ ก็ต้องมีคนเปิด endpoint /metrics ให้ มี 2 วิธีที่ metric เกิดขึ้น:

  1. Exporter — โปรแกรมแยกที่แปลง metric จากระบบที่ ไม่รู้จัก Prometheus (Linux, MySQL, Redis) → ฟอร์แมตที่ scrape ได้
  2. Instrumentation — ฝัง library ใน โค้ดแอปเราเอง เพื่อ expose metric ที่เราสนใจ

Exporters — สำหรับของที่แก้โค้ดไม่ได้

ระบบสำเร็จรูป (OS, database) ไม่ได้พูดภาษา Prometheus เราเลยรัน exporter คู่กับมัน exporter จะอ่านสถานะ (จาก /proc, จาก query ฐานข้อมูล ฯลฯ) แล้วแปลเป็น metric ที่ /metrics

Exporter ดึง metric ของ ตัวอย่าง metric
node_exporter เครื่อง Linux (CPU/RAM/disk/net) node_cpu_seconds_total, node_memory_*
cAdvisor container (Docker/k8s) container_cpu_usage_seconds_total, container_memory_usage_bytes
postgres_exporter PostgreSQL pg_stat_*, connection, replication lag
redis_exporter Redis hit rate, memory, connected clients
blackbox_exporter probe ภายนอก (ping/HTTP/TCP) endpoint up? latency? cert หมดเมื่อไหร่?

node_exporter — ตัวที่ต้องรู้จัก

เป็น exporter ที่ใช้บ่อยสุด ให้ metric สำหรับ USE method (บทที่ 11) ครบ:

# CPU: counter วินาทีที่ CPU อยู่ในแต่ละ mode
node_cpu_seconds_total{cpu="0",mode="idle"}    123456.7
node_cpu_seconds_total{cpu="0",mode="user"}     8901.2

# Memory: gauge
node_memory_MemTotal_bytes      16_000_000_000
node_memory_MemAvailable_bytes   4_200_000_000

# Disk / Filesystem
node_filesystem_avail_bytes{mountpoint="/"}   50_000_000_000

node_cpu_seconds_total เป็น counter ต่อ mode คำนวณ CPU busy % ด้วยการดู "เวลาที่ ไม่ได้ idle": 100 - avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100

Instrumentation — วัด business logic ในโค้ดเอง

Exporter ให้ metric ระดับ infra แต่สิ่งที่มีค่าที่สุด (RED ของ endpoint, business metric) ต้องฝังในโค้ดเอง ด้วย client library ของภาษานั้น (Go, Java, Python, Node...)

ตัวอย่าง Node.js ด้วย prom-client — สร้าง RED metrics ครบ:

const express = require("express");
const client = require("prom-client");

const app = express();
const register = new client.Registry();
// เก็บ metric พื้นฐานของ process (CPU, memory, event loop) ให้อัตโนมัติ
client.collectDefaultMetrics({ register });

// R + E: Counter นับ request แยกตาม method/route/status
const httpRequests = new client.Counter({
  name: "http_requests_total",
  help: "Total HTTP requests",
  labelNames: ["method", "route", "status"],   // ⚠️ label ต้อง low-cardinality
  registers: [register],
});

// D: Histogram วัด latency (มี bucket สำหรับ p95/p99)
const httpDuration = new client.Histogram({
  name: "http_request_duration_seconds",
  help: "HTTP request duration",
  labelNames: ["method", "route"],
  buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 3, 5],   // วินาที
  registers: [register],
});

// middleware วัดทุก request
app.use((req, res, next) => {
  const end = httpDuration.startTimer({ method: req.method });
  res.on("finish", () => {
    const route = req.route?.path ?? "unknown";     // ใช้ route pattern ไม่ใช่ raw url!
    httpRequests.inc({ method: req.method, route, status: res.statusCode });
    end({ route });
  });
  next();
});

// endpoint ที่ Prometheus จะมา scrape
app.get("/metrics", async (req, res) => {
  res.set("Content-Type", register.contentType);
  res.end(await register.metrics());
});

app.listen(3000);

สังเกต route = req.route?.path ที่ใช้ route pattern (/users/:id) ไม่ใช่ raw URL (/users/12345) — ถ้าใช้ raw URL, label จะกลายเป็น high-cardinality (ทุก id = 1 series ใหม่) → cardinality explosion (บทที่ 13) นี่คือกับดักที่คนพลาดบ่อยที่สุดตอน instrument จริง

แนวทางตั้งชื่อ metric (naming convention)

Prometheus มี convention ที่ควรทำตามเพื่อให้อ่านง่ายและ query ถูก:

  • ใช้ snake_case: http_requests_total
  • Counter ลงท้าย _total
  • ใส่หน่วยในชื่อ base unit: _seconds, _bytes (ไม่ใช่ ms/MB)
  • นำหน้าด้วย namespace/subsystem: myapp_http_requests_total
✅ myapp_http_request_duration_seconds
✅ myapp_db_connections_active
❌ requestTime          (camelCase, ไม่มีหน่วย)
❌ latency_ms           (ควรเป็น seconds)

Push แทน pull: OpenTelemetry & OTLP

โลกกำลังขยับไปหา OpenTelemetry (บทที่ 41) ที่เป็นมาตรฐานกลาง instrument ครั้งเดียวได้ทั้ง metrics/logs/traces แล้ว export ไปหลายปลายทาง — แต่แนวคิด counter/gauge/histogram และเรื่อง cardinality ที่เรียนมายังใช้ได้เหมือนเดิมทุกอย่าง

สรุป

  • Exporter = แปลง metric จากระบบสำเร็จรูป (node_exporter สำหรับเครื่อง, cAdvisor สำหรับ container, *_exporter สำหรับ DB)
  • Instrumentation = ฝัง client library ในโค้ดเพื่อวัด RED + business metric เอง
  • ตอน instrument: ใช้ route pattern ไม่ใช่ raw URL เป็น label — กัน cardinality explosion
  • ตั้งชื่อตาม convention: snake_case, _total, หน่วย base (_seconds, _bytes)
  • อนาคตขยับไป OpenTelemetry แต่ concept เดิมยังใช้ได้

ต่อไป: เอา metric ที่ได้มาทำ dashboard ด้วย Grafana