# Infrastructure health monitoring — specification | Field | Value | |---|---| | Author | Anton Afanasyeu | | Revision | R0 | | Creation date | 2026-06-26 | | Last modification date | 2026-06-26 | | Co-authored | Cursor Agent (project assistant) | | Severity | high | | State | accepted | | Document type | spec | | Supersedes | — | | Design review | [docs/DRs/20260626_cluster_monitoring.md](../DRs/20260626_cluster_monitoring.md) (frozen R0) | --- **Document type:** SPEC **Source draft:** [docs/drafts/20260626_cluster_monitoring.txt](../drafts/20260626_cluster_monitoring.txt) **Design review:** [docs/DRs/20260626_cluster_monitoring.md](../DRs/20260626_cluster_monitoring.md) (frozen) **PDF:** [20260626_cluster_monitoring.pdf](20260626_cluster_monitoring.pdf) · Regenerate: `bash ac-docs/build-pdf.sh` **Status:** **Accepted** — implement per §7 deployment plan **Scope:** Passive infrastructure monitoring and alerting for XEN cluster cast01–cast04; Track A only **Related:** [INFRA.md](../INFRA.md) · [DRs/20260626_cluster_monitoring.md](../DRs/20260626_cluster_monitoring.md) · [specs/20100612_1_scaling.md](20100612_1_scaling.md) · [20260620_postgres_timescale_db_platform.md](../20260620_postgres_timescale_db_platform.md) **Track B (future):** [drafts/20260626_cluster_orchestration.txt](../drafts/20260626_cluster_orchestration.txt) **Documentation index:** [README.md](../README.md) --- ## Table of contents - [1. Purpose](#1-purpose) - [2. Scope](#2-scope) - [3. Requirements](#3-requirements) - [3.1 Must](#31-must) - [3.2 Should](#32-should) - [3.3 Non-goals](#33-non-goals) - [4. Monitoring domains](#4-monitoring-domains) - [4.1 Hardware metrics](#41-hardware-metrics) - [4.2 Software and service metrics](#42-software-and-service-metrics) - [4.3 External availability](#43-external-availability) - [4.4 Provider-layer fault escalation](#44-provider-layer-fault-escalation) - [5. Topology — cast04 as dedicated monitoring node](#5-topology--cast04-as-dedicated-monitoring-node) - [6. Component catalog](#6-component-catalog) - [6.1 Alpine apk packages (cast04)](#61-alpine-apk-packages-cast04) - [6.2 Alpine apk packages (cast01–cast03)](#62-alpine-apk-packages-cast0103) - [6.3 Docker-based (cast04)](#63-docker-based-cast04) - [6.4 External SaaS (no install)](#64-external-saas-no-install) - [7. Deployment plan](#7-deployment-plan) - [7.1 Phase 0 — cast04 provisioning](#71-phase-0--cast04-provisioning) - [7.2 Phase 1 — Prometheus + node_exporter](#72-phase-1--prometheus--node_exporter) - [7.3 Phase 2 — Service probes and MariaDB exporter](#73-phase-2--service-probes-and-mariadb-exporter) - [7.4 Phase 3 — Alertmanager](#74-phase-3--alertmanager) - [7.5 Phase 4 — Grafana dashboards](#75-phase-4--grafana-dashboards) - [7.6 Phase 5 — Uptime Kuma + UptimeRobot](#76-phase-5--uptime-kuma--uptimerobot) - [7.7 ac-deploy integration](#77-ac-deploy-integration) - [8. Alert routing](#8-alert-routing) - [8.1 Alert severity levels](#81-alert-severity-levels) - [8.2 Routing rules](#82-routing-rules) - [8.3 Alertmanager SMTP config (skeleton)](#83-alertmanager-smtp-config-skeleton) - [8.4 Telegram receiver config (skeleton)](#84-telegram-receiver-config-skeleton) - [8.5 Alert rule catalog](#85-alert-rule-catalog) - [9. Grafana dashboard catalog](#9-grafana-dashboard-catalog) - [10. Pre-requisite accounts](#10-pre-requisite-accounts) - [11. TimescaleDB relationship](#11-timescaledb-relationship) - [12. Verification and acceptance](#12-verification-and-acceptance) - [13. Risks and mitigations](#13-risks-and-mitigations) - [14. Related documents](#14-related-documents) - [15. Changelog](#15-changelog) --- ## 1. Purpose Define the **normative** implementation of passive infrastructure monitoring and alerting for the AndroidCast XEN-based VM cluster. The system must: - Record hardware and software health metrics from all cluster nodes continuously - Alert operators promptly when measured values exceed defined thresholds - Provide visual dashboards for trend analysis and incident investigation - Detect external (provider-layer) unavailability via an independent probe This SPEC covers **Track A only** (observe, record, alert). Automated cluster remediation is Track B and is explicitly out of scope — see [drafts/20260626_cluster_orchestration.txt](../drafts/20260626_cluster_orchestration.txt). --- ## 2. Scope | In scope | Out of scope | |----------|--------------| | Hardware metrics: CPU, RAM, disk, NIC per node | Automated node provisioning / deprovisioning | | Software metrics: process health, HTTP probes, MariaDB | Log aggregation (Grafana Loki — planned R1) | | Alert routing: email + Telegram | Auto-scaling or auto-failover reactions | | External uptime probe (UptimeRobot) | Changes to FE nginx or router software | | Grafana dashboards (infra layer) | Business / application metrics (→ TimescaleDB) | | cast04 as dedicated monitoring VM | Kubernetes / service mesh | | ac-deploy integration (monitoring role) | SaaS external aggregator for metric data | | Pre-requisite account setup guide | iOS / mobile performance profiling | **Alpha constraint:** Monitoring stack MAY be deployed incrementally per §7 phases. Each phase MUST leave prior phases operational. --- ## 3. Requirements ### 3.1 Must | ID | Requirement | |----|-------------| | R-MON-1 | cast04 dedicated monitoring VM provisioned as Alpine LTS paravirt XEN guest | | R-MON-2 | node_exporter running on cast01, cast02, cast03, cast04 | | R-MON-3 | Prometheus scraping all node_exporters on 15s interval | | R-MON-4 | blackbox_exporter probing all critical HTTP endpoints (§4.2) from cast04 | | R-MON-5 | mysqld_exporter running on MariaDB primary node | | R-MON-6 | Alertmanager routing CRITICAL alerts to email within 2 min of threshold breach | | R-MON-7 | Grafana serving dashboards on cast04 (internal access via SSH tunnel at R0) | | R-MON-8 | UptimeRobot external HTTP probe active for `apps.f0xx.org` | | R-MON-9 | All software installed via Alpine apk or Docker (no source compilation) | | R-MON-10 | No new software installed on FE VM or router | | R-MON-11 | Metric data retained locally on cast04 for minimum 90 days | | R-MON-12 | Alert for CPU steal time > 10% sustained 5 min (Xen overcommit indicator → notify Anton) | | R-MON-13 | Alert for disk write latency > 100ms sustained 5 min on MariaDB node | | R-MON-14 | Alert for MariaDB replication lag > 30s | | R-MON-15 | Hardware guarantee baseline alerts (§4.1 table) — notify when measured < guaranteed | ### 3.2 Should | ID | Requirement | |----|-------------| | R-MON-S1 | Alertmanager Telegram receiver configured alongside email | | R-MON-S2 | Uptime Kuma status page deployed on cast04 (Docker) | | R-MON-S3 | Grafana provisioned with community dashboard for node_exporter (ID 1860) | | R-MON-S4 | Grafana provisioned with community dashboard for MariaDB | | R-MON-S5 | Prometheus metric retention configurable via `--storage.tsdb.retention.time` | | R-MON-S6 | Alertmanager grouping configured to suppress alert storms (group_wait 30s, group_interval 5m) | | R-MON-S7 | ac-deploy `monitoring/` role added to cluster0 scripts for reproducible deployment | ### 3.3 Non-goals - Automated node scale-out or scale-in (Track B) - DDoS automatic mitigation (Track B) - Storage automatic failover (Track B) - Centralized log search (Grafana Loki — R1) - Business metrics dashboards (TimescaleDB + Grafana — separate track) - Monitoring of router or FE internal metrics (config-only access; UptimeRobot covers external availability) --- ## 4. Monitoring domains ### 4.1 Hardware metrics Collected by **node_exporter** on every VM (cast01–cast04). Alert thresholds are guidelines — tune after collecting baseline. | Metric | Prometheus metric name | WARNING threshold | CRITICAL threshold | Xen note | |--------|----------------------|-------------------|--------------------|-----------| | CPU utilization | `rate(node_cpu_seconds_total{mode!="idle"}[5m])` | > 80% (5 min) | > 95% (5 min) | | | **CPU steal time** | `rate(node_cpu_seconds_total{mode="steal"}[5m])` | > 5% (5 min) | > 10% (5 min) | Xen overcommit → notify Anton | | Load average 1m | `node_load1 / node_cpu_count` | > 1.5 | > 3.0 | | | RAM used % | `1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes` | > 80% | > 90% | | | Swap used % | `node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes` | > 50% | > 80% | | | Disk used % | `1 - node_filesystem_avail_bytes / node_filesystem_size_bytes` | > 75% | > 90% | | | Disk write latency | `rate(node_disk_write_time_seconds_total[5m]) / rate(node_disk_writes_completed_total[5m])` | > 50ms | > 100ms | Critical on MariaDB node | | Disk IOPS | `rate(node_disk_io_time_seconds_total[5m])` | > 80% | > 95% | | | NIC ingress bytes/s | `rate(node_network_receive_bytes_total[5m])` | > 150 Mbit/s | > 190 Mbit/s | Guaranteed: 200 Mbit/s | | NIC egress bytes/s | `rate(node_network_transmit_bytes_total[5m])` | > 150 Mbit/s | > 190 Mbit/s | Guaranteed: 200 Mbit/s | | NIC packet drops | `rate(node_network_receive_drop_total[5m])` | > 0.1% | > 1% | | | NIC errors | `rate(node_network_receive_errs_total[5m])` | > 0 | > 10/min | | **Hardware guarantee baseline check:** If `node_memory_MemTotal_bytes` < 7.5 GB, or CPU core count < 8, or disk total < 30 GB → CRITICAL alert (guaranteed resources not provided → notify Anton). ### 4.2 Software and service metrics #### Process / systemd health (node_exporter `systemd` collector or blackbox TCP probe) | Service | Check | Alert trigger | |---------|-------|---------------| | nginx | TCP :80 probe | Connection refused → CRITICAL | | php-fpm | TCP socket probe | No response → CRITICAL | | MariaDB | TCP :3306 probe | Connection refused → CRITICAL | | sshd | TCP :22 probe | Connection refused → WARNING | | Per-microservice HTTP endpoint | HTTP GET /health or / | Non-2xx or timeout > 5s → WARNING; > 30s → CRITICAL | #### MariaDB metrics (mysqld_exporter) | Metric | Prometheus metric | WARNING | CRITICAL | |--------|------------------|---------|---------| | Replication lag | `mysql_slave_status_seconds_behind_master` | > 10s | > 30s | | Open connections | `mysql_global_status_threads_connected` | > 80% of max | > 95% of max | | Slow queries/s | `rate(mysql_global_status_slow_queries[5m])` | > 1/s | > 5/s | | InnoDB buffer pool hit | `mysql_global_status_innodb_buffer_pool_reads / mysql_global_status_innodb_buffer_pool_read_requests` | < 95% | < 90% | | Aborted connections | `rate(mysql_global_status_aborted_connects[5m])` | > 1/min | > 10/min | #### nginx metrics (via node_exporter nginx stub or blackbox HTTP probe) | Metric | Source | WARNING | CRITICAL | |--------|--------|---------|---------| | 5xx response rate | blackbox HTTP probe returning 5xx | Any 5xx returned | > 10% of requests | | Response time | blackbox `probe_duration_seconds` | > 2s | > 10s | #### php-fpm metrics (via php-fpm status endpoint + blackbox or custom exporter) | Metric | WARNING | CRITICAL | |--------|---------|---------| | Active workers % of max | > 70% | > 90% | | Queue length | > 5 | > 20 | ### 4.3 External availability **Tool:** UptimeRobot free tier — no installation; checks from UptimeRobot's global nodes. | Monitor | URL | Check interval | Alert on | |---------|-----|---------------|---------| | Frontend TLS | `https://apps.f0xx.org` | 5 min | Non-2xx or timeout | | API health | `https://apps.f0xx.org/app/androidcast_project/` | 5 min | Non-2xx or timeout | **Internal cross-check:** blackbox_exporter on cast04 also probes the FE endpoint from inside the cluster. If UptimeRobot fires but blackbox does NOT → provider/uplink fault (Anton's layer). If both fire → likely cluster-side issue. ### 4.4 Provider-layer fault escalation When monitoring detects that the root cause is in Anton's infrastructure layer: | Symptom | Likely cause | Action | |---------|-------------|--------| | CPU steal > 10% sustained | Xen host overcommit | Email Anton; await response | | Disk write latency persistent despite low I/O | Xen host disk contention | Email Anton | | NIC saturation below 200 Mbit guarantee | Uplink / Xen host NIC | Email Anton | | UptimeRobot alert + cast04 unreachable | Xen host or uplink down | Email Anton; check intra-network | | Entire cluster unreachable | Router / uplink / payment | Email Anton; check calendar | These events are **alert-only** at R0. No automated remediation. --- ## 5. Topology — cast04 as dedicated monitoring node ```text 10.7.0.0/8 internal network ┌────────────────────────────────────────┐ │ cast04 — monitoring VM (Alpine LTS) │ │ │ cast01 ──[ :9100 node_exporter ]──┤ │ cast02 ──[ :9100 node_exporter ]──┤──► Prometheus :9090 │ cast03 ──[ :9100 node_exporter ]──┤ ├── scrape all :9100 │ cast04 ──[ :9100 node_exporter ]──┘ ├── scrape :9104 (mysqld) │ MariaDB node ─[ :9104 mysqld_exp ] └── evaluate alert rules │ │ │ ▼ │ Alertmanager :9093 │ ├── email (SMTP relay) │ └── Telegram bot API │ │ blackbox_exporter :9115 │ └── HTTP/TCP probes → cast01–03, │ FE endpoint, per-service /health │ │ Grafana :3000 │ ├── datasource: Prometheus │ └── datasource: TimescaleDB (future) │ │ Uptime Kuma :3001 (Docker) │ └── per-service status page │ └───────────────────────────────────────┘ External (no install): UptimeRobot ──► HTTP probe ──► apps.f0xx.org (from internet) ``` **cast04 minimum provisioning request (to Anton):** ``` VM name: cast04 VM type: paravirt XEN guest OS: Alpine Linux latest LTS (64-bit) CPU: 2 vCPU RAM: 2 GB Disk: 32 GB (non-shared) Network: Internal 10.7.0.0/8 + internet egress (for Alertmanager SMTP / Telegram API calls) ``` --- ## 6. Component catalog ### 6.1 Alpine apk packages (cast04) ```bash apk update && apk upgrade apk add prometheus alertmanager grafana \ prometheus-node-exporter \ prometheus-blackbox-exporter \ docker docker-cli docker-compose ``` | Package | Alpine repo | Version policy | |---------|------------|---------------| | `prometheus` | community/edge | latest stable in repo | | `alertmanager` | community/edge | latest stable | | `grafana` | community/edge | latest stable | | `prometheus-node-exporter` | community | latest stable | | `prometheus-blackbox-exporter` | community | latest stable | | `docker` | community | for Uptime Kuma container | If a package is only in Alpine **edge** repo, add the edge community URL to `/etc/apk/repositories` and pin via `/etc/apk/world`. ### 6.2 Alpine apk packages (cast01–cast03) Install only the node exporter on each workload node: ```bash apk add prometheus-node-exporter rc-update add node-exporter default rc-service node-exporter start ``` On the MariaDB primary node, additionally: ```bash apk add prometheus-mysqld-exporter # Create limited MariaDB user for exporter: mysql -e "CREATE USER 'prom_exporter'@'localhost' IDENTIFIED BY '';" mysql -e "GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'prom_exporter'@'localhost';" # Configure /etc/prometheus-mysqld-exporter/mysqld_exporter.env # DATA_SOURCE_NAME='prom_exporter:@(localhost:3306)/' rc-update add mysqld-exporter default rc-service mysqld-exporter start ``` ### 6.3 Docker-based (cast04) ```bash rc-update add docker default rc-service docker start # Uptime Kuma docker run -d --restart=always \ --name uptime-kuma \ -p 3001:3001 \ -v uptime-kuma:/app/data \ louislam/uptime-kuma:latest ``` ### 6.4 External SaaS (no install) | Service | URL | Action | |---------|-----|--------| | UptimeRobot | https://uptimerobot.com | Create free account; add HTTP monitor for `https://apps.f0xx.org`; configure email alert | --- ## 7. Deployment plan Deploy phases in order. Each phase MUST be verified before starting the next. ### 7.1 Phase 0 — cast04 provisioning **Goal:** cast04 VM exists and is reachable from cast01–cast03 on internal network. | Action | Detail | |--------|--------| | Request VM | Ask Anton for cast04 — spec per §5 table | | Base install | Alpine latest LTS; configure apk repos (community + edge) | | SSH access | Add developer SSH key; test login | | Network | Confirm cast04 can reach cast01–03 IPs on internal network | | NTP | `apk add chrony && rc-service chronyd start` | **Verify:** `ssh cast04 ping cast01` succeeds; `apk update` succeeds. ### 7.2 Phase 1 — Prometheus + node_exporter **Goal:** Prometheus scraping hardware metrics from all nodes. ```bash # On cast04: apk add prometheus prometheus-node-exporter # /etc/prometheus/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'node' static_configs: - targets: - 'cast01:9100' - 'cast02:9100' - 'cast03:9100' - 'cast04:9100' rc-update add prometheus default rc-update add node-exporter default rc-service prometheus start rc-service node-exporter start ``` On cast01–cast03: install and start `prometheus-node-exporter` (§6.2). **Verify:** `curl http://cast04:9090/api/v1/targets` shows all 4 nodes in state `up`. ### 7.3 Phase 2 — Service probes and MariaDB exporter **Goal:** HTTP/TCP probes and MariaDB metrics flowing. Add to `prometheus.yml`: ```yaml - job_name: 'blackbox_http' metrics_path: /probe params: module: [http_2xx] static_configs: - targets: - https://apps.f0xx.org - http://cast01:80 # nginx on workload nodes # add per-microservice /health endpoints as they exist relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: cast04:9115 # blackbox_exporter - job_name: 'blackbox_tcp' metrics_path: /probe params: module: [tcp_connect] static_configs: - targets: - cast01:3306 # MariaDB - cast02:3306 # replica - cast01:22 - cast02:22 - cast03:22 relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: cast04:9115 - job_name: 'mariadb' static_configs: - targets: [':9104'] ``` Start services: ```bash rc-service blackbox-exporter start rc-service mysqld-exporter start # on MariaDB node ``` **Verify:** `curl "http://cast04:9115/probe?target=https://apps.f0xx.org&module=http_2xx"` returns `probe_success 1`. ### 7.4 Phase 3 — Alertmanager **Goal:** Alerts route to email and (optionally) Telegram on threshold breach. See §8 for full Alertmanager config skeletons. Deploy skeleton, then fill in SMTP credentials and Telegram token. ```bash apk add alertmanager # Edit /etc/alertmanager/alertmanager.yml (see §8.3 + §8.4) rc-update add alertmanager default rc-service alertmanager start ``` Add `alerting:` and `rule_files:` blocks to `prometheus.yml` (see §8.5 for rule file examples). **Verify:** `amtool --alertmanager.url=http://cast04:9093 alert` returns empty (no active alerts); send a test alert and confirm email receipt. ### 7.5 Phase 4 — Grafana dashboards **Goal:** Visual dashboards accessible for all metrics collected in phases 1–3. ```bash apk add grafana rc-update add grafana default rc-service grafana start # Access: http://cast04:3000 (via SSH tunnel: ssh -L 3000:cast04:3000 cast04) ``` In Grafana UI: 1. Add Prometheus datasource: `http://localhost:9090` 2. Import community dashboard **1860** ("Node Exporter Full") — covers §4.1 metrics 3. Import community dashboard **7362** or **13106** (MariaDB / mysqld_exporter) 4. Create custom panel for blackbox probe results (status per service endpoint) 5. Create overview panel showing hardware guarantee baseline vs measured values **Verify:** All four dashboards render data; node_exporter CPU steal panel shows non-zero (or zero — both valid). ### 7.6 Phase 5 — Uptime Kuma + UptimeRobot **Goal:** Service status page + external probe active. ```bash # Uptime Kuma on cast04 (Docker): rc-service docker start docker run -d --restart=always --name uptime-kuma \ -p 3001:3001 -v uptime-kuma:/app/data louislam/uptime-kuma:latest # Access: http://cast04:3001 (via SSH tunnel) ``` Configure monitors in Uptime Kuma UI: - Add HTTP monitors for each microservice `/health` endpoint - Add TCP monitors for MariaDB :3306, nginx :80 - Configure notification to same email as Alertmanager **UptimeRobot:** - Register free account at https://uptimerobot.com - Add HTTP(s) monitor: `https://apps.f0xx.org`, 5-minute interval - Add email alert notification (same ops email as Alertmanager) **Verify:** Uptime Kuma dashboard shows all green; UptimeRobot sends test notification successfully. ### 7.7 ac-deploy integration Add a `monitoring/` role to cluster0 deploy scripts in `ac-deploy/sim/cluster0/`: ``` ac-deploy/sim/cluster0/ monitoring/ deploy-monitoring.sh # installs cast04 components prometheus.yml.j2 # Jinja2/envsubst template with node IPs alertmanager.yml.j2 # template with SMTP/Telegram creds from env alert-rules/ hardware.yml # §4.1 rules services.yml # §4.2 rules grafana/ datasources.yml # auto-provision Prometheus datasource dashboards/ # provisioned dashboard JSONs ``` Credentials (SMTP password, Telegram token) MUST be passed via environment variables or a secrets file outside git — never committed. **Verify:** `bash deploy-monitoring.sh` on a fresh cast04 produces a running stack within 10 minutes. --- ## 8. Alert routing ### 8.1 Alert severity levels | Level | Meaning | Response | |-------|---------|---------| | `critical` | Service down or hardware limit exceeded | Alert immediately; wake operator if outside hours | | `warning` | Approaching threshold; degraded | Alert during business hours; batch in digest | | `info` | Notable event; no action required | Log only; no alert | ### 8.2 Routing rules ``` critical → email (immediately) + Telegram (immediately) warning → email (grouped, 5 min window) info → no alert (recorded in Prometheus only) ``` ### 8.3 Alertmanager SMTP config (skeleton) ```yaml # /etc/alertmanager/alertmanager.yml global: smtp_smarthost: 'smtp.gmail.com:587' # or Mailgun: smtp.mailgun.org:587 smtp_from: 'alerts@your-domain.com' smtp_auth_username: 'your-gmail@gmail.com' smtp_auth_password: '' # from env / secrets file smtp_require_tls: true route: group_by: ['alertname', 'instance'] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: 'ops-email' routes: - match: severity: critical receiver: 'ops-critical' group_wait: 10s repeat_interval: 1h receivers: - name: 'ops-email' email_configs: - to: 'ops@your-domain.com' send_resolved: true - name: 'ops-critical' email_configs: - to: 'ops@your-domain.com' send_resolved: true telegram_configs: - bot_token: '' # from env / secrets file chat_id: send_resolved: true inhibit_rules: - source_match: severity: 'critical' target_match: severity: 'warning' equal: ['instance'] ``` **Note:** Replace `` and `` with values from environment — never commit credentials to git. ### 8.4 Telegram receiver config (skeleton) Obtain Telegram credentials: 1. Open Telegram, message `@BotFather` 2. `/newbot` → follow prompts → save `TOKEN` 3. Add the bot to your ops group or DM it, then: `curl https://api.telegram.org/bot/getUpdates` → find `chat.id` 4. Set `TELEGRAM_BOT_TOKEN=` and `TELEGRAM_CHAT_ID=` in the secrets file used by deploy script ### 8.5 Alert rule catalog Create `/etc/prometheus/rules/hardware.yml`: ```yaml groups: - name: hardware rules: - alert: CpuHighUtilization expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95 for: 5m labels: severity: critical annotations: summary: "CPU critical on {{ $labels.instance }}" description: "CPU utilization {{ $value | humanize }}% > 95%" - alert: XenCpuStealHigh expr: avg by(instance) (rate(node_cpu_seconds_total{mode="steal"}[5m])) * 100 > 10 for: 5m labels: severity: critical annotations: summary: "Xen CPU steal critical on {{ $labels.instance }} — notify Anton" description: "CPU steal {{ $value | humanize }}% > 10% — hypervisor overcommit" - alert: RamCritical expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90 for: 5m labels: severity: critical annotations: summary: "RAM critical on {{ $labels.instance }}" - alert: DiskCritical expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100 > 90 for: 5m labels: severity: critical annotations: summary: "Disk critical on {{ $labels.instance }} mount {{ $labels.mountpoint }}" - alert: DiskWriteLatencyHigh expr: > rate(node_disk_write_time_seconds_total[5m]) / rate(node_disk_writes_completed_total[5m]) * 1000 > 100 for: 5m labels: severity: critical annotations: summary: "Disk write latency critical on {{ $labels.instance }} — possible HW fault; notify Anton" - alert: HardwareGuaranteeRamBreach expr: node_memory_MemTotal_bytes < 7.5 * 1024 * 1024 * 1024 for: 1m labels: severity: critical annotations: summary: "RAM below guaranteed 8 GB on {{ $labels.instance }} — notify Anton" ``` Create `/etc/prometheus/rules/services.yml`: ```yaml groups: - name: services rules: - alert: ServiceDown expr: probe_success == 0 for: 1m labels: severity: critical annotations: summary: "Service unreachable: {{ $labels.instance }}" - alert: ServiceSlowResponse expr: probe_duration_seconds > 10 for: 2m labels: severity: warning annotations: summary: "Slow response from {{ $labels.instance }}: {{ $value | humanize }}s" - alert: MariaDbReplicationLag expr: mysql_slave_status_seconds_behind_master > 30 for: 2m labels: severity: critical annotations: summary: "MariaDB replication lag {{ $value | humanize }}s on {{ $labels.instance }}" - alert: MariaDbDown expr: mysql_up == 0 for: 1m labels: severity: critical annotations: summary: "MariaDB exporter cannot reach database on {{ $labels.instance }}" ``` Add to `prometheus.yml`: ```yaml rule_files: - "/etc/prometheus/rules/*.yml" alerting: alertmanagers: - static_configs: - targets: ['cast04:9093'] ``` --- ## 9. Grafana dashboard catalog | Dashboard | Grafana ID | Source | Covers | |-----------|-----------|--------|--------| | Node Exporter Full | 1860 | community | CPU, RAM, disk, NIC, load — all §4.1 metrics | | MariaDB Overview | 7362 | community | Connections, queries, replication lag, InnoDB | | Blackbox Exporter | 7587 | community | HTTP probe success/latency per target | | Custom: Hardware Guarantee | — | create | Measured vs Anton's guaranteed values | | Custom: Service Health Overview | — | create | All service probes in one panel grid | **Provisioning via ac-deploy:** export dashboard JSON from Grafana UI after customization and store in `ac-deploy/sim/cluster0/monitoring/grafana/dashboards/`. Add datasource YAML to `grafana/datasources/prometheus.yml`. --- ## 10. Pre-requisite accounts The following must be created by the project owner before Phase 3 deploy: | Account | Service | Purpose | Notes | |---------|---------|---------|-------| | Gmail app password | Gmail Settings → Security → App passwords | Alertmanager SMTP relay | App password, not account password. Alternatively: Mailgun free (10k/month) | | Telegram bot | @BotFather in Telegram | Alertmanager Telegram receiver | `/newbot` → TOKEN; `getUpdates` → CHAT_ID | | UptimeRobot free | https://uptimerobot.com | External HTTP probe | Add monitor for `https://apps.f0xx.org` | **Important:** ImprovMX and addy.io provide inbound email routing (MX records) — they are not outbound SMTP relay services and cannot be used with Alertmanager. Use Gmail app password or a dedicated transactional email service (Mailgun, SendGrid) for outbound alerts. --- ## 11. TimescaleDB relationship This SPEC implements infra-layer monitoring. Business and application metrics are a separate concern handled by the TimescaleDB platform plan ([20260620_postgres_timescale_db_platform.md](../20260620_postgres_timescale_db_platform.md)). | Layer | Store | Examples | Retention | |-------|-------|----------|-----------| | Infrastructure | Prometheus TSDB on cast04 | CPU steal, disk latency, service probe | 90 days | | Business / application | TimescaleDB (PostgreSQL) | Crash rate, session count, graph analytics | Long-term (configurable) | Grafana on cast04 can query both stores simultaneously. No ETL or data duplication is required between them. The TimescaleDB datasource is added to Grafana as a second datasource in a future step — it does not affect R0 monitoring deployment. --- ## 12. Verification and acceptance All phases must pass their gate before the next phase begins. | Phase | Acceptance criteria | |-------|---------------------| | 0 | cast04 reachable from cast01–03; `apk update` succeeds | | 1 | `curl http://cast04:9090/api/v1/targets` shows all 4 nodes state=up | | 2 | Blackbox probe for `https://apps.f0xx.org` returns `probe_success 1`; mysqld_exporter metrics visible in Prometheus | | 3 | Test alert fires and email is received within 2 minutes; Telegram notification received (if configured) | | 4 | Grafana dashboard 1860 renders data for all nodes; MariaDB dashboard shows replication status | | 5 | Uptime Kuma shows all services green; UptimeRobot sends test notification | | ac-deploy | Fresh cast04 reproduced by running `deploy-monitoring.sh` within 10 minutes | **Ongoing regression:** After any cluster change (new microservice, new VM, nginx config change), add corresponding blackbox probe target and Grafana panel within 48 hours. --- ## 13. Risks and mitigations | Risk | Mitigation | |------|------------| | cast04 itself fails — monitoring blind | UptimeRobot external probe continues; Alertmanager writes state to disk and resumes after VM restart | | XEN host failure takes cast04 offline | UptimeRobot fires from external — operator is alerted even without cast04 | | Alpine edge package breaks Prometheus | Pin version in `/etc/apk/world`; test upgrades on cast04 before rolling to workload nodes | | SMTP rate limit during incident storm | Alertmanager `group_interval: 5m` + `repeat_interval: 4h` limit volume; Telegram is parallel channel | | Prometheus disk overflow | 32 GB cast04 disk; estimated < 5 GB for 3 nodes at 15s scrape over 90 days; monitor cast04 disk in Grafana | | Credentials leaked in git | Secrets must be in environment variables or a secrets file excluded from git (`.gitignore`); never in `prometheus.yml` or `alertmanager.yml` committed to repo | | FE/router metrics unavailable | FE nginx stub_status available via config if Anton allows; not mandatory — blackbox HTTP probe sufficient at R0 | --- ## 14. Related documents | Document | Role | |----------|------| | [DRs/20260626_cluster_monitoring.md](../DRs/20260626_cluster_monitoring.md) | Frozen design review (source of this SPEC) | | [drafts/20260626_cluster_orchestration.txt](../drafts/20260626_cluster_orchestration.txt) | Track B — auto-remediation placeholder | | [INFRA.md](../INFRA.md) | FE/BE topology, hosts, ports | | [specs/20100612_1_scaling.md](20100612_1_scaling.md) | VM roles and cluster layout | | [specs/20260618_repos_reorganizing.md](20260618_repos_reorganizing.md) | Microservice split (each new MS needs a blackbox probe) | | [20260620_postgres_timescale_db_platform.md](../20260620_postgres_timescale_db_platform.md) | Business metrics store (complements Prometheus) | | [GRAFANA_vs_others_graphvis_pivot.md](../GRAFANA_vs_others_graphvis_pivot.md) | Earlier Grafana evaluation for application analytics | | [orchestration/sim/cluster0/ARCHITECTURE.md](../../orchestration/sim/cluster0/ARCHITECTURE.md) | Lab cluster node layout | --- ## 15. Changelog | Rev | Date | Change | |-----|------|--------| | R0 | 2026-06-26 | SPEC converted from draft + DR R0; all decisions locked; deployment plan §7; alert rules §8 |