# Prometheus Alerting Rules for Proxmox VE + Ceph + Galera + Blackbox
#
# Copy to /opt/monitoring/prometheus/rules/alerting_rules.yml (or equivalent
# rules dir mounted into the prometheus container) and reload:
#   docker exec prometheus wget -qO- --post-data= http://localhost:9090/-/reload
#
# CRITICAL PITFALLS (see references/alertmanager-webhook-setup.md):
#   - pve_up includes ALL guests (including intentionally stopped ones).
#     Filter with id=~"node/.*" for node-down alerts only.
#   - pve_ha_state has 15 states × hundreds of guests = 600+ series.
#     Only alert on state=~"error|fence|freeze|gone".
#   - After changing rules, restart alertmanager to clear stale cached alerts.

groups:
  # === NODE DOWN ===
  - name: node_alerts
    interval: 30s
    rules:
      - alert: NodeDown
        expr: up{job="node_exporter"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Node {{ $labels.instance }} is down"
          description: "{{ $labels.instance }} has been unreachable for 2 minutes."

      - alert: NodeHighCPU
        expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{job="node_exporter",mode="idle"}[5m])) * 100) > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High CPU on {{ $labels.instance }}"
          description: "CPU usage above 85% for 10 minutes on {{ $labels.instance }}."

      - alert: NodeDiskSpaceLow
        expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxc|overlay|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|fuse.lxc|overlay|squashfs"}) * 100 > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Disk space low on {{ $labels.instance }} {{ $labels.mountpoint }}"
          description: "Filesystem {{ $labels.mountpoint }} on {{ $labels.instance }} is above 85% full."

      - alert: NodeDiskSpaceCritical
        expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxc|overlay|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|fuse.lxc|overlay|squashfs"}) * 100 > 95
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Disk space CRITICAL on {{ $labels.instance }} {{ $labels.mountpoint }}"
          description: "Filesystem {{ $labels.mountpoint }} on {{ $labels.instance }} is above 95% full."

      - alert: NodeHighMemory
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage on {{ $labels.instance }}"
          description: "Memory usage above 90% on {{ $labels.instance }}."

      - alert: NodeSwapUsage
        expr: (node_memory_SwapTotal_bytes > 0) and ((1 - node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes) * 100 > 80)
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High swap usage on {{ $labels.instance }}"
          description: "Swap usage above 80% on {{ $labels.instance }}."

  # === PROXMOX ===
  - name: proxmox_alerts
    interval: 30s
    rules:
      - alert: PVENodeDown
        expr: pve_up{id=~"node/.*"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Proxmox node {{ $labels.id }} is down"
          description: "PVE node {{ $labels.id }} is unreachable."

      - alert: PVEHAServiceError
        expr: pve_ha_state{state=~"error|fence|freeze|gone"} == 1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "PVE HA error: {{ $labels.id }} state={{ $labels.state }}"
          description: "HA-managed resource {{ $labels.id }} is in {{ $labels.state }} state."

      - alert: PVEHighCPU
        expr: pve_cpu_usage_ratio > 0.90
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "PVE {{ $labels.id }} high CPU"
          description: "PVE guest {{ $labels.id }} CPU usage above 90% for 10 minutes."

      - alert: PVEHighMemory
        expr: pve_memory_usage_bytes / pve_memory_size_bytes > 0.90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PVE {{ $labels.id }} high memory"
          description: "PVE guest {{ $labels.id }} memory usage above 90%."

  # === CEPH ===
  - name: ceph_alerts
    interval: 30s
    rules:
      - alert: CephHealthWarning
        expr: ceph_health_status == 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Ceph cluster HEALTH_WARN"
          description: "Ceph cluster is in HEALTH_WARN state for 5 minutes."

      - alert: CephHealthCritical
        expr: ceph_health_status == 2
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Ceph cluster HEALTH_ERR"
          description: "Ceph cluster is in HEALTH_ERR state!"

      - alert: CephOSDDown
        expr: ceph_osd_down > 0
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Ceph OSDs down: {{ $value }}"
          description: "{{ $value }} Ceph OSD(s) are down."

      - alert: CephPoolNearFull
        expr: ceph_pool_percent_used > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Ceph pool {{ $labels.pool }} near full"
          description: "Ceph pool {{ $labels.pool }} is {{ $value }}% full."

  # === GALERA ===
  - name: galera_alerts
    interval: 30s
    rules:
      - alert: GaleraNodeDown
        expr: mysql_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Galera node {{ $labels.instance }} down"
          description: "MySQL/Galera node {{ $labels.instance }} is not responding."

      - alert: GaleraClusterSizeReduced
        expr: mysql_global_status_wsrep_cluster_size < 3
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Galera cluster size reduced to {{ $value }}"
          description: "Galera cluster has only {{ $value }} node(s) — expected 3."

      - alert: GaleraNodeNotSynced
        expr: mysql_global_status_wsrep_local_state != 4
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Galera node {{ $labels.instance }} not synced"
          description: "Galera node {{ $labels.instance }} is not in Synced state."

  # === BLACKBOX ===
  - name: blackbox_alerts
    interval: 30s
    rules:
      - alert: HostUnreachableICMP
        expr: probe_success{job="blackbox_icmp"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Host {{ $labels.instance }} unreachable (ICMP)"
          description: "{{ $labels.instance }} not responding to ping for 2 minutes."

      - alert: HTTPEndpointDown
        expr: probe_success{job="blackbox_http"} == 0
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "HTTP endpoint {{ $labels.instance }} down"
          description: "{{ $labels.instance }} not returning 2xx for 2 minutes."

      - alert: HTTPCertExpiringSoon
        expr: probe_ssl_earliest_cert_expiry{job="blackbox_http"} - time() < 86400 * 14
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "SSL cert expiring soon for {{ $labels.instance }}"
          description: "SSL certificate for {{ $labels.instance }} expires in less than 14 days."
