ObservabilityXXI · Alert InhibitionAlertInhibition
Inhibit by Cluster
What you'll learn
- Configure cluster-wide inhibit rules that cross tenancy boundaries using the cluster label
- Use region labels to scope a rule to a single geography
- Write a staging/production separator that prevents staging alerts from suppressing production alerts
- Recognise the production incident where a staging alert suppressed a production alert
- Validate a region-aware inhibit configuration end-to-end
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A real production incident. On 03 August 2025 at 11:42 UTC, a
staging cluster autoscaler triggered a reboot of
stg-checkout-07. The host-down alert fired on the staging
cluster. Because the inhibit rule for host-down used the same
alertname across clusters and lacked a cluster filter, the
production ServiceDown alert on prod-checkout-12 was
also suppressed — not because the host was down, but because
the staging alert matched the rule’s pattern. The production
service was degraded for nineteen minutes before the on-call
engineer noticed the suppression in the API and traced it
back to the staging cluster. The post-mortem identified three
things: the rule had no cluster equal clause; the rule had
no region filter; and the staging/production separator was
missing from the rule design.
This lesson teaches the discipline of cluster-aware
inhibition: how to use cluster and region labels to scope
every rule to the right tenancy, how to write a staging/
production separator, and how to validate that a rule’s
blast radius does not leak across clusters.
What it is
Cluster-aware inhibition is the practice of carrying a
cluster label (and usually a region label) on every
alert, and writing every inhibit rule so that its blast
radius is explicit. The rule either scopes to a single
cluster, scopes to a region that contains multiple
clusters, or — and this is rare and dangerous — scopes to
every cluster globally.
Clusters in a typical platform:
prod-eu production traffic, EU customers
prod-us production traffic, US customers
prod-ap production traffic, APAC customers
stg-eu staging, mirrors prod-eu
stg-us staging, mirrors prod-us
dev developer sandboxes
Labels on every alert:
cluster="prod-eu" region="eu" env="prod"
cluster="stg-eu" region="eu" env="stg"
cluster="dev" env="dev"
The cluster label identifies the tenancy. The region
label identifies the geography. The env label is the
coarse classification (production, staging, development).
A well-designed inhibit rule uses all three labels to
constrain its blast radius.
Why a sysadmin cares
Most platforms run more than one cluster. A typical mid-size
platform runs production in three regions plus staging and
development. A rule that does not scope by cluster will
fire on staging hosts and suppress production alerts with the
same alertname. The failure mode is silent because the
suppression looks identical to a “real” suppression; only the
cluster label on the source reveals the difference.
Two patterns are common:
- Staging/production separator. A single rule suppresses service-down alerts whenever a host-down alert is firing on the same cluster. The rule must constrain to the same cluster; otherwise, a staging host-down suppresses production service-down alerts.
- Region-wide upstream. A regional upstream (a load
balancer, a database primary) goes down. Every cluster in
the region should be suppressed; clusters in other regions
should be unaffected. The rule must use
region(or a finer label) in the equal clause to allow cross-cluster suppression within a region.
How it works
The matcher primitives are the same as the basic rule. The discipline is the labels.
Pattern 1 — single-cluster rule
- source_matchers:
- alertname=~"^(HostDown|NodeDown)$"
- severity="critical"
target_matchers:
- alertname=~"^ServiceDown$"
- severity=~"warning|critical"
equal: [cluster, instance]
The cluster label in equal: ensures the source and
target are on the same cluster. A staging host-down cannot
suppress a production service-down because their cluster
labels differ.
Pattern 2 — region-wide rule
- source_matchers:
- service="regional-lb-eu"
- severity="critical"
target_matchers:
- depends_on="regional-lb-eu"
- severity=~"warning|critical"
equal: [region]
The region label in equal: allows the rule to suppress
across clusters within the same region. The
regional-lb-eu going down affects every cluster in eu,
including prod-eu and stg-eu. The rule fires for both.
Pattern 3 — staging/production separator
- source_matchers:
- alertname=~"^(HostDown|NodeDown)$"
- severity="critical"
- env="prod"
target_matchers:
- alertname=~"^ServiceDown$"
- severity=~"warning|critical"
- env="prod"
equal: [cluster, instance]
The source matcher restricts to env="prod". A staging
host-down cannot trigger the rule because its env label is
stg. The target matcher also restricts to env="prod",
so production alerts on the same cluster and instance are
suppressed, while staging alerts are unaffected.
Pattern 4 — global rule (rare)
- source_matchers:
- alertname="GlobalAuthDown"
- severity="critical"
target_matchers:
- depends_on_team="auth"
- severity=~"warning|critical"
No equal: clause. The rule fires globally. This is
appropriate only for true global failures (an authentication
service that every region and every cluster depends on). The
default assumption is that the rule should be scoped; a global
rule is the exception that requires an explicit comment.
Under the hood
How to configure it
A complete configuration showing all three patterns in one file. Note the comments on each rule — every rule’s blast radius is explicit.
# /etc/alertmanager/alertmanager.yml
route:
receiver: default
group_by: [cluster, alertname, instance]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
inhibit_rules:
# 1. Single-cluster host rule. Default scope.
# Staging host-down does not suppress production service-down.
- source_matchers:
- alertname=~"^(HostDown|NodeDown|InstanceDown)$"
- severity="critical"
- env="prod"
target_matchers:
- alertname=~"^(ServiceDown|ExporterDown|ScrapeFailed)$"
- severity=~"warning|critical"
- env="prod"
equal: [cluster, instance]
# 2. Same rule, scoped to staging. Staging host-down does not
# suppress production at all; it suppresses staging only.
- source_matchers:
- alertname=~"^(HostDown|NodeDown|InstanceDown)$"
- severity="critical"
- env="stg"
target_matchers:
- alertname=~"^(ServiceDown|ExporterDown|ScrapeFailed)$"
- severity=~"warning|critical"
- env="stg"
equal: [cluster, instance]
# 3. Region-wide upstream rule. A regional load balancer
# outage suppresses every dependent in the region, across
# clusters.
- source_matchers:
- service="regional-lb-eu"
- severity="critical"
target_matchers:
- depends_on="regional-lb-eu"
- severity=~"warning|critical"
equal: [region]
# 4. Global auth rule. GLOBAL blast radius — explicit comment.
# This rule applies to every cluster and region because
# the auth service is a global dependency. Review quarterly.
- source_matchers:
- alertname="AuthServiceDown"
- severity="critical"
target_matchers:
- depends_on_team="auth"
- severity=~"warning|critical"
# NOTE: no equal: clause is intentional for global
# dependencies. Any change here requires an SRE review.
receivers:
- name: default
webhook_configs:
- url: 'http://localhost:5001/alerts'
Three things to read carefully:
- Pattern 1 vs Pattern 2. The same rule exists for
env="prod"andenv="stg". The rules are duplicated rather than parameterised because Alertmanager does not support rule templates. The duplication is intentional; it makes every rule’s scope explicit. - Pattern 3 has no
clusterinequal:. The rule is scoped to a region, not a cluster. Theregionlabel allows the rule to fire across clusters within the region. - Pattern 4 is global. The comment is required. A reviewer reading the rule six months later needs to know why the blast radius is the entire platform.
How to validate it
Four checks, including a staging/production separator test that proves the staging/production boundary holds.
# 1. Static syntax check (CONFIGURATION severity)
amtool check-config /etc/alertmanager/alertmanager.yml
Expected output (illustrative):
Checking /etc/alertmanager/alertmanager.yml
SUCCESS
Parsed and loaded successfully
Found inhibit rule 'HostDown-Suppresses-ServiceDown-Prod'
Found inhibit rule 'HostDown-Suppresses-ServiceDown-Stg'
Found inhibit rule 'RegionalLBDown-Suppresses-Dependents'
Found inhibit rule 'AuthServiceDown-Suppresses-Dependents'
# 2. SIGHUP reload (CONFIGURATION severity)
kill -HUP "$(pidof alertmanager)"
# 3. Loaded-config echo (READ-ONLY)
# The status API returns the running configuration as a YAML
# string in .config.original, not as a parsed object.
curl -s http://localhost:9093/api/v2/status \
| jq -r '.config.original' | grep -A8 '^inhibit_rules:'
# 4. Staging/production separator dry-run (READ-ONLY)
# A staging host-down should NOT suppress a production
# service-down. The dry-run proves the separator works.
amtool alert add alertname=HostDown \
cluster="stg-eu" env="stg" \
instance="stg-checkout-07" severity="critical"
amtool alert add alertname=ServiceDown \
cluster="prod-eu" env="prod" \
instance="prod-checkout-12" severity="warning"
sleep 2
curl -s 'http://localhost:9093/api/v2/alerts?active=true&silenced=true' \
| jq '.[] | select(.labels.alertname=="ServiceDown"
and .labels.cluster=="prod-eu")
| {status: .status.state,
inhibitedBy: .status.inhibitedBy}'
Expected output: status: "active", inhibitedBy: [].
The production alert is NOT suppressed. The staging
host-down does not leak across the staging/production
boundary.
amtool has no command that deletes an alert. Alerts leave
Alertmanager by resolving, not by being removed: an alert
posted without an explicit end time resolves on its own after
the global resolve_timeout (five minutes by default). To
resolve one immediately, re-post the same label set with a
start and an end that are both in the past.
# Clean up: resolve the two synthetic alerts.
START="$(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ)"
END="$(date -u -d '-1 minute' +%Y-%m-%dT%H:%M:%SZ)"
amtool alert add alertname=HostDown \
cluster="stg-eu" env="stg" \
instance="stg-checkout-07" severity="critical" \
--start="$START" --end="$END"
amtool alert add alertname=ServiceDown \
cluster="prod-eu" env="prod" \
instance="prod-checkout-12" severity="warning" \
--start="$START" --end="$END"
The positive case — a production host-down DOES suppress a production service-down on the same cluster and instance:
amtool alert add alertname=HostDown \
cluster="prod-eu" env="prod" \
instance="prod-checkout-12" severity="critical"
amtool alert add alertname=ServiceDown \
cluster="prod-eu" env="prod" \
instance="prod-checkout-12" severity="warning"
sleep 2
curl -s 'http://localhost:9093/api/v2/alerts?active=true&silenced=true' \
| jq '.[] | select(.labels.alertname=="ServiceDown"
and .labels.cluster=="prod-eu")
| {status: .status.state,
inhibitedBy: .status.inhibitedBy}'
Expected output: status: "suppressed",
inhibitedBy: ["HostDown"]. The production alert IS
suppressed. Resolve this pair the same way:
START="$(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ)"
END="$(date -u -d '-1 minute' +%Y-%m-%dT%H:%M:%SZ)"
amtool alert add alertname=HostDown \
cluster="prod-eu" env="prod" \
instance="prod-checkout-12" severity="critical" \
--start="$START" --end="$END"
amtool alert add alertname=ServiceDown \
cluster="prod-eu" env="prod" \
instance="prod-checkout-12" severity="warning" \
--start="$START" --end="$END"
How it can fail
Five failure modes specific to cluster-aware inhibition:
- Missing
clusterinequal:. Symptom: a staging host-down suppresses a production service-down because both share the alertname. Investigation: dump/api/v2/alerts?silenced=trueand check theclusterlabel on the source of every suppression. envlabel is not present on the source. Symptom: the staging/production separator rule does not fire because the source has noenvlabel. The Prometheus rule that produces the alert forgot to set it. Investigation:curl /api/v2/alerts?active=truefor the source alertname; confirmenvis in.labels.- Inconsistent
clusterlabels across Prometheus instances. Symptom: two Prometheuses in the same cluster emit differentclustervalues (prod-euvsproduction-eu). Suppressions do not fire because the labels never agree. Investigation: diff the external labels across every Prometheus job. - Global rule without a comment. Symptom: a reviewer
six months later does not know whether the global blast
radius is intentional. The rule is either too broad
(and silently masking a regional failure) or correct
(and depends on tribal knowledge). Investigation: every
rule without
equal:should have an explicit comment that names the exception. - Region rule with cluster in
equal:by mistake. Symptom: a regional upstream outage affects two clusters in the region; only one cluster’s dependents are suppressed. The other cluster’s dependents page the on-call. Investigation: confirm theequal:clause on every region-wide rule does not containcluster.
How to troubleshoot it
The diagnostic order when a suppression crosses a boundary it should not.
- Is the source alert from the right cluster?
curl /api/v2/alerts?active=true&filter=alertname=HostDown. Inspect.labels.clusteron every HostDown alert. If a staging HostDown is the source of a production suppression, the rule is missingclusterinequal:. - Is the target alert in the same cluster? Compare
.labels.clusterbetween source and target. They must agree for a single-cluster rule to fire. - Is the rule scoped correctly? Read the rule’s source
matchers and equal clause. Confirm the
env,cluster, andregionfilters match the intent. - Are the labels consistent across Prometheus
instances? Diff the external labels across every
Prometheus job. Two different
clusterlabel conventions in the same logical cluster means the rules never fire. - Read the alertmanager log. Run with
--log.level=debug. The lines aroundmatched sourceandevaluated ruleshow which alert pairs were considered and which were suppressed.
Security implications
The cluster, region, and env labels are operationally
sensitive. The labels expose the topology of the platform,
the geographies served, and the staging/production boundary.
Restrict access to the Prometheus rules repository and to the
Alertmanager API accordingly.
A staging rule with no env filter is also a security
concern: it means staging alerts can suppress production
alerts, which is both an operational failure and a
confidentiality concern (the staging alert may carry
internal-only labels).
Performance implications
The cost of adding cluster and region to the equal
clause is constant per evaluation. The cost of maintaining
the labels is non-trivial: every Prometheus instance must
agree on the label vocabulary, and every new cluster requires
a corresponding entry in the configuration. Treat the label
vocabulary as production configuration that is version-
controlled alongside the alert rules.
Production guidance
- Add
clusterto the equal clause of every rule by default. Remove it only after explicit review. - Add
envto the source and target matchers of every staging/production rule. The separator is mandatory. - Use
regionfor rules that legitimately cross clusters within a region. Do not use it as a substitute forclusteron single-cluster rules. - Require an explicit comment on every rule without
equal:. The comment names the exception and the review date. - Validate the staging/production separator end-to-end after every change. The dry-run above takes 30 seconds and catches the most expensive mistake.
Verification
You should now be able to answer:
- Why is
clusterin theequal:clause the default for single-cluster rules? - What is the staging/production separator, and why is it mandatory?
- When is a region-wide rule the right pattern, and what is the equal clause?
- How do you prove the staging/production separator holds with a live dry-run?
Quiz
Knowledge check · 8 questions
Q1. A rule has equal: [instance] but no cluster label in equal. What is the failure shape?
Q2. A region-wide upstream rule should use region in the equal clause and not cluster.
Q3. Which labels should a staging/production separator rule restrict on?
Q4. Name the alert label that identifies the staging/production boundary.
Q5. Two Prometheus instances in the same logical cluster emit cluster="prod-eu" and cluster="production-eu" respectively. What is the production consequence?
Q6. A rule has no equal clause but covers a real global dependency. What is the minimum review discipline?
Q7. A region-wide rule with cluster in equal will fire across clusters within the region.
Q8. You deploy a new inhibit rule. The staging/production separator dry-run shows a production alert suppressed by a staging source. What is the first thing to inspect?
Passing score: 75%. Answers are checked in this browser.