VictoriaLogs в k0s: база логов, которой хватает 128 МБ
Published: 2026-06-10
Метрики в этом кластере давно живут в VictoriaMetrics, а логи так и оставались на уровне kubectl logs и grep. Хотелось, чтобы access-логи Traefik можно было запрашивать — кто ходит на какой сайт, какие запросы падают — и не арендовать под это VPS побольше. VictoriaLogs оказалась полным ответом: один контейнер с лимитом памяти 128 МБ, hostPath-том на 5 ГБ и встроенный UI для запросов. Этот пост про серверную часть; доставка логов — в следующем.
Почему VictoriaLogs
Кандидаты на роль «логи на VPS с двумя ядрами, где живут и сами сервисы»:
- Elasticsearch — хочет heap больше, чем у этого VPS оперативки. Отпал за секунды.
- Loki — спроектирован под object storage и microservices mode; single-binary режим работает, но всё равно ест сотни МБ, а его правила по кардинальности лейблов переносят сложность в конфиг агента.
- VictoriaLogs — один Go-бинарник, без JVM, без object storage, индексирует все поля, включая высококардинальные (IP, пути), и понимает Loki push protocol — значит, работает с любым существующим агентом.
Кластерную VictoriaLogs + Vector я уже эксплуатирую на работе, так что язык запросов (LogsQL) переносится как есть. Домашняя версия радикально меньше: одна реплика, один Deployment, без Helm-чарта — всё умещается в ~90 строк YAML.
Deployment
yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: victoria-logs
namespace: logging
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: victoria-logs
template:
metadata:
labels:
app: victoria-logs
spec:
tolerations:
- operator: Exists
containers:
- name: victoria-logs
image: victoriametrics/victoria-logs:v1.11.0-victorialogs
args:
- --storageDataPath=/data
- --retentionPeriod=7d
- --httpListenAddr=:9428
- --loggerFormat=json
ports:
- containerPort: 9428
name: http
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /health
port: 9428
initialDelaySeconds: 10
periodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: victoria-logs-data
Три решения стоит проговорить:
strategy: Recreate. Том — ReadWriteOnce hostPath. RollingUpdate, применяемый по умолчанию, попытался бы поднять новый под, пока старый ещё держит каталог с данными, — два писателя в одном каталоге это верный способ испортить LSM-дерево. Recreate сначала убивает старый под. Цена — несколько секунд простоя ингеста на каждый деплой, и это не важно: агенты буферизуют и повторяют отправку.
Блок ресурсов. 32 МБ request, лимит 128 МБ — это реальные цифры с работающего экземпляра, который принимает access-логи Traefik для девяти сайтов. В стабильном состоянии VictoriaLogs здесь сидит на 40–60 МБ.
retentionPeriod=7d. Access-логи — операционные данные, не архив. Недели хватает на вопросы «что случилось»; всё, что должно жить дольше — это метрика, и она уже лежит в VictoriaMetrics со сроком хранения 90 дней.
Хранилище
Тот же паттерн, что и у тома для метрик — самодельный PV, прибитый к каталогу хоста, с явным биндингом:
yamlapiVersion: v1
kind: PersistentVolume
metadata:
name: victoria-logs-data
spec:
capacity:
storage: 5Gi
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /var/lib/victoria-logs
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: victoria-logs-data
namespace: logging
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
volumeName: victoria-logs-data
5 ГБ — намеренный запас. VictoriaLogs жмёт агрессивно: неделя access-логов этих сайтов занимает десятки МБ на диске. Запас нужен, чтобы добавление новых источников логов потом не превращалось в миграцию хранилища.
Ингест: три протокола, ноль плагинов
Service — обычный ClusterIP на 9428. Один и тот же порт принимает:
/insert/jsonline— родной формат VictoriaLogs/insert/loki/api/v1/push— протокол Loki, на котором говорят Promtail, Grafana Alloy иloki-sink Vector'а/insert/elasticsearch/_bulk— bulk API Elasticsearch, для Filebeat/Logstash
Это и есть практическая причина, почему VictoriaLogs так легко встраивается в существующую систему: любой агент, который вы уже умеете настраивать, шлёт в неё без модификаций. Мой агент доставки логов шлёт сюда:
http://victoria-logs.logging.svc.cluster.local:9428/insert/loki/api/v1/push
Запросы: VMUI и LogsQL
Встроенный UI живёт на :9428/select/vmui. Port-forward — и вперёд:
bashkubectl -n logging port-forward svc/victoria-logs 9428:9428
# → http://localhost:9428/select/vmui
LogsQL читается как конвейер. Селектор стрима, дальше фильтры:
{job="traefik-access"} # всё из одного стрима
{job="traefik-access"} DownstreamStatus:~"[45][0-9][0-9]" # только 4xx/5xx
{job="traefik-access"} RequestPath:"/feed" _time:1h # один путь, последний час
_time:5m error # слово "error" где угодно, 5 минут
В отличие от Loki, фильтры по полям вроде DownstreamStatus:~"[45][0-9][0-9]" не требуют, чтобы поле было лейблом стрима — VictoriaLogs индексирует сами поля лога. Это снимает целое проектное упражнение «какие лейблы безопасны по кардинальности».
Datasource в Grafana
Grafana нужен плагин victoriametrics-logs-datasource — ставится и провижинится из Helm values:
yamlplugins:
- victoriametrics-logs-datasource
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: VictoriaLogs
type: victoriametrics-logs-datasource
url: http://victoria-logs.logging.svc.cluster.local:9428
access: proxy
После этого logs-панели в дашбордах принимают LogsQL-выражения напрямую.
Что может пойти не так
Liveness-проба убивает под на старте. После нечистого завершения VictoriaLogs проигрывает WAL до того, как начнёт отвечать /health. С коротким initialDelaySeconds kubelet прибивает её посреди реплея — и получается цикл рестартов. Для этого объёма данных хватает 10 секунд; если том больше — масштабируйте задержку вместе с ним (ровно этот класс проблем я уже ловил с другим stateful-подом на этой ноде и пришёл к initialDelaySeconds: 30).
Дашборды из провижининга показывают «datasource not found». Grafana назначает datasource случайный UID, если его не зафиксировать. Дашборд, закоммиченный с "uid": "victorialogs", ломается, когда созданный через провижининг datasource получил UID PD775F2863313E6C7. Либо задавайте uid: явно в блоке провижининга, либо копируйте сгенерированный UID в JSON дашборда. Я выяснил это, разглядывая пустую logs-панель, которая прекрасно работала в Explore.
Диск заполняется несмотря на retention. Очистка по сроку хранения удаляет целые партиции и работает по расписанию — внезапный поток логов (бот долбит сайт, приложение застряло в цикле ошибок) может его обогнать. Лечится на стороне агента: шумные стримы надо дропать до отправки, а не фильтровать на этапе запросов.
Итого
- Один Deployment, один Service, один hostPath PV на 5 ГБ — весь лог-бэкенд в ~90 строках YAML
- 32 МБ request / 128 МБ limit реально хватает для объёма access-логов одного узла
strategy: Recreateобязательна с ReadWriteOnce hostPath-томом — RollingUpdate означает двух писателей- Loki-совместимый push-эндпоинт: Promtail/Alloy/Vector работают без плагинов
- LogsQL фильтрует по любому полю без планирования кардинальности — главный бытовой выигрыш перед Loki
- Фиксируйте UID datasource в Grafana, иначе дашборды из провижининга будут смотреть в никуда