Metrics taps: node_exporter + cAdvisor containers, MediaMTX and Caddy metrics on loopback (Prometheus format, store-agnostic)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,23 @@
|
|||||||
|
{
|
||||||
|
servers {
|
||||||
|
metrics # Prometheus metrics at http://127.0.0.1:2019/metrics (LOGGING.md)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# vanguard:main
|
# vanguard:main
|
||||||
media.example.com {
|
media.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
||||||
redir /logout /outpost.goauthentik.io/sign_out 302
|
redir /logout /outpost.goauthentik.io/sign_out 302
|
||||||
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
||||||
@@ -32,14 +49,20 @@ name: {http.request.header.X-Authentik-Name}"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# vanguard:apex
|
|
||||||
example.com {
|
|
||||||
redir https://media.example.com/ 302
|
|
||||||
}
|
|
||||||
|
|
||||||
# vanguard:files
|
# vanguard:files
|
||||||
files.example.com {
|
files.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
||||||
redir /logout /outpost.goauthentik.io/sign_out 302
|
redir /logout /outpost.goauthentik.io/sign_out 302
|
||||||
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
||||||
@@ -54,7 +77,7 @@ files.example.com {
|
|||||||
# copyparty at the root. It honours X-Authentik-Username / -Groups only when the request also carries a header
|
# copyparty at the root. It honours X-Authentik-Username / -Groups only when the request also carries a header
|
||||||
# whose secret NAME only this proxy knows (copyparty idp-h-key). Clients cannot forge identity.
|
# whose secret NAME only this proxy knows (copyparty idp-h-key). Clients cannot forge identity.
|
||||||
reverse_proxy 127.0.0.1:3923 {
|
reverse_proxy 127.0.0.1:3923 {
|
||||||
header_up X-Vanguard-EXAMPLEKEY 1
|
header_up X-Vanguard-k 1
|
||||||
flush_interval -1
|
flush_interval -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,6 +85,17 @@ files.example.com {
|
|||||||
# vanguard:sync
|
# vanguard:sync
|
||||||
sync.example.com {
|
sync.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
||||||
redir /logout /outpost.goauthentik.io/sign_out 302
|
redir /logout /outpost.goauthentik.io/sign_out 302
|
||||||
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
||||||
@@ -86,6 +120,17 @@ sync.example.com {
|
|||||||
# vanguard:clips
|
# vanguard:clips
|
||||||
clips.example.com {
|
clips.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
||||||
redir /logout /outpost.goauthentik.io/sign_out 302
|
redir /logout /outpost.goauthentik.io/sign_out 302
|
||||||
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
||||||
@@ -111,6 +156,17 @@ clips.example.com {
|
|||||||
# vanguard:live
|
# vanguard:live
|
||||||
live.example.com {
|
live.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
||||||
redir /logout /outpost.goauthentik.io/sign_out 302
|
redir /logout /outpost.goauthentik.io/sign_out 302
|
||||||
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
||||||
@@ -167,6 +223,17 @@ groups: {http.request.header.X-Authentik-Groups}"
|
|||||||
# vanguard:bot
|
# vanguard:bot
|
||||||
bot.example.com {
|
bot.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
# /logout on every gated host = sign out of the Discord login (domain-wide session)
|
||||||
redir /logout /outpost.goauthentik.io/sign_out 302
|
redir /logout /outpost.goauthentik.io/sign_out 302
|
||||||
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
# Authentik forward-auth (embedded outpost). Needs a Proxy Provider in Authentik — domain-level for the
|
||||||
@@ -188,17 +255,50 @@ bot.example.com {
|
|||||||
# vanguard:git
|
# vanguard:git
|
||||||
git.example.com {
|
git.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
reverse_proxy 127.0.0.1:3000
|
reverse_proxy 127.0.0.1:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
# vanguard:auth
|
# vanguard:auth
|
||||||
auth.example.com {
|
auth.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
reverse_proxy 127.0.0.1:9000
|
reverse_proxy 127.0.0.1:9000
|
||||||
}
|
}
|
||||||
|
|
||||||
# vanguard:watch
|
# vanguard:watch
|
||||||
watch.example.com {
|
watch.example.com {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
log {
|
||||||
|
output stderr
|
||||||
|
format filter {
|
||||||
|
wrap json
|
||||||
|
fields {
|
||||||
|
request>headers>Cookie delete
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>X-Stream-Auth delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
reverse_proxy 127.0.0.1:8096
|
reverse_proxy 127.0.0.1:8096
|
||||||
}
|
}
|
||||||
|
|||||||
19
LOGGING.md
19
LOGGING.md
@@ -10,9 +10,19 @@ upstream of it is fixed now, so the choice later is a collector config, not a co
|
|||||||
| **journald** | our systemd units: `dumpd`, `normalize-sweep`, `trash-purge`, `deploy-pending`, `vgdeband` (transient), `deploy.sh` output | `journalctl -u <unit>`; collectors: Alloy `loki.source.journal`, Vector `journald`, VictoriaLogs journald ingestion, Netdata's journal plugin |
|
| **journald** | our systemd units: `dumpd`, `normalize-sweep`, `trash-purge`, `deploy-pending`, `vgdeband` (transient), `deploy.sh` output | `journalctl -u <unit>`; collectors: Alloy `loki.source.journal`, Vector `journald`, VictoriaLogs journald ingestion, Netdata's journal plugin |
|
||||||
| **Docker json-file logs** | every container (`docker-compose.yml`), rotated 5 × 50 MB each | `docker logs <name>`; collectors read `/var/lib/docker/containers/*/*-json.log` and get the container **labels** below for free |
|
| **Docker json-file logs** | every container (`docker-compose.yml`), rotated 5 × 50 MB each | `docker logs <name>`; collectors read `/var/lib/docker/containers/*/*-json.log` and get the container **labels** below for free |
|
||||||
|
|
||||||
Every collector on the shortlist reads both. Metrics are the same story: node_exporter / cAdvisor expose the
|
Every collector on the shortlist reads both. Metrics are the same story: see the taps table below — all four endpoints are on now, on loopback.
|
||||||
Prometheus text format that Alloy, vmagent and Netdata all scrape; MediaMTX (`metrics: yes`) and Caddy have
|
|
||||||
endpoints too, currently off.
|
## Metrics taps (all Prometheus text format, loopback only — scrape them with Alloy, vmagent or Netdata)
|
||||||
|
|
||||||
|
| endpoint | what | from |
|
||||||
|
|---|---|---|
|
||||||
|
| `http://127.0.0.1:9100/metrics` | host: CPU, RAM, disk space + I/O, network, load, md RAID | node_exporter (host network, pid host) |
|
||||||
|
| `http://127.0.0.1:9101/metrics` | per container: CPU, memory, network, block I/O — labelled with `vanguard.service` / `vanguard.category` | cAdvisor |
|
||||||
|
| `http://127.0.0.1:9998/metrics` | MediaMTX: per path bytes/s, readers, publishers, WebRTC/HLS/RTMP sessions, dropped packets | MediaMTX `metrics: yes` |
|
||||||
|
| `http://127.0.0.1:2019/metrics` | Caddy: requests by host/status, durations, TLS | Caddy admin API (`servers { metrics }`) |
|
||||||
|
|
||||||
|
None of these are reachable from outside (loopback binds; nothing in the firewall). Alert thresholds to start with:
|
||||||
|
disk on `/srv` > 85 %, load > cores for 15 min, a container restarting, MediaMTX dropped packets rising on a live path.
|
||||||
|
|
||||||
## Container labels (set in `docker-compose.yml`, `x-logging` anchor + `labels:`)
|
## Container labels (set in `docker-compose.yml`, `x-logging` anchor + `labels:`)
|
||||||
|
|
||||||
@@ -74,8 +84,7 @@ On for every site block (`CADDY_LOG` in `configure.py`): JSON to stderr; `Cookie
|
|||||||
|
|
||||||
- **Grafana + Loki**: `alloy` container with `loki.source.journal` + `loki.source.docker` (labels from the container
|
- **Grafana + Loki**: `alloy` container with `loki.source.journal` + `loki.source.docker` (labels from the container
|
||||||
labels above) → `loki` on loopback → `grafana` behind Authentik OIDC (third provider, like Jellyfin/Gitea).
|
labels above) → `loki` on loopback → `grafana` behind Authentik OIDC (third provider, like Jellyfin/Gitea).
|
||||||
Metrics: Alloy scrapes node_exporter, cAdvisor, MediaMTX `:9998/metrics`, Caddy `:2019/metrics` → Prometheus or
|
Metrics: Alloy scrapes the four taps above → Prometheus or VictoriaMetrics, same Grafana.
|
||||||
VictoriaMetrics, same Grafana.
|
|
||||||
- **VictoriaLogs**: single container, journald + docker via its agent or Vector; built-in UI behind the Caddy
|
- **VictoriaLogs**: single container, journald + docker via its agent or Vector; built-in UI behind the Caddy
|
||||||
forward-auth gate (`media-admins`). VictoriaMetrics beside it for metrics.
|
forward-auth gate (`media-admins`). VictoriaMetrics beside it for metrics.
|
||||||
- **Netdata**: single container, host + container metrics and alerts (Discord) out of the box, journald viewer;
|
- **Netdata**: single container, host + container metrics and alerts (Discord) out of the box, journald viewer;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
|
|||||||
| normalize sweep | editing standard, one copy per clip | live |
|
| normalize sweep | editing standard, one copy per clip | live |
|
||||||
| nftables | host firewall (22, 80/443, 1935, 2222, 3306, 8890/udp, 22000) | live |
|
| nftables | host firewall (22, 80/443, 1935, 2222, 3306, 8890/udp, 22000) | live |
|
||||||
| Caddy | TLS + routing for all hostnames; JSON access logs per site (identity headers kept, cookies/tokens dropped) | live (access logs: deploy pending) |
|
| Caddy | TLS + routing for all hostnames; JSON access logs per site (identity headers kept, cookies/tokens dropped) | live (access logs: deploy pending) |
|
||||||
|
| Metrics taps | node_exporter (host), cAdvisor (containers), MediaMTX and Caddy metrics — Prometheus format on loopback, no store yet | **committed 2026-11-01, deploy pending** |
|
||||||
| Logging framework | rotated Docker logs + `vanguard.service/category` labels on every container; dumpd/sweep/vgdeband/deploy emit logfmt events (`LOGGING.md`); store not chosen (Loki vs VictoriaLogs vs Netdata) | **committed 2026-11-01, deploy pending** (recreates every container) |
|
| Logging framework | rotated Docker logs + `vanguard.service/category` labels on every container; dumpd/sweep/vgdeband/deploy emit logfmt events (`LOGGING.md`); store not chosen (Loki vs VictoriaLogs vs Netdata) | **committed 2026-11-01, deploy pending** (recreates every container) |
|
||||||
| Authentik (+Postgres, Redis) | Discord login, groups, guild-membership policy | live |
|
| Authentik (+Postgres, Redis) | Discord login, groups, guild-membership policy | live |
|
||||||
| Jellyfin | watch page, SSO via Authentik, direct play only | live |
|
| Jellyfin | watch page, SSO via Authentik, direct play only | live |
|
||||||
|
|||||||
11
configure.py
11
configure.py
@@ -303,7 +303,13 @@ groups: {{http.request.header.X-Authentik-Groups}}"
|
|||||||
{CADDY_LOG} reverse_proxy 127.0.0.1:8096
|
{CADDY_LOG} reverse_proxy 127.0.0.1:8096
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
Path("Caddyfile").write_text(f"""# vanguard:main
|
Path("Caddyfile").write_text(f"""{{
|
||||||
|
servers {{
|
||||||
|
metrics # Prometheus metrics at http://127.0.0.1:2019/metrics (LOGGING.md)
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
# vanguard:main
|
||||||
{domain} {{
|
{domain} {{
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
{gate_any} # who does Authentik say I am? (debug: shows exactly the identity headers the apps receive)
|
{gate_any} # who does Authentik say I am? (debug: shows exactly the identity headers the apps receive)
|
||||||
@@ -393,7 +399,8 @@ rtspAddress: 127.0.0.1:8554
|
|||||||
rtspTransports: [tcp]
|
rtspTransports: [tcp]
|
||||||
rtspEncryption: "no"
|
rtspEncryption: "no"
|
||||||
moq: no
|
moq: no
|
||||||
metrics: no
|
metrics: yes # Prometheus text format for whichever metrics store is chosen (LOGGING.md); loopback only
|
||||||
|
metricsAddress: :9998
|
||||||
pprof: no
|
pprof: no
|
||||||
|
|
||||||
# live portal (served to the browser only via Caddy + Authentik on live.HOST; these ports are published on 127.0.0.1)
|
# live portal (served to the browser only via Caddy + Authentik on live.HOST; these ports are published on 127.0.0.1)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ services:
|
|||||||
- "127.0.0.1:8888:8888" # HLS -> Caddy live.HOST/hls/*
|
- "127.0.0.1:8888:8888" # HLS -> Caddy live.HOST/hls/*
|
||||||
- "127.0.0.1:8889:8889" # WebRTC -> Caddy live.HOST/webrtc/*
|
- "127.0.0.1:8889:8889" # WebRTC -> Caddy live.HOST/webrtc/*
|
||||||
- "127.0.0.1:9997:9997" # API -> Caddy live.HOST/api/*
|
- "127.0.0.1:9997:9997" # API -> Caddy live.HOST/api/*
|
||||||
|
- "127.0.0.1:9998:9998" # Prometheus metrics (per-stream bitrate, readers, drops) -> scraper on the host
|
||||||
volumes:
|
volumes:
|
||||||
- ./mediamtx.yml:/mediamtx.yml:ro
|
- ./mediamtx.yml:/mediamtx.yml:ro
|
||||||
- ./bin/mtx-on-ready.sh:/vanguard/on-ready.sh:ro # is-live notice + WebRTC audio mirror (runOnReady)
|
- ./bin/mtx-on-ready.sh:/vanguard/on-ready.sh:ro # is-live notice + WebRTC audio mirror (runOnReady)
|
||||||
@@ -299,6 +300,38 @@ services:
|
|||||||
- gitea
|
- gitea
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# ---- metrics taps (LOGGING.md): Prometheus text format on loopback, scraped by whichever store is chosen later
|
||||||
|
node_exporter:
|
||||||
|
logging: *vglog
|
||||||
|
labels: { vanguard.service: node_exporter, vanguard.category: infra }
|
||||||
|
image: quay.io/prometheus/node-exporter:latest
|
||||||
|
container_name: node_exporter
|
||||||
|
network_mode: host # listens on 127.0.0.1:9100 only
|
||||||
|
pid: host
|
||||||
|
command: ["--path.rootfs=/host", "--web.listen-address=127.0.0.1:9100", "--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|var/lib/docker/.+)($$|/)"]
|
||||||
|
volumes:
|
||||||
|
- /:/host:ro,rslave
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
cadvisor:
|
||||||
|
logging: *vglog
|
||||||
|
labels: { vanguard.service: cadvisor, vanguard.category: infra }
|
||||||
|
image: gcr.io/cadvisor/cadvisor:latest
|
||||||
|
container_name: cadvisor
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:9101:8080" # per-container CPU / memory / network / disk
|
||||||
|
command: ["--housekeeping_interval=10s", "--docker_only=true", "--store_container_labels=false", "--whitelisted_container_labels=vanguard.service,vanguard.category"]
|
||||||
|
volumes:
|
||||||
|
- /:/rootfs:ro
|
||||||
|
- /var/run:/var/run:ro
|
||||||
|
- /sys:/sys:ro
|
||||||
|
- /var/lib/docker/:/var/lib/docker:ro
|
||||||
|
- /dev/disk/:/dev/disk:ro
|
||||||
|
devices:
|
||||||
|
- /dev/kmsg
|
||||||
|
privileged: true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
copyparty_hist:
|
copyparty_hist:
|
||||||
caddy_data:
|
caddy_data:
|
||||||
|
|||||||
Reference in New Issue
Block a user