Skip to content

Monitoring & Observability Guide โ€‹

SpektraBot pilot cluster monitoring stack: what's deployed, how to access it, and what to do when alerts fire.

Cluster: Civo Kubernetes (2 nodes, k3s) Namespace: monitoringHelm charts: helm/monitoring/, helm/loki/, helm/alloy/


Architecture Overview โ€‹

                    +------------------+
                    |     Grafana      |  Dashboards, Explore (logs)
                    |   (port 3000)    |
                    +--------+---------+
                             |
              +--------------+--------------+
              |                             |
     +--------v---------+        +---------v--------+
     |    Prometheus     |        |      Loki        |
     |  (metrics, 30d)  |        |  (logs, 30d)     |
     |   15Gi PVC       |        |   20Gi PVC       |
     +--------+---------+        +---------+--------+
              |                             |
    +---------+---------+          +--------+--------+
    |                   |          |                  |
+---v---+  +----------+ |   +------v------+  +-------v------+
| node- |  | kube-    | |   | Alloy       |  | Alloy        |
|exporter|  | state-   | |   | (node 1)    |  | (node 2)     |
| (x2)  |  | metrics  | |   | DaemonSet   |  | DaemonSet    |
+-------+  +----------+ |   +-------------+  +--------------+
                         |
              +----------v---------+
              |   AlertManager     |
              |  PagerDuty routing |
              |   2Gi PVC         |
              +--------------------+

Components โ€‹

ComponentPodsPurposeStorage
Prometheus1 StatefulSetMetrics collection & alerting15Gi PVC (30d retention)
Grafana1 DeploymentDashboards & log exploration1Gi PVC
AlertManager1 StatefulSetAlert routing to PagerDuty2Gi PVC
Loki1 StatefulSetLog aggregation (monolithic)20Gi PVC (30d retention)
Alloy2 DaemonSet podsLog collection from all nodesNone (streams to Loki)
kube-state-metrics1 DeploymentKubernetes object metricsNone
node-exporter2 DaemonSet podsNode hardware metricsNone
Prometheus Operator1 DeploymentManages Prometheus CRDsNone

Total: 13 pods, 38Gi storage, ~1.5Gi memory requests


Access โ€‹

All services use ClusterIP. Access via kubectl port-forward:

Grafana (dashboards and logs) โ€‹

bash
kubectl port-forward svc/spektrabot-monitoring-grafana 3000:80 -n monitoring

Open http://localhost:3000

Credentials: admin / admin (change on first login)

Prometheus (metrics and PromQL) โ€‹

bash
kubectl port-forward svc/spektrabot-monitoring-kube-prometheus 9090:9090 -n monitoring

Open http://localhost:9090

AlertManager (alert routing and silences) โ€‹

bash
kubectl port-forward svc/spektrabot-monitoring-kube-alertmanager 9093:9093 -n monitoring

Open http://localhost:9093

Loki (log queries via Grafana) โ€‹

Loki is accessed through Grafana's Explore view. No separate port-forward needed.

  1. Open Grafana
  2. Click Explore (compass icon) in the left sidebar
  3. Select Loki from the datasource dropdown
  4. Use LogQL to query logs (see Log Queries below)

Dashboards โ€‹

Built-in (from kube-prometheus-stack) โ€‹

These are auto-provisioned and cover general Kubernetes monitoring:

  • Kubernetes / Compute Resources / Cluster -- cluster-wide CPU, memory, network
  • Kubernetes / Compute Resources / Namespace (Pods) -- per-namespace breakdown
  • Kubernetes / Compute Resources / Pod -- individual pod metrics
  • Kubernetes / Persistent Volumes -- PVC usage
  • Node Exporter / Nodes -- per-node hardware metrics
  • Prometheus / Overview -- Prometheus self-monitoring

Custom dashboards โ€‹

DashboardConfigMapKey panels
Application Performancedashboard-app-performanceAPI latency (p50/p95/p99), request rate, error rate, WebSocket connections, LLM latency, LLM tokens, pgvector search latency
Database Healthdashboard-db-healthPostgres connections vs max, cache hit ratio, WAL archive status, replication lag, slow queries, connection usage, DB size

Custom dashboards are provisioned via ConfigMaps with grafana_dashboard: "1" label, auto-loaded by Grafana's sidecar.


Alert Rules โ€‹

Custom alerts (9 rules in 2 PrometheusRule CRDs) โ€‹

Infrastructure alerts (core-alerts) โ€‹

AlertSeverityThresholdForMeaning
APIDowncriticalup{job="spektrabot-api"} == 01mAPI is unreachable
PostgresDowncriticalcnpg_pg_postmaster_start_time_seconds == 01mPostgres pod is down
PodCrashLoopBackOffcriticalkube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} > 02mA pod is crash-looping
NodeCPUHighwarning>85% avg CPU per node10mNode CPU saturated
NodeMemoryHighwarning>85% memory used per node5mNode memory saturated
PVCUsageHighwarning>85% PVC used5mStorage approaching capacity
PVCFullcritical>95% PVC used1mStorage nearly full

Application alerts (app-alerts) โ€‹

AlertSeverityThresholdForMeaning
APIHighErrorRatecritical>1% HTTP 5xx rate2mAPI returning too many errors
WebSocketMassDisconnectcritical>50% disconnect rate AND >5 active connections2mMass WebSocket disconnection event

PagerDuty integration โ€‹

Critical alerts route to PagerDuty via Events API v2. The routing key is injected at deploy time (never stored in the repo):

bash
helm upgrade spektrabot-monitoring helm/monitoring \
  -f helm/monitoring/values-civo.yaml \
  --set 'pagerdutyRoutingKey=YOUR_KEY' \
  -n monitoring

Without the key, AlertManager runs with a null receiver (alerts visible in UI but no external routing).


Runbooks โ€‹

APIDown โ€‹

Impact: Users cannot access SpektraBot. All API and WebSocket functionality is offline.

Investigate:

bash
# Check pod status
kubectl get pods -n spektrabot-pilot -l app.kubernetes.io/name=spektrabot-api

# Check recent events
kubectl describe pod <api-pod> -n spektrabot-pilot

# Check logs from the previous container (if crash-restarting)
kubectl logs <api-pod> -n spektrabot-pilot --previous

# Check if deployment was recently updated
kubectl rollout history deployment/spektrabot-api -n spektrabot-pilot

Resolve:

  1. If OOMKilled: increase memory limits in deployment
  2. If CrashLoopBackOff: check logs for startup errors (missing env vars, DB connection failure)
  3. If recent deployment: rollback with kubectl rollout undo deployment/spektrabot-api -n spektrabot-pilot
  4. If node issue: check node health with kubectl get nodes

APIHighErrorRate โ€‹

Impact: Users experiencing errors. Some requests failing.

Investigate:

bash
# Check API logs for errors (via Grafana Explore with Loki)
# LogQL: {namespace="spektrabot-pilot", app="spektrabot-api"} |~ "error|Error|ERROR"

# Check if specific endpoints are failing (Prometheus)
# PromQL: rate(http_request_errors_total{job="spektrabot-api"}[5m])

# Check downstream services
kubectl get pods -n spektrabot-pilot

Resolve:

  1. If Postgres-related: check PostgresDown alert, verify DB connectivity
  2. If Azure OpenAI API errors: check LLM call metrics, rate limits
  3. If recent deployment: rollback
  4. If memory pressure: check pod resource usage

PostgresDown โ€‹

Impact: All database operations fail. API will return 500 errors for most endpoints.

Investigate:

bash
# Check CNPG cluster status
kubectl get clusters -n spektrabot-pilot

# Check Postgres pod
kubectl get pods -n spektrabot-pilot -l cnpg.io/cluster=spektrabot-db

# Check operator logs
kubectl logs -n spektrabot-pilot -l app.kubernetes.io/name=cloudnative-pg --tail=50

# Check PVC usage (Postgres data volume)
kubectl exec -n spektrabot-pilot <pg-pod> -- df -h /var/lib/postgresql/data

Resolve:

  1. If OOMKilled: increase Postgres memory limits (currently 2Gi limit)
  2. If PVC full: expand volume or clean WAL logs
  3. If node failure: CNPG operator should auto-failover; check operator logs
  4. If data corruption: restore from S3 backup (see backup runbook)

PodCrashLoopBackOff โ€‹

Impact: Affected service is unstable. Pod repeatedly crashing and restarting.

Investigate:

bash
# Identify which pod
kubectl get pods --all-namespaces --field-selector=status.phase!=Running

# Check pod events
kubectl describe pod <pod-name> -n <namespace>

# Check logs from the crashing container
kubectl logs <pod-name> -n <namespace> --previous

Resolve:

  1. Check for missing environment variables or ConfigMap changes
  2. Check for resource constraints (OOMKilled shows in events)
  3. Check for dependency failures (DB not ready, service unavailable)
  4. If recent deployment: rollback

NodeCPUHigh โ€‹

Impact: Pod scheduling may be affected. Running pods may be throttled.

Investigate:

bash
# Check which pods are consuming CPU
kubectl top pods --all-namespaces --sort-by=cpu | head -20

# Check node utilisation
kubectl top nodes

# Check for pending pods
kubectl get pods --all-namespaces --field-selector=status.phase=Pending

Resolve:

  1. Identify CPU-heavy pods and check for runaway processes
  2. Reduce non-essential workloads (pause crawlers if running)
  3. If sustained: upgrade node pool to larger instance size (see docs/runbooks/node-pool-upgrade.md)

NodeMemoryHigh โ€‹

Impact: OOMKill risk. Kubernetes may evict pods to free memory.

Investigate:

bash
# Check which pods are consuming memory
kubectl top pods --all-namespaces --sort-by=memory | head -20

# Check node memory
kubectl top nodes

# Check for OOMKill events
kubectl get events --all-namespaces --field-selector reason=OOMKilling

Resolve:

  1. Identify memory-heavy pods; check for leaks (growing memory over time)
  2. Restart leaking pods as a short-term fix
  3. Adjust memory limits for overconsuming pods
  4. If sustained: upgrade node pool (see docs/runbooks/node-pool-upgrade.md)

PVCUsageHigh โ€‹

Impact: Storage approaching capacity. If unaddressed, will lead to PVCFull.

Investigate:

bash
# Check all PVC usage
kubectl get pvc -n monitoring
kubectl get pvc -n spektrabot-pilot

# Check specific PVC usage inside pod
kubectl exec -n monitoring prometheus-spektrabot-monitoring-kube-prometheus-0 -- df -h
kubectl exec -n monitoring loki-0 -- df -h

PVC inventory:

PVCSizeServiceRetention
Prometheus data15GiMetrics30d / 14GB
Loki data20GiLogs30d
Grafana1GiDashboard stateN/A
AlertManager2GiSilence/nflog stateN/A

Resolve:

  1. Prometheus PVC: Reduce retention (retention: 15d) or reduce cardinality
  2. Loki PVC: Reduce log retention or add exclusion filters in Alloy config
  3. Either: Expand PVC size via Civo dashboard (civo-volume supports expansion)
  4. Check for stuck compaction jobs in Loki logs

PVCFull โ€‹

Impact: Service write failures. Prometheus stops ingesting, Loki stops accepting logs.

Investigate: Same as PVCUsageHigh but urgent.

Resolve (immediate):

  1. Prometheus: Reduce retention immediately: edit values-civo.yaml, set retention: 7d, redeploy
  2. Loki: Force compaction: kubectl exec -n monitoring loki-0 -- wget -qO- http://localhost:3100/compactor/ring
  3. Expand PVC: Civo supports online volume expansion; increase via dashboard then patch PVC
  4. Emergency: Delete old data manually (last resort)

WebSocketMassDisconnect โ€‹

Impact: Users disconnected from real-time chat. Messages won't be delivered until reconnection.

Note: This alert only fires when there are more than 5 active connections (avoids false positives during low-traffic periods).

Investigate:

bash
# Check API pod health
kubectl get pods -n spektrabot-pilot -l app.kubernetes.io/name=spektrabot-api

# Check for recent deployments or restarts
kubectl rollout history deployment/spektrabot-api -n spektrabot-pilot

# Check WebSocket metrics in Prometheus
# PromQL: websocket_connections_active
# PromQL: increase(websocket_disconnects_total[5m])

# Check node network
kubectl get nodes -o wide

Resolve:

  1. If API pod restarted: connections drop during restart, should auto-recover
  2. If node issue: check node health and network connectivity
  3. If ingress change: verify WebSocket upgrade headers in ingress config
  4. If sustained: check API logs for WebSocket error patterns

Log Queries โ€‹

Access logs via Grafana Explore with Loki datasource.

Common LogQL queries โ€‹

logql
# All API logs
{namespace="spektrabot-pilot", app="spektrabot-api"}

# Error logs only
{namespace="spektrabot-pilot", app="spektrabot-api"} |~ "error|Error|ERROR"

# Logs from a specific pod
{namespace="spektrabot-pilot", pod="spektrabot-api-abc123"}

# All monitoring namespace logs
{namespace="monitoring"}

# Postgres logs
{namespace="spektrabot-pilot", app="spektrabot-db"}

# Rate of error logs per minute
rate({namespace="spektrabot-pilot"} |~ "error" [1m])

Maintenance โ€‹

Deploying changes โ€‹

The monitoring stack is deployed via three Helm charts:

bash
# Monitoring stack (Prometheus, Grafana, AlertManager, alerts, dashboards)
helm upgrade spektrabot-monitoring helm/monitoring \
  -f helm/monitoring/values-civo.yaml \
  --set 'pagerdutyRoutingKey=YOUR_KEY' \
  -n monitoring

# Loki (log aggregation)
helm upgrade loki helm/loki \
  -f helm/loki/values-civo.yaml \
  -n monitoring

# Alloy (log collection)
helm upgrade alloy helm/alloy \
  -f helm/alloy/values-civo.yaml \
  -n monitoring

Adding a new alert rule โ€‹

  1. Edit helm/monitoring/templates/prometheusrule-core.yaml or prometheusrule-app.yaml
  2. Add a new rule following the existing format
  3. Redeploy the monitoring chart
  4. Verify in Prometheus UI (Alerts tab) that the rule appears

Adding a new dashboard โ€‹

  1. Create the dashboard JSON in Grafana UI (or manually)
  2. Export the JSON and save to helm/monitoring/dashboards/
  3. Create a ConfigMap template in helm/monitoring/templates/ with labels:
    yaml
    labels:
      grafana_dashboard: "1"
  4. Redeploy the monitoring chart
  5. Dashboard appears automatically in Grafana

Rotating the PagerDuty key โ€‹

The PagerDuty routing key is injected at deploy time via --set. To rotate:

  1. Generate a new Integration Key in PagerDuty (Service > Integrations > Events API v2)
  2. Redeploy with the new key:
    bash
    helm upgrade spektrabot-monitoring helm/monitoring \
      -f helm/monitoring/values-civo.yaml \
      --set 'pagerdutyRoutingKey=NEW_KEY' \
      -n monitoring
  3. Verify in AlertManager UI that the config updated

Checking cluster health โ€‹

Quick health check commands:

bash
# All monitoring pods
kubectl get pods -n monitoring

# PVC usage
kubectl get pvc -n monitoring

# Active alerts
kubectl port-forward svc/spektrabot-monitoring-kube-alertmanager 9093:9093 -n monitoring
# Then visit http://localhost:9093

# Prometheus targets
kubectl port-forward svc/spektrabot-monitoring-kube-prometheus 9090:9090 -n monitoring
# Then visit http://localhost:9090/targets

Troubleshooting โ€‹

Prometheus not scraping a target โ€‹

  1. Check ServiceMonitor/PodMonitor exists: kubectl get servicemonitors -n monitoring
  2. Verify labels match: the ServiceMonitor must have release: spektrabot-monitoring
  3. Check Prometheus targets page for errors: http://localhost:9090/targets
  4. Verify the target pod has the metrics port exposed

Grafana dashboard not loading โ€‹

  1. Check ConfigMap exists: kubectl get cm -n monitoring -l grafana_dashboard=1
  2. Check sidecar logs: kubectl logs <grafana-pod> -n monitoring -c grafana-sc-dashboard
  3. Verify JSON is valid (common issue: trailing commas)

Loki not receiving logs โ€‹

  1. Check Alloy pods are running: kubectl get pods -n monitoring -l app.kubernetes.io/name=alloy
  2. Check Alloy logs: kubectl logs <alloy-pod> -n monitoring
  3. Verify Loki is healthy: kubectl exec -n monitoring loki-0 -- wget -qO- http://localhost:3100/ready
  4. Check Loki gateway: kubectl logs <loki-gateway-pod> -n monitoring

AlertManager not routing to PagerDuty โ€‹

  1. Verify secret exists: kubectl get secret alertmanager-pagerduty-config -n monitoring
  2. Check AlertManager config: visit http://localhost:9093/#/status
  3. Verify routing key format: must be Events API v2 Integration Key (32-char alphanumeric)
  4. Check AlertManager logs for PagerDuty API errors

Last updated: 2026-02-12

Confidential ยท Spectrum Dynamics CIC