mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 05:34:43 +02:00
feat(monitoring): surveillance réseau active via blackbox_exporter
Sondes actives ICMP/HTTP/DNS qui complètent le monitoring passif
(node_exporter + up{}) : joignabilité hôtes/passerelle, accès internet,
santé DNS *.lab.local + forwarding, latence LAN, services de bout en
bout via Traefik.
- blackbox_exporter (chart 11.13.0, ArgoCD, ns monitoring) — ICMP via
NET_RAW, DNS du pod = dnsmasq (chemin réel lab → Traefik)
- 3 jobs blackbox-icmp/-http/-dns (additionalScrapeConfigs)
- alerts-network.yaml : internet, DNS, passerelle, hôtes, services,
latence, exporter (→ pipeline AlertManager existant)
- Asa : outil network_status + contexte présélectionnable reseau
- doc admin/infra/reseau.md + CLAUDE.md + progress
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rc3dLKfqEPHexvFCCx5ZEF
This commit is contained in:
parent
ebb2eec184
commit
649c6fde8e
11 changed files with 527 additions and 5 deletions
|
|
@ -9,7 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
**État actuel (2026-06-23)** :
|
**État actuel (2026-06-23)** :
|
||||||
- ✅ Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
|
- ✅ Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
|
||||||
- ✅ Qdrant réparé (2026-06-17) — fin du crash-loop (segment `funk-docs` corrompu, voir `admin/incidents.md`). La collection RAG `funk-docs` a depuis été **reconstruite** (436 chunks, 🟢 green, embeddings nomic-embed-text dim 768) — `rag-query` opérationnel
|
- ✅ Qdrant réparé (2026-06-17) — fin du crash-loop (segment `funk-docs` corrompu, voir `admin/incidents.md`). La collection RAG `funk-docs` a depuis été **reconstruite** (436 chunks, 🟢 green, embeddings nomic-embed-text dim 768) — `rag-query` opérationnel
|
||||||
- ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes + n8n
|
- ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes + n8n. **Surveillance réseau active** (blackbox_exporter : sondes ICMP/HTTP/DNS — joignabilité hôtes/passerelle, accès internet, DNS `*.lab.local`, latence LAN) → `alerts-network.yaml` + outil Asa `network_status` / contexte `reseau`. Voir `admin/infra/reseau.md`
|
||||||
- ✅ storage-01 installé — `192.168.10.1` (LAN cluster), `192.168.1.200` (WAN)
|
- ✅ storage-01 installé — `192.168.10.1` (LAN cluster), `192.168.1.200` (WAN)
|
||||||
- ✅ gpu-01 installé — llama-server GPU (:1234, qwen3-8b) + embeddings dédiés nomic-embed-text (:1238, **CPU** — découplé du slot chat GPU) + watchdog/heartbeat de résilience, kernel 5.14.0-687.5.3. ⚠️ Les instances CPU (:1236/:1237 — profils Hermes `system`/`monitor`) sont **manuelles** et souvent éteintes (gap IaC)
|
- ✅ gpu-01 installé — llama-server GPU (:1234, qwen3-8b) + embeddings dédiés nomic-embed-text (:1238, **CPU** — découplé du slot chat GPU) + watchdog/heartbeat de résilience, kernel 5.14.0-687.5.3. ⚠️ Les instances CPU (:1236/:1237 — profils Hermes `system`/`monitor`) sont **manuelles** et souvent éteintes (gap IaC)
|
||||||
- ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent (écoute `0.0.0.0:4000`)
|
- ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent (écoute `0.0.0.0:4000`)
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,4 @@
|
||||||
| [2026-06-18](progress/2026-06-18.md) | STT : HUD avancé (design Claude Design, #17), auto-start systemd, app de bureau (`.desktop` + mode fenêtre), auto-update (`stt --update`), compat AlmaLinux/Debian. Passe de cohérence doc (#15, #16) |
|
| [2026-06-18](progress/2026-06-18.md) | STT : HUD avancé (design Claude Design, #17), auto-start systemd, app de bureau (`.desktop` + mode fenêtre), auto-update (`stt --update`), compat AlmaLinux/Debian. Passe de cohérence doc (#15, #16) |
|
||||||
| [2026-06-21](progress/2026-06-21.md) | STT : portail de services + santé live (#34-36), contextes (visualiseur + presets live, #38/#39), intents vocaux (#40), jeton Ghostfolio unifié (#41), **actions via Hermes** (`hermes -z --yolo`, rôle hermes_exec + contexte agent, #42/#43), ASR Parakeet activé. **Soir** : relevé état réel + RAG re-ingéré 436 chunks + fix batch embed (#44) |
|
| [2026-06-21](progress/2026-06-21.md) | STT : portail de services + santé live (#34-36), contextes (visualiseur + presets live, #38/#39), intents vocaux (#40), jeton Ghostfolio unifié (#41), **actions via Hermes** (`hermes -z --yolo`, rôle hermes_exec + contexte agent, #42/#43), ASR Parakeet activé. **Soir** : relevé état réel + RAG re-ingéré 436 chunks + fix batch embed (#44) |
|
||||||
| [2026-06-22](progress/2026-06-22.md) | STT : **transcription partielle live** dans le HUD + **veille/réveil vocal** (« Asa » réveille / « Asa stop » endort, machine à états), wake word défaut `asa`, client 0.14.0 |
|
| [2026-06-22](progress/2026-06-22.md) | STT : **transcription partielle live** dans le HUD + **veille/réveil vocal** (« Asa » réveille / « Asa stop » endort, machine à états), wake word défaut `asa`, client 0.14.0 |
|
||||||
|
| [2026-06-25](progress/2026-06-25.md) | **Surveillance réseau active** : blackbox_exporter (ICMP/HTTP/DNS, ArgoCD ns monitoring) + sondes (passerelle, compute, gpu-01, internet, DNS lab, services Traefik) + `alerts-network.yaml` + outil Asa `network_status` & contexte `reseau` |
|
||||||
|
|
|
||||||
|
|
@ -167,3 +167,83 @@ ip -s link show enp6s0f3u2c2
|
||||||
# Connexions actives
|
# Connexions actives
|
||||||
ss -tnp
|
ss -tnp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Surveillance réseau active — blackbox_exporter
|
||||||
|
|
||||||
|
Sondes **actives** (ICMP / HTTP / DNS) qui complètent les métriques *passives* du
|
||||||
|
node_exporter. Elles répondent aux questions que `up{}` ne couvre pas : *« a-t-on
|
||||||
|
internet ? »*, *« dnsmasq résout-il encore *.lab.local ? »*, *« quelle latence sur le
|
||||||
|
LAN ? »*, *« le service répond-il de bout en bout via Traefik ? »*.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Prometheus (in-cluster)
|
||||||
|
│ scrape /probe?target=…&module=… (jobs blackbox-icmp / -http / -dns)
|
||||||
|
▼
|
||||||
|
blackbox-exporter.monitoring.svc.cluster.local:9115 (ns monitoring)
|
||||||
|
│ exécute la sonde réelle
|
||||||
|
├── ICMP → 192.168.10.1/.11/.12/.13/.20, 1.1.1.1, 9.9.9.9
|
||||||
|
├── HTTP → http://<service>.lab.local (DNS du pod = dnsmasq → Traefik)
|
||||||
|
└── DNS → 192.168.10.1:53 (dns_lab : *.lab.local ; dns_external : forwarding)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Élément | Emplacement |
|
||||||
|
|---|---|
|
||||||
|
| Déploiement (ArgoCD, chart Helm 11.13.0) | `k8s/apps-of-apps/apps/blackbox-exporter.yaml` |
|
||||||
|
| Config blackbox (modules, ICMP, DNS du pod) | `k8s/infra/blackbox-exporter/values.yaml` |
|
||||||
|
| Cibles + relabeling | `k8s/infra/monitoring/values.yaml` (jobs `blackbox-*`) |
|
||||||
|
| Alertes | `k8s/infra/monitoring/alerts/alerts-network.yaml` |
|
||||||
|
| Outil vocal Asa `network_status` + contexte `reseau` | `stt/server/stt_server/{tools,sources,contexts}.py` |
|
||||||
|
|
||||||
|
> Le pod blackbox a `dnsPolicy: None` + `dnsConfig` pointant sur **dnsmasq (192.168.10.1)**
|
||||||
|
> avec repli `1.1.1.1`. Conséquence : les sondes HTTP `*.lab.local` empruntent le **vrai**
|
||||||
|
> chemin (DNS lab → Traefik), et une panne dnsmasq fait échouer les sondes lab mais **pas**
|
||||||
|
> les sondes internet (qui retombent sur 1.1.1.1).
|
||||||
|
|
||||||
|
### Métriques clés (PromQL)
|
||||||
|
|
||||||
|
```promql
|
||||||
|
probe_success{job=~"blackbox-.*"} # 1 = OK, 0 = échec
|
||||||
|
probe_success{job="blackbox-icmp",scope="internet"} # accès internet (ancres)
|
||||||
|
probe_success{job="blackbox-dns",module="dns_lab"} # dnsmasq résout *.lab.local ?
|
||||||
|
probe_icmp_duration_seconds{scope="lan",phase="rtt"} # latence LAN (RTT)
|
||||||
|
probe_http_status_code{job="blackbox-http"} # code HTTP renvoyé
|
||||||
|
probe_duration_seconds{job="blackbox-http"} # temps de réponse service
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alertes (`alerts-network.yaml`)
|
||||||
|
|
||||||
|
`InternetDown` (crit) · `DnsLabResolutionFailed` (crit) · `DnsForwardingFailed` (warn) ·
|
||||||
|
`GatewayUnreachable` (crit) · `ClusterHostUnreachable` (warn) · `ServiceEndpointDown` (warn) ·
|
||||||
|
`HighLanLatency` (warn) · `SlowServiceResponse` (warn) · `BlackboxExporterDown` (crit).
|
||||||
|
Elles partent dans le même pipeline AlertManager → webhook Hermes + n8n → Gmail.
|
||||||
|
|
||||||
|
### Vérifications après déploiement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Le pod tourne ?
|
||||||
|
kubectl get pods -n monitoring -l app.kubernetes.io/name=prometheus-blackbox-exporter
|
||||||
|
|
||||||
|
# Cibles blackbox vues par Prometheus (doivent être UP, health=up)
|
||||||
|
# prometheus.lab.local → Status → Targets → blackbox-icmp / -http / -dns
|
||||||
|
|
||||||
|
# Tester une sonde à la main depuis un pod du cluster
|
||||||
|
kubectl run -n monitoring bb-test --rm -it --image=curlimages/curl --restart=Never -- \
|
||||||
|
'curl -s "http://blackbox-exporter:9115/probe?target=1.1.1.1&module=icmp" | grep probe_success'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caveats / points de vigilance
|
||||||
|
|
||||||
|
- **ICMP** nécessite la capability `NET_RAW` (activée dans `values.yaml`) — possible car la ns
|
||||||
|
`monitoring` est en PodSecurity **privileged**.
|
||||||
|
- **Pare-feu hôtes** : le ping vers `192.168.10.1` (storage-01) passe (la règle gateway accepte
|
||||||
|
le LAN cluster), mais **gpu-01 / compute** peuvent **dropper l'ICMP** selon leur firewall hôte.
|
||||||
|
Si `ClusterHostUnreachable` se déclenche alors que l'hôte est vivant, vérifier qu'il accepte
|
||||||
|
l'echo-request depuis `192.168.10.0/24` (côté flannel, le trafic pod sort masqué en IP de nœud).
|
||||||
|
- **Sondes HTTP** : un service derrière auth ou qui renvoie 3xx non suivi sortira en échec —
|
||||||
|
ajuster le module (`valid_status_codes`) ou retirer la cible dans `monitoring/values.yaml`.
|
||||||
|
- **Ajouter une cible** = éditer la liste `targets` du job concerné dans
|
||||||
|
`k8s/infra/monitoring/values.yaml`, puis `git push` (ArgoCD resync ~3 min).
|
||||||
|
|
|
||||||
29
k8s/apps-of-apps/apps/blackbox-exporter.yaml
Normal file
29
k8s/apps-of-apps/apps/blackbox-exporter.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: blackbox-exporter
|
||||||
|
namespace: argocd
|
||||||
|
finalizers:
|
||||||
|
- resources-finalizer.argocd.argoproj.io
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
sources:
|
||||||
|
- repoURL: https://prometheus-community.github.io/helm-charts
|
||||||
|
chart: prometheus-blackbox-exporter
|
||||||
|
targetRevision: 11.13.0
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- $values/k8s/infra/blackbox-exporter/values.yaml
|
||||||
|
- repoURL: git@github.com:Alkatrazz24/Funk-lab.git
|
||||||
|
targetRevision: main
|
||||||
|
ref: values
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- ServerSideApply=true
|
||||||
108
k8s/infra/blackbox-exporter/values.yaml
Normal file
108
k8s/infra/blackbox-exporter/values.yaml
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
# prometheus-blackbox-exporter — cluster Funk
|
||||||
|
# Sondes réseau actives (ICMP / HTTP / DNS) scrappées par Prometheus.
|
||||||
|
# Les cibles ne sont PAS définies ici : elles vivent dans
|
||||||
|
# k8s/infra/monitoring/values.yaml (prometheusSpec.additionalScrapeConfigs,
|
||||||
|
# jobs blackbox-*), comme les autres scrapes hors-cluster (storage-01, gpu-01).
|
||||||
|
|
||||||
|
# Nom de service prévisible → blackbox-exporter.monitoring.svc.cluster.local:9115
|
||||||
|
fullnameOverride: blackbox-exporter
|
||||||
|
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
# Petite empreinte (compute-02/03 = 8 GB, OOM-killer actif).
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 32Mi
|
||||||
|
limits:
|
||||||
|
memory: 64Mi
|
||||||
|
|
||||||
|
# ICMP nécessite la capability NET_RAW (raw sockets). La ns monitoring est en
|
||||||
|
# PodSecurity « privileged » → autorisé. On garde le reste verrouillé.
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
add: ["NET_RAW"]
|
||||||
|
|
||||||
|
# Résolution DNS du pod : on tape d'abord dnsmasq (192.168.10.1) pour que les
|
||||||
|
# sondes HTTP des services *.lab.local empruntent le VRAI chemin (DNS lab →
|
||||||
|
# Traefik). Repli sur 1.1.1.1 pour qu'une panne dnsmasq n'aveugle pas les
|
||||||
|
# sondes internet. dnsPolicy=None car blackbox n'a pas besoin du DNS cluster.
|
||||||
|
dnsPolicy: "None"
|
||||||
|
dnsConfig:
|
||||||
|
nameservers:
|
||||||
|
- "192.168.10.1" # dnsmasq (storage-01) — résout *.lab.local
|
||||||
|
- "1.1.1.1" # repli internet
|
||||||
|
searches:
|
||||||
|
- "lab.local"
|
||||||
|
|
||||||
|
# On scrape via additionalScrapeConfigs (cf. monitoring/values.yaml), donc pas
|
||||||
|
# besoin du ServiceMonitor ni des règles packagées par le chart.
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
prometheusRule:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Modules de sonde. Chaque job Prometheus référence un module par son nom.
|
||||||
|
config:
|
||||||
|
modules:
|
||||||
|
# --- HTTP : service vivant et code 2xx (suit les redirections) ---
|
||||||
|
http_2xx:
|
||||||
|
prober: http
|
||||||
|
timeout: 5s
|
||||||
|
http:
|
||||||
|
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||||
|
follow_redirects: true
|
||||||
|
preferred_ip_protocol: "ip4"
|
||||||
|
ip_protocol_fallback: false
|
||||||
|
|
||||||
|
# --- HTTP en HTTPS auto-signé (lab) : ne vérifie pas le certificat ---
|
||||||
|
http_2xx_insecure:
|
||||||
|
prober: http
|
||||||
|
timeout: 5s
|
||||||
|
http:
|
||||||
|
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||||
|
follow_redirects: true
|
||||||
|
preferred_ip_protocol: "ip4"
|
||||||
|
fail_if_not_ssl: false
|
||||||
|
tls_config:
|
||||||
|
insecure_skip_verify: true
|
||||||
|
|
||||||
|
# --- ICMP : joignabilité + latence L3 (hôtes, passerelle, internet) ---
|
||||||
|
icmp:
|
||||||
|
prober: icmp
|
||||||
|
timeout: 5s
|
||||||
|
icmp:
|
||||||
|
preferred_ip_protocol: "ip4"
|
||||||
|
ip_protocol_fallback: false
|
||||||
|
|
||||||
|
# --- DNS : dnsmasq résout-il un nom du lab ? (wildcard *.lab.local) ---
|
||||||
|
dns_lab:
|
||||||
|
prober: dns
|
||||||
|
timeout: 5s
|
||||||
|
dns:
|
||||||
|
query_name: "grafana.lab.local"
|
||||||
|
query_type: "A"
|
||||||
|
preferred_ip_protocol: "ip4"
|
||||||
|
validate_answer_rrs:
|
||||||
|
fail_if_not_matches_regexp:
|
||||||
|
- ".*"
|
||||||
|
|
||||||
|
# --- DNS : forwarding externe (dnsmasq → upstream) fonctionne-t-il ? ---
|
||||||
|
dns_external:
|
||||||
|
prober: dns
|
||||||
|
timeout: 5s
|
||||||
|
dns:
|
||||||
|
query_name: "one.one.one.one"
|
||||||
|
query_type: "A"
|
||||||
|
preferred_ip_protocol: "ip4"
|
||||||
|
|
||||||
|
# --- TCP : poignée de main sur un port (réserve, ex. débogage) ---
|
||||||
|
tcp_connect:
|
||||||
|
prober: tcp
|
||||||
|
timeout: 5s
|
||||||
102
k8s/infra/monitoring/alerts/alerts-network.yaml
Normal file
102
k8s/infra/monitoring/alerts/alerts-network.yaml
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: PrometheusRule
|
||||||
|
metadata:
|
||||||
|
name: funk-network-alerts
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
release: kube-prometheus-stack
|
||||||
|
spec:
|
||||||
|
groups:
|
||||||
|
- name: funk.network
|
||||||
|
interval: 1m
|
||||||
|
rules:
|
||||||
|
|
||||||
|
# --- Joignabilité internet (NAT storage-01 / lien WAN) ---
|
||||||
|
- alert: InternetDown
|
||||||
|
expr: sum(probe_success{job="blackbox-icmp",scope="internet"}) == 0
|
||||||
|
for: 3m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Plus d'accès internet depuis le cluster"
|
||||||
|
description: "Aucune ancre internet (Cloudflare/Quad9) ne répond au ping depuis 3 min. Lien WAN ou NAT storage-01 probablement en cause."
|
||||||
|
|
||||||
|
# --- DNS : résolution *.lab.local (cœur de dnsmasq) ---
|
||||||
|
- alert: DnsLabResolutionFailed
|
||||||
|
expr: probe_success{job="blackbox-dns",module="dns_lab"} == 0
|
||||||
|
for: 3m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "dnsmasq ne résout plus *.lab.local"
|
||||||
|
description: "La sonde DNS sur 192.168.10.1:53 (grafana.lab.local) échoue depuis 3 min. Tout l'accès aux services par nom est cassé."
|
||||||
|
|
||||||
|
# --- DNS : forwarding vers l'upstream ---
|
||||||
|
- alert: DnsForwardingFailed
|
||||||
|
expr: probe_success{job="blackbox-dns",module="dns_external"} == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Forwarding DNS externe en échec"
|
||||||
|
description: "dnsmasq ne résout plus les noms externes (one.one.one.one) depuis 5 min. Résolution interne possiblement OK — vérifier l'upstream."
|
||||||
|
|
||||||
|
# --- Passerelle (storage-01) injoignable en ICMP ---
|
||||||
|
- alert: GatewayUnreachable
|
||||||
|
expr: probe_success{job="blackbox-icmp",instance="192.168.10.1"} == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Passerelle 192.168.10.1 (storage-01) injoignable"
|
||||||
|
description: "storage-01 ne répond plus au ping depuis le cluster (2 min). NFS, DNS, NAT, PostgreSQL probablement impactés."
|
||||||
|
|
||||||
|
# --- Hôte du LAN cluster injoignable (hors passerelle) ---
|
||||||
|
- alert: ClusterHostUnreachable
|
||||||
|
expr: probe_success{job="blackbox-icmp",scope="lan",instance!="192.168.10.1"} == 0
|
||||||
|
for: 3m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Hôte {{ $labels.instance }} injoignable (ICMP)"
|
||||||
|
description: "{{ $labels.instance }} ne répond plus au ping depuis 3 min. Machine éteinte, lien réseau ou switch ?"
|
||||||
|
|
||||||
|
# --- Service exposé (Traefik / *.lab.local) en échec ---
|
||||||
|
- alert: ServiceEndpointDown
|
||||||
|
expr: probe_success{job="blackbox-http"} == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Service {{ $labels.instance }} ne répond pas (HTTP)"
|
||||||
|
description: "La sonde HTTP de bout en bout (DNS lab → Traefik) sur {{ $labels.instance }} échoue depuis 5 min (pas de 2xx). Voir probe_http_status_code pour le code renvoyé."
|
||||||
|
|
||||||
|
# --- Latence LAN anormale ---
|
||||||
|
- alert: HighLanLatency
|
||||||
|
expr: probe_icmp_duration_seconds{job="blackbox-icmp",scope="lan",phase="rtt"} > 0.05
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Latence LAN élevée vers {{ $labels.instance }}"
|
||||||
|
description: "RTT ICMP = {{ $value | printf \"%.3f\" }}s vers {{ $labels.instance }} depuis 10 min (attendu < 5 ms sur le LAN cluster). Switch ou câble ?"
|
||||||
|
|
||||||
|
# --- Service lent (réponse HTTP) ---
|
||||||
|
- alert: SlowServiceResponse
|
||||||
|
expr: probe_duration_seconds{job="blackbox-http"} > 3
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Réponse lente de {{ $labels.instance }}"
|
||||||
|
description: "La sonde HTTP met {{ $value | printf \"%.1f\" }}s sur {{ $labels.instance }} depuis 10 min."
|
||||||
|
|
||||||
|
# --- blackbox_exporter lui-même injoignable ---
|
||||||
|
- alert: BlackboxExporterDown
|
||||||
|
expr: up{job="blackbox-http"} == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "blackbox_exporter injoignable"
|
||||||
|
description: "Prometheus ne peut plus scraper blackbox_exporter depuis 5 min : toute la surveillance réseau active est aveugle."
|
||||||
|
|
@ -81,6 +81,82 @@ prometheus:
|
||||||
model: qwen3-8b
|
model: qwen3-8b
|
||||||
type: gpu
|
type: gpu
|
||||||
|
|
||||||
|
# --- Sondes réseau actives (blackbox_exporter) ---
|
||||||
|
# Le relabeling envoie la cible réelle en ?target=… et fait pointer le
|
||||||
|
# scrape vers le service blackbox. Cf. admin/infra/reseau.md.
|
||||||
|
- job_name: blackbox-icmp
|
||||||
|
metrics_path: /probe
|
||||||
|
params:
|
||||||
|
module: [icmp]
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- 192.168.10.1 # storage-01 — passerelle / NAT / DNS
|
||||||
|
- 192.168.10.11 # compute-01 (control-plane)
|
||||||
|
- 192.168.10.12 # compute-02
|
||||||
|
- 192.168.10.13 # compute-03
|
||||||
|
- 192.168.10.20 # gpu-01
|
||||||
|
labels:
|
||||||
|
scope: lan
|
||||||
|
- targets:
|
||||||
|
- 1.1.1.1 # Cloudflare — joignabilité internet
|
||||||
|
- 9.9.9.9 # Quad9 — joignabilité internet
|
||||||
|
labels:
|
||||||
|
scope: internet
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [__address__]
|
||||||
|
target_label: __param_target
|
||||||
|
- source_labels: [__param_target]
|
||||||
|
target_label: instance
|
||||||
|
- target_label: __address__
|
||||||
|
replacement: blackbox-exporter.monitoring.svc.cluster.local:9115
|
||||||
|
|
||||||
|
- job_name: blackbox-http
|
||||||
|
metrics_path: /probe
|
||||||
|
params:
|
||||||
|
module: [http_2xx]
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- http://grafana.lab.local
|
||||||
|
- http://prometheus.lab.local
|
||||||
|
- http://alertmanager.lab.local
|
||||||
|
- http://argocd.lab.local
|
||||||
|
- http://openwebui.lab.local
|
||||||
|
- http://n8n.lab.local
|
||||||
|
- http://ghostfolio.lab.local
|
||||||
|
- http://searxng.lab.local
|
||||||
|
- http://stt.lab.local
|
||||||
|
labels:
|
||||||
|
scope: ingress
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [__address__]
|
||||||
|
target_label: __param_target
|
||||||
|
- source_labels: [__param_target]
|
||||||
|
target_label: instance
|
||||||
|
- target_label: __address__
|
||||||
|
replacement: blackbox-exporter.monitoring.svc.cluster.local:9115
|
||||||
|
|
||||||
|
# DNS : module choisi par cible (label module → __param_module), serveur
|
||||||
|
# interrogé = dnsmasq (192.168.10.1:53). dns_lab teste le wildcard
|
||||||
|
# *.lab.local, dns_external teste le forwarding vers l'upstream.
|
||||||
|
- job_name: blackbox-dns
|
||||||
|
metrics_path: /probe
|
||||||
|
static_configs:
|
||||||
|
- targets: ['192.168.10.1:53']
|
||||||
|
labels:
|
||||||
|
module: dns_lab
|
||||||
|
instance: 'dnsmasq:grafana.lab.local'
|
||||||
|
- targets: ['192.168.10.1:53']
|
||||||
|
labels:
|
||||||
|
module: dns_external
|
||||||
|
instance: 'dnsmasq:one.one.one.one'
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [module]
|
||||||
|
target_label: __param_module
|
||||||
|
- source_labels: [__address__]
|
||||||
|
target_label: __param_target
|
||||||
|
- target_label: __address__
|
||||||
|
replacement: blackbox-exporter.monitoring.svc.cluster.local:9115
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
||||||
65
progress/2026-06-25.md
Normal file
65
progress/2026-06-25.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# 2026-06-25 — Surveillance réseau active (blackbox_exporter) + outil Asa `network_status`
|
||||||
|
|
||||||
|
## Résumé
|
||||||
|
|
||||||
|
Ajout d'une **surveillance réseau active** au cluster : déploiement de **blackbox_exporter**
|
||||||
|
(chart Helm 11.13.0, ns `monitoring`, ArgoCD) qui exécute des **sondes ICMP / HTTP / DNS**.
|
||||||
|
Ça comble le trou laissé par le monitoring *passif* (node_exporter + `up{}`) : on mesure
|
||||||
|
désormais la **joignabilité** des hôtes et de la passerelle, l'**accès internet**, la **santé
|
||||||
|
DNS** (`*.lab.local` + forwarding) et la **latence LAN**, et on sonde les services **de bout
|
||||||
|
en bout** via Traefik.
|
||||||
|
|
||||||
|
Côté assistant, **Asa** gagne l'outil `network_status` (boucle agentique) et un contexte
|
||||||
|
présélectionnable `reseau`.
|
||||||
|
|
||||||
|
Développé hors-ligne (sans accès cluster) sur la branche `claude/gifted-feynman-qqiyvq` —
|
||||||
|
à déployer via ArgoCD (k8s) + redéploiement du STT-server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## blackbox_exporter (ArgoCD, ns monitoring)
|
||||||
|
|
||||||
|
- `k8s/apps-of-apps/apps/blackbox-exporter.yaml` — Application multi-source (chart
|
||||||
|
`prometheus-blackbox-exporter` 11.13.0 + `$values` repo), calquée sur `kube-prometheus-stack`.
|
||||||
|
- `k8s/infra/blackbox-exporter/values.yaml` — modules (`http_2xx`, `http_2xx_insecure`,
|
||||||
|
`icmp`, `dns_lab`, `dns_external`, `tcp_connect`), **ICMP via `NET_RAW`** (ns en PSS
|
||||||
|
privileged), ressources minimales (32–64 Mi), `serviceMonitor`/`prometheusRule` off,
|
||||||
|
`fullnameOverride: blackbox-exporter` (service prévisible).
|
||||||
|
- **DNS du pod = dnsmasq** (`dnsPolicy: None` + `dnsConfig` 192.168.10.1, repli 1.1.1.1) →
|
||||||
|
les sondes HTTP `*.lab.local` empruntent le vrai chemin DNS lab → Traefik.
|
||||||
|
|
||||||
|
## Sondes (additionalScrapeConfigs)
|
||||||
|
|
||||||
|
`k8s/infra/monitoring/values.yaml` — 3 jobs avec relabeling standard `→` service blackbox :
|
||||||
|
- `blackbox-icmp` : 192.168.10.1/.11/.12/.13/.20 (`scope=lan`) + 1.1.1.1 / 9.9.9.9 (`scope=internet`)
|
||||||
|
- `blackbox-http` : `http://<service>.lab.local` (grafana, prometheus, alertmanager, argocd,
|
||||||
|
openwebui, n8n, ghostfolio, searxng, stt) — `scope=ingress`
|
||||||
|
- `blackbox-dns` : 192.168.10.1:53, module par cible (`dns_lab` = wildcard, `dns_external` = forwarding)
|
||||||
|
|
||||||
|
## Alertes
|
||||||
|
|
||||||
|
`k8s/infra/monitoring/alerts/alerts-network.yaml` (PrometheusRule, `release: kube-prometheus-stack`) :
|
||||||
|
`InternetDown`, `DnsLabResolutionFailed`, `DnsForwardingFailed`, `GatewayUnreachable`,
|
||||||
|
`ClusterHostUnreachable`, `ServiceEndpointDown`, `HighLanLatency`, `SlowServiceResponse`,
|
||||||
|
`BlackboxExporterDown`. Même pipeline AlertManager → webhook Hermes + n8n → Gmail.
|
||||||
|
|
||||||
|
## Asa — `network_status` + contexte `reseau`
|
||||||
|
|
||||||
|
- `stt/server/stt_server/sources.py` — `network_block()` : sondes OK/total, internet, DNS lab,
|
||||||
|
latence LAN max, liste des sondes en échec ; enregistré dans `_FETCHERS` (`network`).
|
||||||
|
- `stt/server/stt_server/tools.py` — outil `network_status` (réutilise `network_block`).
|
||||||
|
- `stt/server/stt_server/contexts.py` — `network_status` ajouté aux outils du contexte `asa` ;
|
||||||
|
nouveau contexte présélectionnable `reseau` (source `network`).
|
||||||
|
|
||||||
|
## Doc
|
||||||
|
|
||||||
|
- `admin/infra/reseau.md` — section « Surveillance réseau active — blackbox_exporter »
|
||||||
|
(architecture, métriques PromQL, alertes, vérifs, caveats firewall/ICMP).
|
||||||
|
- `CLAUDE.md` — bullet monitoring mis à jour.
|
||||||
|
|
||||||
|
## À vérifier au déploiement
|
||||||
|
|
||||||
|
- ICMP vers gpu-01 / compute : leur **firewall hôte** peut dropper l'echo-request (storage-01
|
||||||
|
l'accepte). Si `ClusterHostUnreachable` à tort, ouvrir l'ICMP depuis `192.168.10.0/24`.
|
||||||
|
- Sondes HTTP : services derrière auth / 3xx non suivis → ajuster module ou retirer la cible.
|
||||||
|
- Tests STT non exécutés ici (deps `httpx`/`pydantic` absentes de l'env) — code byte-compilé OK.
|
||||||
|
|
@ -42,8 +42,8 @@ CONTEXTS: dict[str, Context] = {
|
||||||
id="asa", label="Asa · agent", icon="✨",
|
id="asa", label="Asa · agent", icon="✨",
|
||||||
description="Asa répond en s'appuyant sur des outils (doc + état live du homelab).",
|
description="Asa répond en s'appuyant sur des outils (doc + état live du homelab).",
|
||||||
system_prompt=_BASE + " Tu disposes d'outils pour obtenir l'état RÉEL et LIVE du "
|
system_prompt=_BASE + " Tu disposes d'outils pour obtenir l'état RÉEL et LIVE du "
|
||||||
"homelab (santé des machines, état du cluster, métriques Prometheus), chercher dans la "
|
"homelab (santé des machines, état du cluster, état du réseau, métriques Prometheus), "
|
||||||
"documentation Funk, et faire une recherche sur INTERNET (web_search). Dès qu'une "
|
"chercher dans la documentation Funk, et faire une recherche sur INTERNET (web_search). Dès qu'une "
|
||||||
"question porte sur l'état courant, un fait précis du homelab, ou une info externe / "
|
"question porte sur l'état courant, un fait précis du homelab, ou une info externe / "
|
||||||
"récente, APPELLE l'outil adéquat puis réponds à partir de son résultat — n'invente "
|
"récente, APPELLE l'outil adéquat puis réponds à partir de son résultat — n'invente "
|
||||||
"jamais. Si les outils ne donnent pas l'information, dis-le simplement. Pour le "
|
"jamais. Si les outils ne donnent pas l'information, dis-le simplement. Pour le "
|
||||||
|
|
@ -51,8 +51,8 @@ CONTEXTS: dict[str, Context] = {
|
||||||
"explicitement d'AGIR (redémarrer, relancer, modifier…), utilise l'outil admin_action "
|
"explicitement d'AGIR (redémarrer, relancer, modifier…), utilise l'outil admin_action "
|
||||||
"(s'il est disponible) — il proposera l'action et demandera confirmation, sans rien "
|
"(s'il est disponible) — il proposera l'action et demandera confirmation, sans rien "
|
||||||
"exécuter de toi-même.",
|
"exécuter de toi-même.",
|
||||||
tools=("search_docs", "host_health", "cluster_status", "prometheus_query", "web_search",
|
tools=("search_docs", "host_health", "cluster_status", "network_status",
|
||||||
"admin_action"),
|
"prometheus_query", "web_search", "admin_action"),
|
||||||
),
|
),
|
||||||
"funk": Context(
|
"funk": Context(
|
||||||
id="funk", label="Funk · cluster", icon="🛠️",
|
id="funk", label="Funk · cluster", icon="🛠️",
|
||||||
|
|
@ -92,6 +92,15 @@ CONTEXTS: dict[str, Context] = {
|
||||||
"Tu fais de la LECTURE SEULE : tu n'exécutes aucune action.",
|
"Tu fais de la LECTURE SEULE : tu n'exécutes aucune action.",
|
||||||
sources=("cluster", "docs"),
|
sources=("cluster", "docs"),
|
||||||
),
|
),
|
||||||
|
"reseau": Context(
|
||||||
|
id="reseau", label="Réseau", icon="🌐",
|
||||||
|
description="Surveillance réseau active : joignabilité, internet, DNS, latence.",
|
||||||
|
system_prompt=_BASE + " Tu réponds sur l'état du RÉSEAU du homelab à partir des "
|
||||||
|
"sondes actives fournies ci-dessous (joignabilité des hôtes et de la passerelle, "
|
||||||
|
"accès internet, DNS *.lab.local, latence LAN, services injoignables). Reste "
|
||||||
|
"factuel ; si une sonde est en échec, précise laquelle.",
|
||||||
|
sources=("network",),
|
||||||
|
),
|
||||||
# Contexte « agent » : court-circuite le LLM — la demande est confirmée puis
|
# Contexte « agent » : court-circuite le LLM — la demande est confirmée puis
|
||||||
# exécutée par le vrai agent Hermes (hermes-exec). Géré entièrement dans app.py
|
# exécutée par le vrai agent Hermes (hermes-exec). Géré entièrement dans app.py
|
||||||
# (handshake), pas via assemble(). Exposé seulement si actions_enabled + jeton.
|
# (handshake), pas via assemble(). Exposé seulement si actions_enabled + jeton.
|
||||||
|
|
@ -109,6 +118,7 @@ _SOURCE_TITLES = {
|
||||||
"metrics": "Métriques Prometheus (live)",
|
"metrics": "Métriques Prometheus (live)",
|
||||||
"alerts": "Alertes actives (Alertmanager)",
|
"alerts": "Alertes actives (Alertmanager)",
|
||||||
"cluster": "État du cluster (live)",
|
"cluster": "État du cluster (live)",
|
||||||
|
"network": "Réseau — sondes actives (live)",
|
||||||
"docs": "Documentation Funk",
|
"docs": "Documentation Funk",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,12 +263,43 @@ async def metrics_block(client: httpx.AsyncClient) -> str:
|
||||||
return "\n".join(lines) if lines else "Aucune métrique disponible."
|
return "\n".join(lines) if lines else "Aucune métrique disponible."
|
||||||
|
|
||||||
|
|
||||||
|
async def network_block(client: httpx.AsyncClient) -> str:
|
||||||
|
"""Surveillance réseau active (sondes blackbox_exporter) : joignabilité,
|
||||||
|
internet, DNS, latence LAN, et liste des sondes en échec."""
|
||||||
|
try:
|
||||||
|
total = await _prom_query(client, 'count(probe_success{job=~"blackbox-.*"})')
|
||||||
|
up = await _prom_query(client, 'count(probe_success{job=~"blackbox-.*"} == 1)')
|
||||||
|
internet = await _prom_query(client, 'sum(probe_success{job="blackbox-icmp",scope="internet"})')
|
||||||
|
dns_lab = await _prom_query(client, 'probe_success{job="blackbox-dns",module="dns_lab"}')
|
||||||
|
rtt = await _prom_query(client, 'max(probe_icmp_duration_seconds{scope="lan",phase="rtt"})')
|
||||||
|
failing = await _prom_query(client, 'probe_success{job=~"blackbox-.*"} == 0')
|
||||||
|
except httpx.HTTPError:
|
||||||
|
return "Données indisponibles : Prometheus injoignable."
|
||||||
|
if not total:
|
||||||
|
return ("Aucune sonde réseau active (blackbox_exporter pas encore déployé "
|
||||||
|
"ou sans données).")
|
||||||
|
lines = [f"Sondes réseau OK : {int(up[0][1]) if up else 0}/{int(total[0][1])}."]
|
||||||
|
if internet:
|
||||||
|
lines.append("Internet : " + ("accessible" if internet[0][1] > 0 else "INACCESSIBLE") + ".")
|
||||||
|
if dns_lab:
|
||||||
|
lines.append("DNS *.lab.local : " + ("OK" if dns_lab[0][1] == 1 else "EN ÉCHEC") + ".")
|
||||||
|
if rtt:
|
||||||
|
lines.append(f"Latence LAN max : {round(rtt[0][1] * 1000)} ms.")
|
||||||
|
if failing:
|
||||||
|
names = ", ".join(m.get("instance", "?") for m, _ in failing[:8] if m.get("instance"))
|
||||||
|
lines.append(f"⚠️ Sondes en échec : {names}.")
|
||||||
|
else:
|
||||||
|
lines.append("Toutes les sondes répondent.")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
# Aiguillage source_id → fetcher
|
# Aiguillage source_id → fetcher
|
||||||
_FETCHERS = {
|
_FETCHERS = {
|
||||||
"ghostfolio": ghostfolio_block,
|
"ghostfolio": ghostfolio_block,
|
||||||
"alerts": alerts_block,
|
"alerts": alerts_block,
|
||||||
"cluster": cluster_block,
|
"cluster": cluster_block,
|
||||||
"metrics": metrics_block,
|
"metrics": metrics_block,
|
||||||
|
"network": network_block,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,11 @@ async def _cluster_status(args: dict, deps: ToolDeps) -> str:
|
||||||
return await sources.cluster_block(deps.client)
|
return await sources.cluster_block(deps.client)
|
||||||
|
|
||||||
|
|
||||||
|
async def _network_status(args: dict, deps: ToolDeps) -> str:
|
||||||
|
# Réutilise le bloc surveillance réseau (sondes actives blackbox).
|
||||||
|
return await sources.network_block(deps.client)
|
||||||
|
|
||||||
|
|
||||||
async def _prometheus_query(args: dict, deps: ToolDeps) -> str:
|
async def _prometheus_query(args: dict, deps: ToolDeps) -> str:
|
||||||
expr = (args.get("expr") or "").strip()
|
expr = (args.get("expr") or "").strip()
|
||||||
if not expr:
|
if not expr:
|
||||||
|
|
@ -243,6 +248,21 @@ _TOOLS: dict[str, tuple[dict, object]] = {
|
||||||
},
|
},
|
||||||
_cluster_status,
|
_cluster_status,
|
||||||
),
|
),
|
||||||
|
"network_status": (
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "network_status",
|
||||||
|
"description": "État LIVE du réseau du homelab (sondes actives blackbox) : "
|
||||||
|
"joignabilité des hôtes et de la passerelle, accès internet, santé DNS "
|
||||||
|
"(*.lab.local), latence LAN et services injoignables. À utiliser pour "
|
||||||
|
"« est-ce que le réseau / internet / le DNS fonctionne ? » ou "
|
||||||
|
"« quel hôte ne répond plus ? ».",
|
||||||
|
"parameters": {"type": "object", "properties": {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_network_status,
|
||||||
|
),
|
||||||
"prometheus_query": (
|
"prometheus_query": (
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue