یکی از تیمهایی که با آن کار کردم روزانه بیش از ۲۰۰ Alert در کانال Mattermost داشت. نتیجه قابل پیشبینی بود: هیچکس دیگر کانال را نگاه نمیکرد و Alert واقعی بین سروصداها گم میشد. به این وضعیت Alert Fatigue میگویند و از نبودن مانیتورینگ هم خطرناکتر است، چون احساس امنیت کاذب میدهد.
بعد از بازطراحی Alertها و نوشتن Runbook برای هر کدام، میانگین زمان پاسخ به Incident حدود ۴۵٪ کم شد. در این مقاله همان روش را توضیح میدهم.
قانون اول: Alert روی علامت، نه علت
| Alert بد (علت) | Alert خوب (علامت) |
|---|---|
| CPU سرور بالای ۸۰٪ است | زمان پاسخ p95 API بیشتر از ۱ ثانیه است |
| یک Pod ریاستارت شد | درصد خطای 5xx بیشتر از ۲٪ است |
| Memory بالای ۹۰٪ است | صف پرداخت بیش از ۱۰ دقیقه پردازش نشده |
CPU بالا ممکن است کاملاً طبیعی باشد؛ مثلاً زمان یک Batch Job شبانه. اما کاربری که ۵ ثانیه منتظر پاسخ میماند، همیشه یک مشکل واقعی است. علتها را در داشبورد ببینید و فقط برای علامتها کسی را بیدار کنید.
نوشتن Rule درست
با Prometheus Operator، Ruleها را بهصورت PrometheusRule تعریف میکنیم:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: api-alerts
namespace: production
spec:
groups:
- name: api.rules
rules:
- alert: ApiHighErrorRate
expr: |
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (service) (rate(http_requests_total[5m])) > 0.02
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "{{ $labels.service }}: error rate above 2%"
description: "Current value: {{ $value | humanizePercentage }}"
runbook_url: "https://wiki.example.ir/runbooks/api-high-error-rate"
dashboard: "https://grafana.example.ir/d/api?var-service={{ $labels.service }}"
- alert: PodCrashLooping
expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.namespace }}/{{ $labels.pod }} is crash looping"
runbook_url: "https://wiki.example.ir/runbooks/pod-crash-looping"
- alert: DiskWillFillIn24h
expr: predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 24*3600) < 0
for: 30m
labels:
severity: warning
annotations:
summary: "Disk on {{ $labels.instance }} will be full within 24h"
نکتههای کلیدی:
for: 5mیعنی شرط باید ۵ دقیقهی پیوسته برقرار باشد. همین یک خط بیشتر Alertهای لحظهای و بیاهمیت را حذف میکند.predict_linearبهجای «دیسک ۹۰٪ پر است» میپرسد «دیسک کی پر میشود؟». دیسک ۹۵٪ پری که ماههاست ثابت مانده، Alert نمیخواهد.runbook_urlوdashboardدر Annotationها یعنی کسی که Alert را میگیرد، با یک کلیک میداند کجا را نگاه کند و چه کند.
سطحبندی Severity
| Severity | معنی | کانال |
|---|---|---|
critical |
کاربران همین الان آسیب میبینند | تماس یا پیامک به On-Call + کانال Incident |
warning |
اگر رسیدگی نشود، طی ساعتها مشکل میشود | کانال تیم در ساعت کاری |
info |
فقط برای اطلاع | فقط داشبورد، بدون اعلان |
Routing در Alertmanager
route:
receiver: default
group_by: [alertname, namespace, service]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers: [severity="critical"]
receiver: oncall
repeat_interval: 30m
- matchers: [team="backend"]
receiver: backend-team
receivers:
- name: default
slack_configs:
- api_url: https://chat.example.ir/hooks/xxxxxxxx # Mattermost incoming webhook
channel: "#alerts"
send_resolved: true
title: '{{ .CommonLabels.alertname }} ({{ .Status }})'
text: >-
{{ range .Alerts }}• {{ .Annotations.summary }}
📖 {{ .Annotations.runbook_url }}
{{ end }}
- name: oncall
webhook_configs:
- url: http://sms-gateway.monitoring.svc/alert # your SMS/call bridge
- name: backend-team
slack_configs:
- api_url: https://chat.example.ir/hooks/yyyyyyyy
channel: "#backend-alerts"
inhibit_rules:
# If a whole node is down, don't also page for every pod on it
- source_matchers: [alertname="KubeNodeNotReady"]
target_matchers: [severity=~"warning|critical"]
equal: [node]
group_by باعث میشود اگر ۳۰ Pod یک سرویس همزمان خطا بدهند، فقط یک پیام دریافت کنید. inhibit_rules هم وقتی یک Node کامل از دسترس خارج شده، جلوی دهها Alert ثانویه را میگیرد.
Runbook: نیمهی فراموششدهی Alerting
Runbook یک صفحهی ساده در Wiki یا Confluence است که برای هر Alert جواب این چهار سؤال را دارد:
- این Alert یعنی چه؟ چه چیزی از دید کاربر خراب است؟
- چطور بررسی کنم؟ کدام داشبورد، کدام کوئری و کدام دستور
kubectl؟ - چطور موقتاً رفعش کنم؟ Rollback، Scale کردن یا ریاستارت.
- چه کسی را خبر کنم؟ اگر حل نشد، مسئول بعدی کیست؟
قدم بعدی خودکار کردن همین Runbookهاست. مثلاً با n8n یا یک اسکریپت ساده، وقتی Alert «دیسک در حال پر شدن» میآید، لاگهای قدیمی خودکار پاک شوند و نتیجه در کانال گزارش شود.
چکلیست بازبینی Alertها
- هر Alert با
criticalواقعاً به اقدام فوری نیاز دارد - همهی Alertها
forدارند - هر Alert یک
runbook_urlو یک لینک داشبورد دارد - Alertهایی که در ماه گذشته هیچ اقدامی در پی نداشتند حذف یا به
infoتبدیل شدهاند -
send_resolved: trueفعال است تا تیم بداند مشکل برطرف شده
جمعبندی
هدف Alerting این نیست که همهچیز را گزارش کند؛ هدفش این است که فقط چیزهای مهم را، به آدم درست، همراه با راهحل برساند. با Alert روی علامتها، سطحبندی، Routing درست و Runbook برای هر Alert، تیم دوباره به کانال Alertها اعتماد میکند.