1. Choisir son agent #
Alloy est le standard actuel.
Les options #
| Agent | Statut | Remarque |
|---|---|---|
| Grafana Alloy | Recommandé | Pipelines loki.source.*, loki.process, loki.write |
| Promtail | Déprécié (fin de vie annoncée) | À migrer avec alloy convert --source-format=promtail |
| Docker Loki driver | Pratique en local | Plugin Docker, bloque si Loki est indisponible sans réglage |
| OpenTelemetry Collector | Alternatif | Exporter otlphttp vers l'endpoint OTLP de Loki (/otlp) |
| Fluent Bit / Vector | Alternatifs | Plugins de sortie Loki |
# Migrer une configuration Promtail existante
alloy convert --source-format=promtail --output=config.alloy /etc/promtail/config.yml2. Pipelines Alloy courants #
Fichiers, journald, Docker et Kubernetes.
Fichiers de logs #
local.file_match "app" {
path_targets = [{"__path__" = "/var/log/myapp/*.log", "job" = "myapp", "env" = "prod"}]
}
loki.source.file "app" {
targets = local.file_match.app.targets
forward_to = [loki.process.app.receiver]
}
loki.process "app" {
forward_to = [loki.write.default.receiver]
stage.json { expressions = { level = "level", msg = "message" } }
stage.labels { values = { level = "" } }
}
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}journald et conteneurs Docker #
loki.source.journal "system" {
forward_to = [loki.write.default.receiver]
relabel_rules = loki.relabel.journal.rules
labels = { job = "systemd-journal" }
}
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
}
discovery.docker "local" { host = "unix:///var/run/docker.sock" }
discovery.relabel "containers" {
targets = discovery.docker.local.targets
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "container"
}
}
loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.containers.output
forward_to = [loki.write.default.receiver]
}Pods Kubernetes #
discovery.kubernetes "pods" { role = "pod" }
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_kubernetes_io_name"] target_label = "app" }
}
loki.source.kubernetes "pods" {
targets = discovery.relabel.pod_logs.output
forward_to = [loki.write.default.receiver]
}3. Les bons labels #
La règle d'or de Loki.
Peu de labels, à faible cardinalité #
Chaque combinaison unique de labels crée un flux distinct. Trop de valeurs différentes (identifiant utilisateur, adresse IP, trace ID) provoquent une explosion du nombre de flux : ingesters saturés, requêtes lentes, coûts élevés.
| ✅ À utiliser comme labels | ❌ À éviter comme labels |
|---|---|
app, namespace, env, cluster | user_id, session_id, trace_id |
level (quelques valeurs) | ip, url complète, request_id |
job, container | Horodatages, identifiants de commande |
# Mettre les valeurs à forte cardinalité en "structured metadata" plutôt qu'en label
loki.process "app" {
forward_to = [loki.write.default.receiver]
stage.json { expressions = { trace_id = "trace_id" } }
stage.structured_metadata { values = { trace_id = "" } }
}
# Recherche : {app="api"} | trace_id="4bf92f3577b34da6a3ce929d0e0e4736" Erreur Courante : Un label dont le nombre de valeurs dépasse quelques centaines est un signal d'alarme : déplacez-le vers la structured metadata ou le contenu de la ligne.
4. OTLP natif #
Envoyer directement des logs OpenTelemetry.
Endpoint OTLP de Loki #
# Collecteur OpenTelemetry ou Alloy : exporter OTLP HTTP vers Loki
otelcol.exporter.otlphttp "loki" {
client { endpoint = "http://loki:3100/otlp" }
}
# SDK d'application
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://loki:3100/otlp/v1/logs
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobufÀ savoir : Loki convertit les attributs de ressource OTel (service.name,k8s.namespace.name...) en labels indexés et le reste en structured metadata.
