ObservabilityCXII · Production Observability Operating ModelOpsModel
The Operating Model
What you'll learn
- Define the three roles of an observability operating model and the boundaries between them
- Map every dashboard, alert, and runbook to a named owning team
- Route Alertmanager notifications and Grafana permissions by team rather than by individual
- Recognise the failure shapes that appear when ownership is implicit
- Audit an existing observability deployment against the operating model
Prerequisites
- 06-anti-patterns
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
An incident fires at 03:11. PagerDuty wakes an engineer on the infrastructure rota. They look at the alert: “HTTP 5xx rate elevated on checkout service.” They do not own the checkout service. They do not know who does. They page the platform Slack channel. Twelve minutes elapse before someone from the payments team sees the page and starts investigating. Mean time to acknowledge: 12 minutes. Mean time to engage the right team: 14. Mean time to resolve: 47 minutes. None of those minutes were spent actually fixing the problem.
This is what the observability operating model exists to prevent. The operating model is the assignment of every dashboard, alert, and runbook to a named team, plus the routing rules that put the right team in front of the right page. It is the contract between “the platform collects telemetry” and “a named team is responsible for what the telemetry shows.”
What the operating model is
The observability operating model is a RACI matrix for telemetry. For every observability artefact (dashboard, alert rule, log stream, recording rule, runbook, SLO), the model names:
- Responsible — the team that owns the artefact, writes it, reviews it, and is paged for it.
- Accountable — the team whose budget pays for the underlying service (usually the application team that owns the service the artefact observes).
- Consulted — teams that need to be in the loop when the artefact changes (platform, security, compliance).
- Informed — teams that need to see the output but do not own it (leadership dashboards, status pages).
The matrix is a document. The artefact is the wiring that
enforces it: Alertmanager routes, Grafana folder permissions,
Loki tenant labels, Prometheus rule-file team label. The
matrix without the wiring is aspiration; the wiring without the
matrix is chaos.
Why a sysadmin cares
The first incident of every production observability programme is a misrouted page. The team that built the dashboard is on holiday; the team that paged does not own the service; the person who knows the runbook left six months ago. Mean time to resolve triples. The fix is not “another on-call rotation”; the fix is an explicit operating model that names the team and the wiring that routes the page to that team.
The trade-off is real. An explicit model requires the platform team to maintain per-team routing rules, per-team Grafana folders, and per-team contact lists. The cost is paid by the platform team; the benefit is paid by every application team that pages correctly. Without executive sponsorship, the cost often wins.
How it works
The model separates who runs the platform from who uses the platform.
Observability Operating Model
---------------------------------
+-----------------+ +-------------------+
| Observability | | Application |
| / Platform | | Teams |
| Team | | |
+-----------------+ +-------------------+
| |
| owns | own
v v
+-----------------+ +-------------------+
| Shared Stack | | Per-service |
| - Prometheus | | - Dashboards |
| - Loki | | - Alerts |
| - Tempo | | - Runbooks |
| - Grafana | | - SLOs |
| - OTel Coll. | | - Drop rules |
| - Alloy | | |
+-----------------+ +-------------------+
| |
+----------+------------+
|
v
Tenant boundary / RBAC
(one per team)
The boundary is tenant isolation (Loki multi-tenant, Prometheus per-team external_labels or Cortex/Mimir tenant) and permission isolation (Grafana folders per team, RBAC binding per team). Without these, “ownership” is a label that no system enforces.
The right approach is “you build it, you run it.” The team that writes the application writes the application dashboards, the application alerts, and the application runbook. The platform team provides the stack, the policies, and the review. Nothing in this model is novel; the discipline is to enforce it.
Under the hood: where ownership is actually stored
How to configure it
Three files encode the operating model. The first is the Alertmanager route, the second is the Prometheus rule-group header, the third is the Grafana folder provisioning.
Alertmanager: route by team label
# /etc/alertmanager/alertmanager.yml
# CONFIGURATION: routes pages by the team label on the alert.
global:
resolve_timeout: 5m
route:
receiver: 'default-null'
group_by: ['alertname', 'team', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
# Payments team owns checkout, payment-svc, billing-svc.
- matchers:
- team = "payments"
receiver: 'pd-payments-oncall'
continue: false
# Tighter repeat because payments pages often indicate
# revenue impact; the on-call should be re-paged if
# silence is not declared within an hour.
repeat_interval: 1h
# Platform team owns the shared observability stack.
- matchers:
- team = "platform"
receiver: 'pd-platform-oncall'
continue: false
# Default catch-all routes anything without a team label
# to a manual triage queue. Items here are a backlog of
# artefacts that have not been assigned.
- matchers:
- team =~ ".*"
receiver: 'pd-default-triage'
continue: false
receivers:
- name: 'pd-payments-oncall'
pagerduty_configs:
- service_key: '<redacted>'
severity: 'critical'
- name: 'pd-platform-oncall'
pagerduty_configs:
- service_key: '<redacted>'
severity: 'warning'
- name: 'pd-default-triage'
slack_configs:
- channel: '#observability-triage'
send_resolved: true
The catch-all with no explicit receiver is a deliberate choice:
alerts that match no team label land in #observability-triage,
a Slack channel the observability team watches. The backlog of
un-owned alerts is itself a metric.
Prometheus: declare the team in the rule group
# /etc/prometheus/rules/payments.yml
# CONFIGURATION: every alert rule and recording rule carries a
# team label so Alertmanager can route by team.
groups:
- name: payments.slo
interval: 30s
rules:
- alert: Checkout5xxRateHigh
expr: |
sum(rate(checkout_http_requests_total{
service="checkout",status=~"5.."}[5m]))
/
sum(rate(checkout_http_requests_total{
service="checkout"}[5m]))
> 0.01
for: 10m
labels:
team: payments # routes to pd-payments-oncall
severity: critical
service: checkout
slo: availability
annotations:
summary: 'Checkout 5xx rate above 1% for 10m'
description: |
Checkout 5xx rate is {{ $value | humanizePercentage }}
over the last 5 minutes. Page the payments on-call.
runbook_url: 'https://runbooks.example.com/payments/checkout-5xx'
dashboard_url: 'https://grafana.example.com/d/checkout-overview'
The team label on the alert is what Alertmanager matches. A
missing team label routes the alert to the triage queue. The
discipline is: every rule-group header encodes team, every
alert rule preserves that label, every recorder carries it
forward.
Grafana: folder-per-team provisioning
# /etc/grafana/provisioning/folders/payments.yaml
# CONFIGURATION: every team gets a folder with explicit
# permissions. Provisioned, not clickable, so the state is
# reproducible from version control.
apiVersion: 1
orgs:
- orgId: 1
folders:
- title: 'team-payments'
uid: 'team-payments'
permissions:
- team: 'payments'
permission: 2 # Editor
- team: 'sre-readonly'
permission: 1 # Viewer
The folder UID matches the Alertmanager receiver name suffix.
A grep across /etc/grafana/provisioning should reveal every
team folder that has a corresponding Alertmanager route. If the
two drift, the operating model is broken.
How to validate it
Validation here is consistency across the three encodings. Three commands confirm the model is wired:
# READ-ONLY. List every Alertmanager route and the team it
# matches. The output should have one entry per team.
amtool config routes show --alertmanager.url=http://alertmanager:9093
# READ-ONLY. Validate every Prometheus rule file and confirm
# each group declares a team label.
promtool check rules /etc/prometheus/rules/*.yml
grep -RH '^ - alert:' /etc/prometheus/rules/ \
| while read -r line; do
file=$(echo "$line" | cut -d: -f1)
# Confirm each alert carries a team label in its labels
# block. CI rejects rule files that omit it.
grep -q 'team:' "$file" || echo "MISSING team in $file"
done
# READ-ONLY. List every Grafana folder with a team prefix and
# confirm each has a permission entry. Missing permissions are
# a configuration drift.
curl -s -u admin:$GRAFANA_PASS \
http://grafana:3000/api/folders | jq '.[] | {uid, title}'
curl -s -u admin:$GRAFANA_PASS \
http://grafana:3000/api/folders/team-payments/permissions \
| jq '.[] | {role, permission}'
Illustrative output of the Alertmanager route check:
$ amtool config routes show --alertmanager.url=http://alertmanager:9093
Routing tree:
default-route receiver: default-null
|- matchers: team="payments" receiver: pd-payments-oncall continue: false
|- matchers: team="platform" receiver: pd-platform-oncall continue: false
\- matchers: team=~".*" receiver: pd-default-triage continue: false
The shape is n+1 routes: one per application team, plus one
default. If the count grows past that, a team is missing from
the document.
How it can fail
Six failure shapes appear repeatedly when the operating model is implicit rather than wired:
- Wiki-only ownership. A Confluence page lists every team and every dashboard. The page is six months stale. Three dashboards are owned by ex-employees. Symptom: alerts route to a PagerDuty service that no one is on.
- No team label on rules. Rule files omit the
teamlabel. Alertmanager has no matcher; everything lands in the default triage queue. Symptom: the triage channel has 200 alerts a day and is ignored. - Folder sprawl. Every engineer creates a top-level dashboard. There are 800 dashboards and 12 folders. The “payments folder” exists twice, with different permissions. Symptom: a new dashboard is unowned because there is no default folder to inherit from.
- Default-null catch-all catches everything. Alertmanager has a permissive default route that pages the platform team. Symptom: the platform on-call is paged for every alert, including ones they cannot fix.
- Cross-team dashboards with no joint owner. A dashboard shows checkout and fraud-detection. Two teams need it. Neither updates it. Symptom: the dashboard is frozen at the version two teams agreed on, then rots.
- Promotion without ownership migration. A service moves from team A to team B (reorg, split, M&A). The dashboards stay under team A. Symptom: team B pages for an alert they cannot see the dashboard for.
How to troubleshoot it
The diagnostic order is fixed. Start with the artefact that is on fire; trace the owner; confirm the wiring.
- Confirm the alert fired. Open Prometheus’s
/alertspage and locate the firing alert. Note theteamlabel and theservicelabel. - Confirm the route. Run
amtool config routes showand find the matcher. If the matcher does not exist, the alert will fall through to the catch-all and end up in the triage channel. - Confirm the dashboard exists. Open the
dashboard_urlannotation in the alert. If the URL 404s, the dashboard has been deleted or moved. - Confirm the runbook exists. Open the
runbook_urlannotation. If the URL 404s or returns a generic runbook index, the runbook is missing or generic. - Confirm the Grafana permission. Check that the team named in the alert has Editor permission on the folder referenced by the dashboard.
- Form the diagnosis. The alert lacks a
teamlabel, or the Alertmanager route does not match, or the folder is missing permission, or the runbook URL is broken. Each is a separate remediation.
Security implications
The operating model intersects with security at the tenant
boundary. Loki’s auth_enabled: true enables multi-tenancy;
the tenant ID is derived from the X-Scope-OrgID header set
by the gateway (Grafana’s Loki datasource). If the gateway is
misconfigured, one team can query another’s logs. Validate the
gateway explicitly:
# READ-ONLY. Confirm the gateway sets the tenant header.
curl -sI -H 'X-Scope-OrgID: payments' \
http://loki-gateway:3100/loki/api/v1/query?query='{service="checkout"}'
The Grafana RBAC for folders is the second security boundary. A Viewer on a folder can see the dashboard and its variables. A folder with Editor permission for “all users” is an exposure. Audit the folder permissions quarterly.
Performance implications
The operating model has performance implications in two
places: Alertmanager grouping and Grafana dashboard rendering.
Alertmanager groups by ['alertname', 'team', 'service']; if
the team label is missing on many alerts, the grouping
collapses to alertname alone, which produces more groups and
more notifications. The fix is the missing label, not the
group_by. Grafana renders one folder at a time; folders with
500 dashboards take longer to load than folders with 50. The
fix is to split, not to increase the rendering budget.
Production guidance
- Require a
teamlabel in the rule-group header; CI rejects rule files without it. - Provision Grafana folders per team; never click-create them.
- Route in Alertmanager by
teamfirst, then byservice; never by individual user. - Audit the agreement quarterly: every team in the Alertmanager routes should have a Grafana folder and a PagerDuty service.
- Make the operating model visible. A single page listing every team, every Alertmanager route, every Grafana folder UID, and the PagerDuty service is the reference document.
Verification
You should now be able to answer:
- What three roles does the observability operating model separate?
- Where in the configuration is ownership actually stored?
- What is the failure shape when ownership is documented but not wired?
- How does the model appear in Alertmanager, Prometheus, and Grafana, and what happens when the three disagree?
- How do you audit an existing platform against the model?
Quiz
Knowledge check · 8 questions
Q1. In a production observability operating model, who owns an application service dashboard?
Q2. What is the single label that Alertmanager should match to route a page to the correct team?
Q3. Which files encode the operating model so it can be audited?
Q4. A wiki page declaring ownership is sufficient if the configuration routes the page to the right team.
Q5. What is the most operationally costly failure of an implicit operating model?
Q6. Which CLI command lists every Alertmanager route and the matcher it uses?
Q7. Which items belong in the reorg checklist when a service changes owners?
Q8. Where should the team label be declared on a Prometheus rule?
Passing score: 75%. Answers are checked in this browser.