Grafana Loki Log Aggregation บน VPS

📅 5 ตุลาคม 2026 · 🏷️ Monitoring, Logging, Grafana

ติดตั้ง Grafana Loki บน VPS สำหรับ Log Aggregation

Grafana Loki คือระบบ log aggregation แบบ open-source ที่ออกแบบมาให้ประหยัด resource ต่างจาก ELK Stack ที่กิน RAM สูง Loki จะ index เฉพาะ label ของ log ไม่ใช่เนื้อหาทั้งหมด ทำให้สามารถรัน log aggregation บน VPS ขนาดเล็กได้อย่างสบาย

เมื่อจับคู่กับ Promtail (log shipper) และ Grafana (visualization) คุณจะได้ระบบ logging ครบวงจร — เก็บ log จากทุก service ค้นหาด้วย LogQL และสร้าง dashboard ได้ทันที

เปรียบเทียบ Loki vs ELK Stack

คุณสมบัติGrafana LokiELK Stack
RAM ขั้นต่ำ~200MB~2GB+
วิธี indexLabels เท่านั้นFull-text index
Query languageLogQLLucene / KQL
Integration กับ GrafanaNativeผ่าน plugin
ความยากติดตั้งง่ายซับซ้อน

สิ่งที่ต้องมีก่อนเริ่ม

ขั้นตอนที่ 1 — สร้างโครงสร้างไดเรกทอรี

# สร้างโฟลเดอร์หลัก
mkdir -p /opt/loki/{config,data}
mkdir -p /opt/promtail/config
cd /opt/loki

ขั้นตอนที่ 2 — เขียน Loki Config

cat > /opt/loki/config/loki-config.yml <<'EOF'
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

ingester:
  lifecycler:
    address: 127.0.0.1
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
    final_sleep: 0s
  chunk_idle_period: 1h
  max_chunk_age: 1h
  chunk_target_size: 1048576
  chunk_retain_period: 30s

schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /loki/boltdb-shipper-active
    cache_location: /loki/boltdb-shipper-cache
    cache_ttl: 24h
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks

compactor:
  working_directory: /loki/boltdb-shipper-compactor
  shared_store: filesystem

limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h
  retention_period: 30d

chunk_store_config:
  max_look_back_period: 0s

table_manager:
  retention_deletes_enabled: true
  retention_period: 720h
EOF

ขั้นตอนที่ 3 — เขียน Promtail Config

cat > /opt/promtail/config/promtail-config.yml <<'EOF'
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  # เก็บ log จาก systemd journal
  - job_name: journal
    journal:
      max_age: 12h
      labels:
        job: systemd-journal
        host: ${HOSTNAME}
    relabel_configs:
      - source_labels: ['__journal__systemd_unit']
        target_label: 'unit'

  # เก็บ log จาก Nginx
  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx
          host: ${HOSTNAME}
          __path__: /var/log/nginx/*.log

  # เก็บ log จาก Docker containers
  - job_name: docker
    static_configs:
      - targets:
          - localhost
        labels:
          job: docker
          host: ${HOSTNAME}
          __path__: /var/lib/docker/containers/*/*-json.log
    pipeline_stages:
      - json:
          expressions:
            output: log
            stream: stream
            attrs:
      - json:
          source: attrs
          expressions:
            tag:
      - regex:
          source: tag
          expression: '^.*/(?P[^/]+)/.*$'
      - labels:
          stream:
          container_name:
      - output:
          source: output
EOF

ขั้นตอนที่ 4 — สร้าง Docker Compose

cat > /opt/loki/docker-compose.yml <<'EOF'
version: "3.8"

networks:
  loki:
    driver: bridge

services:
  loki:
    image: grafana/loki:2.9.0
    container_name: loki
    restart: unless-stopped
    ports:
      - "127.0.0.1:3100:3100"
    volumes:
      - /opt/loki/config:/etc/loki
      - /opt/loki/data:/loki
    command: -config.file=/etc/loki/loki-config.yml
    networks:
      - loki

  promtail:
    image: grafana/promtail:2.9.0
    container_name: promtail
    restart: unless-stopped
    volumes:
      - /opt/promtail/config:/etc/promtail
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /run/log/journal:/run/log/journal:ro
      - /etc/machine-id:/etc/machine-id:ro
    command: -config.file=/etc/promtail/promtail-config.yml
    environment:
      - HOSTNAME=${HOSTNAME}
    networks:
      - loki

  grafana:
    image: grafana/grafana:10.2.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=changeme123
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-storage:/var/lib/grafana
    networks:
      - loki

volumes:
  grafana-storage:
EOF

ขั้นตอนที่ 5 — ตั้งค่า Permissions และ Start

# ตั้ง ownership ให้ Loki container (UID 10001)
chown -R 10001:10001 /opt/loki/data

# Start ทุก service
cd /opt/loki
docker compose up -d

# ตรวจสอบว่า container รันปกติ
docker compose ps
Tip: ถ้าใช้ Grafana อยู่แล้ว ให้ลบ service grafana ออกจาก docker-compose.yml และแก้ไข network ให้ Loki อยู่ใน network เดียวกับ Grafana ที่มีอยู่

ขั้นตอนที่ 6 — เพิ่ม Loki เป็น Data Source ใน Grafana

  1. เปิด Grafana ที่ http://YOUR_IP:3000 แล้ว login
  2. ไปที่ Configuration → Data Sources → Add data source
  3. เลือก Loki
  4. ใส่ URL: http://loki:3100 (ถ้าใช้ Docker network เดียวกัน)
  5. คลิก Save & Test — ควรขึ้น "Data source connected"

ขั้นตอนที่ 7 — Query Log ด้วย LogQL

LogQL คือ query language ของ Loki คล้าย PromQL ของ Prometheus ตัวอย่าง query:

# ดู log ทั้งหมดจาก Nginx
{job="nginx"}

# กรอง log ที่มีคำว่า "error"
{job="nginx"} |= "error"

# นับ error rate ต่อนาที
rate({job="nginx"} |= "error" [1m])

# ดู log จาก Docker container ชื่อ myapp
{job="docker", container_name="myapp"}

# กรอง HTTP 500 errors จาก access log
{job="nginx"} | pattern '<ip> - - [<time>] "<method> <path> <proto>" <status> <size>' | status = "500"

ขั้นตอนที่ 8 — สร้าง Dashboard ใน Grafana

  1. ไปที่ Dashboards → New → New Dashboard
  2. คลิก Add visualization
  3. เลือก Data source: Loki
  4. ใส่ query เช่น {job="nginx"} |= "error"
  5. เลือก visualization type: Logs หรือ Time series สำหรับ rate
  6. Save dashboard
Tip: Grafana มี dashboard สำเร็จรูปสำหรับ Loki ที่ grafana.com/dashboards ค้นหา "Loki" แล้ว import dashboard ID ได้เลย เช่น dashboard 13639 สำหรับ Linux logs

ตั้งค่า Nginx Reverse Proxy สำหรับ Grafana

cat > /etc/nginx/sites-available/grafana <<'EOF'
server {
    listen 80;
    server_name grafana.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF

ln -s /etc/nginx/sites-available/grafana /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

ตรวจสอบว่า Loki ทำงานถูกต้อง

# ตรวจ Loki health endpoint
curl http://localhost:3100/ready
# ควรตอบ: ready

# ตรวจ Loki metrics
curl http://localhost:3100/metrics | grep loki_ingester

# ดู log ของ Loki container
docker logs loki --tail 20

# ตรวจ Promtail ว่ากำลังส่ง log ไปถูก
curl http://localhost:9080/targets
# จะแสดง scrape targets ทั้งหมด

# ทดสอบ query ผ่าน API โดยตรง
curl -G -s "http://localhost:3100/loki/api/v1/labels" | python3 -m json.tool
Tip: ถ้า Loki ไม่รับ log ให้ตรวจสอบ Promtail log ด้วย docker logs promtail และตรวจว่า path ใน config ของ Promtail ตรงกับไฟล์ที่มีจริงบน host

แก้ปัญหาที่พบบ่อย

Error: "too many outstanding requests"

เกิดเมื่อ Loki รับ log เข้ามาเร็วเกินไป ให้เพิ่ม limit ใน loki-config.yml:

limits_config:
  ingestion_rate_mb: 16
  ingestion_burst_size_mb: 32
  per_stream_rate_limit: 3MB
  per_stream_rate_limit_burst: 15MB

Error: "entry out of order" ใน Promtail

เกิดเมื่อ timestamp ของ log entries ไม่เรียงลำดับ มักเกิดกับ log ที่มี timestamp แบบ custom ให้เพิ่ม max_chunk_age หรือแก้ไข pipeline_stages ให้ parse timestamp ถูกต้อง:

# ใน promtail config เพิ่ม pipeline stage สำหรับ parse timestamp
pipeline_stages:
  - regex:
      expression: '.*(?P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})'
  - timestamp:
      source: timestamp
      format: RFC3339

Error: "context deadline exceeded" เมื่อ query

Query ใช้เวลานานเกินไป มักเกิดเมื่อค้นหา log ช่วงเวลายาวๆ โดยไม่ใช้ label filter ก่อน ให้เพิ่ม label selector เสมอ:

# ❌ Slow — scan ทุก stream
{} |= "error"

# ✅ Fast — filter ด้วย label ก่อน
{job="nginx"} |= "error"

Promtail ไม่ส่ง log จาก Docker

ต้องแน่ใจว่า Docker ใช้ json-file logging driver (default) และ user ที่รัน Promtail มีสิทธิ์อ่าน /var/lib/docker/containers:

# ตรวจ logging driver ของ container
docker inspect CONTAINER_NAME | grep LogConfig -A5

# เพิ่ม promtail user เข้า docker group
usermod -aG docker promtail

# หรือรัน promtail container ด้วย group_add
# ใน docker-compose.yml
group_add:
  - "999"  # docker group GID

Loki disk เต็มเร็ว

ตรวจสอบ retention policy และเพิ่ม compactor ให้ลบ log เก่าอัตโนมัติ:

# ตรวจ disk ที่ Loki ใช้
du -sh /opt/loki/data/

# ใน loki-config.yml ตั้งค่า retention
limits_config:
  retention_period: 720h  # เก็บ 30 วัน

compactor:
  working_directory: /loki/boltdb-shipper-compactor
  shared_store: filesystem
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h

คำถามที่พบบ่อย

Loki ต่างจาก Elasticsearch อย่างไร?

Elasticsearch ทำ full-text indexing ทุก field ทำให้ค้นหาได้ยืดหยุ่นแต่กิน disk และ RAM มาก Loki index เฉพาะ labels (metadata) ไม่ index เนื้อหา log ทำให้ประหยัด resource ได้ 10-50 เท่า แต่ต้องใช้ regex/filter ในการค้นหาเนื้อหา ซึ่งช้ากว่าสำหรับ query ซับซ้อน เหมาะกับ VPS ที่ resource จำกัด

Promtail ต่างจาก Filebeat อย่างไร?

Promtail ออกแบบมาเฉพาะสำหรับส่ง log ไป Loki รองรับ label enrichment และ pipeline stages ที่ทรงพลัง ส่วน Filebeat ยืดหยุ่นกว่าและส่งได้ทั้ง Elasticsearch, Logstash และ Loki แต่ config ซับซ้อนกว่า สำหรับ Loki stack แนะนำใช้ Promtail เพราะ integrate ได้ seamless กว่า

รัน Loki บน VPS RAM 1GB ได้ไหม?

ได้ Loki + Promtail ใช้ RAM รวมประมาณ 200-400MB ถ้าไม่ได้รับ log volume สูงมาก แต่ถ้า ingest log เยอะ (เช่น web server busy) ควรมี RAM อย่างน้อย 2GB และตั้ง retention period ให้สั้นลง (7-14 วัน) เพื่อประหยัด disk