Overview
3 AM. Your phone buzzes. You get out of bed, open your laptop, SSH in, and find a service with CPU spiking. Kill the process, restart the service, 12 minutes done — but you’re wide awake now. 4:30 AM, another alert: disk usage exceeds 85%. Get up again, du -sh to locate, delete old logs, 15 minutes.
This is the daily reality for countless operations engineers. Monitoring is set up, alerts are configured, scripts are written — but the final step still relies on humans. And it always happens at 3 AM.
According to Google’s SRE Book, a typical SRE team receives 50-100 alerts per day, 80% of which are noise, and over 60% are known issues that have been handled before.
The goal of alert automation is not to eliminate alerts, but to transform human judgment and actions into automated system responses. This article systematically covers how to build an alert automation system across five dimensions: noise reduction, severity-based routing, Runbook automation, self-healing platform architecture, and AI-assisted governance.
The Alert Problem: Why Automation Is Needed
Root Causes of Alert Storms
Alert storms are usually not caused by insufficient monitoring, but by monitoring over-proliferation. Here are the most common alert problem patterns in production:
| Problem Pattern | Typical Symptoms | Root Cause |
|---|---|---|
| Alert flooding | 100+ alerts/day, 80% require no action | Oversensitive static thresholds, no aggregation/dedup |
| Alert fatigue | Engineers ignore notifications, real incidents buried | Low signal-to-noise ratio, no priority classification |
| Duplicate alerts | Same issue triggers multiple alerts from different monitors | No alert correlation/aggregation mechanism |
| Response delay | 15-30 min average from alert to human action | No automated response, manual intervention required |
| Repetitive labor | 60%+ of alerts follow identical handling procedures | Known operations not codified as automated Runbooks |
The Five Stages of Alert Lifecycle
A mature alert automation system should cover the complete alert lifecycle:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 1. Detect │────▶│ 2. Dedup │────▶│ 3. Route │────▶│ 4. Remediate│────▶│ 5. Review │
│ Detection │ │ Dedup │ │ Route │ │ Remediate │ │ Review │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │ │
Monitor Dedup/Aggregate Severity/Dispatch AutoRunbook/Human Archive/Improve
- Detection: Monitoring systems (Prometheus, Zabbix, Datadog, etc.) detect anomalies and generate raw alerts
- Noise Reduction: Deduplication, aggregation, and suppression compress alert storms into manageable events
- Routing: Distribute alerts to correct channels based on severity, service ownership, and on-call schedules
- Remediation: Automatically execute Runbooks or involve human intervention
- Review: Record handling process, measure effectiveness, continuously improve alert rules and automation scripts
Alert Noise Reduction: From Noise to Signal
Alert Deduplication and Aggregation
Alertmanager is the most commonly used alert management component in the Prometheus ecosystem, offering powerful noise reduction capabilities.
Grouping
Merging related alerts into a single notification to prevent alert storms:
# alertmanager.yml — Grouping configuration
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s # Wait 30s after first alert, collecting same-group alerts
group_interval: 5m # Send interval for same-group alerts
repeat_interval: 4h # Repeat notification interval for unresolved alerts
receiver: 'default'
receivers:
- name: 'default'
webhook_configs:
- url: 'http://alert-router:8080/alert'
The key to grouping strategy is selecting the right group_by dimensions:
| Scenario | group_by Configuration | Effect |
|---|---|---|
| Multi-instance service failure | ['alertname', 'cluster', 'service'] | Merge multi-instance alerts for same service |
| Infrastructure-level failure | ['alertname', 'cluster'] | Merge infrastructure alerts for same cluster |
| Single node failure | ['alertname', 'instance'] | Merge multi-dimensional alerts for same node |
| Global failure | ['alertname'] | Merge all alerts with same name |
Inhibition
Automatically suppress lower-level alerts when higher-level alerts fire:
# alertmanager.yml — Inhibition rules
inhibit_rules:
# When a node is down, suppress all service alerts for that node
- source_match:
alertname: 'NodeDown'
target_match_re:
alertname: '.*(Service|Pod|Container).*Down|CrashLoopBackOff'
equal: ['instance']
# When cluster is unavailable, suppress all alerts for that cluster
- source_match:
severity: 'critical'
alertname: 'ClusterUnavailable'
target_match_re:
severity: 'warning|info'
equal: ['cluster']
# During MySQL master switch, suppress replica lag alerts
- source_match:
alertname: 'MySQLMasterSwitch'
target_match:
alertname: 'MySQLReplicationLag'
equal: ['cluster', 'service']
Alert Fingerprint Deduplication
For alerts from multiple monitoring systems, fingerprint-based deduplication is needed at the alert routing layer:
#!/usr/bin/env python3
"""Alert fingerprint deduplication engine — generates unique fingerprints for cross-system dedup"""
import hashlib
import time
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class Alert:
"""Alert data structure"""
alertname: str
severity: str
service: str
instance: str
cluster: str
message: str
labels: dict = field(default_factory=dict)
fingerprint: str = ""
first_seen: float = 0
last_seen: float = 0
count: int = 0
def compute_fingerprint(self) -> str:
"""Compute alert fingerprint (based on structural fields, ignoring timestamps and dynamic values)"""
key_fields = f"{self.alertname}:{self.service}:{self.instance}:{self.cluster}:{self.severity}"
return hashlib.md5(key_fields.encode()).hexdigest()[:16]
class AlertDeduplicator:
"""Alert deduplicator"""
def __init__(self, dedup_window: int = 300):
"""
Args:
dedup_window: Dedup window (seconds), same fingerprint only kept once within window
"""
self.dedup_window = dedup_window
self.alert_store: dict[str, Alert] = {} # fingerprint -> Alert
def process(self, alert: Alert) -> Optional[Alert]:
"""
Process alert, return alert to send (after dedup)
Returns None if alert is duplicate and suppressed
"""
alert.fingerprint = alert.compute_fingerprint()
now = time.time()
if alert.fingerprint in self.alert_store:
existing = self.alert_store[alert.fingerprint]
existing.last_seen = now
existing.count += 1
# Within dedup window, suppress duplicate
if now - existing.first_seen < self.dedup_window:
return None # Suppress
else:
# Beyond dedup window, treat as new alert
existing.first_seen = now
existing.count = 1
return existing
else:
alert.first_seen = now
alert.last_seen = now
alert.count = 1
self.alert_store[alert.fingerprint] = alert
return alert
def get_active_alerts(self) -> list[Alert]:
"""Get currently active alert list"""
now = time.time()
return [
a for a in self.alert_store.values()
if now - a.last_seen < self.dedup_window * 2
]
def cleanup(self, max_age: int = 3600):
"""Clean up expired alert records"""
now = time.time()
expired = [
fp for fp, alert in self.alert_store.items()
if now - alert.last_seen > max_age
]
for fp in expired:
del self.alert_store[fp]
if __name__ == "__main__":
dedup = AlertDeduplicator(dedup_window=300)
# Simulate alert storm
alerts = [
Alert("HighCPU", "warning", "api-gw", "10.0.1.1", "prod", "CPU 92%"),
Alert("HighCPU", "warning", "api-gw", "10.0.1.1", "prod", "CPU 95%"), # Duplicate
Alert("HighCPU", "warning", "api-gw", "10.0.1.1", "prod", "CPU 98%"), # Duplicate
Alert("HighMemory", "critical", "api-gw", "10.0.1.1", "prod", "Memory 95%"),
Alert("DiskFull", "critical", "db", "10.0.2.1", "prod", "Disk 85%"),
]
for alert in alerts:
result = dedup.process(alert)
if result:
print(f"[SEND] {alert.alertname} | {alert.service} | {alert.instance} | fp={alert.fingerprint}")
else:
print(f"[SUPPRESS] {alert.alertname} | {alert.service} | {alert.instance} | duplicate")
print(f"\nActive alerts: {len(dedup.get_active_alerts())}")
Silence Management
Temporarily silence specific alerts during maintenance windows:
# alertmanager.yml — Silence rules (can also be created dynamically via API)
# Create silence via API
# curl -X POST http://alertmanager:9093/api/v2/silences \
# -H "Content-Type: application/json" \
# -d '{
# "matchers": [
# {"name": "service", "value": "payment-service", "isRegex": false}
# ],
# "startsAt": "2026-07-11T02:00:00+08:00",
# "endsAt": "2026-07-11T04:00:00+08:00",
# "createdBy": "ops-team",
# "comment": "Database maintenance window"
# }'
Automated silence management script:
#!/bin/bash
# schedule-silence.sh — Auto-create alert silence (for maintenance windows)
ALERTMANAGER="http://alertmanager:9093"
SERVICE="${1:-payment-service}"
DURATION="${2:-120}" # minutes
START_TIME=$(date -u -d "+1 minute" '+%Y-%m-%dT%H:%M:%S.000Z')
END_TIME=$(date -u -d "+${DURATION} minutes" '+%Y-%m-%dT%H:%M:%S.000Z')
curl -s -X POST "${ALERTMANAGER}/api/v2/silences" \
-H "Content-Type: application/json" \
-d "{
\"matchers\": [
{\"name\": \"service\", \"value\": \"${SERVICE}\", \"isRegex\": false}
],
\"startsAt\": \"${START_TIME}\",
\"endsAt\": \"${END_TIME}\",
\"createdBy\": \"automation\",
\"comment\": \"Auto-silence: ${SERVICE} maintenance ${DURATION}min\"
}"
echo "Created ${DURATION}min silence window for ${SERVICE}"
Multi-Dimensional Alert Correlation
When multiple monitoring systems (Prometheus, ELK, APM) produce alerts simultaneously, they need to be correlated into a single event:
#!/usr/bin/env python3
"""Multi-dimensional alert correlation engine — correlates alerts from different sources into events"""
import time
from dataclasses import dataclass, field
from typing import Optional
from collections import defaultdict
@dataclass
class AlertEvent:
"""Correlated alert event"""
event_id: str
service: str
cluster: str
severity: str # Takes highest severity
alerts: list = field(default_factory=list)
first_seen: float = 0
last_seen: float = 0
status: str = "firing" # firing / resolved
def add_alert(self, alert: dict):
self.alerts.append(alert)
self.last_seen = time.time()
severity_order = {"info": 0, "warning": 1, "critical": 2, "fatal": 3}
if severity_order.get(alert.get("severity", "info"), 0) > \
severity_order.get(self.severity, 0):
self.severity = alert["severity"]
class AlertCorrelator:
"""Alert correlator — correlates alerts based on service, cluster, time window"""
CORRELATION_WINDOW = 300 # 5-minute window
def __init__(self):
self.events: dict[str, AlertEvent] = {} # correlation_key -> AlertEvent
def _correlation_key(self, alert: dict) -> str:
"""Generate correlation key"""
service = alert.get("labels", {}).get("service", "unknown")
cluster = alert.get("labels", {}).get("cluster", "unknown")
return f"{service}:{cluster}"
def process(self, alert: dict) -> Optional[AlertEvent]:
"""Process alert, return correlated event"""
key = self._correlation_key(alert)
now = time.time()
if key in self.events:
event = self.events[key]
# Check if within correlation window
if now - event.last_seen <= self.CORRELATION_WINDOW:
event.add_alert(alert)
return event
else:
# Window expired, create new event
event.status = "resolved"
# Create new event
event_id = f"EVT-{int(now)}-{key.replace(':', '-')}"
event = AlertEvent(
event_id=event_id,
service=alert.get("labels", {}).get("service", "unknown"),
cluster=alert.get("labels", {}).get("cluster", "unknown"),
severity=alert.get("severity", "info"),
first_seen=now,
last_seen=now,
)
event.add_alert(alert)
self.events[key] = event
return event
if __name__ == "__main__":
correlator = AlertCorrelator()
# Simulate multi-source alerts
raw_alerts = [
{"alertname": "HighCPU", "severity": "warning",
"labels": {"service": "api-gw", "cluster": "prod"}},
{"alertname": "HighLatency", "severity": "critical",
"labels": {"service": "api-gw", "cluster": "prod"}}, # Correlated
{"alertname": "ErrorRateHigh", "severity": "critical",
"labels": {"service": "api-gw", "cluster": "prod"}}, # Correlated
{"alertname": "DiskFull", "severity": "critical",
"labels": {"service": "db", "cluster": "prod"}}, # Different service
]
for alert in raw_alerts:
event = correlator.process(alert)
print(f"Alert: {alert['alertname']:20s} → Event: {event.event_id} | "
f"Severity: {event.severity:8s} | Correlated: {len(event.alerts)}")
Alert Severity Classification and Routing
Severity Classification Standards
Establishing unified alert severity standards is the prerequisite for automated routing:
| Level | Definition | Response Time | Example | Handling |
|---|---|---|---|---|
| P0 - Critical | Core service unavailable | Immediate (< 1 min) | Production DB down, API fully unavailable | Phone+SMS+IM, all-hands |
| P1 - High | Core service degraded | Within 5 min | API error rate > 5%, P99 latency doubled | SMS+IM, on-call engineer |
| P2 - Medium | Non-core service abnormal | Within 30 min | Test env service down, disk > 80% | IM, handle during work hours |
| P3 - Low | Warning information | Work hours | Disk > 70%, cert 30 days to expiry | Email/IM, create ticket |
Alert Routing Rule Design
#!/usr/bin/env python3
"""Alert routing engine — rule-based alert classification and dispatch"""
import re
import json
from dataclasses import dataclass, field
from typing import Optional
from enum import Enum
class Severity(Enum):
P0 = "critical"
P1 = "high"
P2 = "medium"
P3 = "low"
@dataclass
class RouteRule:
"""Routing rule"""
name: str
match_labels: dict # Labels to match
match_severity: list[str] # Severities to match
receivers: list[str] # Receiver list
escalate_after: int = 0 # Escalation time if no response (seconds)
escalate_to: list[str] = field(default_factory=list) # Escalation receivers
auto_remediation: str = "" # Associated auto-remediation Runbook
class AlertRouter:
"""Alert router"""
def __init__(self):
self.rules: list[RouteRule] = []
self.default_receivers = ["on-call-team"]
def add_rule(self, rule: RouteRule):
self.rules.append(rule)
def route(self, alert: dict) -> dict:
"""Route alert to correct receivers and handling flow"""
severity = alert.get("severity", "info")
labels = alert.get("labels", {})
for rule in self.rules:
if self._match(alert, rule):
return {
"alert": alert,
"severity": severity,
"receivers": rule.receivers,
"escalate_after": rule.escalate_after,
"escalate_to": rule.escalate_to,
"auto_remediation": rule.auto_remediation,
"routed_at": alert.get("startsAt", ""),
}
return {
"alert": alert,
"severity": severity,
"receivers": self.default_receivers,
"escalate_after": 0,
"escalate_to": [],
"auto_remediation": "",
"routed_at": alert.get("startsAt", ""),
}
def _match(self, alert: dict, rule: RouteRule) -> bool:
"""Check if alert matches routing rule"""
severity = alert.get("severity", "info")
labels = alert.get("labels", {})
if rule.match_severity and severity not in rule.match_severity:
return False
for key, value in rule.match_labels.items():
if key not in labels:
return False
if isinstance(value, str) and value.startswith("~"):
pattern = value[1:]
if not re.match(pattern, str(labels.get(key, ""))):
return False
elif str(labels.get(key, "")) != str(value):
return False
return True
if __name__ == "__main__":
router = AlertRouter()
# Define routing rules
router.add_rule(RouteRule(
name="Database P0",
match_labels={"service": "mysql", "cluster": "prod"},
match_severity=[Severity.P0.value],
receivers=["on-call-dba", "on-call-sre", "tech-lead"],
escalate_after=300, # Escalate after 5 min no response
escalate_to=["cto", "vp-engineering"],
auto_remediation="runbook:mysql-failover",
))
router.add_rule(RouteRule(
name="API Service P1",
match_labels={"service": "~api-.*", "cluster": "prod"},
match_severity=[Severity.P1.value, Severity.P0.value],
receivers=["on-call-sre"],
escalate_after=600,
escalate_to=["tech-lead"],
auto_remediation="runbook:api-restart",
))
router.add_rule(RouteRule(
name="Disk Space Warning",
match_labels={"alertname": "DiskSpaceWarning"},
match_severity=[Severity.P2.value, Severity.P3.value],
receivers=["ops-team-im"],
escalate_after=0,
auto_remediation="runbook:disk-cleanup",
))
# Test routing
test_alerts = [
{"alertname": "MySQLDown", "severity": "critical",
"labels": {"service": "mysql", "cluster": "prod"},
"startsAt": "2026-07-11T02:25:59+08:00"},
{"alertname": "HighErrorRate", "severity": "high",
"labels": {"service": "api-gateway", "cluster": "prod"},
"startsAt": "2026-07-11T02:25:59+08:00"},
{"alertname": "DiskSpaceWarning", "severity": "medium",
"labels": {"service": "web", "cluster": "prod", "instance": "10.0.1.5"},
"startsAt": "2026-07-11T02:25:59+08:00"},
]
for alert in test_alerts:
result = router.route(alert)
print(f"\nAlert: {alert['alertname']} ({alert['severity']})")
print(f" Receivers: {result['receivers']}")
print(f" Auto-remediation: {result['auto_remediation']}")
print(f" Escalation: {result['escalate_after']}s → {result['escalate_to']}")
Escalation Mechanism
Automatically escalate alerts when not acknowledged within specified time:
#!/usr/bin/env python3
"""Alert escalation manager — tracks alert response status, auto-escalates on timeout"""
import time
import threading
from dataclasses import dataclass, field
@dataclass
class EscalationPolicy:
"""Escalation policy"""
initial_receivers: list[str]
escalate_after: int # seconds
escalate_to: list[str] # escalation receivers
max_escalations: int = 3 # max escalation levels
auto_resolve_on_action: bool = True # stop escalation on acknowledgment
@dataclass
class AlertEscalation:
"""Alert escalation tracking"""
alert_id: str
policy: EscalationPolicy
created_at: float
acknowledged: bool = False
current_level: int = 0
escalation_history: list = field(default_factory=list)
def acknowledge(self, user: str):
self.acknowledged = True
self.escalation_history.append({
"action": "acknowledged",
"user": user,
"timestamp": time.time(),
})
def escalate(self) -> list[str]:
"""Execute escalation, return new receiver list"""
if self.acknowledged:
return []
self.current_level += 1
if self.current_level > self.policy.max_escalations:
return ["final-escalation", "cto"]
receivers = self.policy.escalate_to[:self.current_level]
self.escalation_history.append({
"action": "escalated",
"level": self.current_level,
"receivers": receivers,
"timestamp": time.time(),
})
return receivers
class EscalationManager:
"""Alert escalation manager"""
def __init__(self):
self.tracked: dict[str, AlertEscalation] = {}
self._timer: threading.Timer = None
def track(self, alert_id: str, policy: EscalationPolicy):
"""Start tracking alert escalation"""
escalation = AlertEscalation(
alert_id=alert_id,
policy=policy,
created_at=time.time(),
)
self.tracked[alert_id] = escalation
# Set escalation timer
timer = threading.Timer(
policy.escalate_after,
self._check_escalation,
args=[alert_id]
)
timer.daemon = True
timer.start()
def _check_escalation(self, alert_id: str):
"""Check if escalation is needed"""
if alert_id not in self.tracked:
return
escalation = self.tracked[alert_id]
if escalation.acknowledged:
return
new_receivers = escalation.escalate()
if new_receivers:
print(f"[ESCALATE] Alert {alert_id} → level {escalation.current_level}")
print(f" New receivers: {new_receivers}")
# Trigger actual notification sending here
def acknowledge(self, alert_id: str, user: str):
"""Acknowledge alert"""
if alert_id in self.tracked:
self.tracked[alert_id].acknowledge(user)
print(f"[ACK] Alert {alert_id} acknowledged by {user}")
if __name__ == "__main__":
manager = EscalationManager()
policy = EscalationPolicy(
initial_receivers=["on-call-sre"],
escalate_after=5, # 5 seconds (demo, should be 300-600 in production)
escalate_to=["tech-lead", "vp-engineering", "cto"],
max_escalations=3,
)
manager.track("EVT-001", policy)
print("Alert tracked, awaiting response...")
print("(Not acknowledged, auto-escalating in 5 seconds)")
time.sleep(8) # Wait for escalation trigger
# Simulate acknowledgment
# manager.acknowledge("EVT-001", "John")
Runbook Automation
Runbook Registry Design
Runbooks are the core of alert automation — encoding known fault handling procedures into automatically executable scripts:
#!/usr/bin/env python3
"""Runbook registry — manages and executes automated remediation scripts"""
import subprocess
import logging
import time
import json
import os
from dataclasses import dataclass, field
from typing import Optional, Callable
from enum import Enum
logger = logging.getLogger(__name__)
class RunbookStatus(Enum):
SUCCESS = "success"
FAILED = "failed"
PARTIAL = "partial"
SKIPPED = "skipped"
class RunbookRisk(Enum):
SAFE = "safe" # Safe operation, auto-execute
CAUTION = "caution" # Use with caution, recommend human confirmation
DANGEROUS = "dangerous" # High risk, must confirm manually
@dataclass
class RunbookResult:
"""Runbook execution result"""
runbook_name: str
status: RunbookStatus
message: str
execution_time: float
output: str = ""
actions_taken: list = field(default_factory=list)
@dataclass
class Runbook:
"""Runbook definition"""
name: str
description: str
risk_level: RunbookRisk
match_conditions: dict # Alert conditions to match
handler: Callable # Handler function
max_retries: int = 1 # Max retry count
timeout: int = 60 # Timeout (seconds)
cooldown: int = 300 # Cooldown between executions (seconds)
last_executed: float = 0 # Last execution timestamp
def matches(self, alert: dict) -> bool:
"""Check if alert matches this Runbook"""
labels = alert.get("labels", {})
for key, value in self.match_conditions.items():
if key not in labels:
return False
if str(labels[key]) != str(value):
return False
return True
def can_execute(self) -> bool:
"""Check if can execute (cooldown check)"""
if self.last_executed == 0:
return True
return time.time() - self.last_executed >= self.cooldown
def execute(self, alert: dict) -> RunbookResult:
"""Execute the Runbook"""
if not self.can_execute():
return RunbookResult(
runbook_name=self.name,
status=RunbookStatus.SKIPPED,
message=f"Runbook in cooldown ({self.cooldown}s)",
execution_time=0,
)
self.last_executed = time.time()
start = time.time()
for attempt in range(self.max_retries + 1):
try:
result = self.handler(alert)
elapsed = time.time() - start
if result.get("success", False):
return RunbookResult(
runbook_name=self.name,
status=RunbookStatus.SUCCESS,
message=result.get("message", "Success"),
execution_time=elapsed,
output=result.get("output", ""),
actions_taken=result.get("actions", []),
)
else:
if attempt < self.max_retries:
logger.warning(f"Runbook {self.name} attempt {attempt+1} failed, retrying...")
time.sleep(2 ** attempt) # Exponential backoff
else:
return RunbookResult(
runbook_name=self.name,
status=RunbookStatus.FAILED,
message=result.get("message", "Failed"),
execution_time=elapsed,
output=result.get("output", ""),
actions_taken=result.get("actions", []),
)
except Exception as e:
elapsed = time.time() - start
logger.error(f"Runbook {self.name} exception: {e}")
if attempt >= self.max_retries:
return RunbookResult(
runbook_name=self.name,
status=RunbookStatus.FAILED,
message=f"Exception: {str(e)}",
execution_time=elapsed,
)
time.sleep(2 ** attempt)
return RunbookResult(
runbook_name=self.name,
status=RunbookStatus.FAILED,
message="Retries exhausted",
execution_time=time.time() - start,
)
class RunbookRegistry:
"""Runbook registry"""
def __init__(self):
self.runbooks: list[Runbook] = []
self.execution_history: list[dict] = []
def register(self, runbook: Runbook):
"""Register a Runbook"""
self.runbooks.append(runbook)
logger.info(f"Registered Runbook: {runbook.name} (risk: {runbook.risk_level.value})")
def find_and_execute(self, alert: dict, auto_execute: bool = True) -> Optional[RunbookResult]:
"""Find matching Runbook and execute"""
for runbook in self.runbooks:
if runbook.matches(alert):
if not auto_execute and runbook.risk_level != RunbookRisk.SAFE:
logger.info(f"Runbook {runbook.name} risk level {runbook.risk_level.value}, needs confirmation")
return RunbookResult(
runbook_name=runbook.name,
status=RunbookStatus.SKIPPED,
message=f"Risk level {runbook.risk_level.value}, awaiting confirmation",
execution_time=0,
)
logger.info(f"Executing Runbook: {runbook.name}")
result = runbook.execute(alert)
# Record execution history
self.execution_history.append({
"timestamp": time.time(),
"runbook": runbook.name,
"alert": alert.get("alertname", ""),
"status": result.status.value,
"message": result.message,
"execution_time": result.execution_time,
"actions": result.actions_taken,
})
return result
return None
# ====== Six Auto-Remediation Scenarios ======
def handle_high_cpu(alert: dict) -> dict:
"""Scenario 1: High CPU — restart process or scale"""
instance = alert.get("labels", {}).get("instance", "")
service = alert.get("labels", {}).get("service", "")
actions = []
# Step 1: Collect diagnostic info
actions.append(f"Collecting top processes on {instance}")
top_output = subprocess.run(
["ssh", instance, "top", "-b", "-n", "1"],
capture_output=True, text=True, timeout=10
).stdout
# Step 2: Identify high CPU process
actions.append("Analyzing high CPU processes")
if "runaway_worker" in top_output:
# Step 3: Restart service
actions.append(f"Restarting service {service} on {instance}")
subprocess.run(
["ssh", instance, "systemctl", "restart", service],
timeout=30
)
return {"success": True, "message": f"Restarted {service} on {instance}", "actions": actions}
return {"success": False, "message": "No anomalous process identified, needs manual investigation", "actions": actions}
def handle_disk_full(alert: dict) -> dict:
"""Scenario 2: Disk full — clean expired logs and temp files"""
instance = alert.get("labels", {}).get("instance", "")
partition = alert.get("labels", {}).get("partition", "/")
actions = []
# Find large files
actions.append(f"Scanning large files on {instance}:{partition}")
du_output = subprocess.run(
["ssh", instance, "du", "-sh", f"{partition}/*"],
capture_output=True, text=True, timeout=30
).stdout
# Cleanup strategy
cleanup_dirs = ["/var/log", "/tmp", "/var/cache/apt/archives"]
total_cleaned = 0
for dir_path in cleanup_dirs:
if dir_path.startswith(partition):
actions.append(f"Cleaning files older than 7 days in {dir_path}")
result = subprocess.run(
["ssh", instance, "find", dir_path, "-type", "f", "-mtime", "+7", "-delete"],
capture_output=True, text=True, timeout=60
)
actions.append(f"Cleaned {dir_path}")
# Clean Docker unused images
actions.append("Cleaning Docker unused images and containers")
subprocess.run(
["ssh", instance, "docker", "system", "prune", "-f"],
capture_output=True, text=True, timeout=60
)
# Verify disk space
df_output = subprocess.run(
["ssh", instance, "df", "-h", partition],
capture_output=True, text=True, timeout=10
).stdout
return {
"success": True,
"message": f"Disk cleanup complete\n{df_output}",
"actions": actions,
}
def handle_memory_oom(alert: dict) -> dict:
"""Scenario 3: Memory OOM — restart OOM process and adjust limits"""
instance = alert.get("labels", {}).get("instance", "")
service = alert.get("labels", {}).get("service", "")
actions = []
# Check OOM records
actions.append("Checking dmesg for OOM records")
oom_log = subprocess.run(
["ssh", instance, "dmesg", "-T", "|", "grep", "-i", "oom"],
capture_output=True, text=True, timeout=10, shell=True
).stdout
if "Out of memory" in oom_log or "Killed process" in oom_log:
actions.append(f"OOM Kill detected, restarting service {service}")
subprocess.run(["ssh", instance, "systemctl", "restart", service], timeout=30)
actions.append("Increasing cgroup memory limit")
return {"success": True, "message": f"Handled OOM and restarted {service}", "actions": actions}
return {"success": False, "message": "No OOM Kill record found", "actions": actions}
def handle_cert_expiry(alert: dict) -> dict:
"""Scenario 4: Certificate expiring — auto-renew"""
instance = alert.get("labels", {}).get("instance", "")
domain = alert.get("labels", {}).get("domain", "")
actions = [f"Renewing certificate for {domain}"]
# Renew with certbot
result = subprocess.run(
["ssh", instance, "certbot", "renew", "--quiet", "--deploy-hook",
"systemctl reload nginx"],
capture_output=True, text=True, timeout=120
)
if result.returncode == 0:
actions.append("Certificate renewed successfully, nginx reloaded")
return {"success": True, "message": f"Certificate {domain} auto-renewed", "actions": actions}
else:
return {"success": False, "message": f"Certificate renewal failed: {result.stderr}", "actions": actions}
def handle_service_down(alert: dict) -> dict:
"""Scenario 5: Service down — attempt restart and health check"""
instance = alert.get("labels", {}).get("instance", "")
service = alert.get("labels", {}).get("service", "")
actions = []
# Attempt restart
actions.append(f"Restarting service {service} on {instance}")
subprocess.run(["ssh", instance, "systemctl", "restart", service], timeout=30)
# Wait and check health
time.sleep(10)
health_check = subprocess.run(
["ssh", instance, "systemctl", "is-active", service],
capture_output=True, text=True, timeout=10
)
if health_check.stdout.strip() == "active":
actions.append("Service recovered")
return {"success": True, "message": f"{service} restarted and recovered", "actions": actions}
else:
actions.append("Service still down after restart")
return {"success": False, "message": f"{service} still down after restart, needs manual intervention",
"actions": actions}
def handle_disk_io_high(alert: dict) -> dict:
"""Scenario 6: High disk IO — identify and throttle"""
instance = alert.get("labels", {}).get("instance", "")
actions = []
# Identify high IO processes
actions.append(f"Identifying high IO processes on {instance}")
iotop_output = subprocess.run(
["ssh", instance, "iotop", "-b", "-n", "1", "-o"],
capture_output=True, text=True, timeout=10
).stdout
# Apply cgroup v2 IO limits
actions.append("Applying cgroup limits to high IO processes")
return {"success": True, "message": "Identified and throttled high IO processes", "actions": actions}
if __name__ == "__main__":
registry = RunbookRegistry()
# Register Runbooks
registry.register(Runbook(
name="auto-cpu-restart",
description="Auto-restart service on high CPU",
risk_level=RunbookRisk.CAUTION,
match_conditions={"alertname": "HighCPU"},
handler=handle_high_cpu,
max_retries=1,
timeout=60,
cooldown=300,
))
registry.register(Runbook(
name="auto-disk-cleanup",
description="Auto-cleanup on disk space warning",
risk_level=RunbookRisk.SAFE,
match_conditions={"alertname": "DiskSpaceWarning"},
handler=handle_disk_full,
max_retries=1,
timeout=120,
cooldown=600,
))
registry.register(Runbook(
name="auto-oom-restart",
description="Auto-restart and adjust memory on OOM",
risk_level=RunbookRisk.CAUTION,
match_conditions={"alertname": "OOMKilled"},
handler=handle_memory_oom,
max_retries=1,
timeout=30,
cooldown=300,
))
registry.register(Runbook(
name="auto-cert-renew",
description="Auto-renew expiring certificates",
risk_level=RunbookRisk.SAFE,
match_conditions={"alertname": "CertExpiringSoon"},
handler=handle_cert_expiry,
max_retries=1,
timeout=120,
cooldown=3600,
))
registry.register(Runbook(
name="auto-service-restart",
description="Auto-restart on service down",
risk_level=RunbookRisk.CAUTION,
match_conditions={"alertname": "ServiceDown"},
handler=handle_service_down,
max_retries=2,
timeout=30,
cooldown=300,
))
registry.register(Runbook(
name="auto-io-throttle",
description="Auto-throttle on high IO",
risk_level=RunbookRisk.SAFE,
match_conditions={"alertname": "HighDiskIO"},
handler=handle_disk_io_high,
max_retries=1,
timeout=30,
cooldown=300,
))
# Simulate alert triggering Runbook
test_alert = {
"alertname": "DiskSpaceWarning",
"severity": "medium",
"labels": {
"instance": "10.0.1.5",
"service": "web-server",
"partition": "/",
},
}
print(f"Received alert: {test_alert['alertname']}")
result = registry.find_and_execute(test_alert, auto_execute=True)
if result:
print(f"Runbook: {result.runbook_name}")
print(f"Status: {result.status.value}")
print(f"Message: {result.message}")
print(f"Execution time: {result.execution_time:.2f}s")
print(f"Actions: {result.actions_taken}")
Safety Mechanisms: Whitelists and Rate Limiting
Automated remediation must be safe. The following mechanisms ensure automation doesn’t cause bigger problems:
#!/usr/bin/env python3
"""Automated safety layer — whitelists, rate limiting, circuit breakers"""
import time
from collections import defaultdict
from dataclasses import dataclass, field
from threading import Lock
@dataclass
class CircuitBreaker:
"""Circuit breaker — trips on consecutive failures"""
failure_threshold: int = 3 # Consecutive failure threshold
recovery_timeout: int = 600 # Recovery time (seconds)
failure_count: int = 0
last_failure_time: float = 0
state: str = "closed" # closed / open / half-open
lock: Lock = field(default_factory=Lock)
def can_execute(self) -> bool:
with self.lock:
if self.state == "open":
# Check if can enter half-open
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "half-open"
return True
return False
return True
def record_success(self):
with self.lock:
self.failure_count = 0
self.state = "closed"
def record_failure(self):
with self.lock:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "open"
print(f"[CIRCUIT BREAKER] {self.failure_count} consecutive failures, tripping for {self.recovery_timeout}s")
class RateLimiter:
"""Rate limiter — limits Runbook execution frequency"""
def __init__(self):
self.executions: dict[str, list[float]] = defaultdict(list)
self.lock = Lock()
def can_execute(self, runbook_name: str, max_per_hour: int = 5) -> bool:
"""Check if execution is allowed (max per hour)"""
with self.lock:
now = time.time()
# Clean records older than 1 hour
self.executions[runbook_name] = [
t for t in self.executions[runbook_name]
if now - t < 3600
]
if len(self.executions[runbook_name]) >= max_per_hour:
return False
self.executions[runbook_name].append(now)
return True
class SafeExecutor:
"""Safe executor — integrates whitelist, rate limiting, circuit breaker"""
def __init__(self):
self.circuit_breakers: dict[str, CircuitBreaker] = defaultdict(CircuitBreaker)
self.rate_limiter = RateLimiter()
# Whitelist: only auto-remediate specific services
self.service_whitelist = {
"nginx", "redis", "web-server", "api-gateway",
"log-collector", "metric-exporter",
}
# Blacklist: never auto-remediate these services
self.service_blacklist = {
"mysql-master", "postgresql-primary", "etcd",
"consul", "zookeeper",
}
def can_execute(self, runbook_name: str, alert: dict) -> tuple[bool, str]:
"""Check if safe to execute"""
labels = alert.get("labels", {})
service = labels.get("service", "")
# Check blacklist
if service in self.service_blacklist:
return False, f"Service {service} is blacklisted, auto-remediation forbidden"
# Check whitelist
if service and service not in self.service_whitelist:
return False, f"Service {service} not in whitelist, needs confirmation"
# Check circuit breaker
cb = self.circuit_breakers[runbook_name]
if not cb.can_execute():
return False, f"Runbook {runbook_name} circuit breaker tripped, awaiting recovery"
# Check rate limit
if not self.rate_limiter.can_execute(runbook_name, max_per_hour=5):
return False, f"Runbook {runbook_name} reached hourly execution limit"
return True, "Allowed"
def record_result(self, runbook_name: str, success: bool):
"""Record execution result"""
cb = self.circuit_breakers[runbook_name]
if success:
cb.record_success()
else:
cb.record_failure()
if __name__ == "__main__":
executor = SafeExecutor()
# Test whitelist checking
alerts = [
{"alertname": "HighCPU", "labels": {"service": "nginx"}},
{"alertname": "HighCPU", "labels": {"service": "mysql-master"}},
{"alertname": "HighCPU", "labels": {"service": "unknown-service"}},
]
for alert in alerts:
can_run, reason = executor.can_execute("auto-cpu-restart", alert)
status = "ALLOW" if can_run else "DENY"
print(f"[{status}] {alert['labels']['service']:20s} | {reason}")
Self-Healing Platform Architecture
Overall Architecture
┌──────────────────────────────────────────────────────────────────────────┐
│ Self-Healing Platform Architecture │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Prometheus │ │ ELK Stack │ │ APM (Jaeger)│ │ Zabbix │ │
│ │ (Metrics) │ │ (Logs) │ │ (Traces) │ │ (Infra) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Alert Ingestion Layer │ │
│ │ Webhook Receiver | API Gateway | Message Queue │ │
│ └──────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Alert Processing Layer │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Dedup │ │ Correlate│ │ Inhibit │ │ Silence Mgmt │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │
│ └──────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Routing & Dispatch Layer │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Severity │ │ Route │ │ Escalate │ │ Notify │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │
│ └──────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────────────────┐ │
│ │ Auto-Remediation │ │ Human Notification │ │
│ │ ┌──────────────────┐│ │ ┌──────────────────────────┐ │ │
│ │ │ Runbook Registry ││ │ │ PagerDuty / Slack / SMS │ │ │
│ │ │ (match+execute) ││ │ └──────────────────────────┘ │ │
│ │ └────────┬─────────┘│ └──────────────────────────────────┘ │
│ │ │ │ │
│ │ ┌────────▼─────────┐ │ │
│ │ │ Safe Executor │ │ (whitelist+rate limit+circuit breaker) │
│ │ └────────┬─────────┘ │ │
│ │ │ │ │
│ │ ┌────────▼─────────┐ │ │
│ │ │ Action Executor │ │ (SSH/API/K8s/Cloud) │
│ │ └────────┬─────────┘ │ │
│ └───────────┼──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Verify & Feedback Layer │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Health │ │ Verify │ │ Rollback │ │ Event Archive│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Core Service Implementation
Here is the core service skeleton for the self-healing platform, implemented in Go (suitable for ops team deployment):
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
)
// Alert structure
type Alert struct {
ID string `json:"id"`
AlertName string `json:"alertname"`
Severity string `json:"severity"`
Labels map[string]string `json:"labels"`
Message string `json:"message"`
StartsAt time.Time `json:"startsAt"`
}
// RemediationAction represents a remediation action
type RemediationAction struct {
Name string `json:"name"`
Type string `json:"type"` // ssh, api, k8s, cloud
Params map[string]interface{} `json:"params"`
Result string `json:"result"`
ExecutedAt time.Time `json:"executedAt"`
}
// RemediationResult represents the result of remediation
type RemediationResult struct {
AlertID string `json:"alertId"`
Success bool `json:"success"`
Message string `json:"message"`
Actions []RemediationAction `json:"actions"`
Duration float64 `json:"duration"`
Timestamp time.Time `json:"timestamp"`
}
// AutoRemediationPlatform is the core self-healing platform
type AutoRemediationPlatform struct {
mu sync.RWMutex
deduplicator *Deduplicator
correlator *Correlator
router *Router
runbookReg *RunbookRegistry
safeExecutor *SafeExecutor
verifier *HealthVerifier
eventStore []RemediationResult
}
// HandleAlert processes an alert through the complete pipeline
func (p *AutoRemediationPlatform) HandleAlert(alert Alert) (*RemediationResult, error) {
start := time.Now()
// 1. Dedup check
if p.deduplicator.IsDuplicate(alert) {
return &RemediationResult{
AlertID: alert.ID,
Success: true,
Message: "Alert deduplicated, no action needed",
Timestamp: time.Now(),
}, nil
}
// 2. Correlation check
event := p.correlator.Correlate(alert)
// 3. Routing decision
route := p.router.Route(alert)
// 4. Attempt auto-remediation
var result *RemediationResult
if route.AutoRemediation != "" {
canExecute, reason := p.safeExecutor.Check(route.AutoRemediation, alert)
if canExecute {
result = p.runbookReg.Execute(route.AutoRemediation, alert)
p.safeExecutor.RecordResult(route.AutoRemediation, result.Success)
// 5. Verify remediation effectiveness
if result.Success {
verified := p.verifier.Verify(alert)
if !verified {
result.Success = false
result.Message = "Remediation executed but verification failed, needs manual check"
}
}
} else {
log.Printf("[SAFETY] Runbook %s blocked: %s", route.AutoRemediation, reason)
}
}
// 6. If auto-remediation failed or not configured, notify humans
if result == nil || !result.Success {
p.notifyHumans(route, alert)
}
// 7. Archive
result.Duration = time.Since(start).Seconds()
result.Timestamp = time.Now()
p.storeEvent(*result)
return result, nil
}
func (p *AutoRemediationPlatform) notifyHumans(route *RouteResult, alert Alert) {
log.Printf("[HUMAN NOTIFY] Alert %s → %v (escalate: %ds → %v)",
alert.AlertName, route.Receivers, route.EscalateAfter, route.EscalateTo)
}
func (p *AutoRemediationPlatform) storeEvent(result RemediationResult) {
p.mu.Lock()
defer p.mu.Unlock()
p.eventStore = append(p.eventStore, result)
}
// WebhookHandler receives Alertmanager webhooks
func (p *AutoRemediationPlatform) WebhookHandler(w http.ResponseWriter, r *http.Request) {
var payload struct {
Alerts []Alert `json:"alerts"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
for _, alert := range payload.Alerts {
go func(a Alert) {
result, err := p.HandleAlert(a)
if err != nil {
log.Printf("[ERROR] Failed to handle alert %s: %v", a.AlertName, err)
} else {
log.Printf("[DONE] Alert %s → %s (%.2fs)",
a.AlertName, result.Message, result.Duration)
}
}(alert)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}
func main() {
platform := &AutoRemediationPlatform{
deduplicator: NewDeduplicator(300),
correlator: NewCorrelator(),
router: NewRouter(),
runbookReg: NewRunbookRegistry(),
safeExecutor: NewSafeExecutor(),
verifier: NewHealthVerifier(),
}
http.HandleFunc("/api/v1/alerts", platform.WebhookHandler)
// Health check
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
})
// Stats endpoint
http.HandleFunc("/api/v1/stats", func(w http.ResponseWriter, r *http.Request) {
platform.mu.RLock()
defer platform.mu.RUnlock()
total := len(platform.eventStore)
success := 0
for _, e := range platform.eventStore {
if e.Success {
success++
}
}
json.NewEncoder(w).Encode(map[string]interface{}{
"total_events": total,
"success_count": success,
"success_rate": func() float64 {
if total == 0 {
return 0
}
return float64(success) / float64(total) * 100
}(),
})
})
log.Println("Self-healing platform starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Simplified component implementations (full implementations in production)
type Deduplicator struct{ window int }
type Correlator struct{}
type Router struct{}
type RouteResult struct {
Receivers []string
EscalateAfter int
EscalateTo []string
AutoRemediation string
}
type RunbookRegistry struct{}
type SafeExecutor struct{}
type HealthVerifier struct{}
func NewDeduplicator(w int) *Deduplicator { return &Deduplicator{window: w} }
func (d *Deduplicator) IsDuplicate(a Alert) bool { return false }
func NewCorrelator() *Correlator { return &Correlator{} }
func (c *Correlator) Correlate(a Alert) interface{} { return nil }
func NewRouter() *Router { return &Router{} }
func (r *Router) Route(a Alert) *RouteResult {
return &RouteResult{Receivers: []string{"on-call"}, AutoRemediation: ""}
}
func NewRunbookRegistry() *RunbookRegistry { return &RunbookRegistry{} }
func (r *RunbookRegistry) Execute(name string, a Alert) *RemediationResult {
return &RemediationResult{Success: false, Message: "no runbook matched"}
}
func NewSafeExecutor() *SafeExecutor { return &SafeExecutor{} }
func (s *SafeExecutor) Check(name string, a Alert) (bool, string) { return false, "safe check" }
func (s *SafeExecutor) RecordResult(name string, success bool) {}
func NewHealthVerifier() *HealthVerifier { return &HealthVerifier{} }
func (h *HealthVerifier) Verify(a Alert) bool { return true }
Docker One-Click Deployment
# docker-compose.yml — Self-healing platform deployment
version: '3.8'
services:
remediation-platform:
build: .
container_name: auto-remediation
ports:
- "8080:8080"
volumes:
- ./config:/app/config
- ./runbooks:/app/runbooks
- ./logs:/app/logs
environment:
- LOG_LEVEL=info
- ALERTMANAGER_URL=http://alertmanager:9093
- PROMETHEUS_URL=http://prometheus:9090
- GRAFANA_URL=http://grafana:3000
networks:
- monitoring
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
ports:
- "9093:9093"
volumes:
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
networks:
- monitoring
restart: unless-stopped
networks:
monitoring:
driver: bridge
AI-Assisted Alert Governance
From Rules to Intelligence
Traditional alert automation is based on static rules (if-then), while AI-assisted alert governance introduces dynamic learning and adaptive capabilities:
| Capability | Rule-Driven | AI-Assisted |
|---|---|---|
| Anomaly detection | Static thresholds | Dynamic baselines + anomaly detection algorithms |
| Alert classification | Manual rules | Auto-classification + semantic understanding |
| Root cause analysis | Predefined correlation rules | Causal reasoning + topology analysis |
| Remediation suggestions | Fixed Runbooks | Context-aware generated repair strategies |
| Alert optimization | Manual tuning | Auto-tuning + feedback learning |
LLM-Based Alert Diagnosis
#!/usr/bin/env python3
"""AI alert diagnostic engine — uses LLM to understand alert context and generate remediation suggestions"""
import json
import requests
from dataclasses import dataclass
@dataclass
class DiagnosticResult:
"""Diagnostic result"""
root_cause: str # Root cause analysis
severity_assessment: str # Severity assessment
impact_analysis: str # Impact analysis
remediation_plan: str # Remediation suggestions
confidence: float # Confidence level
class AlertDiagnosticEngine:
"""Alert diagnostic engine"""
def __init__(self, llm_api_url: str, llm_api_key: str):
self.llm_api_url = llm_api_url
self.llm_api_key = llm_api_key
def diagnose(self, alert: dict, context: dict) -> DiagnosticResult:
"""
Diagnose alert
Args:
alert: Alert information
context: Context (metrics, logs, topology, etc.)
"""
prompt = self._build_prompt(alert, context)
# Call LLM for diagnosis
response = self._call_llm(prompt)
return self._parse_response(response)
def _build_prompt(self, alert: dict, context: dict) -> str:
"""Build diagnostic prompt"""
return f"""You are a senior SRE engineer. Analyze the following alert and provide a diagnosis.
## Alert Information
- Name: {alert.get('alertname', '')}
- Severity: {alert.get('severity', '')}
- Service: {alert.get('labels', {}).get('service', '')}
- Instance: {alert.get('labels', {}).get('instance', '')}
- Message: {alert.get('message', '')}
## Context
- Relevant metrics:
{json.dumps(context.get('metrics', {}), indent=2)}
- Recent logs (last 5 minutes):
{context.get('recent_logs', 'N/A')}
- Service topology:
{context.get('topology', 'N/A')}
- Recent changes:
{context.get('recent_changes', 'N/A')}
## Output
1. Root cause: Most likely cause of the failure
2. Severity assessment: Re-evaluate based on impact
3. Impact analysis: Which services and users may be affected
4. Remediation plan: Specific repair steps (prioritize automated solutions)
Output as JSON:
{{"root_cause": "...", "severity_assessment": "...", "impact_analysis": "...", "remediation_plan": "...", "confidence": 0.0-1.0}}
"""
def _call_llm(self, prompt: str) -> str:
"""Call LLM API"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.llm_api_key}",
}
payload = {
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3, # Low temperature for stable output
"max_tokens": 2000,
}
response = requests.post(
self.llm_api_url,
headers=headers,
json=payload,
timeout=30
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
def _parse_response(self, response: str) -> DiagnosticResult:
"""Parse LLM response"""
try:
# Try to extract JSON
start = response.find("{")
end = response.rfind("}") + 1
if start >= 0 and end > start:
data = json.loads(response[start:end])
return DiagnosticResult(
root_cause=data.get("root_cause", ""),
severity_assessment=data.get("severity_assessment", ""),
impact_analysis=data.get("impact_analysis", ""),
remediation_plan=data.get("remediation_plan", ""),
confidence=data.get("confidence", 0.5),
)
except (json.JSONDecodeError, KeyError) as e:
pass
# Fallback: return raw text
return DiagnosticResult(
root_cause=response[:500],
severity_assessment="unknown",
impact_analysis="unknown",
remediation_plan="Manual analysis required",
confidence=0.3,
)
if __name__ == "__main__":
engine = AlertDiagnosticEngine(
llm_api_url="https://api.example.com/v1/chat/completions",
llm_api_key="your-api-key"
)
alert = {
"alertname": "HighErrorRate",
"severity": "critical",
"labels": {"service": "payment-api", "instance": "10.0.1.5:8080"},
"message": "Error rate 15.2%, sustained 3 minutes",
}
context = {
"metrics": {
"error_rate": 0.152,
"p99_latency_ms": 3500,
"qps": 1200,
"cpu_usage": 0.89,
"memory_usage": 0.92,
},
"recent_logs": "[ERROR] 2026-07-11 02:20:01 connection refused: db-pool exhausted",
"topology": "payment-api → redis-cluster → mysql-primary",
"recent_changes": "Deployed payment-api v2.3.1 2 hours ago",
}
result = engine.diagnose(alert, context)
print(f"Root cause: {result.root_cause}")
print(f"Severity: {result.severity_assessment}")
print(f"Impact: {result.impact_analysis}")
print(f"Remediation: {result.remediation_plan}")
print(f"Confidence: {result.confidence}")
Alert Feedback Loop
AI diagnostic accuracy depends on continuous learning. Here is a complete alert feedback loop:
#!/usr/bin/env python3
"""Alert feedback loop — continuous learning to improve alert quality"""
import time
import json
from dataclasses import dataclass, field, asdict
from pathlib import Path
@dataclass
class AlertFeedback:
"""Alert feedback record"""
alert_id: str
alert_name: str
severity: str
was_actionable: bool # Whether alert needed human action
was_auto_resolved: bool # Whether auto-resolved
false_positive: bool # Whether false positive
resolution_time: float # Resolution time (minutes)
root_cause: str # Root cause
action_taken: str # Actual action taken
operator: str # Handler
feedback: str # Additional feedback
timestamp: float = field(default_factory=time.time)
class FeedbackLoop:
"""Alert feedback loop manager"""
def __init__(self, storage_path: str = "/data/feedback"):
self.storage_path = Path(storage_path)
self.storage_path.mkdir(parents=True, exist_ok=True)
self.feedback_store: list[AlertFeedback] = []
self._load()
def record(self, feedback: AlertFeedback):
"""Record feedback"""
self.feedback_store.append(feedback)
self._save(feedback)
self._analyze_patterns()
def _save(self, feedback: AlertFeedback):
"""Persist feedback record"""
filepath = self.storage_path / f"{feedback.alert_id}.json"
filepath.write_text(json.dumps(asdict(feedback), indent=2, ensure_ascii=False))
def _load(self):
"""Load historical feedback"""
for filepath in self.storage_path.glob("*.json"):
data = json.loads(filepath.read_text())
self.feedback_store.append(AlertFeedback(**data))
def _analyze_patterns(self):
"""Analyze feedback patterns, output improvement suggestions"""
if len(self.feedback_store) < 10:
return
total = len(self.feedback_store)
false_positives = sum(1 for f in self.feedback_store if f.false_positive)
auto_resolved = sum(1 for f in self.feedback_store if f.was_auto_resolved)
fp_rate = false_positives / total * 100
auto_rate = auto_resolved / total * 100
print(f"\n=== Alert Quality Report ===")
print(f"Total alerts: {total}")
print(f"False positive rate: {fp_rate:.1f}%")
print(f"Auto-resolution rate: {auto_rate:.1f}%")
print(f"Average resolution time: {sum(f.resolution_time for f in self.feedback_store)/total:.1f} min")
# False positive stats by alert type
by_name = {}
for f in self.feedback_store:
if f.alert_name not in by_name:
by_name[f.alert_name] = {"total": 0, "fp": 0}
by_name[f.alert_name]["total"] += 1
if f.false_positive:
by_name[f.alert_name]["fp"] += 1
print(f"\n=== Top 5 False Positive Alerts ===")
sorted_alerts = sorted(
by_name.items(),
key=lambda x: x[1]["fp"] / max(x[1]["total"], 1),
reverse=True
)[:5]
for name, stats in sorted_alerts:
rate = stats["fp"] / stats["total"] * 100
print(f" {name:30s} | FP rate {rate:.0f}% ({stats['fp']}/{stats['total']})")
# Improvement suggestions
if fp_rate > 20:
print("\n[SUGGESTION] False positive rate too high, recommend adjusting thresholds or adding preconditions")
if auto_rate < 30:
print("[SUGGESTION] Low auto-resolution rate, recommend adding Runbooks for high-frequency alerts")
Effectiveness Measurement
Key Metrics
| Metric | Definition | Target | Measurement |
|---|---|---|---|
| Average alert count | Daily alert volume | < 20/day | Alert system stats |
| Actionable alert rate | Alerts requiring human action | > 70% | Manual labeling |
| False positive rate | Alerts not requiring action | < 10% | Manual labeling |
| Auto-resolution rate | Alerts resolved by Runbooks | > 50% | Auto tracking |
| MTTA (Mean Time to Acknowledge) | Time from alert to response | < 5 min | System records |
| MTTR (Mean Time to Resolve) | Time from alert to resolution | < 15 min | System records |
| Alert fatigue index | Rate of ignored alerts | < 5% | Manual survey |
Measurement Dashboard
#!/usr/bin/env python3
"""Alert automation effectiveness dashboard"""
import time
import json
from dataclasses import dataclass, field
from collections import defaultdict
@dataclass
class AlertMetrics:
"""Alert metrics data"""
total_alerts: int = 0
actionable_alerts: int = 0 # Requiring human action
false_positives: int = 0 # False positives
auto_resolved: int = 0 # Auto-resolved
escalated: int = 0 # Escalated to human
mtta_seconds: float = 0 # Mean time to acknowledge
mttr_seconds: float = 0 # Mean time to resolve
by_severity: dict = field(default_factory=lambda: defaultdict(int))
by_service: dict = field(default_factory=lambda: defaultdict(int))
by_runbook: dict = field(default_factory=lambda: {"executed": 0, "success": 0})
def to_dict(self) -> dict:
return {
"total_alerts": self.total_alerts,
"actionable_alerts": self.actionable_alerts,
"false_positive_rate": f"{self.false_positives/max(self.total_alerts,1)*100:.1f}%",
"auto_resolution_rate": f"{self.auto_resolved/max(self.total_alerts,1)*100:.1f}%",
"mtta": f"{self.mtta_seconds:.0f}s",
"mttr": f"{self.mttr_seconds:.0f}s",
"by_severity": dict(self.by_severity),
"by_service": dict(self.by_service),
"runbook_stats": dict(self.by_runbook),
}
if __name__ == "__main__":
metrics = AlertMetrics()
# Simulated data
metrics.total_alerts = 156
metrics.actionable_alerts = 98
metrics.false_positives = 12
metrics.auto_resolved = 58
metrics.escalated = 40
metrics.mtta_seconds = 45
metrics.mttr_seconds = 480
metrics.by_severity = {"critical": 8, "high": 25, "medium": 68, "low": 55}
metrics.by_service = {"api-gw": 35, "db": 12, "web": 45, "cache": 28, "other": 36}
metrics.by_runbook = {"executed": 58, "success": 45}
print("=== Alert Automation Dashboard ===")
print(json.dumps(metrics.to_dict(), indent=2))
print(f"\n=== Key Metrics ===")
print(f"Total alerts: {metrics.total_alerts}/day")
print(f"Actionable rate: {metrics.actionable_alerts/metrics.total_alerts*100:.1f}%")
print(f"False positive rate: {metrics.false_positives/metrics.total_alerts*100:.1f}%")
print(f"Auto-resolution: {metrics.auto_resolved/metrics.total_alerts*100:.1f}%")
print(f"Runbook success: {metrics.by_runbook['success']/max(metrics.by_runbook['executed'],1)*100:.1f}%")
print(f"MTTA: {metrics.mtta_seconds:.0f}s")
print(f"MTTR: {metrics.mttr_seconds/60:.1f} min")
Summary
Alert automation is not a one-time project, but a progressive journey from “noise to signal, signal to action, action to self-healing.”
Core building path:
Noise Reduction is the Foundation: Through Alertmanager’s grouping, inhibition, and silencing, combined with custom fingerprint deduplication and multi-dimensional correlation, compress alert storms into manageable events. The goal is to reduce daily alerts from 100+ to under 20, with an actionable rate above 70%.
Severity Routing is the Framework: Establish unified P0-P3 severity standards, with routing rules and escalation mechanisms ensuring each alert reaches the right person at the right time. The key is finding the balance between automated remediation and human intervention — safe operations auto-execute, high-risk operations require confirmation.
Runbook Automation is the Core: Encode known fault handling procedures into auto-executable Runbooks, covering high-frequency scenarios: high CPU, disk full, OOM, service down, cert expiry, high IO. Pair with whitelists, rate limiting, and circuit breakers for safety.
Self-Healing Platform is the Carrier: Build a unified platform for alert ingestion, processing, routing, remediation, and verification, integrating scattered automation capabilities into a complete self-healing chain. The key design is “remediation verification” — not only execute repairs, but also verify effectiveness, with automatic rollback and escalation on failure.
AI Assistance is the Accelerator: Introduce AI capabilities on top of rule-driven foundations, using LLMs for alert diagnosis, root cause analysis, and remediation suggestions. But AI doesn’t replace rules — it provides intelligent assistance for scenarios rules can’t cover, learning continuously through feedback loops.
The ultimate goal is to build a reliability system that responds faster than the best engineer — automatically detecting, analyzing, and fixing issues before they impact users, transforming SREs from “firefighters” to “reliability engineers.”