feat(ops): ajouter supervision automatique des applications
This commit is contained in:
11
ops/README.md
Normal file
11
ops/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Supervision des applications
|
||||||
|
|
||||||
|
Le script `healthcheck-apps.sh` est exécuté par le timer systemd toutes les cinq minutes et trois minutes après chaque démarrage serveur. Il vérifie les conteneurs Docker Compose, leur healthcheck, leur politique de redémarrage et les routes HTTPS Traefik déclarées. Une tâche ponctuelle terminée avec succès est reconnue comme telle. Toute réponse HTTP inférieure à 500 confirme la joignabilité d’une route, y compris une API sans page racine ou un tableau protégé. En cas d’anomalie, il produit un rapport et une entrée de journal, **sans redémarrer de conteneur**. Les redémarrages automatiques restent assurés par les politiques Docker `restart` et par `manus-apps.service` au démarrage du serveur.
|
||||||
|
|
||||||
|
Le dernier rapport est écrit dans `/var/lib/manus-apps-health/latest.txt`. Les anomalies sont aussi consignées dans le journal système avec le tag `manus-apps-health`.
|
||||||
|
|
||||||
|
Après mise à jour Git, installer ou actualiser le service avec :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./ops/install-healthcheck.sh
|
||||||
|
```
|
||||||
120
ops/healthcheck-apps.sh
Normal file
120
ops/healthcheck-apps.sh
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Vérifie les conteneurs Compose, leur healthcheck, leur redémarrage et leurs routes Traefik.
|
||||||
|
# Ce contrôle est volontairement non intrusif : Docker et manus-apps.service assurent les redémarrages.
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
readonly STATE_DIR="/var/lib/manus-apps-health"
|
||||||
|
readonly REPORT_FILE="${STATE_DIR}/latest.txt"
|
||||||
|
readonly LOCK_FILE="/run/manus-apps-health.lock"
|
||||||
|
readonly MODE="${1:---check-only}"
|
||||||
|
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
exec 9>"$LOCK_FILE"
|
||||||
|
flock -n 9 || exit 0
|
||||||
|
|
||||||
|
declare -A SEEN_HTTP_HOSTS=()
|
||||||
|
declare -a REPORT_LINES=()
|
||||||
|
HAS_FAILURE=false
|
||||||
|
|
||||||
|
log_line() {
|
||||||
|
local level="$1"
|
||||||
|
local message="$2"
|
||||||
|
local line="[$(date -Is)] [$level] $message"
|
||||||
|
REPORT_LINES+=("$line")
|
||||||
|
echo "$line"
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_failure() {
|
||||||
|
HAS_FAILURE=true
|
||||||
|
log_line "KO" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_healthy_http_status() {
|
||||||
|
# Toute réponse HTTP (< 500) confirme que Traefik et le service restent joignables.
|
||||||
|
# Les 401/404 sont légitimes pour les tableaux protégés ou API sans page racine.
|
||||||
|
[[ "$1" =~ ^[1-4][0-9][0-9]$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
check_http_route() {
|
||||||
|
local container_id="$1"
|
||||||
|
local project="$2"
|
||||||
|
local labels host_rule host status
|
||||||
|
|
||||||
|
labels="$(docker inspect -f '{{range $key, $value := .Config.Labels}}{{$key}}={{$value}}{{"\n"}}{{end}}' "$container_id")"
|
||||||
|
host_rule="$(printf '%s\n' "$labels" | grep -oE 'Host\(`[^`]+`\)' | head -n 1 || true)"
|
||||||
|
[[ -n "$host_rule" ]] || return 0
|
||||||
|
|
||||||
|
host="${host_rule#Host(\`}"
|
||||||
|
host="${host%\`)}"
|
||||||
|
[[ -n "$host" ]] || return 0
|
||||||
|
[[ -z "${SEEN_HTTP_HOSTS[$host]:-}" ]] || return 0
|
||||||
|
SEEN_HTTP_HOSTS["$host"]=1
|
||||||
|
|
||||||
|
status="$(curl --silent --show-error --location --max-redirs 3 --connect-timeout 5 --max-time 8 \
|
||||||
|
--output /dev/null --write-out '%{http_code}' "https://${host}" 2>/dev/null || true)"
|
||||||
|
if is_healthy_http_status "$status"; then
|
||||||
|
log_line "OK" "${project}: HTTPS ${host} → ${status}"
|
||||||
|
else
|
||||||
|
# Un échec HTTPS peut dépendre de Traefik, DNS ou d’une redirection applicative.
|
||||||
|
# Il déclenche une alerte, jamais un redémarrage Compose automatique.
|
||||||
|
mark_failure "${project}: HTTPS ${host} → ${status:-erreur réseau}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_containers() {
|
||||||
|
local container_id name project workdir config_files status health restart_policy exit_code
|
||||||
|
mapfile -t container_ids < <(docker ps -aq --filter label=com.docker.compose.project)
|
||||||
|
|
||||||
|
if [[ "${#container_ids[@]}" -eq 0 ]]; then
|
||||||
|
mark_failure "Aucun conteneur Docker Compose détecté"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for container_id in "${container_ids[@]}"; do
|
||||||
|
name="$(docker inspect -f '{{.Name}}' "$container_id" | sed 's#^/##')"
|
||||||
|
project="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project"}}' "$container_id")"
|
||||||
|
workdir="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "$container_id")"
|
||||||
|
config_files="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project.config_files"}}' "$container_id")"
|
||||||
|
status="$(docker inspect -f '{{.State.Status}}' "$container_id")"
|
||||||
|
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id")"
|
||||||
|
restart_policy="$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$container_id")"
|
||||||
|
exit_code="$(docker inspect -f '{{.State.ExitCode}}' "$container_id")"
|
||||||
|
|
||||||
|
if [[ "$status" != "running" ]]; then
|
||||||
|
if [[ "$status" == "exited" && "$exit_code" == "0" ]]; then
|
||||||
|
log_line "INFO" "${project}/${name}: tâche ponctuelle terminée"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
mark_failure "${project}/${name}: état Docker ${status}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$health" == "unhealthy" ]]; then
|
||||||
|
mark_failure "${project}/${name}: healthcheck unhealthy"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$restart_policy" != "unless-stopped" && "$restart_policy" != "always" ]]; then
|
||||||
|
mark_failure "${project}/${name}: politique de redémarrage ${restart_policy:-none}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_line "OK" "${project}/${name}: running, health=${health}, restart=${restart_policy}"
|
||||||
|
check_http_route "$container_id" "$project"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
check_containers
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "Supervision Santinova / Itinova"
|
||||||
|
echo "Généré : $(date -Is)"
|
||||||
|
echo "Statut : $([[ "$HAS_FAILURE" == true ]] && echo KO || echo OK)"
|
||||||
|
printf '%s\n' "${REPORT_LINES[@]}"
|
||||||
|
} > "${REPORT_FILE}.tmp"
|
||||||
|
mv "${REPORT_FILE}.tmp" "$REPORT_FILE"
|
||||||
|
|
||||||
|
if [[ "$HAS_FAILURE" == true ]]; then
|
||||||
|
logger -p daemon.err -t manus-apps-health "Anomalie détectée : consulter ${REPORT_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
logger -p daemon.info -t manus-apps-health "Toutes les applications contrôlées sont opérationnelles"
|
||||||
15
ops/install-healthcheck.sh
Normal file
15
ops/install-healthcheck.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Installe la supervision depuis une copie versionnée du dépôt manus-dashboard.
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
install -D -m 0750 "${SCRIPT_DIR}/healthcheck-apps.sh" /opt/manus-deploy/scripts/healthcheck-apps.sh
|
||||||
|
install -D -m 0644 "${SCRIPT_DIR}/manus-apps-health.service" /etc/systemd/system/manus-apps-health.service
|
||||||
|
install -D -m 0644 "${SCRIPT_DIR}/manus-apps-health.timer" /etc/systemd/system/manus-apps-health.timer
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now manus-apps-health.timer
|
||||||
|
# Un échec de contrôle doit rester visible dans systemd, sans bloquer l’installation du timer.
|
||||||
|
systemctl start manus-apps-health.service || true
|
||||||
|
systemctl status manus-apps-health.service --no-pager || true
|
||||||
13
ops/manus-apps-health.service
Normal file
13
ops/manus-apps-health.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Contrôle de santé des applications Santinova / Itinova
|
||||||
|
Documentation=https://git.santinova-soft.org/manus-admin/manus-dashboard
|
||||||
|
After=docker.service network-online.target manus-apps.service
|
||||||
|
Requires=docker.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/opt/manus-deploy/scripts/healthcheck-apps.sh --check-only
|
||||||
|
TimeoutStartSec=5min
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
12
ops/manus-apps-health.timer
Normal file
12
ops/manus-apps-health.timer
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Planification de la supervision des applications Santinova / Itinova
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=3min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
RandomizedDelaySec=30s
|
||||||
|
Persistent=true
|
||||||
|
Unit=manus-apps-health.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Reference in New Issue
Block a user