Skip to content

Commit c8400fe

Browse files
scotwellsclaude
andcommitted
docs(telemetry): add runbooks for the certificate gating alerts
Each cert-health alert now links to a runbook (runbook_url) with what it means, the customer impact, how to diagnose it, and how to remediate, so an on-call responder has a clear path from alert to action. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsQQoiXVgC1VaiWye4eEz8
1 parent f2d49dd commit c8400fe

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

config/telemetry/alerts/gateways.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ spec:
7373
annotations:
7474
summary: "Gateway listener {{ $labels.namespace }}/{{ $labels.name }}/{{ $labels.listener }} has an unusable TLS certificate"
7575
description: "NSO has withheld listener {{ $labels.listener }} (hostname {{ $labels.hostname }}) on Gateway {{ $labels.name }} in namespace {{ $labels.namespace }} because its TLS certificate is unusable (reason: {{ $labels.reason }}). The customer cannot serve HTTPS on this hostname. Check the cert-manager Certificate and Secret in the downstream cluster."
76+
runbook_url: "https://github.com/datum-cloud/network-services-operator/blob/main/docs/runbooks/gateway-tls-certificates.md#gatewaylistenercertunusable"
7677

7778
# Fires when a managed TLS certificate is within 7 days of expiry while it
7879
# is still healthy. cert-manager renews automatically, but renewal fails if
@@ -87,6 +88,7 @@ spec:
8788
annotations:
8889
summary: "TLS certificate for Gateway listener {{ $labels.namespace }}/{{ $labels.name }}/{{ $labels.listener }} expires in less than 7 days"
8990
description: "The cert-manager Certificate for listener {{ $labels.listener }} (hostname {{ $labels.hostname }}, secret {{ $labels.secret }}) on Gateway {{ $labels.name }} in namespace {{ $labels.namespace }} expires within 7 days. cert-manager should renew it automatically, but renewal fails if the domain's DNS no longer points to Datum. Verify the Certificate is Ready=True in the downstream cluster."
91+
runbook_url: "https://github.com/datum-cloud/network-services-operator/blob/main/docs/runbooks/gateway-tls-certificates.md#gatewaylistenercertexpiringsoon"
9092

9193
# Fires when the extension server is actively dropping broken certificates
9294
# from the configuration it sends to the edge. This is expected briefly
@@ -102,6 +104,7 @@ spec:
102104
annotations:
103105
summary: "Extension server is dropping {{ $value }} broken certificate(s) to protect the edge listener"
104106
description: "The extension server is dropping {{ $value }} broken certificate(s) from the configuration it sends to the edge gateway. Check extension server logs for 'pruned invalid TLS chains' to find the affected hostnames. If GatewayListenerCertUnusable is also firing, both layers of protection are working as expected. If only this alert fires, the controller may have missed the listener."
107+
runbook_url: "https://github.com/datum-cloud/network-services-operator/blob/main/docs/runbooks/gateway-tls-certificates.md#tlsbackstoppruningchains"
105108

106109
# Fires when the extension server could not protect a listener because every
107110
# certificate on it is broken. It never removes a listener entirely, so the
@@ -117,3 +120,4 @@ spec:
117120
annotations:
118121
summary: "{{ $value }} edge listener(s) have every TLS certificate broken and cannot be protected"
119122
description: "The extension server left {{ $value }} edge listener(s) untouched because every certificate on them is broken. It never removes a listener entirely, so the edge will reject the configuration update for those listeners (EnvoyListenerUpdateRejected confirms it). This means the controller did not withhold the listener before it reached the edge. Check extension server logs for 'listeners_left_intact' and why the controller did not withhold the listener."
123+
runbook_url: "https://github.com/datum-cloud/network-services-operator/blob/main/docs/runbooks/gateway-tls-certificates.md#tlsbackstoplistenerallcertsbroken"
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Runbook: Gateway TLS certificate alerts
2+
3+
These alerts cover the health of the TLS certificates that gateway listeners use
4+
to serve HTTPS. Every HTTPS hostname on a gateway shares a single edge listener,
5+
so an unusable certificate is handled in two layers:
6+
7+
1. The **controller** leaves a listener with an unusable certificate out of the
8+
downstream gateway, so one bad certificate only affects its own hostname and
9+
every other hostname keeps serving. The affected listener reports the problem
10+
to the customer through its status conditions.
11+
2. The **extension server** is a backstop: if a bad certificate reaches the edge
12+
anyway, it drops only the affected part of the listener rather than letting
13+
the whole listener fail.
14+
15+
A certificate is "unusable" when it has expired, is not valid yet, is missing,
16+
its certificate and key do not match, or it has not been issued yet.
17+
18+
Related: issue [#212](https://github.com/datum-cloud/network-services-operator/issues/212).
19+
The infra-side `EnvoyListenerUpdateRejected` alert fires when the edge actually
20+
rejects a listener update — the alerts here are designed to fire *before* that
21+
happens, or to explain it when it does.
22+
23+
## Shared diagnosis
24+
25+
Each alert carries labels identifying the affected object: `namespace`, `name`
26+
(the gateway), `listener`, and usually `hostname`.
27+
28+
Find the gateway and the failing listener's status:
29+
30+
```sh
31+
kubectl -n <namespace> get gateway <name> -o yaml | yq '.status.listeners'
32+
```
33+
34+
A gated listener reports `Programmed: False` (reason `Invalid`) and
35+
`ResolvedRefs: False` (reason `InvalidCertificateRef`) with a plain-language
36+
message naming the hostname.
37+
38+
Inspect the backing certificate on the downstream (edge) cluster. The Certificate
39+
and its Secret are named `<gateway>-<listener>`:
40+
41+
```sh
42+
kubectl --context <downstream> -n <downstream-ns> get certificate <gateway>-<listener> -o yaml
43+
kubectl --context <downstream> -n <downstream-ns> get secret <gateway>-<listener> -o yaml
44+
```
45+
46+
The most common root cause is a customer pointing their domain away from Datum:
47+
ACME renewal then fails, the certificate goes `Ready: False`, and it eventually
48+
expires. That is a customer action, not a platform fault — the listener is
49+
correctly withheld and recovers on its own once the certificate can be issued.
50+
51+
## GatewayListenerCertUnusable
52+
53+
**Meaning.** The controller is withholding a listener because its certificate is
54+
unusable. The customer's HTTPS hostname is unavailable until the certificate
55+
recovers.
56+
57+
**Impact.** Limited to the one hostname. Other hostnames on the gateway are
58+
unaffected — this is the isolation working as intended.
59+
60+
**Diagnose.** Read the `reason` label and the listener status message (see Shared
61+
diagnosis). Check the downstream Certificate's `Ready` condition and its
62+
`status.notAfter`.
63+
64+
**Remediate.** Usually no platform action is needed — confirm whether the
65+
customer's domain still points to Datum. If it does and issuance is genuinely
66+
stuck, investigate cert-manager (the issuer, ACME order, and challenge for that
67+
hostname). The listener returns automatically once the certificate is issued.
68+
69+
## GatewayListenerCertExpiringSoon
70+
71+
**Meaning.** A currently-healthy certificate expires within seven days. This is a
72+
warning to act before it starts gating the listener.
73+
74+
**Impact.** None yet. It becomes `GatewayListenerCertUnusable` if the certificate
75+
expires before it is renewed.
76+
77+
**Diagnose.** Check the downstream Certificate's `status.renewalTime` and whether
78+
recent renewal attempts are failing (cert-manager events / logs for that
79+
Certificate). Confirm the hostname's DNS still resolves to Datum, since ACME
80+
renewal depends on it.
81+
82+
**Remediate.** If renewal is failing because DNS moved away, this will become a
83+
customer-driven gating event — no platform fix. If renewal is failing for a
84+
platform reason, fix the issuer / ACME path so cert-manager can renew.
85+
86+
## TLSBackstopPruningChains
87+
88+
**Meaning.** The extension server is actively dropping broken certificates from
89+
the configuration it sends to the edge. This is expected for a short window
90+
between a certificate failing and the controller withholding the listener.
91+
92+
**Impact.** None on its own — the backstop is protecting the listener. The
93+
affected hostname is the one whose certificate is broken.
94+
95+
**Diagnose.** Check extension server logs for `pruned invalid TLS chains` to find
96+
the affected hostnames:
97+
98+
```sh
99+
kubectl -n <ext-server-ns> logs -l <ext-server-selector> | grep 'pruned invalid TLS chains'
100+
```
101+
102+
If `GatewayListenerCertUnusable` is also firing for the same hostname, both
103+
layers are working as expected and no action is needed. If **only** this alert
104+
fires, the controller did not withhold the listener — see the next alert and
105+
check why (start with the listener's status conditions and the controller logs).
106+
107+
**Remediate.** Generally none. If it persists without a matching
108+
`GatewayListenerCertUnusable`, treat it as a controller gap and investigate the
109+
gateway reconcile for that listener.
110+
111+
## TLSBackstopListenerAllCertsBroken
112+
113+
**Meaning (critical).** Every certificate on an edge listener is broken. The
114+
backstop never removes a listener entirely, so the edge will reject the
115+
configuration update for that listener and its config will freeze on its last
116+
good state.
117+
118+
**Impact.** The listener stops accepting configuration changes. Because the edge
119+
listener is shared, this can affect every hostname on it — this is the
120+
fleet-impacting failure the two-layer design exists to prevent, so reaching it
121+
means the controller-side protection did not catch the listener.
122+
123+
**Diagnose.**
124+
125+
```sh
126+
kubectl -n <ext-server-ns> logs -l <ext-server-selector> | grep 'listeners_left_intact'
127+
```
128+
129+
Cross-check the infra `EnvoyListenerUpdateRejected` alert, which confirms the
130+
edge is rejecting the update. Identify every certificate on the affected listener
131+
and why each is broken (expired, not yet valid, or mismatched), then determine
132+
why the controller did not withhold the listener before it reached the edge.
133+
134+
**Remediate.** Restore or remove the broken certificates so the listener has at
135+
least one usable certificate, which lets the edge accept the update again. Then
136+
follow up on the controller gap that allowed an all-broken listener to be
137+
programmed.

0 commit comments

Comments
 (0)