ObservabilityCXIV · Final Production Reference ArchitectureReferenceArchitecture
The Presentation Layer
What you'll learn
- Distinguish provisioned dashboards from ad-hoc dashboards and the operational cost of each
- Trace an alert from a Prometheus rule through Alertmanager to a PagerDuty notification
- Apply the dashboard hierarchy (service, host, dependency) and the alert hierarchy (symptom, cause, action)
- Recognise the four failure shapes of a presentation layer that has drifted from the storage layer
- Validate a dashboard and an alert with a synthetic event and a documented response
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
The on-call engineer opens Grafana at 03:00. The dashboard titled “Checkout Service” has three panels. The first panel shows a red error rate. The second panel shows a green CPU usage. The third panel is empty. The on-call engineer clicks on the empty panel and the panel renders a single line: “datasource not found”. The dashboard is correct; the datasource was renamed during a maintenance window two weeks ago. The on-call engineer has a dashboard that renders an operational state that cannot be trusted.
The presentation layer is the operator-facing surface. The discipline of the layer is the discipline of the contract between the operator and the data. When the discipline is absent, the operator is paid to read the wrong answer.
What it is
The presentation layer is the set of components that render the data and notify the operator. In this course:
- Grafana 11.x for dashboards and unified alerting. The component that turns a query into a panel and a panel into a dashboard.
- Alertmanager for alert routing and deduplication. The component that turns a Prometheus alert into a PagerDuty notification, a Slack message, or an email.
The presentation layer is the only layer that the operator sees. The other layers are heard about. The presentation layer is the surface the operator looks at when the system is on fire.
Why a sysadmin cares
The presentation layer is where the system meets the human. The four risks:
- Unowned dashboards. A dashboard exists with no owner. The queries break. Nobody notices. The on-call engineer clicks on the dashboard and gets a wall of “datasource not found” panels.
- Alert fatigue. The alertmanager fires every minute. The on-call engineer mutes the channel. The real alert arrives. The channel is muted. The page is missed.
- Drift between dashboard and data. The dashboard is rendered against a datasource UID that no longer exists. The panel renders a state that is not the data.
- Paging without context. The alertmanager fires with
summary: "CPU above 80%". The on-call engineer has to start the investigation from the page.
The presentation layer is the only layer where the engineering discipline of the platform team directly shapes the operational experience of the operator.
How it works
The mental model: the operator reads a dashboard, the operator receives a page, the operator takes an action. The presentation layer is the bridge between the two.
+---------- Grafana ----------+ +--------- Alertmanager ---------+
| | | |
| datasource -- query | <----- | prometheus rule -- alerts |
| panel -- render | | (silences, inhibitions, |
| dashboard -- correlate | | routing tree) |
| variable -- pivot | | |
| alert rule -- fire | -----> | receiver (Slack, PagerDuty, |
| (Grafana 11 unified) | | email, webhook) |
| | | |
+-----------------------------+ +--------------------------------+
| |
v v
operator view operator page
The alert path is the most operationally important. A Prometheus rule fires. The alertmanager receives the alert. The alertmanager applies the routing tree. The alertmanager groups the alert. The alertmanager deduplicates repeats. The alertmanager delivers to the receiver that the alert matches. The receiver page the operator.
The dashboard path is the investigation surface. A panel queries a datasource. The datasource returns a result. The panel renders the result. The dashboard correlates panels through variables. The operator pivots from one panel to the next.
Under the hood
Grafana renders a panel by:
- Loading the panel definition from the dashboard JSON.
- Resolving the panel’s template variables.
- Issuing the query to the configured datasource.
- Receiving the response.
- Applying the panel’s transformations.
- Rendering the panel to the operator’s browser.
The datasource is the bridge to the backend. A Grafana datasource is a YAML/JSON definition that maps a UID to a backend URL, an authentication credential, and a default database. The UID is the stable identifier; the URL is the operational detail.
Alertmanager deduplicates alerts by a label fingerprint. The fingerprint is the hash of the alert’s labels. Two alerts with the same labels are the same alert. The grouping key is the set of labels that the routing tree uses to decide the receiver.
The routing tree is a YAML file with a route block and a
receivers block. The route matches alerts by label
selector and forwards to the receiver. The receiver is the
notification target.
How to configure it
Grafana provisioned datasource. The right pattern is provisioned YAML in a repository, not a hand-edited datasource in the UI. The provisioning file is read at startup.
# /etc/grafana/provisioning/datasources/datasources.yaml
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus-prod
type: prometheus
access: proxy
url: http://prometheus.monitoring.svc:9090
isDefault: true
basicAuth: true
basicAuthUser: grafana
secureJsonData:
basicAuthPassword: ${PROM_BASIC_AUTH_PASSWORD}
jsonData:
timeInterval: 15s
httpMethod: POST
- name: Loki
uid: loki-prod
type: loki
access: proxy
url: http://loki.monitoring.svc:3100
basicAuth: true
basicAuthUser: grafana
secureJsonData:
basicAuthPassword: ${LOKI_BASIC_AUTH_PASSWORD}
- name: Tempo
uid: tempo-prod
type: tempo
access: proxy
url: http://tempo.monitoring.svc:3200
jsonData:
httpMethod: GET
tracesToLogsV2:
datasourceUid: loki-prod
serviceMap:
datasourceUid: prometheus-prod
nodeGraph:
enabled: true
The uid is the stable identifier. The dashboard panel
references the datasource by uid, not by name. The renames
of the datasource do not break the panel.
Grafana provisioned dashboard. The dashboard JSON is committed to a repository and provisioned at startup:
# /etc/grafana/provisioning/dashboards/dashboards.yaml
apiVersion: 1
providers:
- name: service-dashboards
orgId: 1
folder: Services
type: file
disableDeletion: false
updateIntervalSeconds: 30
options:
path: /var/lib/grafana/dashboards/services
Alertmanager routing tree. The routing tree is the operational contract. The right pattern is one that separates symptom alerts from cause alerts and routes them to different receivers.
# /etc/alertmanager/alertmanager.yml
global:
resolve_timeout: 5m
smtp_smarthost: smtp.internal:25
smtp_from: alertmanager@monitoring.internal
smtp_require_tls: false
route:
receiver: default
group_by: [alertname, cluster, service]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers:
- severity="critical"
receiver: pagerduty-critical
group_wait: 10s
repeat_interval: 1h
- matchers:
- severity="warning"
receiver: slack-warnings
group_wait: 1m
- matchers:
- severity="info"
receiver: slack-info
inhibit_rules:
- source_matchers: [severity="critical"]
target_matchers: [severity="warning"]
equal: [cluster, service]
receivers:
- name: default
slack_configs:
- api_url: https://hooks.slack.com/services/XXX
channel: "#alerts-default"
send_resolved: true
- name: pagerduty-critical
pagerduty_configs:
- service_key_file: /etc/alertmanager/pagerduty.key
severity: critical
send_resolved: true
- name: slack-warnings
slack_configs:
- api_url: https://hooks.slack.com/services/XXX
channel: "#alerts-warnings"
send_resolved: true
- name: slack-info
slack_configs:
- api_url: https://hooks.slack.com/services/XXX
channel: "#alerts-info"
send_resolved: true
The inhibit_rules block is the discipline. A critical
alert on the same cluster and service suppresses the
warning alerts. The on-call engineer receives one page, not
ten.
Prometheus alert rule. The rule is the input. The alertmanager is the output. The rule fires a single alert when the predicate holds.
# /etc/prometheus/rules/checkout.yml
groups:
- name: checkout
rules:
- alert: CheckoutHighErrorRate
expr: |
sum(rate(http_requests_total{job="checkout", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="checkout"}[5m]))
> 0.05
for: 2m
labels:
severity: critical
service: checkout
cluster: prod-eu-west-1
annotations:
summary: "Checkout error rate above 5%"
description: "Checkout is returning 5xx for more than 5% of requests for 2 minutes."
runbook_url: "https://runbooks.internal/checkout-high-error-rate"
dashboard_url: "https://grafana.internal/d/checkout-prod"
The runbook_url is the canonical reference. The dashboard_url
is the investigation surface. The summary is what the
on-call engineer sees on the page.
How to validate it
CONFIGURATION — validate the configs before reload.
amtool check-config /etc/alertmanager/alertmanager.yml
# Valid configuration file
READ-ONLY — confirm the alertmanager is routing.
curl -sf http://alertmanager.monitoring.svc:9093/-/ready
# Alertmanager is ready.
curl -sf -u admin:admin http://alertmanager.monitoring.svc:9093/api/v2/silences | jq '.[] | {id, matchers, status}'
# Confirm the silence list is correct.
READ-ONLY — confirm the Grafana datasource is healthy.
curl -sf -u admin:admin \
http://grafana.monitoring.svc:3000/api/datasources/uid/prometheus-prod/health | jq .
# { "status": "ok", "message": "Data source is working" }
READ-ONLY — confirm the alert is firing.
curl -sf 'http://prometheus.monitoring.svc:9090/api/v1/alerts' | jq '.data.alerts[] | select(.labels.alertname=="CheckoutHighErrorRate")'
# { "labels": { "alertname": "CheckoutHighErrorRate", "severity": "critical", "service": "checkout" },
# "state": "firing", "activeAt": "2026-08-13T03:14:00Z" }
READ-ONLY — confirm the alertmanager is delivering.
curl -sf -u admin:admin http://alertmanager.monitoring.svc:9093/api/v2/alerts | jq '.[] | {labels, receivers}'
# { "labels": { "alertname": "CheckoutHighErrorRate", "severity": "critical" },
# "receivers": [{ "name": "pagerduty-critical" }] }
READ-ONLY — confirm the dashboard renders.
curl -sf -u admin:admin \
http://grafana.monitoring.svc:3000/api/dashboards/uid/checkout-prod | jq '.dashboard.title'
# "Checkout Service"
SERVICE-IMPACT — reload after a config change.
curl -sf -X POST -u admin:admin \
http://alertmanager.monitoring.svc:9093/-/reload
# Alertmanager reloaded.
curl -sf -X POST -u admin:admin \
http://grafana.monitoring.svc:3000/api/admin/provisioning/dashboards/reload
# Dashboard provisioning reloaded.
How it can fail
- Datasource UID renamed. The dashboard panel references the old UID. The panel renders “datasource not found”. The fix is to commit the datasource config with a stable UID and reference the UID from the dashboard.
- Alertmanager routing tree mismatch. The alert route
matches a label that the Prometheus rule does not emit.
The alertmanager receives the alert but no route matches.
The alert falls through to the
defaultreceiver. - PagerDuty silent failure. The PagerDuty integration is misconfigured. The alertmanager receives the alert but the notification is dropped. The on-call engineer never receives the page.
- Alert spam. The Prometheus rule is too tight. The
alert fires every minute. The
group_waitandrepeat_intervalare ignored. The on-call engineer mutes the channel. - Dashboard drift. The dashboard is provisioned from one repository. The reality is in another. The dashboards in Grafana are the ones in the repository, not the ones the operator edits in the UI.
- Runbook link broken. The
runbook_urlannotation points to a document that was renamed. The on-call engineer clicks the link and gets a 404.
How to troubleshoot it
The diagnostic order for “the dashboard is wrong or the alert is missing”:
- Is the alert firing? The Prometheus
/api/v1/alertsendpoint is the source of truth. The alertmanager is the next step. - Is the alertmanager delivering? The
/api/v2/alertsendpoint shows the active alerts and their receivers. The route that fired is the next step. - Is the receiver configured? The
amtoolconfig shows the routing tree. Thetestcommand sends a synthetic alert. - Is the dashboard rendering? The Grafana UI is the
source of truth. The
/api/datasources/uid/<uid>/healthendpoint shows the datasource health. - Is the query correct? The Prometheus query returns the result that the operator expects. The Grafana Explore view is the right place to confirm.
Security implications
The presentation layer is the operator-facing surface. The risks:
- Grafana RBAC. The Grafana user has roles. The roles map to organizations, folders, and dashboards. The default admin role is too open for production. The right pattern is a least-privilege role per team.
- Alertmanager receivers. The PagerDuty and Slack integrations hold credentials. The credentials are read from secret files with strict permissions.
- Datasource credentials. The Prometheus and Loki
datasources hold credentials. The
secureJsonDatafield is the right place. The credentials are not in the committed YAML. - Runbook URLs. The
runbook_urlannotation is visible to the on-call engineer. The URL should not expose internal-only infrastructure to the public internet. - Dashboard export. The Grafana dashboard export contains the queries. The export is a credential. The repository is the right home.
Performance implications
The presentation layer is the query layer. The four knobs:
- Refresh interval. The default is 30s. The right pattern is 30s for operational dashboards, 5m for long-tail dashboards.
- Query range. The default is 1 hour. The right pattern is the smallest range that answers the question.
- Panel count. The dashboard renders every panel on every refresh. A 50-panel dashboard at 30s refresh is 50 queries every 30 seconds. The right pattern is to split the dashboard into multiple dashboards.
- Variable resolution. A variable with a query that scans the entire TSDB is the most expensive query. The right pattern is to use a small set of bounded values.
Production guidance
- Provision everything. Datasources, dashboards, alert rules, and alertmanager routing. The repository is the source of truth; the UI is a viewer.
- Use stable UIDs. The datasource UID is the contract. Renames are a maintenance event.
- Test the alert path. A synthetic alert that fires
every minute is a useful canary. The
amtooltest command is the right tool. - Document the runbook. The
runbook_urlannotation is the contract. The runbook is the document. - Monitor the dashboard rendering. The
grafana_dashboard_panel_render_timemetric is the early-warning signal.
Verification
You should now be able to answer:
- What is the difference between a provisioned dashboard and an ad-hoc dashboard?
- Trace an alert from a Prometheus rule to a PagerDuty notification.
- What is the difference between symptom alerts and cause alerts in the routing tree?
- Why is the datasource UID the right contract for a panel reference?
- What is the failure mode when the routing tree does not match the alert labels?
Quiz
Knowledge check · 8 questions
Q1. Which is the right default for a provisioned Grafana datasource?
Q2. Which metric tells the operator the alertmanager is delivering alerts?
Q3. Which of these are required fields in a Prometheus alert rule?
Q4. A dashboard that references a datasource by name is the right contract for a Grafana panel.
Q5. Name the Alertmanager config block that suppresses a warning alert when a critical alert is firing on the same service.
Q6. A Prometheus rule fires but the alertmanager does not deliver. The most likely cause is:
Q7. A Grafana panel renders "datasource not found" after a maintenance window. The fix is:
Q8. What is the right pattern for a dashboard refresh interval on an operational dashboard?
Passing score: 75%. Answers are checked in this browser.