ติดตั้ง 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 Loki | ELK Stack |
|---|---|---|
| RAM ขั้นต่ำ | ~200MB | ~2GB+ |
| วิธี index | Labels เท่านั้น | Full-text index |
| Query language | LogQL | Lucene / KQL |
| Integration กับ Grafana | Native | ผ่าน plugin |
| ความยากติดตั้ง | ง่าย | ซับซ้อน |
สิ่งที่ต้องมีก่อนเริ่ม
- VPS Linux (Ubuntu 20.04/22.04 หรือ Debian 11/12) RAM อย่างน้อย 1GB
- Docker และ Docker Compose ติดตั้งแล้ว
- Grafana รันอยู่แล้ว (หรือจะติดตั้งพร้อมกันในบทนี้)
- Port 3100 เปิดให้ Loki รับ log (หรือใช้ internal network)
ขั้นตอนที่ 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
grafana ออกจาก docker-compose.yml และแก้ไข network ให้ Loki อยู่ใน network เดียวกับ Grafana ที่มีอยู่ขั้นตอนที่ 6 — เพิ่ม Loki เป็น Data Source ใน Grafana
- เปิด Grafana ที่
http://YOUR_IP:3000แล้ว login - ไปที่ Configuration → Data Sources → Add data source
- เลือก Loki
- ใส่ URL:
http://loki:3100(ถ้าใช้ Docker network เดียวกัน) - คลิก 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
- ไปที่ Dashboards → New → New Dashboard
- คลิก Add visualization
- เลือก Data source: Loki
- ใส่ query เช่น
{job="nginx"} |= "error" - เลือก visualization type: Logs หรือ Time series สำหรับ rate
- Save dashboard
ตั้งค่า 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
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