Aller au contenu principal

Alloy + Loki — log shipping HA

Pipeline observability : les 3 hosts shipent leurs logs (journald + docker + fichiers) vers deux instances Loki, pour survivre a la perte d'une.

Architecture​

Schéma agrandi
100 %

Depuis 2026-04-19 : galahad + lancelot ecrivent aussi vers la replica (avant, seul penny le faisait).

Hosts avec Alloy​

Les valeurs de job ci-dessous sont celles réellement déclarées dans les fichiers .alloy, relevées le 2026-09-24. Elles comptent, parce que les règles d'alerte sélectionnent dessus.

HostPaquetConfigjob collectés
pennyalloy apt/etc/alloy/config.alloyjournald, docker, audit, fail2ban, monitor, watchdog
galahadalloy apt/etc/alloy/config.alloyjournald, audit, fail2ban, proxmox
lancelotalloy apt/etc/alloy/config.alloyjournald, audit, fail2ban, proxmox

Les sources versionnées sont dans homelab-config/system/alloy/<hôte>.alloy, un fichier par machine.

Les LXC, depuis le 2026-09-02​

Les huit LXC existantes expédient (relevé du 2026-11-04 par pct exec <id> -- systemctl is-active alloy). 109 iptv n'expédiait rien depuis sa création le 2026-10-26 : Alloy n'y était pas installé. Corrigé le 2026-11-04 avec iptv.alloy.

LXCHôteSources
100 dns-failovergalahadjournald
101 logslancelotjournald + Docker (sauf le conteneur loki)
102 vaultgalahadjournald + Docker (vaultwarden)
103 pbslancelotjournald
104 zomboidgalahaddétruit le 2026-11-01
105 sucrelancelotdétruit le 2026-11-03
106 pulsegalahadjournald + Docker (pulse, filtré)
107 waterlinegalahadjournald
108 ci-runnerlancelotjournald seulement
109 iptvgalahadjournald + Docker (iptvnator) — depuis le 2026-11-04
110 securolancelotdétruit le 2026-11-03 (Alloy n'y a jamais été installé)
attention
La LXC 110 securo n'expédie rien, constaté le 2026-09-24

Alloy n'y est pas installé : /etc/alloy/ n'existe pas, l'unité est inactive, et aucun securo.alloy ne figure dans homelab-config/system/alloy/. Ses six conteneurs — backend, frontend, deux workers Celery, Redis, Postgres — sont invisibles dans Loki.

Ce n'est pas un détail pour un service qui traite des données financières : une boucle de redémarrage, un échec d'authentification ou un import raté n'y laisseront aucune trace consultable, et aucune règle d'alerte ne peut porter sur ce qui n'arrive jamais.

Le raisonnement qui a fait installer Alloy dans vault le 2026-09-02 s'applique mot pour mot : un backup n'est pas une observation.

Cette page disait le contraire jusqu'au 2026-09-02 — voici pourquoi il a changé

La version précédente actait : « LXC 100/102/103 : pas d'Alloy. Trade-off accepté : vault a son backup restic, PBS ne log pas grand chose, dns-failover pareil. » Le compromis a été renversé, sur trois constats.

Un backup n'est pas une observation. Restic protège les données de Vaultwarden. Il ne dit rien d'une série de tentatives d'authentification, d'un redémarrage en boucle ni d'une erreur de déchiffrement — et c'est le coffre à mots de passe.

« PBS ne log pas grand chose » a été démenti. Le 2026-07-06, rpc.nfsd a rendu ENOMEM en silence dans le LXC 103, le montage NFS est resté pendu et le proxy PBS est parti en D-state. Tout cela s'est écrit dans un journal que personne ne pouvait lire à distance. Même histoire pour Pulse, mort 3 jours et demi sans témoin.

Le seul invité observé était le seul à l'arrêt. Avant la bascule, Loki voyait 5 sources sur 13, et la seule LXC qui expédiait en plus de finance était sucre — dont le service est arrêté depuis le 2026-08-25.

Le coût réel s'est avéré être le volume, pas le principe : un seul conteneur (pulse) produisait à lui seul plus de lignes que les trois hôtes réunis. La réponse est le filtrage par niveau, pas le renoncement à la source. Pour revenir en arrière sur un invité : pct exec <id> -- systemctl disable --now alloy.

Ce qui n'est délibérément pas collecté​

SourcePourquoi
conteneurs de job du ci-runnerun conteneur éphémère par job de CI : les collecter noierait Loki à chaque push, et les logs de job sont déjà dans Forgejo. Seul le runner lui-même est suivi — enregistrement perdu, jeton refusé, service mort.
conteneur loki du LXC 101Loki journalise ses push refusés. Lui renvoyer ces lignes lui en fait écrire d'autres : une erreur passagère s'auto-alimenterait. Le journald du LXC garde la trace côté systemd.
level=info de pulsemesuré le 2026-09-02 : 61 lignes/minute, ~88 000/jour pour ce seul conteneur — « Starting background polling », « No alerts needed cleanup ». warn et au-delà passent.
Temperature collection using direct SSH de pulseun avertissement d'état répété 12 fois par minute, jamais actionnable. Après ces deux filtres : de 61 à environ 5 lignes/minute.

Loki instances​

InstanceHostRôleRetention
primaryLXC 101 sur lancelot, port 3100Canonical, scrape par Grafana30 jours
replicacontainer loki-replica sur penny, port 3101Backup / query si primary KO30 jours

Replica tourne dans le stack Docker penny (compose : loki-replica service). Image pinnee grafana/loki:latest@sha256:73e905....

Pattern dual-write​

Chaque Alloy host definit 2 sinks + forward a chaque source :

loki.write "default" {
endpoint { url = "http://192.168.1.31:3100/loki/api/v1/push" }
external_labels = { host = "<hostname>" }
}

loki.write "replica" {
endpoint { url = "http://192.168.1.28:3101/loki/api/v1/push" }
external_labels = { host = "<hostname>" }
}

loki.source.journal "system" {
// penny uniquement : journald est en Storage=volatile (RAM), Alloy doit lire
// /run/log/journal et PAS /var/log/journal (tronque par dietpi-logclear -> SIGBUS).
path = "/run/log/journal"
forward_to = [loki.write.default.receiver, loki.write.replica.receiver]
...
}
penny : journal en RAM (volatile)

Sur penny (DietPi RAMlog), journald est en Storage=volatile et Alloy lit /run/log/journal via path. Sans ca, dietpi-logclear tronque le system.journal mmap'd a chaque :17 → SIGBUS d'Alloy. Detail : operations/depannage.md → "Alloy crashe a chaque :17" et projet/decisions.md → "Journald penny : volatile". galahad/lancelot gardent le journald par defaut.

Alloy a un WAL interne : si un Loki est down, les chunks sont bufferises localement et rejoues au retour.

Vérification​

Labels recus par chaque Loki​

# primary
curl -sG http://192.168.1.31:3100/loki/api/v1/label/host/values
# replica
curl -sG http://192.168.1.28:3101/loki/api/v1/label/host/values

Les deux doivent montrer 13 sources : les 3 hôtes et les 10 LXC.

Un label présent ne prouve pourtant rien sur le présent — il survit à la rétention. Ce qui compte est le débit :

curl -sG http://192.168.1.28:3101/loki/api/v1/query \
--data-urlencode 'query=sum by (host, job) (count_over_time({job=~"journald|docker"}[10m]))'

Une source absente de cette réponse est soit muette, soit débranchée, et rien dans systemctl is-active ne fera la différence.

control-drift-check fait la vérification symétrique toutes les six heures : chaque LXC dont une config existe dans system/alloy/ doit avoir un alloy actif. Voir le journal du 2026-09-02 pour le piège rencontré en l'écrivant — ssh dans un while read ne contrôlait qu'un invité sur dix, en rendant un verdict vert.

Alloy service actif​

systemctl is-active alloy # sur chaque host
# active

DR : re-provisioning d'un host​

Configs Alloy versionnees dans homelab-config/system/alloy/<host>.alloy.

# Sur un host reinstalle
apt install -y alloy
cp <host>.alloy /etc/alloy/config.alloy
systemctl enable --now alloy

Impact fix Docker log-driver (2026-04-19)​

Docker penny est passe de journald a json-file log-driver (cf operations/depannage.md section "Docker daemon crash loop"). Consequence : avant, docker logs arrivaient via journald → Alloy les captait via loki.source.journal. Après, docker logs sont dans /var/lib/docker/containers/*/*-json.log — Alloy a une source loki.source.docker qui lit via socket Docker API, fonctionne idem.

Les logs pre-switch (avant 2026-04-19 17:17) sont dans journald encore, queryables avec filter unit="docker.service" OU CONTAINER_NAME=... (Docker les taguait).

Dashboards Grafana​

4 dashboards CrowdSec + Homelab Overview + Auth & Securite + Traefik Access + Logs Explorer provisionnes via logs/grafana-provisioning/dashboards/ (LXC 101). Acces : logs.home.gabin-simond.fr (Authelia OIDC GrafanaAdmin).

Alerting Grafana​

Contact point ntfy configuré. Règles :

  • Authelia auth failures
  • fail2ban bans
  • Traefik 5xx rate
  • auditd sudo events

Topic ntfy hex 32 chars (ae8fcbd...), partagé avec homelab_monitor.sh (même canal, categories distinctes par title).