1276 lines
47 KiB
Markdown
1276 lines
47 KiB
Markdown
# Сбор логов в Kubernetes: PLG-стек и переезд на Graylog
|
||
|
||
**Кластер:** Rocky Linux 9 · Kubernetes 1.33 · Flannel CNI
|
||
**Текущая топология:** k8s-manager-01 (10.203.0.92) · k8s-master-01 (10.203.0.97) · k8s-worker-01 (10.203.0.96)
|
||
**Дата исследования:** 2026-05-28
|
||
**Проверено на кластере:** 2026-05-28
|
||
|
||
> **⚠️ ВАЖНО — EOL-статус компонентов (проверено 2026-05-28):**
|
||
>
|
||
> | Компонент | Статус | Детали |
|
||
> |---|---|---|
|
||
> | **Promtail** | **EOL с 2 марта 2026** | [Официальное объявление Grafana Labs](https://community.grafana.com/t/promtail-end-of-life-eol-march-2026-how-to-migrate-to-grafana-alloy-for-existing-loki-server-deployments/159636). Баги и CVE не исправляются. Замена: **Grafana Alloy** |
|
||
> | **Loki** | Активно развивается | Последняя: v3.6.7. Helm chart 7.0.0 актуален. EOL не объявлен |
|
||
> | **Grafana** | Активно развивается | EOL не объявлен |
|
||
>
|
||
> **Все разделы про Promtail (5, 9, 11) обновлены на Grafana Alloy.**
|
||
|
||
---
|
||
|
||
## Состояние кластера на дату исследования
|
||
|
||
```
|
||
MinIO (namespace: minio)
|
||
pod/minio-696468d6b6-wnm7c 1/1 Running
|
||
service/minio ClusterIP 10.101.211.198 9000/TCP
|
||
service/minio-console ClusterIP 10.111.84.4 9001/TCP
|
||
pvc/minio Bound 1Ti longhorn-minio (Retain)
|
||
IngressRoutes: route-minio-api (10005), route-minio-console (10006) ✓
|
||
Buckets: loki-chunks ✓ backups ✓ artifacts ✓ (все пустые)
|
||
Пользователь loki: не создан ← нужно создать перед деплоем Loki
|
||
|
||
Namespace monitoring: не существует ← PLG не развёрнут
|
||
StorageClasses: longhorn (default), longhorn-minio (Retain), longhorn-static
|
||
```
|
||
|
||
**Вывод:** MinIO полностью готов к роли S3 backend для Loki. Filesystem-режим не нужен — Loki деплоится сразу с S3/MinIO.
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Обзор проблемы](#1-обзор-проблемы)
|
||
2. [PLG-стек: архитектура](#2-plg-стек-архитектура)
|
||
3. [Подготовка MinIO для Loki](#3-подготовка-minio-для-loki)
|
||
4. [Loki: хранение в MinIO (S3)](#4-loki-хранение-в-minio-s3)
|
||
5. [Grafana Alloy: сбор логов с нод](#5-grafana-alloy-сбор-логов-с-нод) *(Promtail EOL 2026-03-02)*
|
||
6. [Grafana: визуализация и алерты](#6-grafana-визуализация-и-алерты)
|
||
6а. [Мониторинг ресурсов кластера: Prometheus stack](#6а-мониторинг-ресурсов-кластера-prometheus-stack)
|
||
7. [Grafana as Code: хранение настроек в GitLab](#7-grafana-as-code-хранение-настроек-в-gitlab)
|
||
8. [Интеграция с Traefik](#8-интеграция-с-traefik)
|
||
9. [Ansible-роли и плейбуки](#9-ansible-роли-и-плейбуки)
|
||
10. [Масштабирование до 10 нод / 100 сервисов](#10-масштабирование-до-10-нод--100-сервисов)
|
||
11. [Переезд на Graylog](#11-переезд-на-graylog)
|
||
12. [Итоговое сравнение и рекомендации](#12-итоговое-сравнение-и-рекомендации)
|
||
|
||
---
|
||
|
||
## 1. Обзор проблемы
|
||
|
||
В кластере Kubernetes логи существуют на трёх уровнях:
|
||
|
||
| Уровень | Источник | Путь на ноде |
|
||
|---|---|---|
|
||
| Приложения | stdout/stderr контейнеров | `/var/log/pods/<ns>_<pod>_<uid>/<container>/N.log` |
|
||
| Control plane | apiserver, etcd, scheduler, controller-manager | `journald` (Rocky Linux 9) |
|
||
| Нода | kubelet, kube-proxy, containerd | `journald` |
|
||
|
||
containerd по умолчанию ротирует файлы логов (10 MB × 5 файлов), что означает потерю истории при интенсивной нагрузке. Без централизованного сбора:
|
||
|
||
- Логи недоступны после рестарта пода
|
||
- Нет единого поиска по неймспейсам
|
||
- Нет алертов на паттерны ошибок
|
||
- Корреляция событий между сервисами невозможна
|
||
|
||
---
|
||
|
||
## 2. PLG-стек: архитектура
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ k8s-worker-01 / k8s-master-01 │
|
||
│ │
|
||
│ [Pod stdout] → containerd → /var/log/pods/ │
|
||
│ ↓ │
|
||
│ [Grafana Alloy DaemonSet] (файлы + journald) │
|
||
└───────────────────────┬──────────────────────────────────────────┘
|
||
│ HTTP push (loki.write)
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ Loki (namespace: monitoring) │
|
||
│ single-binary pod │
|
||
│ ├── Ingester — буферизует и пишет чанки │
|
||
│ ├── Querier — выполняет LogQL запросы │
|
||
│ └── Compactor — уплотняет, применяет retention │
|
||
└───────────────────────┬──────────────────────────────────────────┘
|
||
│ S3 API (chunks + index)
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ MinIO (namespace: minio) — УЖЕ ЗАПУЩЕН ✓ │
|
||
│ pod/minio-696468d6b6-wnm7c Running │
|
||
│ pvc/minio 1Ti longhorn-minio │
|
||
│ bucket: loki-chunks ✓ (пустой, готов) │
|
||
│ http://minio.minio.svc.cluster.local:9000 │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
│ datasource
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ Grafana (namespace: monitoring) │
|
||
│ ├── Loki datasource — LogQL explore + dashboards │
|
||
│ └── Alerting — правила на паттерны ошибок │
|
||
│ Traefik → port 10003 → http://10.203.0.96:10003 │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Выбор компонентов
|
||
|
||
| Компонент | Helm chart | Версия chart | Статус |
|
||
|---|---|---|---|
|
||
| Loki | `grafana/loki` | 7.0.0 | ✅ Актуален |
|
||
| ~~Promtail~~ → **Grafana Alloy** | `grafana/alloy` | 1.8.2 | ⚠️ Promtail EOL 2026-03-02; Alloy — официальная замена |
|
||
| Grafana | `grafana/grafana` | 10.5.15 | ✅ Актуален |
|
||
|
||
---
|
||
|
||
## 3. Подготовка MinIO для Loki
|
||
|
||
MinIO уже запущен. Требуется только создать выделенного пользователя с доступом исключительно к бакету `loki-chunks`.
|
||
|
||
### Создание пользователя loki
|
||
|
||
```bash
|
||
# Выполнить на k8s-manager-01 или через kubectl exec
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
|
||
|
||
# Создать пользователя
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc admin user add local loki ПАРОЛЬ_LOKI
|
||
|
||
# Создать политику — только бакет loki-chunks
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc admin policy create local loki-policy /dev/stdin <<'EOF'
|
||
{
|
||
"Version": "2012-10-17",
|
||
"Statement": [
|
||
{
|
||
"Effect": "Allow",
|
||
"Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject","s3:ListBucket","s3:GetBucketLocation"],
|
||
"Resource": [
|
||
"arn:aws:s3:::loki-chunks",
|
||
"arn:aws:s3:::loki-chunks/*"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
EOF
|
||
|
||
# Привязать политику
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc admin policy attach local loki-policy --user loki
|
||
```
|
||
|
||
### Secret для Loki в namespace monitoring
|
||
|
||
```bash
|
||
kubectl create namespace monitoring
|
||
|
||
kubectl create secret generic loki-minio-secret \
|
||
--from-literal=AWS_ACCESS_KEY_ID=loki \
|
||
--from-literal=AWS_SECRET_ACCESS_KEY='ПАРОЛЬ_LOKI' \
|
||
-n monitoring
|
||
```
|
||
|
||
### Проверка доступа
|
||
|
||
```bash
|
||
# Проверить что loki-user видит только свой бакет
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc alias set loki-test http://localhost:9000 loki ПАРОЛЬ_LOKI
|
||
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc ls loki-test
|
||
# Ожидаем: [дата] 0B loki-chunks/
|
||
|
||
kubectl exec -n minio deployment/minio -- \
|
||
mc ls loki-test/loki-chunks
|
||
# Ожидаем: пустой список (бакет пустой и готов)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Loki: хранение в MinIO (S3)
|
||
|
||
Loki деплоится сразу в режиме **single-binary с S3 backend** — MinIO уже в кластере, filesystem не нужен.
|
||
|
||
### Helm values: `loki-values.yml`
|
||
|
||
```yaml
|
||
loki:
|
||
auth_enabled: false
|
||
|
||
commonConfig:
|
||
replication_factor: 1
|
||
|
||
# S3 backend — MinIO в namespace minio
|
||
storage:
|
||
type: s3
|
||
s3:
|
||
endpoint: http://minio.minio.svc.cluster.local:9000
|
||
region: us-east-1 # MinIO игнорирует region, поле обязательно
|
||
bucketnames: loki-chunks
|
||
access_key_id: loki
|
||
secret_access_key: "${LOKI_MINIO_SECRET_KEY}" # из Secret через envFrom
|
||
insecure: true # http, не https
|
||
s3forcepathstyle: true # обязательно для MinIO
|
||
|
||
schemaConfig:
|
||
configs:
|
||
- from: "2024-01-01"
|
||
store: tsdb
|
||
object_store: s3
|
||
schema: v13
|
||
index:
|
||
prefix: loki_index_
|
||
period: 24h
|
||
|
||
limits_config:
|
||
retention_period: 744h # 31 день
|
||
ingestion_rate_mb: 16
|
||
ingestion_burst_size_mb: 32
|
||
max_streams_per_user: 10000
|
||
max_chunks_per_query: 2000000
|
||
|
||
compactor:
|
||
working_directory: /loki/compactor
|
||
retention_enabled: true
|
||
delete_request_store: s3 # ← s3, не filesystem
|
||
|
||
singleBinary:
|
||
replicas: 1
|
||
# Нет PVC — данные в MinIO, только небольшой volume для WAL
|
||
persistence:
|
||
enabled: true
|
||
storageClass: longhorn
|
||
size: 10Gi # только WAL/temp, не логи
|
||
|
||
extraEnv:
|
||
- name: LOKI_MINIO_SECRET_KEY
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: loki-minio-secret
|
||
key: AWS_SECRET_ACCESS_KEY
|
||
|
||
# Отключаем встроенные компоненты (используем Promtail отдельно)
|
||
gateway:
|
||
enabled: false
|
||
backend:
|
||
replicas: 0
|
||
read:
|
||
replicas: 0
|
||
write:
|
||
replicas: 0
|
||
```
|
||
|
||
### Объём хранилища: расчёт для текущего кластера
|
||
|
||
```
|
||
2 ноды, ~20 подов, средний lograte 1 KB/s:
|
||
20 pods × 1 KB/s × 86400 s/day ≈ 1.7 GB/day (raw)
|
||
Loki сжимает ~10:1 → ~170 MB/day
|
||
31 день retention → ~5 GB в loki-chunks бакете MinIO
|
||
|
||
MinIO PVC = 1 TiB → запас до ~100 нод (≈ 500 MB/day × 31 дней ≈ 15 GB)
|
||
```
|
||
|
||
### Проверка после деплоя
|
||
|
||
```bash
|
||
# Статус пода
|
||
kubectl get pod -n monitoring -l app.kubernetes.io/name=loki
|
||
|
||
# Готовность API
|
||
kubectl exec -n monitoring deployment/loki -- wget -qO- http://localhost:3100/ready
|
||
|
||
# Появились ли объекты в MinIO
|
||
kubectl exec -n minio deployment/minio -- mc ls local/loki-chunks --recursive | head -20
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Grafana Alloy: сбор логов с нод
|
||
|
||
> **Promtail EOL с 2 марта 2026.** Официальная замена — **Grafana Alloy** (`grafana/alloy`, chart 1.8.2).
|
||
> Alloy — дистрибутив OpenTelemetry Collector от Grafana Labs. Собирает логи, метрики и трейсы единым агентом.
|
||
> Конфиг Promtail конвертируется автоматически: `alloy convert --source-format=promtail --output=config.alloy promtail.yml`
|
||
|
||
### Что собирает
|
||
|
||
- `/var/log/pods/**/*.log` — логи всех контейнеров (через `loki.source.kubernetes`)
|
||
- `journald` — kubelet, kube-proxy, containerd, sshd (через `loki.source.journal`)
|
||
|
||
### Как работает Alloy
|
||
|
||
Alloy использует собственный язык конфигурации (River/Alloy syntax), а не YAML. Конфиг передаётся в Helm как строка в `alloy.configMap.content`.
|
||
|
||
### Helm values: `alloy-values.yml`
|
||
|
||
```yaml
|
||
# Развёртывание как DaemonSet — один под на каждую ноду
|
||
controller:
|
||
type: daemonset
|
||
|
||
tolerations:
|
||
# Запускать и на control plane (k8s-master-01)
|
||
- key: node-role.kubernetes.io/control-plane
|
||
operator: Exists
|
||
effect: NoSchedule
|
||
|
||
# Монтирование journald с хоста
|
||
alloy:
|
||
mounts:
|
||
varlog: true # /var/log (поды)
|
||
dockercontainers: false
|
||
|
||
extraVolumes:
|
||
- name: journal
|
||
hostPath:
|
||
path: /var/log/journal
|
||
|
||
extraVolumeMounts:
|
||
- name: journal
|
||
mountPath: /var/log/journal
|
||
readOnly: true
|
||
|
||
configMap:
|
||
create: true
|
||
content: |
|
||
// ── Обнаружение подов Kubernetes ───────────────────────────────
|
||
discovery.kubernetes "pods" {
|
||
role = "pod"
|
||
}
|
||
|
||
// ── Relabeling: namespace, pod, container, app из метаданных ──
|
||
discovery.relabel "pod_logs" {
|
||
targets = discovery.kubernetes.pods.targets
|
||
|
||
rule {
|
||
source_labels = ["__meta_kubernetes_namespace"]
|
||
target_label = "namespace"
|
||
}
|
||
rule {
|
||
source_labels = ["__meta_kubernetes_pod_name"]
|
||
target_label = "pod"
|
||
}
|
||
rule {
|
||
source_labels = ["__meta_kubernetes_pod_container_name"]
|
||
target_label = "container"
|
||
}
|
||
rule {
|
||
source_labels = ["__meta_kubernetes_pod_label_app"]
|
||
target_label = "app"
|
||
}
|
||
// Отброс debug-логов на уровне агента
|
||
rule {
|
||
source_labels = ["__meta_kubernetes_pod_annotation_filter_debug"]
|
||
regex = "true"
|
||
action = "drop"
|
||
}
|
||
}
|
||
|
||
// ── Чтение файлов логов подов ──────────────────────────────────
|
||
loki.source.kubernetes "pods" {
|
||
targets = discovery.relabel.pod_logs.output
|
||
forward_to = [loki.process.parse.receiver]
|
||
}
|
||
|
||
// ── Парсинг JSON-логов, отброс debug ──────────────────────────
|
||
loki.process "parse" {
|
||
// Попытка распарсить как JSON и извлечь level
|
||
stage.json {
|
||
expressions = {level = "level", msg = "message"}
|
||
}
|
||
stage.labels {
|
||
values = {level = ""}
|
||
}
|
||
// Отброс debug-записей
|
||
stage.drop {
|
||
expression = ".*level=\"debug\".*"
|
||
drop_counter_reason = "debug_dropped"
|
||
}
|
||
forward_to = [loki.write.local.receiver]
|
||
}
|
||
|
||
// ── Сбор journald (kubelet, containerd, sshd) ─────────────────
|
||
loki.source.journal "systemd" {
|
||
path = "/var/log/journal"
|
||
max_age = "12h"
|
||
labels = {job = "systemd-journal"}
|
||
forward_to = [loki.write.local.receiver]
|
||
|
||
relabel_rules = discovery.relabel.journal.rules
|
||
}
|
||
|
||
discovery.relabel "journal" {
|
||
targets = []
|
||
rule {
|
||
source_labels = ["__journal__systemd_unit"]
|
||
target_label = "unit"
|
||
}
|
||
rule {
|
||
source_labels = ["__journal__hostname"]
|
||
target_label = "node"
|
||
}
|
||
}
|
||
|
||
// ── Отправка в Loki ────────────────────────────────────────────
|
||
loki.write "local" {
|
||
endpoint {
|
||
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Grafana: визуализация и алерты
|
||
|
||
### Helm values: `grafana-values.yml`
|
||
|
||
```yaml
|
||
grafana.ini:
|
||
server:
|
||
root_url: http://10.203.0.96:10003
|
||
security:
|
||
admin_user: admin
|
||
auth.anonymous:
|
||
enabled: false
|
||
unified_alerting:
|
||
enabled: true
|
||
alerting:
|
||
enabled: false # legacy alerting отключаем
|
||
|
||
admin:
|
||
existingSecret: grafana-admin-secret
|
||
userKey: admin-user
|
||
passwordKey: admin-password
|
||
|
||
persistence:
|
||
enabled: true
|
||
storageClassName: longhorn
|
||
size: 5Gi
|
||
|
||
# Datasources — provisioning при старте пода (Loki + Prometheus)
|
||
datasources:
|
||
datasources.yaml:
|
||
apiVersion: 1
|
||
datasources:
|
||
- name: Loki
|
||
type: loki
|
||
url: http://loki.monitoring.svc.cluster.local:3100
|
||
access: proxy
|
||
isDefault: true
|
||
jsonData:
|
||
maxLines: 5000
|
||
derivedFields:
|
||
- name: TraceID
|
||
matcherRegex: '"trace_id":"(\w+)"'
|
||
url: '$${__value.raw}'
|
||
urlDisplayLabel: Open Trace
|
||
|
||
# Prometheus datasource — появляется после деплоя kube-prometheus-stack (раздел 6а)
|
||
- name: Prometheus
|
||
type: prometheus
|
||
url: http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090
|
||
access: proxy
|
||
isDefault: false
|
||
jsonData:
|
||
timeInterval: 30s
|
||
|
||
# Автоимпорт дашбордов из ConfigMap с label grafana_dashboard=1
|
||
# Подхватывает: ручные дашборды + ConfigMaps от kube-prometheus-stack (раздел 6а)
|
||
sidecar:
|
||
dashboards:
|
||
enabled: true
|
||
label: grafana_dashboard
|
||
labelValue: "1"
|
||
folder: /var/lib/grafana/dashboards/default
|
||
searchNamespace: ALL # искать ConfigMaps во всех namespace
|
||
datasources:
|
||
enabled: true
|
||
label: grafana_datasource
|
||
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 256Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 512Mi
|
||
```
|
||
|
||
### Создание Secret с паролем администратора
|
||
|
||
```bash
|
||
kubectl create secret generic grafana-admin-secret \
|
||
--from-literal=admin-user=admin \
|
||
--from-literal=admin-password='ВАШ_ПАРОЛЬ' \
|
||
-n monitoring
|
||
```
|
||
|
||
### Ключевые дашборды для Kubernetes
|
||
|
||
| Дашборд | Grafana ID | Назначение |
|
||
|---|---|---|
|
||
| Kubernetes Cluster Logs | 15141 | Логи всех нод и неймспейсов |
|
||
| Loki Dashboard | 13639 | Состояние самого Loki |
|
||
| Pod Logs | 18748 | Логи конкретного пода с фильтрами |
|
||
|
||
### Алерт на высокий уровень ошибок (provisioning)
|
||
|
||
```yaml
|
||
# ConfigMap: grafana-alert-rules, label: grafana_dashboard=1
|
||
apiVersion: 1
|
||
groups:
|
||
- orgId: 1
|
||
name: kubernetes-errors
|
||
folder: Kubernetes
|
||
interval: 1m
|
||
rules:
|
||
- uid: high-error-rate
|
||
title: High Error Rate
|
||
condition: C
|
||
data:
|
||
- refId: A
|
||
model:
|
||
expr: 'sum(rate({namespace=~".+"} |= "error" [5m])) by (namespace)'
|
||
- refId: C
|
||
type: classic_conditions
|
||
model:
|
||
conditions:
|
||
- evaluator:
|
||
type: gt
|
||
params: [10] # > 10 ошибок/сек
|
||
query:
|
||
params: [A]
|
||
for: 2m
|
||
annotations:
|
||
summary: "Высокий уровень ошибок в {{ $labels.namespace }}"
|
||
```
|
||
|
||
---
|
||
|
||
## 6а. Мониторинг ресурсов кластера: Prometheus stack
|
||
|
||
### Что покрывает PLG — и чего не хватает
|
||
|
||
Текущий PLG-стек собирает **только логи**. Метрики (CPU, RAM, диски, сеть, состояние объектов k8s) не собираются вообще. Без метрик невозможно:
|
||
|
||
- Видеть загрузку нод в реальном времени
|
||
- Строить алерты на исчерпание RAM/диска **до** инцидента
|
||
- Отслеживать статус Deployments, PVC, ReplicaSet
|
||
- Мониторить latency и error rate API-сервера
|
||
|
||
### Решение: kube-prometheus-stack
|
||
|
||
`prometheus-community/kube-prometheus-stack` — стандартный Helm chart, который включает:
|
||
|
||
| Компонент | Назначение |
|
||
|---|---|
|
||
| **Prometheus** | Сбор и хранение метрик (time-series DB, pull model) |
|
||
| **node-exporter** | Метрики нод: CPU, RAM, диски, сеть (DaemonSet) |
|
||
| **kube-state-metrics** | Метрики объектов k8s: pod count, deployment status, PVC |
|
||
| **ServiceMonitors** | Auto-discovery k8s компонентов: kubelet, apiserver, etcd |
|
||
| **Dashboard ConfigMaps** | Готовые дашборды Kubernetes для Grafana sidecar |
|
||
|
||
Встроенную Grafana в chart отключаем (`grafana.enabled: false`) — используем уже развёрнутую. ConfigMaps с дашбордами создаём через `forceDeployDashboards: true` — Grafana sidecar подхватывает их автоматически.
|
||
|
||
### Обновлённая архитектура
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ k8s-worker-01 / k8s-master-01 │
|
||
│ │
|
||
│ [Pod stdout] → /var/log/pods/ │
|
||
│ [node-exporter DaemonSet] ← CPU/RAM/диск/сеть (метрики нод) │
|
||
│ [Grafana Alloy DaemonSet] ← файлы + journald (логи) │
|
||
└──────────┬────────────────────────┬─────────────────────────────┘
|
||
│ HTTP push (logs) │ HTTP scrape (metrics)
|
||
▼ ▼
|
||
┌──────────────────┐ ┌────────────────────────────┐
|
||
│ Loki │ │ Prometheus │
|
||
│ (monitoring) │ │ (monitoring) │
|
||
│ S3 → MinIO │ │ PVC 20Gi (longhorn) │
|
||
└────────┬─────────┘ └────────────┬───────────────┘
|
||
│ datasource │ datasource
|
||
└────────────┬─────────────┘
|
||
▼
|
||
┌────────────────────────────┐
|
||
│ Grafana (monitoring) │
|
||
│ ├── Loki datasource │ ← логи
|
||
│ ├── Prometheus datasource │ ← метрики
|
||
│ ├── Logs dashboards │ ← из ConfigMaps
|
||
│ └── K8s cluster dashboards│ ← из kube-prometheus-stack
|
||
│ Traefik → :10003 │
|
||
└────────────────────────────┘
|
||
```
|
||
|
||
### Helm values: `prometheus-values.yml`
|
||
|
||
```yaml
|
||
# Встроенную Grafana отключаем — используем свою
|
||
grafana:
|
||
enabled: false
|
||
# Создать ConfigMaps с готовыми k8s дашбордами для нашего Grafana sidecar
|
||
forceDeployDashboards: true
|
||
sidecar:
|
||
dashboards:
|
||
label: grafana_dashboard
|
||
labelValue: "1"
|
||
|
||
prometheus:
|
||
prometheusSpec:
|
||
# Хранение метрик на Longhorn PVC
|
||
storageSpec:
|
||
volumeClaimTemplate:
|
||
spec:
|
||
storageClassName: longhorn
|
||
accessModes: ["ReadWriteOnce"]
|
||
resources:
|
||
requests:
|
||
storage: 20Gi
|
||
retention: 30d
|
||
scrapeInterval: 30s
|
||
evaluationInterval: 30s
|
||
|
||
# Разрешить сбор метрик из всех namespace
|
||
ruleSelectorNilUsesHelmValues: false
|
||
serviceMonitorSelectorNilUsesHelmValues: false
|
||
podMonitorSelectorNilUsesHelmValues: false
|
||
|
||
alertmanager:
|
||
enabled: false # алерты через Grafana Alerting
|
||
|
||
nodeExporter:
|
||
enabled: true
|
||
|
||
kubeStateMetrics:
|
||
enabled: true
|
||
|
||
# Rocky Linux 9: явно указать IP control plane для etcd/scheduler/controller-manager
|
||
kubeControllerManager:
|
||
enabled: true
|
||
endpoints:
|
||
- 10.203.0.97
|
||
service:
|
||
enabled: true
|
||
port: 10257
|
||
targetPort: 10257
|
||
|
||
kubeScheduler:
|
||
enabled: true
|
||
endpoints:
|
||
- 10.203.0.97
|
||
service:
|
||
enabled: true
|
||
port: 10259
|
||
targetPort: 10259
|
||
|
||
kubeEtcd:
|
||
enabled: true
|
||
endpoints:
|
||
- 10.203.0.97
|
||
service:
|
||
enabled: true
|
||
port: 2381
|
||
targetPort: 2381
|
||
|
||
kubeProxy:
|
||
enabled: true
|
||
endpoints:
|
||
- 10.203.0.97
|
||
- 10.203.0.96
|
||
```
|
||
|
||
> **Rocky Linux 9 / kubeadm gotcha:** kubeadm по умолчанию настраивает `controller-manager` и `scheduler` слушать только `127.0.0.1`. Если метрики этих компонентов нужны — потребуется патч `/etc/kubernetes/manifests/kube-controller-manager.yaml` и `kube-scheduler.yaml`: заменить `--bind-address=127.0.0.1` на `--bind-address=0.0.0.0`. Остальные компоненты (kubelet, node-exporter, kube-state-metrics, etcd на порту 2381) работают без изменений.
|
||
|
||
### Готовые дашборды после деплоя
|
||
|
||
`forceDeployDashboards: true` создаёт ConfigMaps в namespace `monitoring`. Grafana sidecar подхватывает их автоматически при старте:
|
||
|
||
| Дашборд | Что показывает |
|
||
|---|---|
|
||
| Kubernetes / Compute Resources / Cluster | CPU/RAM/сеть по неймспейсам |
|
||
| Kubernetes / Compute Resources / Node (Pods) | Ресурсы по подам на конкретной ноде |
|
||
| Kubernetes / Compute Resources / Workload | Потребление по Deployment/StatefulSet |
|
||
| Node Exporter / Nodes | CPU, RAM, диски, сеть по нодам |
|
||
| Kubernetes / Persistent Volumes | Статус и заполнение PVC |
|
||
| Kubernetes / API server | Latency, error rate, RPS API-сервера |
|
||
| Kubernetes / Kubelet | Состояние kubelet на каждой ноде |
|
||
| Kubernetes / etcd | Состояние etcd, latency, лидер |
|
||
|
||
### Datasource Prometheus в Grafana
|
||
|
||
Добавляется в `grafana-values.yml.j2` рядом с Loki:
|
||
|
||
```yaml
|
||
datasources:
|
||
datasources.yaml:
|
||
apiVersion: 1
|
||
datasources:
|
||
- name: Loki
|
||
type: loki
|
||
url: http://loki.monitoring.svc.cluster.local:3100
|
||
access: proxy
|
||
isDefault: true
|
||
jsonData:
|
||
maxLines: 5000
|
||
|
||
- name: Prometheus
|
||
type: prometheus
|
||
url: http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090
|
||
access: proxy
|
||
isDefault: false
|
||
jsonData:
|
||
timeInterval: 30s
|
||
```
|
||
|
||
### Проверка после деплоя
|
||
|
||
```bash
|
||
# Статус подов Prometheus stack
|
||
kubectl get pod -n monitoring -l release=kube-prometheus-stack
|
||
|
||
# Метрики node-exporter доступны
|
||
kubectl exec -n monitoring -l app.kubernetes.io/name=prometheus -- \
|
||
wget -qO- 'http://localhost:9090/api/v1/query?query=up' | python3 -m json.tool
|
||
|
||
# ConfigMaps с дашбордами созданы (должно быть ~20+ штук)
|
||
kubectl get configmap -n monitoring -l grafana_dashboard=1
|
||
|
||
# Prometheus видит все таргеты (все должны быть UP)
|
||
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090
|
||
# → http://localhost:9090/targets
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Grafana as Code: хранение настроек в GitLab
|
||
|
||
### Проблема
|
||
|
||
Дашборды и datasource, созданные через UI Grafana, хранятся в SQLite внутри пода. При пересоздании пода — настройки теряются.
|
||
|
||
### Решение: отдельный GitLab-проект `k8s/grafana-config`
|
||
|
||
```
|
||
gitlab.gigacoms.info/k8s/grafana-config
|
||
├── dashboards/
|
||
│ ├── kubernetes-logs.json # экспорт из UI: Share → Export JSON
|
||
│ ├── loki-overview.json
|
||
│ └── pod-errors.json
|
||
├── provisioning/
|
||
│ ├── datasources/
|
||
│ │ └── loki.yaml
|
||
│ ├── dashboards/
|
||
│ │ └── provider.yaml
|
||
│ └── alerting/
|
||
│ ├── rules.yaml
|
||
│ └── contact-points.yaml
|
||
└── README.md
|
||
```
|
||
|
||
> Секреты (пароли, токены) — **не хранить в Git**. Создаются через `kubectl create secret` или GitLab CI Variables.
|
||
|
||
### Flux: автосинхронизация ConfigMap из grafana-config репо
|
||
|
||
Flux уже запущен в кластере (`flux bootstrap gitlab`). Добавить синхронизацию grafana-config:
|
||
|
||
```yaml
|
||
# clusters/production/monitoring/grafana-config-source.yaml
|
||
apiVersion: source.toolkit.fluxcd.io/v1
|
||
kind: GitRepository
|
||
metadata:
|
||
name: grafana-config
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 1m
|
||
url: https://gitlab.gigacoms.info/k8s/grafana-config.git
|
||
secretRef:
|
||
name: flux-system # тот же PAT что используется для fleet-repo
|
||
ref:
|
||
branch: main
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: grafana-config
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
path: ./
|
||
prune: true
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: grafana-config
|
||
targetNamespace: monitoring
|
||
```
|
||
|
||
Grafana sidecar подхватывает любой ConfigMap с label `grafana_dashboard: "1"` — Flux обновляет ConfigMap, Grafana перечитывает дашборды без рестарта.
|
||
|
||
### Рабочий процесс изменения дашборда
|
||
|
||
```
|
||
Grafana UI → изменил дашборд
|
||
↓
|
||
Share → Export → Save to file → dashboard.json
|
||
↓
|
||
git commit в grafana-config репо
|
||
↓
|
||
Flux (~1 мин) → ConfigMap обновляется
|
||
↓
|
||
Grafana sidecar (~30 сек) → дашборд применён
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Интеграция с Traefik
|
||
|
||
Добавить в `inventory/prod/group_vars/traefik.yml`:
|
||
|
||
```yaml
|
||
traefik_port_map:
|
||
# ... существующие записи (10001 dashboard, 10002 longhorn, 10005 minio-api, 10006 minio-console) ...
|
||
|
||
- name: grafana
|
||
description: "Grafana (Logs & Metrics)"
|
||
port: 10003
|
||
backend:
|
||
namespace: monitoring
|
||
service: grafana
|
||
port: 80 # service port (targetPort: 3000 — внутренний порт контейнера)
|
||
scheme: http
|
||
basicauth:
|
||
enabled: false # Grafana имеет собственную аутентификацию
|
||
```
|
||
|
||
После добавления запустить `setup_traefik.yml`.
|
||
|
||
**Grafana:** http://10.203.0.96:10003
|
||
|
||
> Порт 10003 выбран как следующий свободный после 10001 (dashboard), 10002 (longhorn), 10005 (minio-api), 10006 (minio-console).
|
||
|
||
---
|
||
|
||
## 9. Ansible-роли и плейбуки
|
||
|
||
### Структура роли `roles/logging/`
|
||
|
||
```
|
||
roles/logging/
|
||
├── defaults/main.yml
|
||
├── tasks/
|
||
│ ├── main.yml
|
||
│ ├── namespace.yml — создать namespace monitoring
|
||
│ ├── minio-user.yml — создать пользователя loki в MinIO + Secret
|
||
│ ├── loki.yml — helm install loki (S3 backend)
|
||
│ ├── alloy.yml — helm install grafana/alloy (DaemonSet)
|
||
│ ├── prometheus.yml — helm install kube-prometheus-stack (метрики + дашборды)
|
||
│ └── grafana.yml — helm install grafana (Loki + Prometheus datasources)
|
||
└── templates/
|
||
├── loki-values.yml.j2
|
||
├── alloy-values.yml.j2
|
||
├── prometheus-values.yml.j2
|
||
└── grafana-values.yml.j2
|
||
```
|
||
|
||
### `defaults/main.yml`
|
||
|
||
```yaml
|
||
logging_namespace: monitoring
|
||
|
||
loki_chart_version: "7.0.0"
|
||
alloy_chart_version: "1.8.2"
|
||
grafana_chart_version: "10.5.15"
|
||
prometheus_stack_chart_version: "68.4.4" # prometheus-community/kube-prometheus-stack
|
||
|
||
# MinIO — уже в кластере, только реквизиты
|
||
loki_minio_endpoint: "http://minio.minio.svc.cluster.local:9000"
|
||
loki_minio_bucket: "loki-chunks"
|
||
loki_minio_user: "loki"
|
||
loki_minio_secret_name: "loki-minio-secret"
|
||
|
||
loki_retention_days: 31
|
||
loki_wal_storage_size: "10Gi"
|
||
loki_wal_storage_class: longhorn
|
||
|
||
prometheus_storage_size: "20Gi"
|
||
prometheus_storage_class: longhorn
|
||
prometheus_retention: "30d"
|
||
|
||
grafana_storage_size: "5Gi"
|
||
grafana_storage_class: longhorn
|
||
grafana_admin_secret: "grafana-admin-secret"
|
||
grafana_root_url: "http://10.203.0.96:10003"
|
||
```
|
||
|
||
### `tasks/minio-user.yml`
|
||
|
||
```yaml
|
||
- name: Loki | Create MinIO user for loki
|
||
kubernetes.core.k8s_exec:
|
||
namespace: minio
|
||
pod: "{{ minio_pod.resources[0].metadata.name }}"
|
||
command: >
|
||
mc admin user add local {{ loki_minio_user }} {{ loki_minio_password }}
|
||
no_log: true
|
||
|
||
- name: Loki | Attach loki-policy in MinIO
|
||
kubernetes.core.k8s_exec:
|
||
namespace: minio
|
||
pod: "{{ minio_pod.resources[0].metadata.name }}"
|
||
command: mc admin policy attach local loki-policy --user {{ loki_minio_user }}
|
||
|
||
- name: Loki | Create Secret with MinIO credentials
|
||
kubernetes.core.k8s:
|
||
state: present
|
||
definition:
|
||
apiVersion: v1
|
||
kind: Secret
|
||
metadata:
|
||
name: "{{ loki_minio_secret_name }}"
|
||
namespace: "{{ logging_namespace }}"
|
||
stringData:
|
||
AWS_ACCESS_KEY_ID: "{{ loki_minio_user }}"
|
||
AWS_SECRET_ACCESS_KEY: "{{ loki_minio_password }}"
|
||
no_log: true
|
||
```
|
||
|
||
### `tasks/main.yml`
|
||
|
||
```yaml
|
||
---
|
||
- ansible.builtin.import_tasks: namespace.yml
|
||
- ansible.builtin.import_tasks: minio-user.yml
|
||
- ansible.builtin.import_tasks: loki.yml
|
||
- ansible.builtin.import_tasks: alloy.yml
|
||
- ansible.builtin.import_tasks: prometheus.yml # метрики + готовые дашборды кластера
|
||
- ansible.builtin.import_tasks: grafana.yml
|
||
```
|
||
|
||
### `tasks/prometheus.yml`
|
||
|
||
```yaml
|
||
---
|
||
- name: Prometheus | Add prometheus-community Helm repo
|
||
kubernetes.core.helm_repository:
|
||
name: prometheus-community
|
||
repo_url: https://prometheus-community.github.io/helm-charts
|
||
|
||
- name: Prometheus | Render values
|
||
ansible.builtin.template:
|
||
src: prometheus-values.yml.j2
|
||
dest: /tmp/prometheus-values.yml
|
||
mode: "0600"
|
||
|
||
- name: Prometheus | Install kube-prometheus-stack
|
||
kubernetes.core.helm:
|
||
name: kube-prometheus-stack
|
||
chart_ref: prometheus-community/kube-prometheus-stack
|
||
chart_version: "{{ prometheus_stack_chart_version }}"
|
||
release_namespace: "{{ logging_namespace }}"
|
||
create_namespace: false
|
||
values_files:
|
||
- /tmp/prometheus-values.yml
|
||
wait: true
|
||
wait_timeout: 600s
|
||
```
|
||
|
||
### `templates/prometheus-values.yml.j2`
|
||
|
||
```yaml
|
||
grafana:
|
||
enabled: false
|
||
forceDeployDashboards: true
|
||
sidecar:
|
||
dashboards:
|
||
label: grafana_dashboard
|
||
labelValue: "1"
|
||
|
||
prometheus:
|
||
prometheusSpec:
|
||
storageSpec:
|
||
volumeClaimTemplate:
|
||
spec:
|
||
storageClassName: {{ prometheus_storage_class }}
|
||
accessModes: ["ReadWriteOnce"]
|
||
resources:
|
||
requests:
|
||
storage: {{ prometheus_storage_size }}
|
||
retention: {{ prometheus_retention }}
|
||
scrapeInterval: 30s
|
||
evaluationInterval: 30s
|
||
ruleSelectorNilUsesHelmValues: false
|
||
serviceMonitorSelectorNilUsesHelmValues: false
|
||
podMonitorSelectorNilUsesHelmValues: false
|
||
|
||
alertmanager:
|
||
enabled: false
|
||
|
||
nodeExporter:
|
||
enabled: true
|
||
|
||
kubeStateMetrics:
|
||
enabled: true
|
||
|
||
kubeControllerManager:
|
||
enabled: true
|
||
endpoints:
|
||
{% for host in groups['control_plane'] %}
|
||
- {{ hostvars[host]['ansible_host'] }}
|
||
{% endfor %}
|
||
service:
|
||
enabled: true
|
||
port: 10257
|
||
targetPort: 10257
|
||
|
||
kubeScheduler:
|
||
enabled: true
|
||
endpoints:
|
||
{% for host in groups['control_plane'] %}
|
||
- {{ hostvars[host]['ansible_host'] }}
|
||
{% endfor %}
|
||
service:
|
||
enabled: true
|
||
port: 10259
|
||
targetPort: 10259
|
||
|
||
kubeEtcd:
|
||
enabled: true
|
||
endpoints:
|
||
{% for host in groups['control_plane'] %}
|
||
- {{ hostvars[host]['ansible_host'] }}
|
||
{% endfor %}
|
||
service:
|
||
enabled: true
|
||
port: 2381
|
||
targetPort: 2381
|
||
|
||
kubeProxy:
|
||
enabled: true
|
||
endpoints:
|
||
{% for host in groups['k8s_cluster'] %}
|
||
- {{ hostvars[host]['ansible_host'] }}
|
||
{% endfor %}
|
||
```
|
||
|
||
### `playbooks/setup_logging.yml`
|
||
|
||
```yaml
|
||
---
|
||
- name: Deploy PLG + Prometheus monitoring stack
|
||
hosts: manager_nodes
|
||
become: false
|
||
roles:
|
||
- logging
|
||
```
|
||
|
||
### `.gitlab-ci.yml` — новый job
|
||
|
||
```yaml
|
||
setup/logging:
|
||
stage: setup
|
||
script:
|
||
- ansible-playbook -i inventory/prod playbooks/setup_logging.yml
|
||
rules:
|
||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||
when: manual
|
||
resource_group: production
|
||
```
|
||
|
||
### GitLab CI/CD Variables (добавить)
|
||
|
||
| Переменная | Описание |
|
||
|---|---|
|
||
| `LOKI_MINIO_PASSWORD` | Пароль пользователя `loki` в MinIO |
|
||
| `GRAFANA_ADMIN_PASSWORD` | Пароль администратора Grafana |
|
||
|
||
---
|
||
|
||
## 10. Масштабирование до 10 нод / 100 сервисов
|
||
|
||
### Promtail
|
||
|
||
Не требует изменений — DaemonSet автоматически запустится на новых нодах. Убедиться, что `tolerations` включают control-plane taint (уже в конфиге выше).
|
||
|
||
### Loki: переход на distributed-mode
|
||
|
||
Триггер для перехода: объём > 50 GB/day или > 50 одновременных запросов Grafana.
|
||
|
||
```yaml
|
||
# loki-values.yml — distributed режим
|
||
loki:
|
||
deploymentMode: Distributed
|
||
|
||
ingester:
|
||
replicas: 3
|
||
querier:
|
||
replicas: 2
|
||
distributor:
|
||
replicas: 2
|
||
compactor:
|
||
replicas: 1
|
||
queryFrontend:
|
||
replicas: 2
|
||
|
||
# S3 backend остаётся тем же — только меняется deploymentMode
|
||
loki:
|
||
storage:
|
||
type: s3
|
||
s3:
|
||
endpoint: http://minio.minio.svc.cluster.local:9000
|
||
# ... те же параметры MinIO
|
||
```
|
||
|
||
**Преимущество текущей архитектуры:** переход single-binary → distributed не требует миграции данных — бакет `loki-chunks` в MinIO остаётся тем же, меняется только Helm release Loki.
|
||
|
||
### Расчёт ресурсов при 10 нодах / 100 сервисах
|
||
|
||
```
|
||
100 сервисов × средний lograte 2 KB/s = 17 GB/day (raw)
|
||
Сжатие Loki ~10:1 → 1.7 GB/day
|
||
31 день → ~53 GB в loki-chunks бакете MinIO
|
||
|
||
MinIO PVC = 1 TiB → запас ~19x от текущей нагрузки
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Переезд на Graylog
|
||
|
||
### Когда переезд оправдан
|
||
|
||
| Триггер | Описание |
|
||
|---|---|
|
||
| Нужен поиск по содержимому | Loki: grep по чанкам; Graylog: полнотекстовый индекс OpenSearch |
|
||
| Нужны встроенные Pipelines | Нормализация, маскирование PII без Logstash |
|
||
| Встроенные алерты без Grafana | Graylog имеет собственный alerting |
|
||
| Объём > 100 GB/day | Loki становится менее эффективным для full-text search |
|
||
|
||
### Архитектура Graylog
|
||
|
||
```
|
||
[Grafana Alloy / Fluent Bit] ← агент остаётся (Promtail EOL)
|
||
↓ GELF HTTP (port 12202)
|
||
[Graylog Server]
|
||
↓ ↓
|
||
[OpenSearch] [MongoDB]
|
||
(хранит логи) (конфиг, метаданные)
|
||
↓
|
||
Traefik → port 10004 → http://10.203.0.96:10004
|
||
```
|
||
|
||
### MinIO при переезде на Graylog
|
||
|
||
MinIO продолжает использоваться — OpenSearch поддерживает S3 snapshot repository. Бэкапы индексов OpenSearch можно хранить в бакете `backups` MinIO:
|
||
|
||
```bash
|
||
# Настройка S3 snapshot repository в OpenSearch
|
||
curl -X PUT http://opensearch:9200/_snapshot/minio-backup \
|
||
-H 'Content-Type: application/json' -d '{
|
||
"type": "s3",
|
||
"settings": {
|
||
"bucket": "backups",
|
||
"endpoint": "minio.minio.svc.cluster.local:9000",
|
||
"protocol": "http",
|
||
"path_style_access": "true"
|
||
}
|
||
}'
|
||
```
|
||
|
||
### Стратегия миграции: Dual-write
|
||
|
||
Alloy поддерживает несколько `loki.write` и `otelcol.exporter` — dual-write настраивается в его конфиге:
|
||
|
||
```alloy
|
||
// alloy config — dual-write период (2 недели)
|
||
loki.write "loki_backend" {
|
||
endpoint {
|
||
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
|
||
}
|
||
}
|
||
|
||
// Экспорт в Graylog через OTLP или loki.write с GELF-форматом
|
||
otelcol.exporter.otlphttp "graylog" {
|
||
client {
|
||
endpoint = "http://graylog.monitoring.svc.cluster.local:4318"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Порядок действий при переезде
|
||
|
||
```
|
||
1. Развернуть OpenSearch + MongoDB + Graylog (параллельно с PLG)
|
||
2. Настроить Graylog inputs (OTLP HTTP port 4318 или GELF HTTP port 12202)
|
||
3. Переключить Grafana Alloy на dual-write (loki.write + otelcol.exporter.otlphttp)
|
||
4. Настроить Streams, Pipelines, алерты в Graylog
|
||
5. Добавить порт 10004 в traefik_port_map → http://10.203.0.96:10004
|
||
6. Переключить команду на Graylog UI
|
||
7. Через 2 недели: убрать loki.write из конфига Alloy
|
||
8. Удалить Loki Helm release (бакет loki-chunks оставить для истории)
|
||
9. Grafana остаётся для метрик Prometheus; Alloy продолжает работать как агент Graylog
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Итоговое сравнение и рекомендации
|
||
|
||
| Критерий | ALG (Alloy + Loki + Grafana) | Graylog |
|
||
|---|---|---|
|
||
| RAM стека | ~1 GB | ~6-8 GB (JVM: Graylog + OpenSearch + MongoDB) |
|
||
| Агент сбора | **Grafana Alloy** (активен, замена EOL Promtail) | Grafana Alloy / Fluent Bit |
|
||
| Поиск по тексту | LogQL grep по чанкам (медленнее) | Полнотекстовый индекс OpenSearch (быстрее) |
|
||
| Обработка логов | Alloy pipeline stages (River syntax) | Встроенные Pipelines (мощнее) |
|
||
| Алерты | Grafana Alerting | Встроены в Graylog |
|
||
| Единый UI с метриками | Да (Prometheus + Loki + трейсы в Grafana) | Нет (отдельный UI) |
|
||
| Переход S3 → distributed | Без миграции данных | — |
|
||
| Сложность эксплуатации | Низкая | Высокая |
|
||
| EOL-риски | Loki ✅, Alloy ✅ (Promtail заменён) | Нет |
|
||
|
||
### Рекомендуемый путь
|
||
|
||
```
|
||
Сейчас (1-2 ноды)
|
||
└── Loki (логи) + Prometheus/kube-prometheus-stack (метрики) + Grafana (единый UI)
|
||
└── Alloy DaemonSet собирает логи; node-exporter + kube-state-metrics собирают метрики
|
||
└── Grafana: Loki + Prometheus datasources provisioned, ~20 готовых k8s дашбордов
|
||
└── Promtail НЕ использовать — EOL с 2026-03-02
|
||
|
||
При росте до 5+ нод
|
||
└── Loki: distributed (только смена deploymentMode, данные в MinIO без миграции)
|
||
└── Prometheus: увеличить PVC или перейти на Thanos для long-term storage
|
||
└── Alloy / node-exporter: DaemonSet автоматически разворачивается на новых нодах
|
||
|
||
При требовании глубокого поиска или 10+ нод
|
||
└── Параллельный запуск Graylog (dual-write 2 недели через Alloy)
|
||
└── Grafana остаётся для метрик Prometheus
|
||
└── MinIO — snapshot repository для OpenSearch
|
||
└── Alloy продолжает работать как единый агент (Loki → Graylog смена в конфиге)
|
||
```
|
||
|
||
### Следующие шаги
|
||
|
||
**Подготовка (ручные действия перед запуском playbook):**
|
||
- [ ] Создать Secret `grafana-admin-secret` в namespace `monitoring`
|
||
- [ ] Создать GitLab-проект `k8s/grafana-config` на `gitlab.gigacoms.info` (для хранения дашбордов — раздел 7)
|
||
|
||
**Ansible-роль `roles/logging/` (по структуре из раздела 9):**
|
||
- [ ] Написать `tasks/namespace.yml`
|
||
- [ ] Написать `tasks/minio-user.yml` + создать пользователя `loki` в MinIO
|
||
- [ ] Написать `tasks/loki.yml` + шаблон `loki-values.yml.j2`
|
||
- [ ] Написать `tasks/alloy.yml` + шаблон `alloy-values.yml.j2` (**Alloy, не Promtail**)
|
||
- [ ] Написать `tasks/prometheus.yml` + шаблон `prometheus-values.yml.j2` (метрики + дашборды кластера)
|
||
- [ ] Написать `tasks/grafana.yml` + шаблон `grafana-values.yml.j2` (Loki + Prometheus datasources)
|
||
|
||
**CI/CD:**
|
||
- [ ] Добавить `grafana` в `traefik_port_map` (порт 10003) и запустить `setup_traefik.yml`
|
||
- [ ] Добавить переменные `LOKI_MINIO_PASSWORD`, `GRAFANA_ADMIN_PASSWORD` в GitLab CI/CD Variables
|
||
- [ ] Добавить job `setup/logging` в `.gitlab-ci.yml` с `resource_group: production`
|
||
|
||
**Проверка после деплоя:**
|
||
- [ ] Grafana открывается на http://10.203.0.96:10003
|
||
- [ ] В Grafana → Connections → Data sources: видны Loki и Prometheus со статусом OK
|
||
- [ ] В Grafana → Dashboards: папка `kubernetes-mixin` с ~20 готовыми дашбордами кластера
|
||
- [ ] Prometheus Targets: все таргеты в состоянии UP (`kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090`)
|