Grafana Loki — agregacja logów z promtail i LogQL
Opublikowano: 10 kwietnia 2026 · Kategoria: VPS / Monitoring
Gdy masz kilka serwisów na VPS — Nginx, PHP-FPM, WordPress, Docker — logi rozjeżdżają się po różnych katalogach i szukanie błędu zamienia się w polowanie z grep. Grafana Loki rozwiązuje ten problem: agreguje logi z wszystkich źródeł, indeksuje je po etykietach (nie po treści) i pozwala odpytywać przez LogQL w tym samym UI co Prometheus. Ten artykuł pokazuje pełną instalację Loki + Promtail + Grafana w Docker Compose oraz praktyczne zapytania LogQL.
Dlaczego Loki zamiast ELK?
Klasyczny ELK Stack (Elasticsearch + Logstash + Kibana) indeksuje każde pole każdego logu, co daje szybkie full-text search, ale kosztuje dużo RAM i dysku. Na VPS z 2 GB RAM Elasticsearch po prostu się nie uruchomi — minimum to 1 GB heap + bufory = realnie 2 GB. Loki działa odwrotnie: indeksuje tylko labels (nazwa joba, host, level), a samą treść logu trzyma w paczkach gzip. Wynik: typowy Loki + Promtail zużywa 300-500 MB RAM i mieści się na VPS-ie za 25 PLN miesięcznie.
Instalacja Loki w Docker Compose
Najszybszy sposób uruchomienia stosu Loki + Promtail + Grafana to jeden plik
docker-compose.yml. Wszystkie trzy komponenty dzielą jedną sieć Dockera.
# /opt/loki-stack/docker-compose.yml
version: "3.8"
services:
loki:
image: grafana/loki:2.9.0
container_name: loki
restart: unless-stopped
ports:
- "127.0.0.1:3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:2.9.0
container_name: promtail
restart: unless-stopped
volumes:
- ./promtail-config.yaml:/etc/promtail/config.yaml:ro
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: -config.file=/etc/promtail/config.yaml
depends_on:
- 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=zmien_to_haslo
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- loki
volumes:
loki-data:
grafana-data: # loki-config.yaml — minimalna konfiguracja (filesystem storage)
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v12
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h # 30 dni
max_query_length: 721h
compactor:
working_directory: /loki/compactor
retention_enabled: true
delete_request_store: filesystem Promtail — zbieranie logów z plików i Dockera
Promtail to agent, który czyta pliki logów (ogonem, jak tail -f) i wysyła je do
Loki. Konfiguracja deklaruje scrape jobs — podobnie jak w Prometheusie. Każdy job dostaje
unikalne labels, które potem filtrujesz w LogQL.
# promtail-config.yaml
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:
# Nginx access + error log
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
host: vps-production
__path__: /var/log/nginx/*.log
# PHP-FPM slow log + error
- job_name: php-fpm
static_configs:
- targets:
- localhost
labels:
job: php-fpm
host: vps-production
__path__: /var/log/php*-fpm.log
# Systemd journal (wymaga socket mount)
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
relabel_configs:
- source_labels: ["__journal__systemd_unit"]
target_label: unit
# Logi kontenerów Docker
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: ["__meta_docker_container_name"]
target_label: container LogQL — zapytania do logów
LogQL składa się z selektora labels (co zwrócić) oraz filtrów treści (czego szukać w liniach). Selektor jest obowiązkowy — minimum jedna label. Filtry są opcjonalne i można je łączyć w łańcuch.
# Wszystkie logi Nginxa
{job="nginx"}
# Tylko błędy 5xx z access log
{job="nginx", filename="/var/log/nginx/access.log"} |~ " 5\\d\\d "
# Logi PHP-FPM zawierające "Out of memory"
{job="php-fpm"} |= "Out of memory"
# Kombinacja: Nginx error log, bez wpisów o favicon
{job="nginx"} |= "error" != "favicon.ico"
# Regex match: 4xx lub 5xx
{job="nginx"} |~ " [45]\\d\\d "
# Agregacja: liczba błędów 500 na sekundę w ostatnich 5 minutach
rate({job="nginx"} |~ " 500 " [5m])
# Top 5 kontenerów z największą liczbą logów
topk(5, sum by (container) (rate({job="docker"} [5m]))) Loki vs ELK Stack — porównanie
| Kryterium | Grafana Loki | ELK Stack |
|---|---|---|
| Zużycie RAM (min) | 300-500 MB | 2-4 GB |
| Indeksowanie | Tylko labels (metadane) | Każde pole + full-text |
| Full-text search | Grep po bloku (wolniejsze) | Natywny inverted index |
| Storage backend | Filesystem, S3, GCS | Elasticsearch (własny format) |
| Język zapytań | LogQL (PromQL-like) | Lucene / KQL |
| UI | Grafana (współdzielone z metrykami) | Kibana (osobne) |
| Koszt storage | Niski (gzip, tanie obiektowe) | Wysoki (index + source) |
| Idealne dla | Małe/średnie VPS, cloud-native | Duże klastry, zaawansowany search |
Dashboard Grafana + retention
Po uruchomieniu stosu otwórz http://twoj-vps:3000, zaloguj się jako
admin i dodaj data source typu Loki z URL
http://loki:3100. Zakładka Explore pozwala testować LogQL na żywo. Gotowe
dashboardy dla Nginxa, Dockera i systemd są dostępne na
grafana.com/dashboards
— szukaj tagu loki.
Retention 30 dni na średnim VPS zajmuje typowo 2-5 GB (po kompresji). Dla 90 dni licz 10-15
GB. Jeśli miejsce zaczyna się kończyć, przełącz object_store z
filesystem na s3 i wyślij logi do Backblaze B2 lub MinIO — Loki natywnie
to wspiera i odczytuje chunki przez HTTP range requests.