Monitoring¶
Where to look¶
| What | URL |
|---|---|
| Grafana, dashboard Homelab-SPOG | https://grafana.local.bigd.no/d/mns9wmn |
| Alertmanager | https://alertmanager.local.bigd.no |
| Prometheus | https://prometheus.local.bigd.no |
| Discord | #homelab-alerts |
SPOG rows for the infrastructure around the cluster:
| Row | Shows |
|---|---|
| Internet & Backups | public sites up/down and response time, internet up/down, availability, downtime and outages over the range, up/down timeline, probe latency; backup job ages, VolSync sources behind |
| Network (UniFi) | WAN latency, internet session uptime, WAN drops, gateway CPU, traffic to and from each switch port, port errors and drops |
| Proxmox | host up/down, VMs running and stopped (templates excluded), host CPU and memory, storage used per host (local, local-lvm) and shared (pbs, nfs-vmstore) |
| NAS (Synology) | system status, Volume 1 used and free, RAID status, unhealthy disks, temperature, eth0 traffic, disk temperatures |
Stack¶
| Component | Role | Retention | Defined in |
|---|---|---|---|
| Prometheus | metrics, alert rules | 7 days | k8s/talos/infra/kube-prometheus-stack/values.yaml |
| Alertmanager | routing to Discord, 2 replicas | 5 days of silences and notification state | k8s/talos/infra/kube-prometheus-stack/values.yaml |
| Loki | logs, single binary | 7 days, Kubernetes events 30 days | k8s/talos/infra/loki/values.yaml |
| Alloy | DaemonSet shipping pod logs and Kubernetes events to Loki | none | k8s/talos/infra/loki/alloy-values.yaml |
| Tempo | traces, pushed over OTLP by Traefik | 24 h | k8s/talos/infra/tempo/values.yaml |
| Falco | runtime security events, modern eBPF driver | none | k8s/talos/infra/falco/values.yaml |
Alert path¶
Prometheus rules → Alertmanager → Discord. Routing (k8s/talos/infra/kube-prometheus-stack/values.yaml):
alertname=Watchdog(always firing) →heartbeat: a POST to the Cloudflare watchdog Worker every 5 min.severity=critical→discord, with resolved messages.- namespace
proxmoxorsynology→discord-event: one-shot pushes from Proxmox, PBS and DSM, no resolved message (they expire, they do not resolve). - namespace
reelsmith→discordat every severity. - everything else is dropped.
Falco does not go through Alertmanager: falcosidekick posts to Discord directly at priority
notice and above.
During an internet outage Discord is unreachable; Alertmanager retries and delivers once the line is back.
The watchdog Worker (terraform/cloudflare/watchdog, served at watchdog.bigd.no) runs
on Cloudflare and posts to the same Discord channel directly, as [WATCHDOG] DOWN and
[WATCHDOG] UP:
- no heartbeat for 15 min: the cluster, Prometheus, Alertmanager or the home line is down;
- a public site failing three one-minute checks in a row, seen from the internet.
To test the heartbeat path without silencing other alerts, add an Alertmanager silence
on alertname=Watchdog for 25 minutes (amtool silence add alertname=Watchdog
--duration=25m in the alertmanager container): the Worker posts DOWN about 15 minutes
after the last heartbeat and UP within minutes of the silence expiring. The Worker's
state is in its KV namespace, keys heartbeat and state:heartbeat.
Operating notes for the Worker (heartbeat URL rotation, the site list) are in
../network/cloudflare.md.
Sources¶
| Source | Scrapes | Auth | Defined in |
|---|---|---|---|
| kube-prometheus-stack | cluster, nodes, kube-state-metrics | none | k8s/talos/infra/kube-prometheus-stack/ |
blackbox-exporter, Probe internet |
HTTPS to google.com/generate_204 and cloudflare.com/cdn-cgi/trace every 15 s | none | k8s/talos/infra/blackbox-exporter/ |
blackbox-exporter, Probe public-sites |
nordbye.it, blog.nordbye.it, logeverylift.com, auth.bigd.no, hub.bigd.no through public DNS and Cloudflare, every 60 s | none | k8s/talos/infra/blackbox-exporter/probe.yaml |
| unpoller | UniFi gateway https://10.3.10.1, scraped every 30 s; the gateway's system log pushed to Loki every minute as {application="unifi_system_log"} |
local UniFi user unpoller, Network View Only, other apps None, Bitwarden unpoller-unifi-password |
k8s/talos/infra/unpoller/ |
| snmp-exporter | NAS 10.3.10.10, modules if_mib + synology, every 60 s |
SNMPv3 snmp-exporter, SHA/AES, Bitwarden synology-snmp-auth-password, synology-snmp-priv-password |
k8s/talos/infra/snmp-exporter/ |
| pve-exporter | Proxmox API on hyper1-3 (/pve, hyper1 also cluster-wide) every 60 s |
token of prometheus@pve (PVEAuditor), Bitwarden proxmox-exporter-token |
k8s/talos/infra/pve-exporter/, identity in terraform/proxmox/hyper-cluster/datacenter/access.tf |
| VolSync metrics | ReplicationSource sync state | none | k8s/talos/infra/volsync/ |
| exportarr, qbittorrent-exporter | media apps | app API keys | k8s/talos/apps/arr-stack/ |
| Proxmox VE, PBS webhooks | push to Alertmanager on errors | none | terraform/proxmox/hyper-cluster/datacenter, terraform/proxmox/pbs |
| DSM webhook "Alertmanager" | push on Warning and above | none | DSM, see below |
Alerts¶
Rules live in k8s/talos/infra/kube-prometheus-stack/homelab-alerts.yaml, except reelsmith's,
which live in k8s/talos/apps/reelsmith/monitoring.yaml and reach Discord at every severity.
Everywhere else only critical reaches Discord.
| Alert | Fires when | Severity |
|---|---|---|
InternetDown |
both internet probes fail for 1 min | critical |
PublicSiteDown |
a public site fails for 5 min while the internet is up | critical |
BackupJobStale |
etcd, a *-db-backup or a *-backup-check CronJob not successful for 26 h, or scheduled and never succeeded |
critical |
RestoreTestFailed |
the monthly restore test did not pass, or has not passed for 32 days | critical |
VolSyncBackupStale |
a ReplicationSource out of sync for 6 h | critical |
NasDiskUnhealthy |
diskStatus or diskHealthStatus not 1 for 10 min |
critical |
NasRaidDegraded |
raidStatus not 1 for 10 min |
critical |
NasVolumeAlmostFull |
Volume 1 above 90 % for 1 h | critical |
NasMetricsDown |
SNMP scrape failing for 30 min | critical |
ProxmoxHostDown |
a Proxmox host offline for 5 min | critical |
ProxmoxStorageAlmostFull |
a Proxmox storage above 85 % for 30 min | critical |
ProxmoxMetricsDown |
Proxmox exporter scrape failing for 30 min | critical |
DeploymentUnavailable, PodCrashLooping |
a workload down | critical |
ArrQueueStuck, MediaRootFolderLow, ArrAppUnreachable, MediaExporterDown, QbittorrentDisconnected |
media stack | critical |
CertificateExpiringSoon |
a cert-manager certificate within 7 days of expiry | critical |
ContainerRestartingFrequently, ContainerOOMKilled, ArgoCDAppDegraded, ArgoCDAppSyncStuck, ExternalSecretNotReady, KubeHpaMaxedOut |
platform | warning (not sent) |
SynologyNotification |
DSM pushed a Warning or Critical event | critical, discord-event |
ProxmoxNotification |
Proxmox or PBS pushed an error | critical, discord-event |
Reelsmith* |
the reelsmith gateway stalls, its token expires, a post or backup goes stale | critical and warning, all sent |
Falco¶
Falco runs as a DaemonSet on all six nodes with the modern_ebpf driver, the only one
that works on Talos since it needs no kernel headers
(values.yaml, chart falco 9.1.0 in
kustomization.yaml). The only event
source is syscalls; no plugins are loaded, so there is no Kubernetes audit log source.
Rules come from two places:
- the upstream stable ruleset,
falco-rules:5, which falcoctl pulls from ghcr.io in an init container and a sidecar re-checks every 168 h (chart defaults). Falco does not start without it, which is why its CiliumNetworkPolicy allows egress to the world on 443; customRules.tuning.yamlinvalues.yaml, rendered into a ConfigMap. Reloader restarts the DaemonSet when it changes.
Events go to falcosidekick (two replicas), which posts to Discord at priority notice and
above. The webhook URL comes from the ExternalSecret falco-falcosidekick-discord
(falcosidekick-discord-secret.yaml),
and a kustomize patch puts the Reloader annotation on the falcosidekick Deployment so a new
URL is picked up. The web UI is off. Falco emits its own metrics on a 15 min stats
interval and exposes them to Prometheus through a ServiceMonitor.
Writing an exemption¶
Tune through the hooks the upstream rules already consume, never by copying and editing an upstream rule, so a ruleset update keeps the change:
- a macro the rule references (
known_drop_and_execute_activities,user_known_stand_streams_redirect_activities,allowed_clear_log_files), overridden withcondition: replace; - a list the rule references (
known_drop_and_execute_containers,read_sensitive_file_images), extended withitems: append; - the rule itself with
condition: appendand anand not (...)clause, when the only hook is a list matched on something too broad, such as a bare process name.
Scope every exemption to the process, image and path that cause it, so the same behaviour
elsewhere still alerts. replace on a macro drops whatever upstream put in it, so the new
condition has to carry the full intent.
Some containers show up as a generic upstream image. The verksted dind sidecar is seen as
docker.io/library/docker, so its exemptions go through the verksted_dind macro, which
also pins k8s.ns.name=verksted. An exemption on that image without the namespace would
exempt every docker container in the cluster.
Expected noise¶
The current exemptions cover:
| Signal | Exempted for |
|---|---|
| drop and execute | the Cilium CNI plugin from /opt/cni/bin; the verksted image |
| symlink over sensitive files | links created under /tmp/vk-repos-* (verksted test suites inside dind, where Falco has no container metadata) |
| stdio redirected to a socket | kubelet; authentik in the Authentik server image |
| read of a sensitive file | the verksted image (systemd-sysusers reading /etc/shadow on apt install) |
| clear log files | containerd in the verksted dind sidecar, under its own snapshot root only |
| packet socket in a container | cilium-agent in the Cilium image; dockerd in the verksted dind sidecar |
verksted is exempted at the image level because its sessions run arbitrary code as root;
the rules that watch the container boundary (escape, kernel module load, release_agent,
debugfs) stay armed for it. The verksted namespace runs a privileged dind container; the
comment in its namespace.yaml says
Falco will flag it, and no exemption targets it. See
../../apps/verksted/README.md.
Configured outside Git¶
| Where | Setting |
|---|---|
| DSM Terminal & SNMP | SNMP on, v1/v2c off, SNMPv3 user snmp-exporter, SHA, privacy AES |
| DSM Notification > Webhooks | Custom "Alertmanager", rule Warning, POST https://alertmanager.local.bigd.no/api/v2/alerts, Content-Type: application/json, body [{"labels":{"alertname":"SynologyNotification","severity":"critical","namespace":"synology","node":"nas"},"annotations":{"summary":"@@TEXT@@",...}}] |
| DSM Notification > Email | Synology Account, rule All |
| UniFi Admins & Users | local user unpoller, restricted to local access, custom role: Network View Only, everything else None |
DSM Shared Folder shared-data |
quota 31 TiB (NasVolumeAlmostFull watches the volume, not share quotas) |
Constraints¶
- CoreDNS forwards to the node resolver, which is the gateway
10.3.10.1, so trouble on the gateway or ISP DNS also breaks in-cluster name resolution for external names.