From 649c6fde8eb85f782801dc5b988520b48962d4ac Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Jun 2026 12:43:13 +0000 Subject: [PATCH] =?UTF-8?q?feat(monitoring):=20surveillance=20r=C3=A9seau?= =?UTF-8?q?=20active=20via=20blackbox=5Fexporter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01Rc3dLKfqEPHexvFCCx5ZEF --- CLAUDE.md | 2 +- PROGRESS.md | 1 + admin/infra/reseau.md | 80 +++++++++++++ k8s/apps-of-apps/apps/blackbox-exporter.yaml | 29 +++++ k8s/infra/blackbox-exporter/values.yaml | 108 ++++++++++++++++++ .../monitoring/alerts/alerts-network.yaml | 102 +++++++++++++++++ k8s/infra/monitoring/values.yaml | 76 ++++++++++++ progress/2026-06-25.md | 65 +++++++++++ stt/server/stt_server/contexts.py | 18 ++- stt/server/stt_server/sources.py | 31 +++++ stt/server/stt_server/tools.py | 20 ++++ 11 files changed, 527 insertions(+), 5 deletions(-) create mode 100644 k8s/apps-of-apps/apps/blackbox-exporter.yaml create mode 100644 k8s/infra/blackbox-exporter/values.yaml create mode 100644 k8s/infra/monitoring/alerts/alerts-network.yaml create mode 100644 progress/2026-06-25.md diff --git a/CLAUDE.md b/CLAUDE.md index bb55679..fb3e154 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **État actuel (2026-06-23)** : - ✅ 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 -- ✅ 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) - ✅ 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`) diff --git a/PROGRESS.md b/PROGRESS.md index 5f3b574..829e889 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -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-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-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` | diff --git a/admin/infra/reseau.md b/admin/infra/reseau.md index 71e1b12..b7c5d56 100644 --- a/admin/infra/reseau.md +++ b/admin/infra/reseau.md @@ -167,3 +167,83 @@ ip -s link show enp6s0f3u2c2 # Connexions actives 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://.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). diff --git a/k8s/apps-of-apps/apps/blackbox-exporter.yaml b/k8s/apps-of-apps/apps/blackbox-exporter.yaml new file mode 100644 index 0000000..524e1da --- /dev/null +++ b/k8s/apps-of-apps/apps/blackbox-exporter.yaml @@ -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 diff --git a/k8s/infra/blackbox-exporter/values.yaml b/k8s/infra/blackbox-exporter/values.yaml new file mode 100644 index 0000000..f842686 --- /dev/null +++ b/k8s/infra/blackbox-exporter/values.yaml @@ -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 diff --git a/k8s/infra/monitoring/alerts/alerts-network.yaml b/k8s/infra/monitoring/alerts/alerts-network.yaml new file mode 100644 index 0000000..428d51c --- /dev/null +++ b/k8s/infra/monitoring/alerts/alerts-network.yaml @@ -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." diff --git a/k8s/infra/monitoring/values.yaml b/k8s/infra/monitoring/values.yaml index 60ddb20..fcbc940 100644 --- a/k8s/infra/monitoring/values.yaml +++ b/k8s/infra/monitoring/values.yaml @@ -81,6 +81,82 @@ prometheus: model: qwen3-8b 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: ingress: enabled: true diff --git a/progress/2026-06-25.md b/progress/2026-06-25.md new file mode 100644 index 0000000..42cd4cd --- /dev/null +++ b/progress/2026-06-25.md @@ -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://.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. diff --git a/stt/server/stt_server/contexts.py b/stt/server/stt_server/contexts.py index 3d7cb70..64376f1 100644 --- a/stt/server/stt_server/contexts.py +++ b/stt/server/stt_server/contexts.py @@ -42,8 +42,8 @@ CONTEXTS: dict[str, Context] = { id="asa", label="Asa · agent", icon="✨", 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 " - "homelab (santé des machines, état du cluster, métriques Prometheus), chercher dans la " - "documentation Funk, et faire une recherche sur INTERNET (web_search). Dès qu'une " + "homelab (santé des machines, état du cluster, état du réseau, métriques Prometheus), " + "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 / " "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 " @@ -51,8 +51,8 @@ CONTEXTS: dict[str, Context] = { "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 " "exécuter de toi-même.", - tools=("search_docs", "host_health", "cluster_status", "prometheus_query", "web_search", - "admin_action"), + tools=("search_docs", "host_health", "cluster_status", "network_status", + "prometheus_query", "web_search", "admin_action"), ), "funk": Context( 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.", 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 # 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. @@ -109,6 +118,7 @@ _SOURCE_TITLES = { "metrics": "Métriques Prometheus (live)", "alerts": "Alertes actives (Alertmanager)", "cluster": "État du cluster (live)", + "network": "Réseau — sondes actives (live)", "docs": "Documentation Funk", } diff --git a/stt/server/stt_server/sources.py b/stt/server/stt_server/sources.py index 34991cc..7cb39af 100644 --- a/stt/server/stt_server/sources.py +++ b/stt/server/stt_server/sources.py @@ -263,12 +263,43 @@ async def metrics_block(client: httpx.AsyncClient) -> str: 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 _FETCHERS = { "ghostfolio": ghostfolio_block, "alerts": alerts_block, "cluster": cluster_block, "metrics": metrics_block, + "network": network_block, } diff --git a/stt/server/stt_server/tools.py b/stt/server/stt_server/tools.py index 49c407e..9134c8f 100644 --- a/stt/server/stt_server/tools.py +++ b/stt/server/stt_server/tools.py @@ -131,6 +131,11 @@ async def _cluster_status(args: dict, deps: ToolDeps) -> str: 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: expr = (args.get("expr") or "").strip() if not expr: @@ -243,6 +248,21 @@ _TOOLS: dict[str, tuple[dict, object]] = { }, _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": ( { "type": "function",