Зачем Loki
Elasticsearch, full-text search всех полей log'а. Каждое слово индексируется. Стоимость:
- 1 TB/день логов = ~30 GB/день RAM на heap (3-5× source size)
- $30K/месяц ES cluster vs $5K/месяц для S3-Loki
- Indexing latency, реальная боль на >100K events/s
Loki (Grafana Labs, 2018) перевернул подход:
- Индексируется только labels (как в [[prometheus-basics|Prometheus]])
- Сам log payload, compressed chunks на S3-compatible storage
- Поиск = «выбор stream'ов по labels» + «grep по chunk'ам»
- Дёшево, scale почти бесконечный
Trade-off: полно-текстовый поиск медленнее, но 95% запросов в
observability, {service=X, level=error} |= "timeout", что Loki
обрабатывает быстро.
Архитектура
┌─────────┐ push ┌──────────┐ write ┌─────────┐
│ Promtail│ ─────────► │ Loki │ ─────────► │ S3 │
│ (agent) │ │ (server) │ │ (chunks)│
└─────────┘ └──────────┘ └─────────┘
┌─────────┐ push ▲ ▲
│ Vector │ ────────────────┘ │
└─────────┘ │
▲ │
│ tail files / journald / docker read │
│ │
┌────┴────┐ ┌──────┴───┐
│ /var/log│ ◄── │ Grafana │
└─────────┘ └──────────┘
LogQL query
Components Loki в кластере:
- distributor, принимает push, hashing
- ingester, буферизует chunks в RAM, flush на S3 каждые 10-30 мин
- querier, читает chunks с S3 + ingester для recent
- query-frontend, splits большие queries, кэширует
- compactor, мерджит index'ы, retention enforcement
Stream и labels
Stream в Loki = уникальная комбинация labels:
{service="api", env="prod", host="node-12", level="info"}Каждый stream, отдельный chunked-file на S3. Внутри stream, log lines в порядке времени.
Подобно [[metric-types|Prometheus series]], кардинальность = product unique values каждого label. >10K активных streams в одном tenant → деградация. Опасные labels:
request_id(миллионы), никогдаuser_id, никогда, в payload пишиpod_name(k8s), может быть тысячи, OK с retentionhost, десятки-сотни, OKlevel, 4-5 значений, идеально
Правило: labels = низкая кардинальность, остальное, в log line.
LogQL, query language
PromQL-like, но на логах.
Stream selector + line-filter:
{service="api", env="prod"} |= "error"{service="api"} |~ "timeout|refused" # regex{service="api"} != "healthcheck" # exclude{service=~"api.*"} | json | level="error" # parse JSONOperators:
| Op | Что делает |
|---|---|
|= | line contains substring |
\!= | line not contains |
|~ | line matches regex |
\!~ | line not matches regex |
Parsers (после |):
json, парсит JSON, поля доступны какlevel,user_id, etclogfmt, дляkey=valueлоговregexp,| regexp "(?P<status>\d+)"pattern,| pattern "<_> [<level>] <msg>"unpack, для Fluentbit-обёрнутых
Metrics из логов (Loki как time-series):
rate({service="api"} |= "error" [5m]) # error rate в req/ssum by (status)(count_over_time({service="api"} | json [1m]))Это дёшево, Loki считает на лету без индекса. Используется в alerting когда метрика отсутствует (alerting-rules-alertmanager).
Promtail, Loki-native agent
Discovers log files, парсит, добавляет labels, пушит:
scrape_configs:
- job_name: system
static_configs:
- targets: [localhost]
labels:
job: varlogs
__path__: /var/log/*.log
- job_name: containers
docker_sd_configs:
- host: unix:///var/run/docker.sock
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: container
- source_labels: ['__meta_docker_container_log_stream']
target_label: stream
pipeline_stages:
- cri: {}- json:
expressions: {level: level, msg: msg, trace_id: trace_id}- labels:
level:
- structured_metadata:
trace_id:
pipeline_stages, преобразование log line. structured_metadata
(Loki 2.9+), поля без cardinality cost (trace_id, request_id),
searchable но не индексируется как label. Решение проблемы high-card
identifiers.
Vector, alternative agent
Vector (Datadog, opensource), более мощный pipeline:
[sources.in]
type = "kubernetes_logs"
[transforms.parse]
type = "remap"
inputs = ["in"]
source = '''
. = parse_json!(.message) ?? .
.level = downcase(string!(.level))
'''
[sinks.loki]
type = "loki"
inputs = ["parse"]
endpoint = "http://loki:3100"
labels = {service = "{{ kubernetes.container_name }}", level = "{{ level }}"}remove_label_fields = true
Vector умеет:
- Multi-sink: одновременно Loki + S3 + Kafka
- VRL (Vector Remap Language), JS-like для парсинга
- Backpressure handling: disk-buffered queue
- Sampling, filtering до отправки
Используй Vector если pipeline сложный или нужны несколько backend'ов.
Retention и storage
Loki стоимость почти 100%, S3 storage. Расчёт:
- 1 GB/день logs → compressed ~100-200 MB chunks
- 90d retention → ~15 GB на S3 → $0.35/мес (S3 standard)
- Index ~5% от chunks: $0.02/мес
Total: <$1/мес для ~100 GB logs. Сравни с Datadog ($1.27/GB/мес).
Конфиг retention:
limits_config:
retention_period: 90d
compactor:
retention_enabled: true
retention_delete_delay: 2h
Sizing rules of thumb
- 1 TB/day ingest = 3 ingester + 2 querier + S3
- Ingester RAM ≈ chunks_in_flight × 1.5 MB
- Compactor, 1-2 vCPU, не нагружен
- Index lookup в querier, fast, бутылочное горлышко обычно chunk-fetch
Loki vs Elastic vs ClickHouse
| Критерий | Loki | Elastic | ClickHouse |
|---|---|---|---|
| Index | label-only | full-text | columnar |
| Storage | S3 (cheap) | local SSD | local/S3 |
| Cost @ 1TB/day | ~$5K/мес | ~$30K/мес | ~$10K/мес |
| Full-text speed | средне | very fast | fast (with skip-index) |
| Aggregations | LogQL metrics | aggregations API | SQL |
| Multi-tenancy | yes | через index | через DB |
ClickHouse-based (SigNoz, Quickwit), компромисс: дешевле Elastic, быстрее Loki на full-text. Растёт в популярности.
Когда что-то пошло не так
- Cardinality explosion, десятки тысяч streams.
loki-canaryпоказывает active streams. Удали dynamic labels. См. cardinality-explosion. - Logs не приходят, Promtail logs (
journalctl -u promtail): auth failure, network, disk full в /tmp. - «too many outstanding requests», query frontend rate-limit. Сужай range, добавляй labels selector.
entry too far behind, log line >max_line_size(default 256 KB). Truncate в agent или подними limit.- Search возвращает 0 хотя логи есть, wrong tenant header
(
X-Scope-OrgID), или label selector не матчится. Проверь{__path__=~".+"}. - Loki OOM на ingester, chunks_per_user_per_target превышен. Уменьшай interval flush'а или растяни retention в memory.
- Promtail отстаёт от логов, disk-IO на read; кубер pod logs rotated. Используй Vector с persistent buffer.