Supervision automatisée des vhosts Apache et Ingress Kubernetes avec Uptime Kuma v2

🔍 Introduction

Dans un environnement DevOps moderne, la supervision des services est essentielle pour garantir leur disponibilité et leurs performances. Uptime Kuma, une alternative open-source à Uptime Robot, permet de surveiller les services web et d’envoyer des alertes en cas de panne.

Dans cet article, nous allons voir comment :
Superviser automatiquement les vhosts Apache et les Ingress Kubernetes
Vérifier la disponibilité des backends définis en ProxyPass
Automatiser l’ajout des services dans Uptime Kuma
Configurer des alertes via Teams et Discord
Optimiser l’authentification sur Ingress NGINX

💡 Public cible : DevOps, SysAdmins, SRE et toute personne gérant un environnement Kubernetes avec des services web.


🛠️ Architecture de la solution

Notre solution repose sur les éléments suivants :

  • Apache : Héberge des vhosts et agit en tant que reverse proxy
  • Kubernetes : Orchestre les services et expose des Ingress
  • Uptime Kuma : Supervise les services et génère des alertes
  • Webhook Teams & Discord : Notifie en cas de problème
  • CronJob Kubernetes : Automatise la mise à jour des moniteurs

🔽 Vue d’ensemble du workflow :
1️⃣ Apache expose des vhosts avec ProxyPass vers des backends
2️⃣ Un script Python sur chaque RP analyse la config et envoie les vhosts valides à une API interne
3️⃣ Une API Flask en Kubernetes reçoit et enregistre les vhosts dans Uptime Kuma
4️⃣ Un CronJob Kubernetes détecte les nouvelles URLs Ingress et met à jour Uptime Kuma
5️⃣ Teams & Discord envoient des alertes en cas de panne


📌 Détection automatique des vhosts Apache

Nous utilisons un script Python exécuté périodiquement via Cron pour récupérer automatiquement les vhosts actifs sur Apache.

Ce script:

  • Extrait les vhosts via apachectl -S
  • Filtre les vhosts non pertinents (port autre que 443, configurations par défaut, vhosts de développement)
  • Vérifie l’accessibilité du backend défini par ProxyPass
  • Envoie la liste des vhosts valides vers l’API Kubernetes Uptime Kuma

📜 Script send_vhosts.py sur les reverse proxies

Ce script analyse la configuration Apache et détecte les vhosts utilisant ProxyPass.

import os
import requests
import subprocess
import re

# Récupérer les identifiants d'authentification Basic depuis les variables d'environnement
API_AUTH_USERNAME = os.getenv("API_AUTH_USERNAME", "uptime")
API_AUTH_PASSWORD = os.getenv("API_AUTH_PASSWORD", "password")

# 🌍 Adresses des serveurs API dans Kubernetes
API_URLS = [
    "http://uptime-kuma-api.test.kube.test.fr/api/vhosts",
    "http://uptime-kuma-api.prod.kube.test.fr/api/vhosts"
]

# 📡 Récupérer les vhosts Apache et leur fichier de config via apachectl -S
try:
    result = subprocess.run("apachectl -S", shell=True, capture_output=True, text=True)
    apachectl_output = result.stdout.strip().split("\n")
except Exception as e:
    print(f"❌ Erreur lors de l'exécution d'apachectl -S : {e}")
    exit(1)

vhosts = []
vhost_config_files = {}  # mapping vhost -> fichier de config

# Capture également le port et ignorer :
# - Les vhosts en port 80
# - Les configurations issues de 000-default.conf
# - Les vhosts contenant "dev-www"
vhost_line_regex = re.compile(r'\s*port\s+(\d+)\s+namevhost\s+(\S+)\s+\(([^:]+):\d+\)')
for line in apachectl_output:
    if "namevhost" in line:
        m = vhost_line_regex.search(line)
        if m:
            port, vhost, config_file = m.groups()
            if port != "443":
                continue
            if "000-default.conf" in config_file:
                continue
            if "dev-www" in vhost:
                continue
            if vhost.endswith(".test.fr"):
                vhosts.append(vhost)
                if vhost not in vhost_config_files:
                    vhost_config_files[vhost] = config_file

if not vhosts:
    print("❌ Aucun vhost en '*.test.fr' trouvé sur le port 443 (excluant 000-default et les dev-www).")
    exit(0)

# Dictionnaire pour indiquer si le vhost utilise une configuration de type Directory/DocumentRoot
vhost_has_directory = {}

# 🔍 Pour chaque vhost, ouvrir son fichier de config et extraire :
# - Le backend défini dans la directive ProxyPass (s'il existe)
# - La directive ProxyPreserveHost
# - Une éventuelle règle RewriteRule pour "^/$"
# - Et détecter la présence d'une directive DocumentRoot ou d'un bloc <Directory>
vhost_to_backend = {}
vhost_to_preserve_host = {}
vhost_to_rewrite_target = {}

for vhost in vhosts:
    config_file = vhost_config_files.get(vhost)
    if not config_file:
        continue
    try:
        with open(config_file, 'r') as f:
            config_content = f.read()
    except Exception as e:
        print(f"❌ Erreur lors de la lecture du fichier de config {config_file} pour {vhost} : {e}")
        continue

    # Retirer les blocs <Location ...>...</Location>
    config_no_location = re.sub(r'<Location\b[^>]*>.*?</Location>', '', config_content, flags=re.DOTALL)
    # Retirer les lignes commentées
    config_no_comments = "\n".join(
        line for line in config_no_location.splitlines() if not line.strip().startswith("#")
    )

    # Détecter la présence d'une directive DocumentRoot ou d'un bloc <Directory>
    has_directory = bool(re.search(r'DocumentRoot\s+', config_no_comments, flags=re.IGNORECASE)) \
                    or bool(re.search(r'<Directory\b', config_no_comments, flags=re.IGNORECASE))
    vhost_has_directory[vhost] = has_directory

    # Vérifier si ProxyPreserveHost est activé
    preserve = re.search(r'ProxyPreserveHost\s+On', config_no_comments, flags=re.IGNORECASE)
    vhost_to_preserve_host[vhost] = bool(preserve)

    # Extraire la directive ProxyPass (ou ProxyPassMatch) pour le chemin "/"
    pp_match = re.search(
        r'ProxyPass(?:Match)?\s+["\']?(/)["\']?\s+["\']?(\S+)["\']?',
        config_no_comments,
        flags=re.IGNORECASE
    )
    if pp_match:
        local_path, backend = pp_match.groups()
        if local_path.strip() == "/":
            vhost_to_backend[vhost] = backend.strip()
        else:
            if vhost not in vhost_to_backend:
                vhost_to_backend[vhost] = backend.strip()

    # Rechercher une RewriteRule pour le chemin racine "^/$"
    rewrite_match = re.search(
        r'RewriteRule\s+["\']?\^/\$["\']?\s+["\']?([^"\s]+)["\']?',
        config_no_comments,
        flags=re.IGNORECASE
    )
    if rewrite_match:
        rewrite_target = rewrite_match.group(1).strip()
        vhost_to_rewrite_target[vhost] = rewrite_target

# 🔍 Tester le backend pour chaque vhost
valid_vhosts = []

for vhost in vhosts:
    backend_url = vhost_to_backend.get(vhost)
    if backend_url:
        # Si une RewriteRule pour le root est définie, ajuster l'URL de test
        rewrite_target = vhost_to_rewrite_target.get(vhost)
        final_url = backend_url
        if rewrite_target and rewrite_target.startswith("/"):
            final_url = backend_url.rstrip("/") + rewrite_target

        print(f"🔄 Test de {vhost} → {final_url}")
        preserve_host = vhost_to_preserve_host.get(vhost, False)
        host_header = f"-H 'Host: {vhost}'" if preserve_host else ""
        curl_command = f"curl -k -s -o /dev/null -w '%{{http_code}}' {host_header} {final_url} --max-time 10"
        try:
            curl_result = subprocess.run(curl_command, shell=True, capture_output=True, text=True, timeout=15)
            status_code_str = curl_result.stdout.strip()
            try:
                status_code = int(status_code_str)
            except ValueError:
                print(f"❌ {vhost} a renvoyé une réponse invalide : {status_code_str}")
                continue

            # Pour les vhosts avec ProxyPass, considérer valide si HTTP <400 et != 0
            if status_code < 400 and status_code != 0:
                valid_vhosts.append(vhost)
                print(f"✅ {vhost} est valide (Backend {final_url} répond HTTP {status_code})")
            else:
                print(f"❌ {vhost} est invalide (Backend {final_url} a renvoyé HTTP {status_code})")
        except subprocess.TimeoutExpired:
            print(f"❌ Timeout lors du test du backend {final_url} pour {vhost}")
        except Exception as e:
            print(f"❌ Erreur lors du test du backend {final_url} pour {vhost} : {e}")
    elif vhost_has_directory.get(vhost):
        # Aucun ProxyPass, mais présence d'un Directory/DocumentRoot → ne pas tester et considérer valide par défaut
        valid_vhosts.append(vhost)
        print(f"✅ {vhost} est validé par défaut (Directory configuration)")
    else:
        print(f"❌ {vhost} n'a pas de ProxyPass configuré et pas de Directory défini.")

if not valid_vhosts:
    print("❌ Aucun vhost valide trouvé avec ProxyPass ou Directory configuré.")
    exit(0)

# 📡 Envoyer les vhosts validés aux API de test et prod avec authentification Basic
for api_url in API_URLS:
    try:
        response = requests.post(
            api_url,
            json={"vhosts": valid_vhosts},
            auth=(API_AUTH_USERNAME, API_AUTH_PASSWORD)
        )
        if response.status_code == 200:
            print(f"✅ Vhosts envoyés avec succès à {api_url} :", valid_vhosts)
        else:
            print(f"❌ Erreur API ({response.status_code}) sur {api_url} : {response.text}")
    except Exception as e:
        print(f"❌ Erreur lors de l'envoi des vhosts à {api_url} : {e}")

Ce script est lancé périodiquement grâce à une entrée dans /etc/cron.d/uptime-kuma :

0 * * * * root /usr/bin/python3 /usr/local/bin/send_vhosts.py


📌 API flask pour la gestion des vhosts

Nous utilisons Flask pour exposer une API REST qui reçoit les vhosts valides et les enregistre dans Uptime Kuma.

apiVersion: v1
kind: ConfigMap
metadata:
  name: uptime-kuma-vhost-api-script
  namespace: monitoring
data:
  server.py: |
    import os
    import json
    from flask import Flask, request, jsonify
    from uptime_kuma_api import UptimeKumaApi, MonitorType

    app = Flask(__name__)

    # 📡 Configuration Uptime Kuma
    KUMA_URL = os.getenv("KUMA_URL", "http://uptime-kuma.monitoring.svc.cluster.local")
    KUMA_USER = os.getenv("KUMA_USER", "admin")
    KUMA_PASSWORD = os.getenv("KUMA_PASSWORD", "password")

    # 🔗 Connexion à Uptime Kuma
    api = UptimeKumaApi(KUMA_URL)
    api.login(KUMA_USER, KUMA_PASSWORD)

    @app.route("/api/vhosts", methods=["POST"])
    def receive_vhosts():
        data = request.json
        if not data or "vhosts" not in data:
            return jsonify({"error": "Données invalides"}), 400

        vhosts = data["vhosts"]
        print(f"📡 Reçu {len(vhosts)} vhosts :", vhosts)

        # 🔎 Récupérer les moniteurs existants
        existing_monitors = {monitor['url'] for monitor in api.get_monitors() if 'url' in monitor}

        # ➕ Ajouter les nouveaux moniteurs sans doublon
        added = []
        # Dédupliquer la liste de vhosts pour éviter les répétitions dans la même requête
        unique_vhosts = set(vhosts)
        for vhost in unique_vhosts:
            full_url = f"https://{vhost}"
            if full_url not in existing_monitors:
                api.add_monitor(
                    type=MonitorType.HTTP,
                    name=vhost,
                    url=full_url,
                    interval=120,
                    expiryNotification=True,
                    maxretries=2,
                    notificationIDList=[1]
                )
                # Mise à jour de l'ensemble pour éviter d'ajouter plusieurs fois le même monitor
                existing_monitors.add(full_url)
                added.append(vhost)

        return jsonify({"message": f"{len(added)} nouveaux moniteurs ajoutés.", "added": added}), 200

    @app.route("/", methods=["GET"])
    @app.route("/health", methods=["GET"])
    def health_check():
        return jsonify({"status": "ok"}), 200
        
    if __name__ == "__main__":
        app.run(host="0.0.0.0", port=5000)

📌 Automatisation avec un CronJob Kubernetes

Un CronJob détecte les Ingress exposés et les ajoute dans Uptime Kuma.

uptime-kuma-cronjob.yaml :
apiVersion: v1
kind: ConfigMap
metadata:
  name: uptime-kuma-auto-monitor-script
  namespace: monitoring
data:
  auto_add_monitors.py: |
    import os
    from kubernetes import client, config
    from uptime_kuma_api import UptimeKumaApi, MonitorType

    # 🔐 Charger la configuration interne Kubernetes (TLS interne)
    config.load_incluster_config()

    # 🌐 Initialiser l'API Kubernetes
    k8s_api = client.NetworkingV1Api()

    # 📡 Configuration Uptime Kuma
    KUMA_URL = os.getenv("KUMA_URL", "http://uptime-kuma.monitoring.svc.cluster.local")
    KUMA_USER = os.getenv("KUMA_USER", "admin")
    KUMA_PASSWORD = os.getenv("KUMA_PASSWORD", "password")

    # 🔗 Se connecter à l'API Uptime Kuma
    api = UptimeKumaApi(KUMA_URL)
    api.login(KUMA_USER, KUMA_PASSWORD)

    # 🏗️ Récupérer les URLs des Ingress
    print("📡 Récupération des Ingress Kubernetes...")
    ingresses = k8s_api.list_ingress_for_all_namespaces()

    urls = []
    for ingress in ingresses.items:
        if ingress.spec and ingress.spec.rules:
            for rule in ingress.spec.rules:
                if rule.host and "*" not in rule.host:
                    urls.append(rule.host)

    # 🚨 Vérifier s'il y a des URLs avant de continuer
    if not urls:
        print("⚠️ Aucune URL valide trouvée. Rien à ajouter dans Uptime Kuma.")
        exit(0)  # Arrêter le script proprement

    print(f"🔍 {len(urls)} URLs détectées : {urls}")

    # 🔎 Récupérer les moniteurs existants dans Uptime Kuma et stocker leurs URLs
    existing_monitors = {monitor['url'] for monitor in api.get_monitors() if 'url' in monitor}

    # ➕ Ajouter les nouveaux moniteurs uniquement s'ils n'existent pas déjà
    for url in urls:
        full_url = f"https://{url}"
        if full_url not in existing_monitors:
            api.add_monitor(
                type=MonitorType.HTTP,
                name=url,
                url=full_url,
                interval=120,
                expiryNotification=True,
                maxretries=2,
                notificationIDList=[1]
            )
            print(f"✅ Moniteur ajouté pour {url}")
        else:
            print(f"🔍 Moniteur déjà existant pour {url}, pas d'ajout.")

    print("🚀 Mise à jour terminée !")
---
apiVersion: v1
kind: Secret
metadata:
  name: uptime-kuma-api-credentials
  namespace: monitoring
type: Opaque
data:
  username: XXXXXXXXXX
  password: XXXXXXXXXX
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: uptime-kuma-monitor
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: uptime-kuma-monitor-role
  namespace: monitoring
rules:
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: uptime-kuma-monitor-binding
  namespace: monitoring
subjects:
- kind: ServiceAccount
  name: uptime-kuma-monitor
  namespace: monitoring
roleRef:
  kind: ClusterRole
  name: uptime-kuma-monitor-role
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: uptime-kuma-auto-monitor
  namespace: monitoring
spec:
  schedule: "*/60 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: uptime-kuma-auto-monitor
            image: python:3.9
            command:
              - /bin/sh
              - "-c"
              - "pip install uptime-kuma-api && pip install kubernetes && python /scripts/auto_add_monitors.py"
            volumeMounts:
            - name: script-volume
              mountPath: /scripts
            env:
            - name: KUMA_URL
              value: "http://uptime-kuma.monitoring.svc.cluster.local"
            - name: KUMA_USER
              valueFrom:
                secretKeyRef:
                  name: uptime-kuma-api-credentials
                  key: username
            - name: KUMA_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: uptime-kuma-api-credentials
                  key: password
          serviceAccountName: uptime-kuma-monitor
          restartPolicy: OnFailure
          volumes:
          - name: script-volume
            configMap:
              name: uptime-kuma-auto-monitor-script
              defaultMode: 0777

Configuration ArgoCD et Kubernetes

Stockage et base de données

Uptime Kuma utilise MariaDB pour stocker ses données, avec une configuration spécifique Kubernetes :

apiVersion: v1
kind: Secret
metadata:
  name: uptime-kuma-mariadb-secret
  namespace: monitoring
type: Opaque
data:
  MARIADB_ROOT_PASSWORD: XXXXXXXXX
  MARIADB_USER: XXXXXXXXXXXXX
  MARIADB_PASSWORD: XXXXXXXXXX
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: uptime-kuma-mariadb-config
  namespace: monitoring
data:
  my.cnf: |
    [mysqld]
    skip-host-cache
    skip-name-resolve
    max-connections=1000
    !includedir /etc/mysql/mariadb.conf.d/
    !includedir /etc/mysql/conf.d/
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: uptime-kuma-mariadb
  namespace: monitoring
  labels:
    app.kubernetes.io/name: uptime-kuma-mariadb
    app.kubernetes.io/part-of: uptime-kuma
    app.kubernetes.io/component: database
spec:
  serviceName: uptime-kuma-mariadb
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: uptime-kuma-mariadb
      app.kubernetes.io/part-of: uptime-kuma
      app.kubernetes.io/component: database
  template:
    metadata:
      labels:
        app.kubernetes.io/name: uptime-kuma-mariadb
        app.kubernetes.io/part-of: uptime-kuma
        app.kubernetes.io/component: database
    spec:
      containers:
      - name: mariadb
        image: mariadb:11.2.3
        ports:
        - containerPort: 3306
          name: mariadb
        env:
        - name: MARIADB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: uptime-kuma-mariadb-secret
              key: MARIADB_ROOT_PASSWORD
        - name: MARIADB_DATABASE
          value: uptimekuma
        - name: MARIADB_USER
          valueFrom:
            secretKeyRef:
              name: uptime-kuma-mariadb-secret
              key: MARIADB_USER
        - name: MARIADB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: uptime-kuma-mariadb-secret
              key: MARIADB_PASSWORD
        - name: TZ
          value: "Pacific/Noumea"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        - name: config
          mountPath: /etc/mysql/my.cnf
          subPath: my.cnf
          readOnly: true
      volumes:
      - name: config
        configMap:
          name: uptime-kuma-mariadb-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi
---
apiVersion: v1
kind: Service
metadata:
  name: uptime-kuma-mariadb
  namespace: monitoring
  labels:
    app.kubernetes.io/name: uptime-kuma-mariadb
    app.kubernetes.io/part-of: uptime-kuma
    app.kubernetes.io/component: database
spec:
  type: ClusterIP
  ports:
  - name: mariadb
    targetPort: 3306
    port: 3306
    protocol: TCP
  selector:
    app.kubernetes.io/name: uptime-kuma-mariadb
    app.kubernetes.io/part-of: uptime-kuma
    app.kubernetes.io/component: database

Déploiement de l’API Flask

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: uptime-kuma-vhost-api
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: uptime-kuma-vhost-api
  template:
    metadata:
      labels:
        app: uptime-kuma-vhost-api
    spec:
      containers:
      - name: uptime-kuma-vhost-api
        image: python:3.9
        command: ["/bin/sh", "-c", "pip install flask uptime-kuma-api && python /app/server.py"]
        volumeMounts:
        - name: uptime-kuma-script-volume
          mountPath: /app/server.py
          subPath: server.py
        env:
        - name: KUMA_URL
          value: "http://uptime-kuma.monitoring.svc.cluster.local"
        - name: KUMA_USER
          valueFrom:
            secretKeyRef:
              name: uptime-kuma-api-credentials
              key: username
        - name: KUMA_PASSWORD
          valueFrom:
            secretKeyRef:
              name: uptime-kuma-api-credentials
              key: password
        ports:
        - containerPort: 5000
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 3
          periodSeconds: 5
      volumes:
      - name: uptime-kuma-script-volume
        configMap:
          name: uptime-kuma-vhost-api-script
---
apiVersion: v1
kind: Service
metadata:
  name: uptime-kuma-vhost-api
  namespace: monitoring
spec:
  selector:
    app: uptime-kuma-vhost-api
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000
---
apiVersion: v1
kind: Secret
metadata:
  name: uptime-kuma-api-basic-auth
  namespace: monitoring
type: Opaque
data:
  # La valeur de "auth" doit être en base64. Pour obtenir cette valeur, exécutez :
  # htpasswd -nb uptime password | base64
  auth: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: uptime-kuma-vhost-api
  namespace: monitoring
  annotations:
    nginx.ingress.kubernetes.io/auth-type: "basic"
    nginx.ingress.kubernetes.io/auth-secret: "uptime-kuma-api-basic-auth"
    nginx.ingress.kubernetes.io/auth-realm: "Accès à uptime-kuma-vhost-api"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  rules:
  - host: uptime-kuma-api.prod.kube.test.fr
    http:
      paths:
      - path: /api/vhosts
        pathType: Prefix
        backend:
          service:
            name: uptime-kuma-vhost-api
            port:
              number: 80
  tls:
  - hosts:
    - uptime-kuma-api.prod.kube.test.fr
    secretName: uptime-kuma-api-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: uptime-kuma-vhost-api-unprotected
  namespace: monitoring
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  rules:
  - host: uptime-kuma-api.prod.kube.test.fr
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: uptime-kuma-vhost-api
            port:
              number: 80
      - path: /health
        pathType: Prefix
        backend:
          service:
            name: uptime-kuma-vhost-api
            port:
              number: 80
  tls:
  - hosts:
    - uptime-kuma-api.prod.kube.test.fr
    secretName: uptime-kuma-api-tls
---

Déploiement d’Uptime Kuma v2

Le déploiement d’Uptime Kuma suit un schéma Kubernetes classique :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: uptime-kuma
  namespace: monitoring
  labels:
    app: uptime-kuma
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "3001"
    prometheus.io/path: "/metrics"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: uptime-kuma
  template:
    metadata:
      labels:
        app: uptime-kuma
    spec:
      containers:
      - name: uptime-kuma
        image: louislam/uptime-kuma:beta
        ports:
        - containerPort: 3001
          name: http
        volumeMounts:
        - name: data
          mountPath: /app/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "2048Mi"
            cpu: "500m"
        env:
        - name: DATABASE_URL
          value: "mysql://$(MARIADB_USER):$(MARIADB_PASSWORD)@uptime-kuma-mariadb.monitoring.svc.cluster.local:3306/uptimekuma"
        envFrom:
        - secretRef:
            name: uptime-kuma-mariadb-secret
        livenessProbe:
          httpGet:
            path: /
            port: 3001
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 3001
          initialDelaySeconds: 30
          periodSeconds: 10
        securityContext:
          allowPrivilegeEscalation: false
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: uptime-kuma-data 
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: uptime-kuma-data
  namespace: monitoring
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
---
apiVersion: v1
kind: Service
metadata:
  name: uptime-kuma
  namespace: monitoring
  labels:
    app: uptime-kuma
spec:
  ports:
  - port: 80
    targetPort: 3001
    protocol: TCP
    name: http
  selector:
    app: uptime-kuma 
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: uptime-kuma
  namespace: monitoring
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  rules:
  - host: uptime.prod.kube.test.fr
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: uptime-kuma
            port:
              number: 3001
  tls:
  - hosts:
    - uptime.prod.kube.test.fr
    secretName: uptime-kuma-tls
---

📌 Exemple de configuration de l’authentification sur Ingress NGINX

Nous protégeons l’API avec Basic Auth, sauf pour / et /health.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: uptime-kuma-vhost-api
  namespace: monitoring
  annotations:
    nginx.ingress.kubernetes.io/auth-type: "basic"
    nginx.ingress.kubernetes.io/auth-secret: "uptime-kuma-api-basic-auth"
    nginx.ingress.kubernetes.io/auth-realm: "Accès à uptime-kuma-vhost-api"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  rules:
  - host: uptime-kuma-api.prod.kube.test.fr
    http:
      paths:
      - path: /api/vhosts
        pathType: Prefix
        backend:
          service:
            name: uptime-kuma-vhost-api
            port:
              number: 80
  tls:
  - hosts:
    - uptime-kuma-api.prod.kube.test.fr
    secretName: uptime-kuma-api-tls

📌 Envoi d’alertes vers Teams & Discord

Les moniteurs dans Uptime Kuma sont configurés pour envoyer des alertes en cas de panne.

🚀 Ajout du Webhook Teams

Dans Uptime Kuma :

  • Aller dans Paramètres > Notifications
  • Ajouter Microsoft Teams avec un webhook https://outlook.office.com/webhook/...

🚀 Ajout du Webhook Discord

  • Aller dans Paramètres > Notifications
  • Ajouter Discord avec un webhook https://discord.com/api/webhooks/...

🎯 Conclusion

Avec cette solution :

Les vhosts Apache et les Ingress Kubernetes sont surveillés automatiquement
Les backends sont testés avant ajout dans Uptime Kuma
Les alertes sont envoyées en temps réel sur Teams & Discord
L’authentification est optimisée sur Ingress NGINX