feat(ops): ajouter supervision automatique des applications
This commit is contained in:
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"
|
||||
Reference in New Issue
Block a user