19 KiB
Стандарт централизованного сбора логов: PLG + Prometheus
Стек: Loki · Grafana Alloy · Grafana · kube-prometheus-stack
Namespace: monitoring
Доступ: http://10.203.0.96:10003 (Traefik port 10003)
Исследование: research/logging/plg.md
Архитектура
┌──────────────────────────────────────────────────────────────────┐
│ k8s-worker-01 / k8s-master-01 │
│ [Pod stdout] → /var/log/pods/ ← Alloy: loki.source.k8s │
│ [journald] → kubelet, containerd ← Alloy: loki.source.journal│
│ [node-exporter DaemonSet] ← CPU / RAM / диск / сеть │
│ │
│ [Grafana Alloy DaemonSet] (namespace: monitoring) │
└────────────┬──────────────────────────────────────────────────────┘
│ HTTP push (loki.write)
▼
┌──────────────────────────────────────────────────────────────────┐
│ Loki single-binary (namespace: monitoring) │
│ S3 backend: MinIO bucket loki-chunks │
│ 10 GiB PVC longhorn — только WAL/temp (не логи) │
│ service/loki:3100 │
└─────────────────────┬────────────────────────────────────────────┘
│ S3 API
▼
┌──────────────────────────────────────────────────────────────────┐
│ MinIO (namespace: minio) — bucket: loki-chunks │
│ StorageClass: longhorn-minio (Retain, 1 TiB) │
│ Пользователь: loki (только bucket loki-chunks) │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ kube-prometheus-stack (namespace: monitoring) │
│ Prometheus PVC 20 GiB · node-exporter · kube-state-metrics │
│ ~27 готовых дашбордов кластера → ConfigMaps → Grafana sidecar │
└─────────────────────┬────────────────────────────────────────────┘
│ datasource
▼
┌──────────────────────────────────────────────────────────────────┐
│ Grafana (namespace: monitoring) │
│ Datasource: Loki (isDefault) + Prometheus (provisioned) │
│ Sidecar: подхватывает ConfigMaps grafana_dashboard=1 (ALL ns) │
│ PVC: 5 GiB longhorn │
│ Traefik → port 10003 → http://10.203.0.96:10003 │
└──────────────────────────────────────────────────────────────────┘
Компоненты
| Компонент | Chart | Версия | Примечание |
|---|---|---|---|
| Loki | grafana/loki |
7.0.0 | single-binary, S3 backend, retention 31d |
| Grafana Alloy | grafana/alloy |
1.8.2 | DaemonSet; Promtail EOL с 2026-03-02 |
| kube-prometheus-stack | prometheus-community/kube-prometheus-stack |
68.4.4 | Prometheus + node-exporter + kube-state-metrics |
| Grafana | grafana/grafana |
10.5.15 | provisioned Loki + Prometheus datasources, sidecar |
Promtail не используется — объявлен EOL 2 марта 2026. Grafana Alloy — официальная замена.
Предварительные условия
1. Создать namespace и Secret с паролем Grafana
kubectl create namespace monitoring
kubectl create secret generic grafana-admin-secret \
--from-literal=admin-user=admin \
--from-literal=admin-password='СИЛЬНЫЙ_ПАРОЛЬ' \
-n monitoring
Секрет создаётся вручную — пароль не хранится в Ansible-переменных и не попадает в Git.
Если секрет не создать до запуска playbook, задача Grafana | Assert grafana-admin-secret exists упадёт с подробным сообщением об ошибке.
2. Задать переменную окружения
| Переменная | Описание |
|---|---|
LOKI_MINIO_PASSWORD |
Пароль пользователя loki в MinIO — Ansible создаст пользователя с этим паролем |
GRAFANA_ADMIN_PASSWORD |
Не читается Ansible — только для справки при ручном создании Secret выше |
Пароль LOKI_MINIO_PASSWORD можно посмотреть в любой момент:
kubectl get secret loki-minio-secret -n monitoring \
-o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d
Запуск
# Локально (с заданной переменной окружения)
LOKI_MINIO_PASSWORD='ПАРОЛЬ_LOKI' \
ansible-playbook -i inventory/prod playbooks/setup_logging.yml
# Через GitLab CI/CD
# → Pipelines → setup:logging (ручной запуск, ветка main)
Playbook выполняет задачи в порядке:
- Создаёт namespace
monitoring - Создаёт bucket
loki-chunksв MinIO, пользователяloki, политику доступа, Secretloki-minio-secret - Устанавливает Loki (Helm)
- Устанавливает Grafana Alloy (Helm)
- Устанавливает kube-prometheus-stack (Helm) — Prometheus + node-exporter + kube-state-metrics + ~27 дашбордов
- Устанавливает Grafana (Helm) — проверяет наличие
grafana-admin-secretперед установкой
Playbook идемпотентен: повторный запуск пропустит уже выполненные шаги.
Конфигурация
Loki: параметры хранения
Loki работает в режиме single-binary. Логи хранятся в MinIO (loki-chunks), не на диске.
PVC 10 GiB используется только для WAL и временных файлов компактора.
# roles/logging/defaults/main.yml (переопределять в group_vars при необходимости)
loki_retention_days: 31 # срок хранения логов
loki_wal_storage_size: "10Gi" # PVC только для WAL
loki_wal_storage_class: longhorn
loki_minio_bucket: loki-chunks
Расчёт объёма для текущего кластера (2 ноды, ~20 подов):
- ~170 MB/day после сжатия Loki (~10:1)
- 31 дней → ~5 GB в bucket
loki-chunks - MinIO PVC 1 TiB → запас до ~100 нод
Alloy: сбор логов
Alloy запускается как DaemonSet (по одному поду на каждую ноду, включая control plane).
Что собирает:
/var/log/pods/**/*.log— все контейнеры черезloki.source.kubernetesjournald— kubelet, containerd, sshd черезloki.source.journal
Фактические метки на каждой записи в Loki:
| Метка | Источник | Описание |
|---|---|---|
namespace |
Kubernetes API | namespace пода |
pod |
Kubernetes API | имя пода |
container |
Kubernetes API | имя контейнера |
app |
label пода | значение app label |
level |
JSON-парсинг | уровень лога (info/warn/error) |
job |
константа | systemd-journal для journald-логов |
node |
journald | hostname ноды (только journald) |
unit |
journald | systemd unit (только journald) |
stream |
containerd | stdout или stderr |
Debug-записи (label level=debug) отбрасываются на уровне агента — не попадают в Loki.
Prometheus: метрики кластера
# roles/logging/defaults/main.yml
prometheus_stack_chart_version: "68.4.4"
prometheus_storage_size: "20Gi"
prometheus_storage_class: longhorn
prometheus_retention: "30d"
kube-prometheus-stack устанавливается с отключённой встроенной Grafana (grafana.enabled: false) и forceDeployDashboards: true — создаёт ~27 ConfigMaps с дашбордами, которые Grafana sidecar подхватывает автоматически.
Rocky Linux 9 / kubeadm gotcha:
controller-managerиschedulerпо умолчанию слушают только127.0.0.1→ их метрики в Prometheus будут недоступны (таргеты Down). Остальные таргеты работают без изменений. Для исправления нужен ручной патч/etc/kubernetes/manifests/kube-controller-manager.yamlиkube-scheduler.yaml: замена--bind-address=127.0.0.1на--bind-address=0.0.0.0.
Grafana: доступ и дашборды
URL: http://10.203.0.96:10003
Логин: admin / пароль из Secret grafana-admin-secret
Datasources provisioned при старте — не требуют ручной настройки:
- Loki —
http://loki.monitoring.svc.cluster.local:3100(isDefault) - Prometheus —
http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090
Дашборды (загружаются автоматически через sidecar):
| Дашборд | Источник | Описание |
|---|---|---|
| Kubernetes Logs | ConfigMap grafana-dashboard-kubernetes-logs |
Фильтрация по namespace / pod / container / level, график интенсивности + поток логов |
| Kubernetes / Compute Resources / Cluster | kube-prometheus-stack | CPU/RAM по namespace |
| Kubernetes / Compute Resources / Node (Pods) | kube-prometheus-stack | Ресурсы по подам на ноде |
| Node Exporter / Nodes | kube-prometheus-stack | CPU, RAM, диски, сеть нод |
| Kubernetes / Persistent Volumes | kube-prometheus-stack | Статус и заполнение PVC |
| Kubernetes / API server | kube-prometheus-stack | Latency, error rate, RPS |
| ...и ещё ~22 дашборда | kube-prometheus-stack | Kubelet, etcd, сеть, workloads |
Дашборды Grafana.com с ID 15141, 13639, 18748 не работают с Grafana 12.x — используют устаревший API плагина. Не импортировать.
Диагностика
Проверка после установки
# Статус всех подов стека
kubectl get pod -n monitoring
# Loki готов (запрос через временный под — в контейнере нет curl/wget)
kubectl run loki-check --image=curlimages/curl --restart=Never -n monitoring \
-- curl -s http://loki:3100/ready
sleep 5 && kubectl logs -n monitoring loki-check
kubectl delete pod loki-check -n monitoring
# Метки, которые реально есть в Loki
kubectl run loki-labels --image=curlimages/curl --restart=Never -n monitoring \
-- curl -s 'http://loki:3100/loki/api/v1/labels'
sleep 5 && kubectl logs -n monitoring loki-labels
kubectl delete pod loki-labels -n monitoring
# Alloy открыл потоки логов со всех подов (нет level=error)
kubectl logs -n monitoring -l app.kubernetes.io/name=alloy --tail=50 | grep -v "opened log stream"
# Данные появились в MinIO
kubectl exec -n minio deployment/minio -- \
mc ls local/loki-chunks --recursive | head -10
Loki CrashLoopBackOff при старте
Симптом 1: mkdir /loki/compactor: read-only file system
Причина: неверный путь compactor. Должен быть /var/loki/compactor, не /loki/compactor.
Проверить в ConfigMap: kubectl get configmap loki -n monitoring -o jsonpath='{.data.config\.yaml}' | grep working_directory
Симптом 2: SignatureDoesNotMatch или InvalidAccessKeyId
Причина: env var expansion ${VAR} в Loki 3.x не работает для S3 credentials в config-файле.
Решение: пароль передаётся через --set loki.storage.s3.secret_access_key=... в helm-команде (реализовано в tasks/loki.yml).
Симптом 3: error running loki при повторном запуске после CrashLoop + another operation in progress
Причина: helm upgrade завис в предыдущей попытке.
helm rollback loki -n monitoring
# затем повторить helm upgrade
Предупреждение loki-memberlist: no such host — некритично для single-binary, можно игнорировать.
Loki 500 при запросе через Grafana
# Проверить пользователя loki в MinIO
MINIO_POD=$(kubectl get pod -n minio -l app=minio -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n minio $MINIO_POD -- mc admin user info local loki
# Проверить Secret и его содержимое
kubectl get secret loki-minio-secret -n monitoring
kubectl get secret loki-minio-secret -n monitoring \
-o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d
# Тест доступа с теми же учётными данными
SECRET=$(kubectl get secret loki-minio-secret -n monitoring \
-o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d)
kubectl exec -n minio $MINIO_POD -- mc alias set lokitest http://localhost:9000 loki "$SECRET"
kubectl exec -n minio $MINIO_POD -- mc ls lokitest/
Alloy не отправляет логи
# Ошибки в логах Alloy
kubectl logs -n monitoring -l app.kubernetes.io/name=alloy --tail=100 \
| grep -i "error\|level=error"
# Loki endpoint доступен из пода Alloy
kubectl exec -n monitoring -l app.kubernetes.io/name=alloy -c alloy -- \
wget -qO- http://loki.monitoring.svc.cluster.local:3100/ready 2>&1
Grafana не видит данные (No Data)
# Проверить что Loki datasource здоров после рестарта Grafana
kubectl rollout restart deployment/grafana -n monitoring
kubectl rollout status deployment/grafana -n monitoring
# Проверить UID datasource (нужен для ручного исправления импортированных дашбордов)
GRAFANA_PASS=$(kubectl get secret grafana-admin-secret -n monitoring \
-o jsonpath='{.data.admin-password}' | base64 -d)
kubectl run gf-check --image=curlimages/curl --restart=Never -n monitoring \
-- curl -s -u "admin:${GRAFANA_PASS}" http://grafana/api/datasources
sleep 5 && kubectl logs -n monitoring gf-check | python3 -m json.tool | grep -E '"uid"|"name"'
kubectl delete pod gf-check -n monitoring
Если Grafana была запущена в момент когда Loki находился в CrashLoop — datasource кэширует нерабочее состояние. Перезапуск пода исправляет.
Grafana не открывается
# Состояние подов
kubectl get pod -n monitoring -l app.kubernetes.io/name=grafana
# IngressRoute создан
kubectl get ingressroute route-grafana -n traefik
# Если IngressRoute отсутствует — перезапустить setup_traefik.yml
# (порт 10003 уже есть в Traefik DaemonSet)
ansible-playbook -i inventory/prod playbooks/setup_traefik.yml
MinIO kubectl cp завершается ошибкой tar not found
MinIO-контейнер — минимальный образ без tar. kubectl cp использует tar внутри.
Решение: использовать kubectl exec -i ... -- mc ... /dev/stdin для передачи файлов.
Уже реализовано в tasks/minio-user.yml.
Масштабирование
До 5+ нод: Loki distributed mode
Триггер: объём > 50 GB/day или > 50 одновременных запросов.
Изменить в loki-values.yml.j2: deploymentMode: Distributed + задать реплики ingester/querier/distributor.
Данные в MinIO не мигрируют — bucket loki-chunks остаётся тем же.
Prometheus: Thanos для long-term storage
Триггер: retention > 30d или несколько кластеров.
Thanos sidecar к Prometheus pod + remote_write в объектное хранилище.
Alloy и node-exporter
Не требуют изменений — DaemonSet автоматически запускается на новых нодах.
Известные ограничения
| Проблема | Описание |
|---|---|
deploymentMode обязателен на верхнем уровне |
В Loki chart 7.x deploymentMode: SingleBinary должен быть вне блока loki:, иначе деплоится distributed-режим с 0 репликами |
| Env var expansion не работает для S3 credentials | Loki 3.x не раскрывает ${VAR} в полях access_key_id / secret_access_key. Пароль передаётся через helm --set |
| Controller-manager / Scheduler метрики недоступны | kubeadm привязывает их к 127.0.0.1. Требует ручного патча static pod манифестов |
| Дашборды Grafana.com 15141 / 13639 / 18748 сломаны | Несовместимы с Grafana 12.x. Использовать встроенный дашборд Kubernetes Logs |
Переход на Graylog
Alloy поддерживает dual-write: одновременная отправка в Loki и Graylog. Порядок переезда описан в research/logging/plg.md (раздел 11).
MinIO остаётся востребованным при Graylog — как S3 snapshot repository для OpenSearch (bucket backups).