@@ -9,6 +9,7 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
const pollInterval = 30 * time.Second
|
||||
@@ -29,23 +30,31 @@ var badWaitingReasons = map[string]bool{
|
||||
type podKey struct{ namespace, pod string }
|
||||
|
||||
// Watcher scrute périodiquement les pods des namespaces actifs et notifie
|
||||
// tout changement d'état (sain -> panne, panne -> rétabli). Les destinataires
|
||||
// (recipients) sont résolus à chaque tick où une notification est nécessaire
|
||||
// — chaque admin configure son propre webhook Discord / bot Telegram dans
|
||||
// son profil (voir internal/profile), rien n'est figé au démarrage.
|
||||
// tout changement d'état (sain -> panne, panne -> rétabli), ainsi que les
|
||||
// dépassements de seuil CPU/mémoire (voir resources.go) et de remplissage
|
||||
// des volumes (PVC). Les destinataires (recipients) sont résolus à chaque
|
||||
// tick où une notification est nécessaire — chaque admin configure son
|
||||
// propre webhook Discord / bot Telegram dans son profil (voir
|
||||
// internal/profile), rien n'est figé au démarrage.
|
||||
type Watcher struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
recipients func(ctx context.Context) ([]Notifier, error)
|
||||
namespaces func() ([]string, error)
|
||||
state map[podKey]string // dernière raison de panne connue ("" = sain)
|
||||
k8sClient *kubernetes.Clientset
|
||||
metricsClient *metricsclient.Clientset // optionnel : nil désactive les alertes CPU/mémoire (le reste du watcher continue de fonctionner)
|
||||
recipients func(ctx context.Context) ([]Notifier, error)
|
||||
namespaces func() ([]string, error)
|
||||
state map[podKey]string // dernière raison de panne connue ("" = sain)
|
||||
resourceHigh map[podKey]int // ticks consécutifs au-dessus du seuil CPU/mémoire
|
||||
pvcHigh map[pvcKey]int // ticks consécutifs au-dessus du seuil de remplissage PVC
|
||||
}
|
||||
|
||||
func NewWatcher(k8sClient *kubernetes.Clientset, recipients func(ctx context.Context) ([]Notifier, error), namespaces func() ([]string, error)) *Watcher {
|
||||
func NewWatcher(k8sClient *kubernetes.Clientset, metricsClient *metricsclient.Clientset, recipients func(ctx context.Context) ([]Notifier, error), namespaces func() ([]string, error)) *Watcher {
|
||||
return &Watcher{
|
||||
k8sClient: k8sClient,
|
||||
recipients: recipients,
|
||||
namespaces: namespaces,
|
||||
state: make(map[podKey]string),
|
||||
k8sClient: k8sClient,
|
||||
metricsClient: metricsClient,
|
||||
recipients: recipients,
|
||||
namespaces: namespaces,
|
||||
state: make(map[podKey]string),
|
||||
resourceHigh: make(map[podKey]int),
|
||||
pvcHigh: make(map[pvcKey]int),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +78,10 @@ func (w *Watcher) tick(ctx context.Context) {
|
||||
log.Printf("alerts: liste des démos actives indisponible: %v", err)
|
||||
return
|
||||
}
|
||||
nsSet := make(map[string]bool, len(nsList))
|
||||
for _, ns := range nsList {
|
||||
nsSet[ns] = true
|
||||
}
|
||||
|
||||
// Les destinataires ne sont chargés (requête DB) que si une notification
|
||||
// s'avère nécessaire ce tick-ci, et une seule fois par tick.
|
||||
@@ -92,6 +105,9 @@ func (w *Watcher) tick(ctx context.Context) {
|
||||
}
|
||||
|
||||
seen := make(map[podKey]bool, len(w.state))
|
||||
seenHigh := make(map[podKey]bool, len(w.resourceHigh))
|
||||
nodeNames := make(map[string]bool) // nœuds hébergeant un pod Running d'un namespace actif (voir checkPVCUsage)
|
||||
|
||||
for _, ns := range nsList {
|
||||
pods, err := w.k8sClient.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
@@ -114,7 +130,13 @@ func (w *Watcher) tick(ctx context.Context) {
|
||||
}
|
||||
w.state[key] = reason
|
||||
}
|
||||
|
||||
if pod.Status.Phase == corev1.PodRunning && pod.Spec.NodeName != "" {
|
||||
nodeNames[pod.Spec.NodeName] = true
|
||||
}
|
||||
}
|
||||
|
||||
w.checkResourceUsage(ctx, loadNotifier, ns, pods.Items, seenHigh)
|
||||
}
|
||||
|
||||
// Pods disparus (démo supprimée, rollout) : on oublie leur état pour ne
|
||||
@@ -124,12 +146,11 @@ func (w *Watcher) tick(ctx context.Context) {
|
||||
delete(w.state, key)
|
||||
}
|
||||
}
|
||||
w.clearResourceRecovered(ctx, loadNotifier, seenHigh)
|
||||
w.checkPVCUsage(ctx, loadNotifier, nsSet, nodeNames)
|
||||
}
|
||||
|
||||
func (w *Watcher) notify(ctx context.Context, notifier Notifier, key podKey, prev, reason string) {
|
||||
if notifier == nil {
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
switch {
|
||||
case prev == "" && reason != "":
|
||||
@@ -139,9 +160,7 @@ func (w *Watcher) notify(ctx context.Context, notifier Notifier, key podKey, pre
|
||||
default:
|
||||
msg = fmt.Sprintf("🔴 [%s] pod %s toujours en erreur : %s", key.namespace, key.pod, reason)
|
||||
}
|
||||
if err := notifier.Notify(ctx, msg); err != nil {
|
||||
log.Printf("alerts: notification échouée: %v", err)
|
||||
}
|
||||
w.send(ctx, notifier, msg)
|
||||
}
|
||||
|
||||
// problemReason retourne une raison de panne non vide si le pod est dans un
|
||||
|
||||
Reference in New Issue
Block a user