1. Les blocs de configuration #
Attributs, blocs et expressions en langage River.
Comprendre la syntaxe #
Votre alloy config s'écrit dans un fichier (généralement config.alloy) utilisant une syntaxe déclarative fortement inspirée de Terraform. Dans cette alloy configuration, un composant est défini par un nom et un identifiant. À l'intérieur du bloc, vous définissez des attributs (ex: url = "http://...") et potentiellement des sous-blocs. Les composants exportent des valeurs qui peuvent être référencées par d'autres composants (comme alloy prometheus ou prometheus.exporter.self), créant ainsi le pipeline.
prometheus.scrape "default" {
targets = [{"__address__" = "localhost:9090"}]
forward_to = [prometheus.remote_write.mimir.receiver]
}Gestion des virgules #
En River, la gestion des virgules dans les tableaux et les définitions est très souple. Il est recommandé d'ajouter une virgule après le dernier élément d'une liste ou d'une configuration multi-lignes (trailing comma). Cela facilite l'ajout d'éléments ultérieurs et réduit significativement les risques de conflits lors des fusions de code (merges Git).
prometheus.exporter.unix "example" {
enable_collectors = [
"cpu",
"meminfo",
"diskstats",
"textfile",
]
textfile {
directory = "/var/lib/node_exporter/textfile_collector"
}
}2. River vs HCL #
Ce que la syntaxe Alloy (ex-River) emprunte à HCL, ce qui l'en distingue et pourquoi elle est évaluée en continu.
De River à la « syntaxe Alloy » #
Le langage s'appelait River dans Grafana Agent Flow. Depuis Grafana Alloy 1.0, Grafana parle simplement de syntaxe de configuration Alloy et les fichiers portent l'extension .alloy. Le mot « River » reste très présent dans la documentation ancienne, les articles et les recherches : c'est le même langage.
Sa syntaxe est volontairement proche du HCL (HashiCorp Configuration Language) de Terraform : un ingénieur qui écrit du Terraform lit une configuration Alloy sans effort. Mais les deux langages ne servent pas le même but. HCL décrit un état cible à atteindre une fois ; Alloy décrit un pipeline de télémétrie qui tourne en permanence.
Ce que la syntaxe Alloy emprunte à HCL #
| Élément | HCL (Terraform) | Syntaxe Alloy |
|---|---|---|
| Bloc avec étiquette | resource "aws_s3_bucket" "logs" { … } | prometheus.scrape "app" { … } |
| Attribut | bucket = "logs" | scrape_interval = "30s" |
| Sous-bloc | lifecycle { … } | endpoint { … }, rule { … } |
| Référence | aws_s3_bucket.logs.arn | prometheus.remote_write.mimir.receiver |
| Commentaires | //, /* */ et # | // et /* */ uniquement |
| Formatage | terraform fmt | alloy fmt |
Dans les deux langages, une référence crée une dépendance : le composant qui lit une valeur dépend du composant qui l'exporte. C'est ce qui permet de construire le graphe sans indiquer d'ordre d'exécution.
Ce qui les distingue #
| Sujet | HCL (Terraform) | Syntaxe Alloy |
|---|---|---|
| Évaluation | Une fois, au plan / apply | En continu : un composant est réévalué dès qu’une valeur dont il dépend change |
| Variables | Blocs variable et locals | Pas de bloc variable : les valeurs viennent de composants (local.file, remote.http…) ou de fonctions (sys.env) |
| Chaînes | Interpolation "logs-${var.env}" | Pas d’interpolation : string.format() ou l’opérateur + |
| Répétition | Méta-arguments count et for_each | Pas de boucle : on réutilise avec declare (composant personnalisé) et import.* |
| Étiquettes | Une ou plusieurs (resource "type" "nom") | Une seule étiquette par composant, obligatoire et unique pour un même type |
| Types | string, number, bool, list, map, object… | Mêmes types de base, plus secret (jamais affiché) et capsule (récepteurs, cibles) |
| Séparateurs | Retour à la ligne entre les éléments d’un objet | Virgules entre les éléments des tableaux et objets (virgule finale autorisée) |
Exemple côte à côte #
Le même besoin, une valeur qui dépend de la région, exprimé dans les deux langages :
# Terraform (HCL) : évalué une fois, au plan / apply
variable "region" {
default = "eu-west-3"
}
locals {
bucket_name = "logs-${var.region}" # interpolation ${...}
}
resource "aws_s3_bucket" "logs" {
count = 2 # méta-argument
bucket = "${local.bucket_name}-${count.index}"
}// Alloy : réévalué en continu, à chaque changement d'une valeur exportée
local.file "region" {
filename = "/etc/alloy/region"
}
prometheus.remote_write "mimir" {
endpoint {
// pas d'interpolation : string.format() ou l'opérateur +
url = string.format("https://mimir-%s.example.com/api/v1/push", local.file.region.content)
}
external_labels = {
region = local.file.region.content,
host = constants.hostname,
}
}Côté Terraform, si la région change, il faut relancer terraform apply. Côté Alloy, il suffit de modifier le fichier /etc/alloy/region : local.file le relit, exporte la nouvelle valeur, et prometheus.remote_write se reconfigure tout seul.
L'évaluation continue : le graphe de composants #
Au démarrage, Alloy construit un graphe orienté acyclique (DAG) à partir des références entre composants. Chaque fois qu'un composant exporte une nouvelle valeur, seuls ses dépendants sont réévalués. C'est indispensable pour l'observabilité, où les cibles apparaissent et disparaissent en permanence (pods Kubernetes, instances cloud).
// 1. Découverte : la liste des pods change en permanence
discovery.kubernetes "pods" {
role = "pod"
}
// 2. Filtrage : ne garder que les pods annotés
discovery.relabel "annotated" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_scrape"]
regex = "true"
action = "keep"
}
}
// 3. Collecte : ses cibles suivent automatiquement les étapes 1 et 2
prometheus.scrape "pods" {
targets = discovery.relabel.annotated.output
forward_to = [prometheus.remote_write.mimir.receiver]
}Quand un pod est créé, discovery.kubernetes exporte une nouvelle liste ; discovery.relabel est réévalué, puis prometheus.scrape reçoit ses nouvelles cibles. Aucun redémarrage, aucune action manuelle. L'interface d'Alloy (port 12345) affiche ce graphe et l'état de chaque composant.
Piège classique : une référence circulaire (A lit B qui lit A) est refusée au chargement. Le message d'erreur indique les composants concernés ; découpez alors le traitement en deux composants distincts.
Outils et commandes à connaître #
# Vérifier et formater la syntaxe (comme terraform fmt)
alloy fmt config.alloy
alloy fmt --write config.alloy
# Lancer Alloy avec l'interface de débogage (graphe des composants)
alloy run config.alloy --server.http.listen-addr=0.0.0.0:12345
# Recharger la configuration sans redémarrer
curl -X POST http://localhost:12345/-/reload
# Convertir une configuration existante vers la syntaxe Alloy
alloy convert --source-format=prometheus --output=config.alloy prometheus.yml
alloy convert --source-format=promtail --output=logs.alloy promtail.ymlMigration :alloy convertaccepte aussi--source-format=static(Grafana Agent Static) et--source-format=otelcol(OpenTelemetry Collector). Relisez toujours le résultat : les commentaires et certaines options peu courantes ne sont pas repris.
3. Variables et Fonctions #
Types, sources de valeurs, fonctions intégrées et cinq exemples concrets pour rendre une configuration Alloy dynamique et réutilisable.
Les types de valeurs #
Une configuration Alloy manipule des valeurs typées : nombres, chaînes, booléens, null, tableaux [ ] et objets { }. S'y ajoutent deux types propres à Alloy : secret, pour les mots de passe et jetons, et capsule, pour les valeurs internes comme les récepteurs ou les cibles.
// Nombres, chaînes, booléens, null
scrape_interval = "30s"
sample_limit = 5000
enabled = true
// Tableau : virgule finale recommandée
enable_collectors = ["cpu", "meminfo", "filesystem",]
// Objet : clés en identifiant ou en chaîne
targets = [
{ "__address__" = "app-1:8080", env = "prod" },
{ "__address__" = "app-2:8080", env = "prod" },
]
// Référence à la valeur exportée par un autre composant
forward_to = [prometheus.remote_write.mimir.receiver]Une erreur de type est détectée au chargement : par exemple une chaîne passée là où un nombre est attendu. Le message indique le fichier, la ligne et l'attribut fautif.
D'où viennent les « variables » ? #
Il n'existe pas de bloc variable ou locals comme en Terraform. Une valeur réutilisable provient toujours de l'une de ces sources :
| Source | Usage typique | Exemple |
|---|---|---|
sys.env("NOM") | Différencier les environnements (URL, tenant, cluster) | sys.env("MIMIR_URL") |
constants.* | Informations sur la machine | constants.hostname, constants.os |
local.file | Lire un fichier (secret monté, liste de cibles) | local.file.token.content |
remote.http, remote.s3 | Lire une valeur sur le réseau, relue périodiquement | remote.http.config.content |
remote.kubernetes.secret, remote.vault | Lire un secret Kubernetes ou Vault | remote.vault.creds.data.password |
argument (dans declare) | Paramètre d’un composant personnalisé | argument.url.value |
Les sources local.* et remote.* sont elles-mêmes des composants. Quand leur contenu change, tous les composants qui les lisent sont réévalués automatiquement.
Pour le détail de ce qu'Alloy lit dans l'environnement, et notamment OTEL_RESOURCE_ATTRIBUTES, voyez la leçon Variables d'environnement et OpenTelemetry.
Opérateurs et fonctions intégrées #
Les expressions acceptent les opérateurs arithmétiques (+ - * / % ^), de comparaison (== != < <= > >=) et logiques (&& || !). L'opérateur + concatène aussi les chaînes. Les fonctions de la bibliothèque standard sont rangées par espace de noms :
| Fonction | Rôle | Exemple |
|---|---|---|
sys.env | Lire une variable d’environnement (chaîne vide si absente) | sys.env("CLUSTER") |
coalesce | Première valeur non vide : idéal pour une valeur par défaut | coalesce(sys.env("ENV"), "dev") |
string.format | Construire une chaîne (verbes %s, %d…) | string.format("%s:%d", host, 9100) |
string.join / string.split | Assembler ou découper | string.split("a,b", ",") |
string.to_lower / to_upper / trim_space | Normaliser une valeur | string.to_lower("PROD") |
string.replace, trim_prefix, trim_suffix | Nettoyer une chaîne | string.trim_suffix(url, "/") |
array.concat | Fusionner des listes de cibles | array.concat(a.targets, b.targets) |
encoding.from_json / from_yaml / from_base64 | Décoder un contenu | encoding.from_json(local.file.t.content) |
json_path | Extraire une valeur d’un JSON | json_path(doc, ".endpoints[0]") |
convert.nonsensitive | Rendre lisible une valeur secrète (à éviter) | convert.nonsensitive(secret) |
Anciens noms : dans Grafana Agent Flow (River), ces fonctions s'appelaientenv(),format(),join(),concat(),json_decode()ounonsensitive(). Si un exemple trouvé en ligne échoue avec « unknown function », renommez-les avec leur espace de noms.
Exemple 1 : une configuration par environnement #
Le même fichier config.alloy est déployé partout ; les valeurs propres à chaque environnement sont passées par des variables d'environnement (Docker, Helm, systemd).
// Même fichier pour la préproduction et la production :
// seules les variables d'environnement changent.
prometheus.remote_write "mimir" {
endpoint {
// coalesce : première valeur non vide (valeur par défaut si la variable est absente)
url = coalesce(sys.env("MIMIR_URL"), "http://mimir:9009/api/v1/push")
headers = {
"X-Scope-OrgID" = coalesce(sys.env("MIMIR_TENANT"), "anonymous"),
}
}
external_labels = {
cluster = coalesce(sys.env("CLUSTER_NAME"), "local"),
environment = string.to_lower(coalesce(sys.env("ENV"), "DEV")),
host = constants.hostname,
}
}# Préproduction
MIMIR_URL=https://mimir-preprod.example.com/api/v1/push CLUSTER_NAME=preprod ENV=PREPROD alloy run config.alloy
# Docker Compose
services:
alloy:
image: grafana/alloy:latest
environment:
MIMIR_URL: https://mimir.example.com/api/v1/push
CLUSTER_NAME: prod-eu
ENV: PRODExemple 2 : un secret lu dans un fichier #
// Le mot de passe est lu dans un fichier monté (secret Kubernetes, Vault agent…)
local.file "mimir_password" {
filename = "/var/run/secrets/mimir/password"
is_secret = true // la valeur est de type secret : jamais affichée dans l'UI
}
prometheus.remote_write "mimir" {
endpoint {
url = "https://mimir.example.com/api/v1/push"
basic_auth {
username = sys.env("MIMIR_USER")
password = local.file.mimir_password.content
}
}
}Avec is_secret = true, la valeur exportée est de type secret : elle n'apparaît ni dans l'interface d'Alloy ni dans les journaux. Si le fichier est mis à jour (rotation du mot de passe), Alloy le relit et reconfigure l'envoi sans redémarrage.
N'écrivez jamais un mot de passe en clair dansconfig.alloy: le fichier finit presque toujours dans un dépôt Git. Préférezlocal.file,remote.kubernetes.secretouremote.vault.
Exemple 3 : des cibles décrites dans un fichier JSON #
// targets.json, maintenu par un autre outil (CMDB, script, Ansible…) :
// [{"__address__": "db-1:9187", "role": "primary"}, {"__address__": "db-2:9187", "role": "replica"}]
local.file "targets" {
filename = "/etc/alloy/targets.json"
}
prometheus.scrape "postgres" {
// encoding.from_json transforme le contenu du fichier en tableau d'objets
targets = encoding.from_json(local.file.targets.content)
forward_to = [prometheus.remote_write.mimir.receiver]
}
// Le fichier modifié est relu automatiquement : les nouvelles cibles sont collectées sans redémarrage.Ce modèle permet à une équipe de déclarer ses cibles dans un fichier simple, sans toucher à la configuration d'Alloy. Les champs autres que __address__ (ici role) deviennent des étiquettes des métriques collectées.
Exemple 4 : construire des chaînes et des étiquettes #
loki.process "normalize" {
forward_to = [loki.write.default.receiver]
stage.static_labels {
values = {
// Concaténation avec +
service = "checkout-" + coalesce(sys.env("REGION"), "eu"),
// Mise en forme
team = string.to_upper("payments"),
// Assemblage d'une liste
zones = string.join(["eu-west-3a", "eu-west-3b"], ","),
}
}
}Il n'y a pas d'interpolation ${...} comme en HCL : la concaténation avec + suffit pour les cas simples, string.format devient plus lisible dès qu'il y a plusieurs valeurs.
Exemple 5 : factoriser avec declare et import #
Pour éviter de copier-coller le même bloc, Alloy propose les composants personnalisés avec declare. Ce sont l'équivalent des modules Terraform : des paramètres d'entrée (argument), des composants internes et des valeurs exportées (export).
// Composant personnalisé réutilisable : l'équivalent d'un module Terraform
declare "mimir_writer" {
argument "url" {}
argument "tenant" {
optional = true
default = "anonymous"
}
prometheus.remote_write "default" {
endpoint {
url = argument.url.value
headers = { "X-Scope-OrgID" = argument.tenant.value }
}
}
export "receiver" {
value = prometheus.remote_write.default.receiver
}
}
// Deux instances, deux tenants, sans dupliquer la configuration
mimir_writer "prod" {
url = "https://mimir.example.com/api/v1/push"
tenant = "prod"
}
mimir_writer "audit" {
url = "https://mimir.example.com/api/v1/push"
tenant = "audit"
}
prometheus.scrape "app" {
targets = [{ "__address__" = "app:8080" }]
forward_to = [mimir_writer.prod.receiver, mimir_writer.audit.receiver]
}Pour partager ces composants entre plusieurs équipes ou serveurs, placez-les dans un fichier séparé et chargez-les avec import.file, import.http ou import.git :
// Charger des composants partagés depuis un dépôt Git (relu toutes les 5 minutes)
import.git "shared" {
repository = "https://github.com/acme/alloy-modules.git"
revision = "main"
path = "modules/mimir.alloy"
pull_frequency = "5m"
}
shared.mimir_writer "prod" {
url = sys.env("MIMIR_URL")
}Bonnes pratiques : un fichier par rôle (collecte, traitement, envoi) ; des valeurs par défaut aveccoalesce; les secrets danslocal.fileou un coffre-fort ;alloy fmtdans la CI ; et un coup d'œil au graphe de l'interface après chaque modification.
4. Gestion des Secrets #
Variables d’environnement, fichiers, Secrets Kubernetes et HashiCorp Vault : protéger et faire tourner les secrets de Grafana Alloy.
Pourquoi les secrets demandent une attention particulière #
Un collecteur de télémétrie manipule beaucoup d'identifiants : clés d'API Grafana Cloud, mots de passe Mimir ou Loki, jetons de Datadog ou de Dynatrace, certificats TLS, identifiants de bases de données à superviser. Le fichier config.alloy, lui, est fait pour être versionné, relu et partagé. Un secret écrit en clair dans ce fichier se retrouve dans Git, dans les sauvegardes, dans les captures d'écran et dans les tickets de support.
Grafana Alloy répond à ce problème de trois façons : un type de valeur dédié (secret), des sources de secrets (variables d'environnement, fichiers, Kubernetes, Vault) et une réévaluation automatique lorsque le secret change. La vidéo de la session présente la démarche ; cet article la détaille, exemples à l'appui.
// ❌ À ne jamais faire : le jeton finit dans Git, dans les sauvegardes et dans les tickets
prometheus.remote_write "cloud" {
endpoint {
url = "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push"
basic_auth {
username = "123456"
password = "glc_eyJvIjoiMTIzNDU2Iiwibi..."
}
}
}Le type secret : ce qu'Alloy masque, et ce qu'il ne masque pas #
Les attributs sensibles des composants, comme password, bearer_token ou api_key, sont de type secret. Une valeur de ce type n'est jamais affichée : l'interface d'Alloy (port 12345) et les journaux montrent (secret) à la place.
| Situation | Comportement |
|---|---|
Une chaîne est affectée à un attribut secret | Elle est convertie automatiquement en secret |
Un secret est affecté à un attribut string (ex. username, une étiquette) | Erreur au chargement : la conversion doit être explicite |
Conversion explicite avec convert.nonsensitive(...) | La valeur redevient une chaîne visible : à réserver aux données non sensibles (un nom d’utilisateur) |
local.file avec is_secret = true | Le contenu exporté est un secret |
remote.kubernetes.secret et remote.vault | Toutes les valeurs exportées dans data sont des secrets |
À retenir : le masquage protège l'affichage, pas le stockage. Le secret reste lisible là où il est stocké (variable d'environnement, fichier, Secret Kubernetes) : c'est là qu'il faut restreindre les accès.
Méthode 1 : les variables d'environnement #
C'est la méthode la plus simple et la plus portable : la configuration lit le secret avec sys.env(), et c'est l'outil de déploiement qui l'injecte.
// Variables d'environnement : simple et universel (Docker, systemd, Kubernetes)
prometheus.remote_write "cloud" {
endpoint {
url = sys.env("GRAFANA_CLOUD_PROM_URL")
basic_auth {
username = sys.env("GRAFANA_CLOUD_PROM_USER")
password = sys.env("GRAFANA_CLOUD_API_KEY") // converti en secret par l'attribut password
}
}
}# systemd : /etc/default/alloy (droits 600, propriétaire root)
GRAFANA_CLOUD_PROM_URL=https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push
GRAFANA_CLOUD_PROM_USER=123456
GRAFANA_CLOUD_API_KEY=glc_xxxxxxxx
# Docker : fichier .env non versionné (ajouté au .gitignore)
docker run --env-file ./alloy.env -v ./config.alloy:/etc/alloy/config.alloy grafana/alloy:latest \
run /etc/alloy/config.alloyAvantages : rien à installer, fonctionne partout. Limites : la valeur est visible par quiconque peut inspecter le processus (docker inspect, /proc/<pid>/environ), et un changement impose un redémarrage d'Alloy.
Méthode 2 : un fichier de secret lu par local.file #
Le secret est déposé dans un fichier aux droits restreints, par Docker, Kubernetes, Ansible ou un agent Vault. Alloy le lit avec local.file et le surveille : si le fichier change, par exemple lors d'une rotation de clé, les composants qui l'utilisent sont reconfigurés sans redémarrage.
// Fichier monté : le secret ne transite ni par l'environnement ni par la configuration
local.file "api_key" {
filename = "/run/secrets/grafana_cloud_api_key"
is_secret = true // valeur de type secret : masquée dans l'UI et les journaux
}
loki.write "cloud" {
endpoint {
url = sys.env("GRAFANA_CLOUD_LOKI_URL")
basic_auth {
username = sys.env("GRAFANA_CLOUD_LOKI_USER")
password = local.file.api_key.content
}
}
}# docker-compose.yml : secret Docker monté dans /run/secrets
services:
alloy:
image: grafana/alloy:latest
command: run /etc/alloy/config.alloy --server.http.listen-addr=0.0.0.0:12345
volumes:
- ./config.alloy:/etc/alloy/config.alloy:ro
secrets:
- grafana_cloud_api_key
secrets:
grafana_cloud_api_key:
file: ./secrets/grafana_cloud_api_key.txt # droits 600, hors du dépôt GitRotation sans coupure : remplacez le contenu du fichier, Alloy détecte la modification et rouvre les connexions avec la nouvelle clé. Gardez l'ancienne clé valide quelques minutes côté serveur, le temps de la bascule.
Méthode 3 : les Secrets Kubernetes #
Sur Kubernetes, deux approches coexistent. La première monte le Secret en variable d'environnement ou en fichier via le chart Helm, puis revient aux méthodes 1 et 2. La seconde utilise remote.kubernetes.secret, qui lit le Secret directement dans l'API Kubernetes et suit ses modifications.
# values.yaml du chart Helm grafana/alloy : secret exposé en variable d'environnement
alloy:
extraEnv:
- name: GRAFANA_CLOUD_API_KEY
valueFrom:
secretKeyRef:
name: grafana-cloud
key: api-key// Lecture directe d'un Secret Kubernetes (le compte de service d'Alloy doit pouvoir le lire)
remote.kubernetes.secret "mimir" {
namespace = "monitoring"
name = "mimir-credentials"
}
prometheus.remote_write "mimir" {
endpoint {
url = "https://mimir.example.com/api/v1/push"
basic_auth {
username = convert.nonsensitive(remote.kubernetes.secret.mimir.data["username"])
password = remote.kubernetes.secret.mimir.data["password"]
}
}
}# Droits minimaux : lire UN secret précis, dans UN namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: alloy-read-mimir-credentials
namespace: monitoring
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["mimir-credentials"]
verbs: ["get", "watch", "list"]Avec remote.kubernetes.secret, il n'y a ni volume ni redémarrage du pod : la modification du Secret est prise en compte en quelques secondes. En contrepartie, le compte de service d'Alloy doit avoir le droit de lire ce Secret ; limitez-le avec resourceNames.
Méthode 4 : HashiCorp Vault #
Pour les organisations qui centralisent leurs secrets dans Vault, le composant remote.vault lit un chemin du coffre et le relit périodiquement. Il gère plusieurs méthodes d'authentification : auth.token, auth.approle, auth.kubernetes, auth.aws, auth.gcp, auth.userpass…
// HashiCorp Vault : authentification Kubernetes, relecture toutes les 5 minutes
remote.vault "mimir" {
server = "https://vault.example.com:8200"
path = "secret/data/monitoring/mimir" // moteur KV v2
auth.kubernetes {
role = "alloy"
}
reread_frequency = "5m"
}
prometheus.remote_write "mimir" {
endpoint {
url = "https://mimir.example.com/api/v1/push"
bearer_token = remote.vault.mimir.data["token"]
}
}Le jeton d'authentification à Vault est lui-même un secret. Préférez une authentification sans secret statique (auth.kubernetes,auth.aws) ; sinon, lisez le jeton aveclocal.fileetis_secret = true.
Quelle méthode choisir ? #
| Méthode | Rotation sans redémarrage | Complexité | Cas d’usage |
|---|---|---|---|
sys.env() | Non | Très faible | Poste de développement, VM simple, Docker |
local.file | Oui | Faible | Secrets Docker, fichiers déposés par Ansible ou un agent Vault |
remote.kubernetes.secret | Oui | Moyenne (droits RBAC) | Clusters Kubernetes |
remote.vault | Oui (reread_frequency) | Plus élevée | Secrets centralisés, rotation automatique, audit |
Erreurs courantes et bonnes pratiques #
- Secret en clair dans
config.alloy: ajoutez un contrôle dans la CI (gitleaks,git grep) et révoquez immédiatement toute clé publiée par erreur. convert.nonsensitive()utilisé pour « faire passer » une erreur : le secret devient visible dans l'interface. Réservez cette fonction aux valeurs réellement publiques, comme un nom d'utilisateur.- Fichier de secret lisible par tous : droits
600, propriétaire l'utilisateuralloy. - Interface d'Alloy exposée sur Internet : les secrets y sont masqués, mais la topologie et les URL des backends restent visibles. Écoutez sur
127.0.0.1ou protégez le port 12345. - Un secret partagé par tous les environnements : utilisez une clé par environnement et par usage (écriture des métriques, écriture des logs), avec le minimum de droits.
# Créer le secret Kubernetes sans l'écrire dans un fichier versionné
kubectl -n monitoring create secret generic mimir-credentials \
--from-literal=username=alloy --from-literal=password="$(openssl rand -base64 24)"
# Protéger un fichier de secret sur une VM
sudo install -m 600 -o alloy -g alloy /dev/stdin /etc/alloy/secrets/api_key <<< "glc_xxxxxxxx"
# Vérifier qu'aucun secret n'est versionné
git grep -nE "glc_|password *= *\"" -- '*.alloy'
# Contrôler dans l'interface que la valeur apparaît bien comme (secret)
curl -s http://localhost:12345/api/v0/web/components | grep -c secret5. Observabilité d'Alloy #
Surveiller la santé de ses propres pipelines via l'interface UI native.
L'interface utilisateur locale #
Alloy intègre un serveur HTTP (via l'alloy port 12345 par défaut) qui expose la grafana alloy ui, une interface graphique très utile. Cette UI permet de visualiser le graphe (DAG) de vos composants, de vérifier leur état (Healthy/Unhealthy), et d'inspecter les arguments évalués et les exports de chaque composant en temps réel. C'est l'outil indispensable pour l'alloy live debugging de vos configurations.
Ports de Grafana Alloy #
Ce qui écoute, et où.
Ports #
| Port | Usage | Réglage |
|---|---|---|
| 12345 | Interface web, API et métriques internes d'Alloy | --server.http.listen-addr=0.0.0.0:12345 (127.0.0.1 par défaut) |
| 4317 / 4318 | OTLP gRPC / HTTP si un otelcol.receiver.otlp est déclaré | Bloc grpc et http du composant |
| 12347 | Récepteur Faro (front-end) si faro.receiver est déclaré | Argument server du composant |
| Selon composant | loki.source.syslog, loki.source.api, etc. | Adresse d'écoute du composant |
Bonne Pratique : Par défaut l'interface n'écoute que sur 127.0.0.1 : exposez-la explicitement (et protégez-la) pour y accéder depuis un autre poste ou depuis Kubernetes.
