Skip to content

fix(truvami-monitoring)!: make Alertmanager optional and default it off - #70

Open
michaelbeutler wants to merge 4 commits into
mainfrom
fix/monitoring-alerting-delivery
Open

fix(truvami-monitoring)!: make Alertmanager optional and default it off#70
michaelbeutler wants to merge 4 commits into
mainfrom
fix/monitoring-alerting-delivery

Conversation

@michaelbeutler

@michaelbeutler michaelbeutler commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Why

Alerting is handled by Grafana-managed alert rules, so this chart should not be standing up an Alertmanager at all. It was doing so anyway.

Both truvami-dev and truvami-prod set alertmanager.enabled: false, yet all four Alertmanager pods were running in prod. The Alertmanager CR lived inside templates/prometheus.yaml with no if guard — the flag only ever gated the AlertmanagerConfig and the receiver Secrets, never the Alertmanager itself.

That left the alerting path broken in a way that looked healthy from the outside. Four independent breaks, all in this chart:

# Break Effect
1 spec.alerting addresses Alertmanager by Service name; no template ever created that Service Prometheus reports activeAlertmanagers: [] with ~200 dropped
2 Alertmanager CR referenced configSecret: <fullname>-alertmanager-config, which no template creates operator falls back to its default config → root route sends everything to the null receiver
3 alertmanagerConfigSelector unset the AlertmanagerConfig this chart does render was never selected
4 Watchdog route matched alertname="Watchdog" exactly, but rule-watchdog.yaml emits TruvamiWatchdog dead-man's-switch heartbeat could never reach watchdog.webhookUrl

Net effect in prod: alertmanager_alerts_received_total{status="firing"} = 0 on both pods for all time, while 771 alerts were firing. A six-week Thanos compactor halt and a 34-day block-upload outage both passed unnoticed.

What changed

  • Gate the Alertmanager CR on alertmanager.enabled, and omit the Prometheus spec.alerting block when it is off — so Prometheus is never left pointed at a Service that was not rendered.
  • Default alertmanager.enabled to false. Grafana owns alerting.
  • Add templates/alertmanager-service.yaml so the Prometheus reference resolves when Alertmanager is enabled. It selects on the alertmanager label that prometheus-operator stamps with the CR name, so it matches only this release — unlike the operator's namespace-wide alertmanager-operated, which would fan alerts out to every Alertmanager in the namespace.
  • Replace the dangling configSecret with alertmanagerConfiguration pointing at this chart's own AlertmanagerConfig, plus a configurable alertmanagerConfigSelector.
  • Fix the watchdog route matcher to TruvamiWatchdog.
  • Expose alertmanager.replicas / portName / retention / configSelectorLabels.

⚠️ BREAKING

Upgrading to 0.5.0 removes the Alertmanager StatefulSet and its pods wherever alertmanager.enabled is false — which is both truvami-dev and truvami-prod today.

Those Alertmanagers have never delivered a single alert, so nothing is lost, but the pods will disappear. Set alertmanager.enabled: true to keep them.

ArgoCD prune is disabled on these apps, so the existing Alertmanager CRs will not be garbage-collected automatically and must be deleted by hand after rollout:

kubectl -n truvami-prod delete alertmanager truvami-monitoring

Verification

  • helm lint clean; all pre-commit hooks pass (Helm Docs, Chart Version Check, Values Schema Check).
  • Server-side dry-run of the rendered Alertmanager + Service against the live CRDs on sbcc-cluster-7401 — accepted, so alertmanagerConfiguration / alertmanagerConfigSelector are valid for the installed operator.
  • Rendering prod values against 0.5.0 differs from 0.4.0 by exactly: the removed Alertmanager CR, the removed alerting: block, and chart labels. 61 → 60 objects.
  • Rendering with --set alertmanager.enabled=true produces a complete, self-consistent alerting stack (CR + Service + Config, no configSecret).

Rollout note

This does not deploy on merge — prod is pinned to targetRevision: 0.4.0 in sbckube. Bumping that pin is a separate, deliberate step.

Context

Found while diagnosing the 2026-07 monitoring outage. The root cause of that outage is separate — a label-cardinality bug in truvami-bridge (topic="truvami-uplinks[0]@<offset>", 749,844 distinct values, 83% of head series). This PR fixes why nobody was told about it.

Summary by CodeRabbit

  • New Features
    • Made Alertmanager components optional and driven by a single enabled value.
    • Added configurable Alertmanager replicas, service/port name, retention, and configuration label selection.
    • Added an Alertmanager Service for stable internal access when enabled, and updated Prometheus to reference Alertmanager only when applicable.
  • Bug Fixes
    • Corrected watchdog routing to match the exact emitted Alertmanager alertname.
  • Documentation
    • Updated the chart version and README values/defaults for the new Alertmanager settings.

Alerting is handled by Grafana-managed alert rules, so this chart should not
be standing up an Alertmanager at all. It was doing so anyway.

Both truvami-dev and truvami-prod set `alertmanager.enabled: false`, yet all
four Alertmanager pods were running in prod. The Alertmanager CR lived in
templates/prometheus.yaml with no `if` guard, so the flag only ever gated the
AlertmanagerConfig and the receiver Secrets - never the Alertmanager itself.

That left the alerting path broken in a way that looked fine from the outside:

  - Prometheus reported activeAlertmanagers: [] with ~200 dropped. Its
    spec.alerting entry addresses Alertmanager by *Service* name, and no
    template in this chart has ever created that Service.
  - The Alertmanager CR referenced configSecret
    "<fullname>-alertmanager-config", a Secret this chart also never creates.
    prometheus-operator therefore fell back to its built-in default config,
    whose root route sends everything to the "null" receiver.
  - alertmanagerConfigSelector was unset, so the AlertmanagerConfig this chart
    does render was never selected.
  - The watchdog route matched alertname="Watchdog" exactly, but
    rule-watchdog.yaml emits "TruvamiWatchdog", so the dead-man's-switch
    heartbeat could never reach watchdog.webhookUrl.

Net effect in prod: alertmanager_alerts_received_total{status="firing"} = 0 on
both pods for all time, while 771 alerts were firing. A six-week Thanos
compactor halt and a 34-day block-upload outage both passed unnoticed.

Changes:
  - Gate the Alertmanager CR on alertmanager.enabled, and omit the Prometheus
    spec.alerting block when it is off, so Prometheus is never left pointed at
    a Service that was not rendered.
  - Default alertmanager.enabled to false. Grafana owns alerting.
  - Add templates/alertmanager-service.yaml so the Prometheus reference
    resolves when Alertmanager IS enabled. It selects on the `alertmanager`
    label that prometheus-operator stamps with the CR name, so it matches only
    this release - unlike the operator's namespace-wide alertmanager-operated.
  - Replace the dangling configSecret with alertmanagerConfiguration pointing
    at this chart's own AlertmanagerConfig, plus a configurable
    alertmanagerConfigSelector.
  - Fix the watchdog route matcher to TruvamiWatchdog.
  - Expose alertmanager.replicas/portName/retention/configSelectorLabels.

BREAKING CHANGE: upgrading to 0.5.0 removes the Alertmanager StatefulSet and
its pods wherever alertmanager.enabled is false - which is both truvami-dev and
truvami-prod today. Those Alertmanagers have never delivered an alert, so
nothing is lost, but the pods will disappear. Set alertmanager.enabled: true to
keep them. Note ArgoCD prune is disabled on these apps, so the existing
Alertmanager CRs must be deleted by hand after rollout.

Verified: helm lint clean; server-side dry-run of the rendered Alertmanager and
Service against the live CRDs accepted; rendering prod values against 0.5.0
differs from 0.4.0 only by the removed Alertmanager CR, the removed alerting
block, and chart labels (61 -> 60 objects).
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The chart release is updated to 0.5.0. Alertmanager is disabled by default, gains configurable runtime settings, renders conditionally with a Service, and is wired conditionally into Prometheus. The watchdog route now matches TruvamiWatchdog.

Changes

Alertmanager integration

Layer / File(s) Summary
Alertmanager configuration defaults
charts/truvami-monitoring/Chart.yaml, charts/truvami-monitoring/README.md, charts/truvami-monitoring/values.yaml
Chart metadata and documentation are updated to version 0.5.0, with Alertmanager disabled by default and new replica, port, retention, and selector-label settings.
Conditional Alertmanager resources
charts/truvami-monitoring/templates/alertmanager-service.yaml, charts/truvami-monitoring/templates/prometheus.yaml
The Alertmanager Service and custom resource render only when enabled and use configurable ports, retention, replicas, configuration references, and label selectors.
Prometheus and alert routing wiring
charts/truvami-monitoring/templates/prometheus.yaml, charts/truvami-monitoring/templates/alertmanager-config.yaml
Prometheus conditionally targets Alertmanager through the configured port, and the watchdog route matches TruvamiWatchdog.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Helm
  participant Prometheus
  participant AlertmanagerService
  participant Alertmanager
  Helm->>Prometheus: Render alerting targets when enabled
  Helm->>AlertmanagerService: Render Service when enabled
  Prometheus->>AlertmanagerService: Send alerts through configured port
  AlertmanagerService->>Alertmanager: Forward traffic to Alertmanager pods
  Alertmanager->>Alertmanager: Match TruvamiWatchdog route
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Alertmanager is now optional and defaults off in the truvami-monitoring chart.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

📊 Chart Diagram

Visual representation of Kubernetes resources in changed charts:

truvami-monitoring (Before/After)
Before After
Before After

💡 Diagrams generated with KubeDiagrams

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@charts/truvami-monitoring/templates/prometheus.yaml`:
- Around line 142-144: Update the alertmanagerConfigSelector rendering to
preserve an explicitly configured empty alertmanager.configSelectorLabels map
instead of applying the default label selector. Distinguish an omitted value
from an explicit {} using the chart’s values/template logic, while retaining the
existing default selector when the key is absent and allowing the empty selector
to select every AlertmanagerConfig.

In `@charts/truvami-monitoring/values.yaml`:
- Around line 384-386: Update the Alertmanager upgrade comment near the
`enabled` configuration to state that, when ArgoCD pruning is disabled, setting
`enabled: false` stops rendering but does not remove the existing Alertmanager
CR or its owned pods, so operators must clean them up manually. Remove the
inaccurate claim that upgrading to chart 0.5.0 automatically removes them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 72ff751e-ee8c-4d32-8053-03b659aeca49

📥 Commits

Reviewing files that changed from the base of the PR and between 4df51d1 and 57a8e8d.

⛔ Files ignored due to path filters (2)
  • diagrams/truvami-monitoring.png is excluded by !**/*.png
  • diagrams/truvami-monitoring.previous.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • charts/truvami-monitoring/Chart.yaml
  • charts/truvami-monitoring/README.md
  • charts/truvami-monitoring/templates/alertmanager-config.yaml
  • charts/truvami-monitoring/templates/alertmanager-service.yaml
  • charts/truvami-monitoring/templates/prometheus.yaml
  • charts/truvami-monitoring/values.yaml

Comment thread charts/truvami-monitoring/templates/prometheus.yaml Outdated
Comment thread charts/truvami-monitoring/values.yaml Outdated
…lector

Review follow-up. Two issues, both confirmed against the branch.

1. An explicitly configured `alertmanager.configSelectorLabels: {}` was silently
   replaced by the default label, contradicting the documented behaviour that
   `{}` selects every AlertmanagerConfig in the namespace.

   `default` could not fix this - it treats {} as empty - and neither could
   `hasKey`, because values.yaml declared the key, so it was always present.
   Worse, because Helm merges map values, a non-empty default in values.yaml
   could be added to but never reduced back to {}: a user setting
   `{team: platform}` got the default label merged in alongside it.

   The declared default is now null, with the real default expressed in the
   template. Only null means "not configured", and Helm replaces a null base
   with a user map wholesale. Verified via values files (not --set, which
   merges and masks the bug):

     omitted        -> matchLabels: {truvami.com/alertmanager-config: "true"}
     {}             -> matchLabels: {}
     {team: ...}    -> matchLabels: {team: platform}   (no default merged in)

2. The `alertmanager.enabled` comment claimed upgrading to 0.5.0 removes the
   existing Alertmanager pods. It does not. The chart only stops rendering
   them, and both dev-truvami-monitoring and prod-truvami-monitoring run with
   `syncPolicy.automated.prune: false` (verified live), so ArgoCD leaves the
   Alertmanager CR and its pods in place. The comment now says so and gives the
   manual cleanup command.

Validated: helm lint clean; server-side dry-run of the empty-selector render
accepted by the live CRD; prod values render byte-identical to the previous
commit (these fixes are inert where alertmanager.enabled is false).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@charts/truvami-monitoring/README.md`:
- Around line 20-21: Update the alertmanager.configSelectorLabels entry in the
chart README table to document its type as a map rather than a string, while
preserving the existing default value nil.

In `@charts/truvami-monitoring/templates/prometheus.yaml`:
- Around line 152-157: Update the alertmanagerConfigSelector logic around
$configSelector to accept only map values, including an explicitly empty {} map,
and call Helm fail for strings, lists, or any other non-map value before
rendering matchLabels. Preserve the existing default selector behavior when the
value is unset or invalid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d55346b3-aaee-4c5d-b69c-493160dac215

📥 Commits

Reviewing files that changed from the base of the PR and between 57a8e8d and ba4ca4d.

📒 Files selected for processing (3)
  • charts/truvami-monitoring/README.md
  • charts/truvami-monitoring/templates/prometheus.yaml
  • charts/truvami-monitoring/values.yaml

Comment on lines +20 to +21
| alertmanager.configSelectorLabels | string | `nil` | |
| alertmanager.enabled | bool | `false` | |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document configSelectorLabels as a map, not a string.

The value accepts nil, {}, or a label map. Documenting it as string conflicts with values.yaml and can lead users to render an invalid matchLabels value.

Proposed documentation fix
-| alertmanager.configSelectorLabels | string | `nil` |  |
+| alertmanager.configSelectorLabels | object | `nil` |  |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| alertmanager.configSelectorLabels | string | `nil` | |
| alertmanager.enabled | bool | `false` | |
| alertmanager.configSelectorLabels | object | `nil` | |
| alertmanager.enabled | bool | `false` | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/truvami-monitoring/README.md` around lines 20 - 21, Update the
alertmanager.configSelectorLabels entry in the chart README table to document
its type as a map rather than a string, while preserving the existing default
value nil.

Comment on lines +152 to +157
{{- $configSelector := .Values.alertmanager.configSelectorLabels }}
{{- if kindIs "invalid" $configSelector }}
{{- $configSelector = dict "truvami.com/alertmanager-config" "true" }}
{{- end }}
alertmanagerConfigSelector:
matchLabels: {{- toYaml $configSelector | nindent 6 }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

values="$(mktemp)"
trap 'rm -f "$values"' EXIT

cat >"$values" <<'EOF'
alertmanager:
  enabled: true
  configSelectorLabels: invalid
EOF

if helm template review charts/truvami-monitoring -f "$values" >/dev/null 2>&1; then
  echo "Non-map configSelectorLabels was accepted" >&2
  exit 1
fi

Repository: truvami/helm

Length of output: 150


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Locate the relevant chart files and show the surrounding template code.
git ls-files charts/truvami-monitoring | sed -n '1,200p'
printf '\n--- template slice ---\n'
sed -n '130,190p' charts/truvami-monitoring/templates/prometheus.yaml

printf '\n--- values references ---\n'
rg -n "configSelectorLabels|alertmanager\.configSelectorLabels|alertmanager:" charts/truvami-monitoring -n

Repository: truvami/helm

Length of output: 6267


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Probe rendering behavior for several selector shapes to see what actually fails.
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

cases=(
  "null|alertmanager:\n  enabled: true\n  configSelectorLabels: null\n"
  "empty-map|alertmanager:\n  enabled: true\n  configSelectorLabels: {}\n"
  "string|alertmanager:\n  enabled: true\n  configSelectorLabels: invalid\n"
  "list|alertmanager:\n  enabled: true\n  configSelectorLabels:\n    - a\n    - b\n"
)

for item in "${cases[@]}"; do
  name="${item%%|*}"
  body="${item#*|}"
  file="$tmpdir/$name.yaml"
  printf '%b' "$body" >"$file"
  printf '\n== %s ==\n' "$name"
  if out="$(helm template review charts/truvami-monitoring -f "$file" 2>&1)"; then
    echo "OK"
    printf '%s\n' "$out" | sed -n '1,40p'
  else
    echo "FAIL"
    printf '%s\n' "$out" | sed -n '1,80p'
  fi
done

Repository: truvami/helm

Length of output: 381


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect docs/defaults to see whether the field is meant to be a map or string.
rg -n "configSelectorLabels|matchLabels|AlertmanagerConfig" README.md charts/truvami-monitoring -g '!**/Chart.lock'

Repository: truvami/helm

Length of output: 2954


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Show any chart schema or template-side validation relevant to this field.
for f in charts/truvami-monitoring/values.schema.json charts/truvami-monitoring/values.yaml charts/truvami-monitoring/templates/prometheus.yaml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    sed -n '1,240p' "$f" | sed -n '/configSelectorLabels/,+40p'
  fi
done

Repository: truvami/helm

Length of output: 839


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "--- values.schema.json ---"
cat -n charts/truvami-monitoring/values.schema.json

echo
echo "--- values.yaml around configSelectorLabels ---"
sed -n '404,428p' charts/truvami-monitoring/values.yaml

echo
echo "--- README table entry ---"
sed -n '14,26p' charts/truvami-monitoring/README.md

Repository: truvami/helm

Length of output: 2105


Reject non-map alertmanager.configSelectorLabels values.

matchLabels must be a map; strings or lists will render an invalid Alertmanager CR. Keep {} working, but fail on anything else.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/truvami-monitoring/templates/prometheus.yaml` around lines 152 - 157,
Update the alertmanagerConfigSelector logic around $configSelector to accept
only map values, including an explicitly empty {} map, and call Helm fail for
strings, lists, or any other non-map value before rendering matchLabels.
Preserve the existing default selector behavior when the value is unset or
invalid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant