Alerting Best Practices
A comprehensive guide to Kubernetes alerting best practices covering alert rules, receiver configuration, on-call management, runbooks, and practical implementation strategies for effective incident response.
Alerting is the bridge between monitoring and incident response. Effective alerting ensures that the right people are notified at the right time about the right issues. Poor alerting leads to:
- Alert Fatigue: Too many alerts causing teams to ignore important notifications
- Missed Incidents: Important issues being overlooked or delayed
- Slow Response: Delays in detecting and responding to problems
- Burned Out Teams: Constant alerting leading to team exhaustion
- Actionable: Every alert should require a specific action
- Accurate: Minimize false positives and negatives
- Timely: Notify when action is needed, not too early or too late
- Contextual: Provide enough information to understand the issue
- Throttled: Avoid alert storms and duplicates
Alert Rules
AlertManager
Receivers
On-Call Management
Runbooks
# Prometheus Alert Rules
groups:
- name: kubernetes-alerts
interval: 30s
rules:
# Good: Specific, actionable, with clear threshold
- alert: HighCPUUsage
expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m # Prevents flapping
labels:
severity: warning
team: platform
annotations:
summary: "High CPU usage on node {{ $labels.node }}"
description: "Node {{ $labels.node }} CPU usage is {{ $value }}% for 5 minutes"
runbook: "https://wiki.example.com/high-cpu"
# Bad: Too broad, no context
- alert: PodProblems
expr: kube_pod_container_status_restarts_total > 0
# No for clause = noise
# Good: Error rate with threshold
- alert: HighErrorRate
expr: sum(rate(nginx_ingress_controller_requests{status=~"5.."}[5m])) / sum(rate(nginx_ingress_controller_requests[5m])) > 0.05
for: 2m
labels:
severity: critical
team: app
annotations:
summary: "High error rate on ingress"
description: "5xx error rate is {{ $value | humanizePercentage }}"
# Good: Service down alert
- alert: ServiceDown
expr: up{job="kubernetes-service"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service {{ $labels.service }} is down"
description: "Service {{ $labels.service }} has been down for 1 minute"
# Good: Memory usage with trend
- alert: MemoryPressure
expr: (sum(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / sum(node_memory_MemTotal_bytes)) * 100 > 90
for: 10m
labels:
severity: warning
annotations:
summary: "Cluster memory pressure"
description: "Memory usage is {{ $value }}% for 10 minutes"
- Use
forclauses to prevent flapping (avoid alerts on transient spikes) - Set appropriate severity levels (critical, warning, info)
- Include actionable descriptions with context
- Add runbook links for easy escalation
- Use
teamlabels for routing to the right team - Avoid alerting on every minor issue
- Use recording rules for complex queries
# AlertManager Configuration
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s # Wait to group alerts
group_interval: 5m # Wait between groups
repeat_interval: 4h # Resend after 4 hours if unresolved
receiver: 'slack-general'
routes:
# Critical alerts go to PagerDuty
- match:
severity: critical
receiver: 'pagerduty-critical'
continue: true
# Warning alerts go to Slack
- match:
severity: warning
receiver: 'slack-warning'
# Team-specific routing
- match:
team: platform
receiver: 'slack-platform'
- match:
team: app
receiver: 'slack-app'
receivers:
- name: 'slack-general'
slack_configs:
- api_url: 'https://hooks.slack.com/services/XXX/XXX/XXX'
channel: '#alerts'
title: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
text: |
{{ range .Alerts }}
*Severity:* {{ .Labels.severity }}
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*Runbook:* {{ .Annotations.runbook }}
{{ end }}
- name: 'slack-warning'
slack_configs:
- channel: '#warnings'
color: 'warning'
- name: 'slack-platform'
slack_configs:
- channel: '#platform-alerts'
- name: 'slack-app'
slack_configs:
- channel: '#app-alerts'
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: 'your-pagerduty-key'
routing_key: 'your-routing-key'
severity: 'critical'
# Email receiver
- name: 'email-alerts'
email_configs:
- to: 'oncall@example.com'
from: 'alertmanager@example.com'
smarthost: 'smtp.example.com:587'
auth_username: 'user'
auth_password: 'password'
# Webhook receiver
- name: 'webhook'
webhook_configs:
- url: 'http://webhook-service:8080/alerts'
send_resolved: true
# Inhibit rules - suppress related alerts
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'cluster']
- Never hardcode API keys or secrets in config files (use environment variables or secrets)
- Set appropriate
group_waitandgroup_intervalto avoid alert storms - Use
repeat_intervalto prevent alert fatigue - Route alerts to the right teams using labels
- Test webhooks and integrations before deploying
# PagerDuty Integration
# Configure PagerDuty service with AlertManager
receivers:
- name: 'pagerduty'
pagerduty_configs:
- service_key: 'your-service-key'
routing_key: 'your-routing-key'
severity: 'critical'
# PagerDuty escalation policy
# Level 1: Primary on-call (weekday)
# Level 2: Secondary on-call (weekend)
# Level 3: Engineering manager
# Opsgenie Integration
- name: 'opsgenie'
opsgenie_configs:
- api_key: 'your-api-key'
api_url: 'https://api.opsgenie.com'
message: 'Alert: {{ .Annotations.summary }}'
description: '{{ .Annotations.description }}'
responders:
- name: 'platform-team'
type: 'team'
# AlertManager with Opsgenie routing
route:
routes:
- match:
team: platform
receiver: 'opsgenie-platform'
- match:
team: app
receiver: 'opsgenie-app'
# On-call Schedule in Opsgenie
# apiVersion: opsgenie.com/v1
# kind: Schedule
# metadata:
# name: platform-oncall
# spec:
# name: "Platform Team On-Call"
# timezone: "America/New_York"
# rotations:
# - name: "Primary"
# participants:
# - "user1@example.com"
# - "user2@example.com"
# - "user3@example.com"
# timeRestriction:
# type: "weekly"
# restrictions:
# - startDay: "monday"
# startTime: "09:00"
# endDay: "friday"
# endTime: "17:00"
# On-call Rotation Manager (with Slack)
# Use Slack integrations to notify on-call team
- name: 'slack-oncall'
slack_configs:
- channel: '#oncall-platform'
text: |
@channel
*Alert:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*Severity:* {{ .Labels.severity }}
- Define clear escalation policies with multiple levels
- Rotate on-call responsibilities regularly (weekly)
- Use primary and secondary on-call for redundancy
- Integrate with PagerDuty or Opsgenie for robust management
- Notify the team on Slack for visibility
- Use Opsgenie/PagerDuty mobile apps for alerts
- Document on-call responsibilities and expectations
Runbooks provide step-by-step instructions for responding to alerts. They reduce response time and ensure consistent incident handling.
# Example Runbook: High CPU Usage
---
name: High CPU Usage
description: Response procedure for high CPU alerts
severity: warning
tags:
- performance
- capacity
steps:
- step: 1
action: "Check CPU usage across nodes"
command: "kubectl top nodes"
expected: "CPU usage should be below 80%"
- step: 2
action: "Identify high-CPU pods"
command: "kubectl top pods --all-namespaces | sort -k3 -rn | head -20"
expected: "Identify pods with high CPU usage"
- step: 3
action: "Check pod logs for issues"
command: "kubectl logs -n {{ namespace }} {{ pod-name }} --tail=100"
expected: "Look for errors or high resource consumption"
- step: 4
action: "Check resource limits"
command: "kubectl describe pod {{ pod-name }} -n {{ namespace }}"
expected: "Verify CPU requests and limits are appropriate"
- step: 5
action: "Scale application if needed"
command: "kubectl scale deployment {{ deployment-name }} -n {{ namespace }} --replicas={{ new-replicas }}"
expected: "Reduce CPU pressure on individual pods"
- step: 6
action: "If unresolved, escalate to team lead"
contact: "#platform-team on Slack"
# Runbook Template
---
name: {{ alert-name }}
description: {{ description }}
severity: {{ severity }}
tags:
- {{ tag1 }}
- {{ tag2 }}
steps:
- step: 1
action: "{{ action }}"
command: "{{ command }}"
expected: "{{ expected }}"
- step: 2
action: "{{ action }}"
command: "{{ command }}"
expected: "{{ expected }}"
- step: 3
action: "{{ action }}"
command: "{{ command }}"
expected: "{{ expected }}"
escalation:
level1: "Contact primary on-call"
level2: "Contact team lead"
level3: "Contact engineering manager"
# Store runbooks in Git
# /runbooks/high-cpu.md
# /runbooks/service-down.md
# /runbooks/memory-pressure.md
- Create runbooks for every critical alert
- Keep runbooks concise and actionable
- Include commands with exact syntax
- Define clear escalation paths
- Store runbooks in Git for version control
- Review and update runbooks regularly
- Test runbooks during incident drills
- Include links to dashboards and logs
Infrastructure Alerts
Application Alerts
Security Alerts
Capacity Alerts
Improving alerting quality is a journey. Use this maturity model to assess and improve your alerting practices:
| Level | Characteristics | Key Practices |
|---|---|---|
| Level 1: Reactive | Ad-hoc alerts, high noise, frequent false positives | Setup basic monitoring, establish on-call |
| Level 2: Organized | Defined alert rules, severity levels, basic runbooks | Create runbooks, use AlertManager grouping |
| Level 3: Proactive | Automated responses, low false positive rate, team routing | Automate remediation, team-specific routing |
| Level 4: Optimized | Predictive alerts, self-healing, continuous improvement | Machine learning for anomalies, auto-scaling |
alertmanager web UI for testing, or use promtool for unit testing. Run promtool check rules alert.rules for syntax validation. Use recording rules for complex queries before alerting.amtool silence add --duration=2h.Effective alerting is critical for maintaining reliable Kubernetes clusters. Implement these best practices to reduce alert fatigue, improve incident response, and build a culture of operational excellence.