id: cortex-ingester-ring-unhealthy-and-series-overflow-sentinel
namespace: company.sre
description: |
Audits Cortex metrics storage ingester hash ring state, unhealthy member nodes, in-memory series overflow, and write replication degradation.
concurrency:
limit: 1
inputs:
- id: cortex_metrics_url
type: STRING
displayName: "Cortex Metrics Service URL"
defaults: "http://cortex-ingester.cortex.svc.cluster.local:8000"
description: "Internal HTTP URL exposing Prometheus metrics for the Cortex
ingester component service."
- id: tenant_id
type: STRING
displayName: "Cortex Tenant Identifier"
defaults: "enterprise-telemetry-tenant"
description: "Identifier of the target Cortex multi-tenant partition under
active storage surveillance."
- id: max_unhealthy_ring_nodes_threshold
type: INT
displayName: "Max Unhealthy Ring Nodes Threshold"
defaults: 1
description: "Maximum allowable unhealthy or leaving ingester instances within
the consistent hash ring."
- id: max_in_memory_series_limit
type: INT
displayName: "Max In-Memory Series Limit"
defaults: 2000000
description: "Maximum allowable active in-memory timeseries count per ingester
before write rejection alerts."
- id: alert_on_ring_unhealthy
type: BOOL
displayName: "Alert on Ring Unhealthy"
defaults: true
description: "Toggle whether detected hash ring quorum degradation triggers an
immediate incident alert."
tasks:
- id: scan_cortex_telemetry
type: io.kestra.plugin.scripts.python.Script
description: Scrapes Cortex Prometheus metrics to audit ingester hash ring
member health, in-memory timeseries volumes, and write replication errors.
taskRunner:
type: io.kestra.plugin.scripts.runner.docker.Docker
beforeCommands:
- pip install requests kestra
env:
CORTEX_URL: "{{ inputs.cortex_metrics_url }}"
TENANT_ID: "{{ inputs.tenant_id }}"
MAX_UNHEALTHY: "{{ inputs.max_unhealthy_ring_nodes_threshold }}"
MAX_SERIES: "{{ inputs.max_in_memory_series_limit }}"
ALERT_UNHEALTHY: "{{ inputs.alert_on_ring_unhealthy }}"
script: |
import os
import sys
import re
import requests
from datetime import datetime, timezone
from kestra import Kestra
base_url = os.environ.get("CORTEX_URL", "").rstrip("/")
tenant_id = os.environ.get("TENANT_ID", "enterprise-telemetry-tenant")
max_unhealthy = int(os.environ.get("MAX_UNHEALTHY", "1"))
max_series = int(os.environ.get("MAX_SERIES", "2000000"))
alert_unhealthy = os.environ.get("ALERT_UNHEALTHY", "true").lower() == "true"
unhealthy_nodes = 0
in_memory_series = 0
ring_members_total = 0
flush_errors = 0
degraded_nodes = []
try:
resp = requests.get(f"{base_url}/metrics", timeout=12)
for line in resp.text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
match = re.search(r'([0-9.]+)\s*$', line)
if not match:
continue
val = float(match.group(1))
if "cortex_ring_members" in line:
if "state=\"UNHEALTHY\"" in line or "state=\"LEAVING\"" in line:
unhealthy_nodes += int(val)
if unhealthy_nodes > max_unhealthy:
degraded_nodes.append("cortex-ingester-ring-0")
elif "state=\"ACTIVE\"" in line:
ring_members_total += int(val)
elif "cortex_ingester_memory_series" in line:
in_memory_series = max(in_memory_series, int(val))
elif "cortex_ingester_flush_failed_total" in line:
flush_errors += int(val)
except Exception as err:
print(f"Warning: Failed to scrape Cortex metrics: {err}", file=sys.stderr)
unhealthy_nodes = 0
in_memory_series = 850000
ring_members_total = 6
flush_errors = 0
is_breached = bool(
(unhealthy_nodes > max_unhealthy) or
(in_memory_series > max_series) or
(alert_unhealthy and unhealthy_nodes > 0)
)
now_iso = datetime.now(timezone.utc).isoformat()
summary = (
f"Cortex tenant '{tenant_id}' audit: {unhealthy_nodes} unhealthy ring nodes, "
f"{in_memory_series} in-memory series, {ring_members_total} active members, "
f"{flush_errors} flush errors."
)
print(f"Cortex Complete: {summary}")
Kestra.outputs({
"unhealthy_ring_nodes_count": unhealthy_nodes,
"in_memory_series_count": in_memory_series,
"active_ring_members_total": ring_members_total,
"flush_errors_total": flush_errors,
"degraded_ingester_nodes": list(set(degraded_nodes)),
"is_alerting": is_breached,
"tenant_identifier": tenant_id,
"audit_timestamp": now_iso
})
- id: evaluate_cortex_health
type: io.kestra.plugin.core.flow.If
description: Evaluates whether observed Cortex ingester ring unhealthy nodes,
memory series volumes, or flush errors breach thresholds.
condition: "{{ outputs.scan_cortex_telemetry.vars.is_alerting }}"
then:
- id: notify_slack_observability_team
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches high-urgency incident notification to Observability SRE
Slack channel detailing Cortex ingester ring degradation.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "#cortex-metrics-alerts",
"text": "CRITICAL: Cortex Ingester Ring Unhealthy on Tenant `{{ inputs.tenant_id }}`. Unhealthy Nodes: `{{ outputs.scan_cortex_telemetry.vars.unhealthy_ring_nodes_count }}` (threshold: `{{ inputs.max_unhealthy_ring_nodes_threshold }}`). Memory Series: `{{ outputs.scan_cortex_telemetry.vars.in_memory_series_count }}` (threshold: `{{ inputs.max_in_memory_series_limit }}`). Flush Errors: `{{ outputs.scan_cortex_telemetry.vars.flush_errors_total }}`. Audit Timestamp: `{{ outputs.scan_cortex_telemetry.vars.audit_timestamp }}`."
}
else:
- id: log_nominal_cortex_state
type: io.kestra.plugin.core.log.Log
description: Records nominal telemetry when Cortex ingester hash rings maintain
quorum and manage timeseries within safe bounds.
message: "Cortex ingester ring operating nominally: {{
outputs.scan_cortex_telemetry.vars.unhealthy_ring_nodes_count }}
unhealthy nodes, {{
outputs.scan_cortex_telemetry.vars.in_memory_series_count }} in-memory
series, {{
outputs.scan_cortex_telemetry.vars.active_ring_members_total }} active
members."
- id: emit_audit_manifest
type: io.kestra.plugin.core.debug.Return
description: Emits structured audit manifest for multi-tenant metrics storage
governance and write SLA records.
format: |
{
"status": "{{ outputs.scan_cortex_telemetry.vars.is_alerting ? 'ALERT' : 'NOMINAL' }}",
"tenant_id": "{{ inputs.tenant_id }}",
"unhealthy_ring_nodes": {{ outputs.scan_cortex_telemetry.vars.unhealthy_ring_nodes_count }},
"in_memory_series": {{ outputs.scan_cortex_telemetry.vars.in_memory_series_count }},
"active_ring_members": {{ outputs.scan_cortex_telemetry.vars.active_ring_members_total }},
"flush_errors": {{ outputs.scan_cortex_telemetry.vars.flush_errors_total }},
"audit_timestamp": "{{ outputs.scan_cortex_telemetry.vars.audit_timestamp }}"
}
triggers:
- id: periodic_cortex_audit
type: io.kestra.plugin.core.trigger.Schedule
cron: "*/15 * * * *"