Compare commits

..
Author SHA1 Message Date
dannygroenewegen 62696e64b7 chore: prepare config versions for release 2026-08-23 16:39:09 +02:00
dannygroenewegen ec65d8d782 fix: reduce cadvisor collection overhead
Drop unused process metrics and disable root cgroup stats (host-wide
totals already come from prometheus.exporter.unix).
2026-08-23 16:37:40 +02:00
dannygroenewegen 83b39ae95e fix: remove unused default-alert-provider causing continuous provisioning errors 2026-08-23 15:38:56 +02:00
dannygroenewegen c4bfaa88fd fix: default GRAFANA_DOMAIN to $DOMAIN explicitly in .env.sample 2026-08-23 15:33:42 +02:00
dannygroenewegen 382f6353fc fix: strip monitoring. prefix from instance labels for cleaner dashboard views 2026-08-20 16:50:25 +02:00
dannygroenewegen c8f74e2a3c fix: refresh dashboard variables on time range change, not just page load 2026-08-20 14:07:10 +02:00
dannygroenewegen 6dbb6c5f55 fix: mount containerd socket read-only, avoid abra#900 2026-08-17 16:09:44 +02:00
dannygroenewegen 17c4f6237b docs: rewrite README and release notes for the Alloy migration 2026-08-17 15:58:04 +02:00
dannygroenewegen 50fc916107 feat: add healthchecks to Alloy, Prometheus and Pushgateway 2026-08-17 15:08:08 +02:00
dannygroenewegenandClaude Sonnet 5 7480a8d6ab fix abra linting secret length
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 14:59:03 +02:00
dannygroenewegen 067f013fb7 fix: don't expose Alloy's web UI by default, but optionally with compose.alloy-webui.yml 2026-08-17 14:48:44 +02:00
dannygroenewegen 536fa7f37d fix Traefik dashboard 2026-08-17 13:37:27 +02:00
dannygroenewegen fafd1f89f0 fix: discover containers by task instead of service for scraping
Swarm service discovery misses dnsrr-mode services like traefik; switch
to swarm task discovery, scoped to this host, with a stable instance
label across redeploys and a default port when none is labeled.
2026-08-10 21:24:38 +02:00
dannygroenewegen 7ab6cc74be fix: make swarm/stacks dashboards work with old and new metric data
Replace node_meta-based joins with label_replace() of the instance
label, so old (pull-model) and new (Alloy push) series for the same
host render as one continuous series. Each rewritten query has a TODO
noting the simpler form to use once old data ages out of retention.
2026-08-07 16:57:39 +02:00
dannygroenewegen 0f989c4567 fix: drop unfiltered discovery.docker.containers.targets from default scrape
prometheus.exporter.cadvisor.docker.targets already reports resource
metrics for every container on the host. The unfiltered discovery just
added targets with internal overlay IPs, most of which failed to
scrape anything useful.
2026-08-07 15:52:31 +02:00
dannygroenewegen 402e022114 feat: separate scrape_auth secret, configurable usernames
Added an optional scrape_auth secret for authenticating scrapes of
containers that opt in via prometheus.io/auth labels, no longer
reusing the basic_auth secret meant for Prometheus/Loki writes.

Made usernames for both write endpoints and authenticated scraping
configurable in .env.
2026-08-07 15:52:19 +02:00
dannygroenewegen 7f3d98fd9f - Restore GRAFANA_DOMAIN
- Start documenting the migration in release/next
- Various cleanups
2026-08-07 14:47:22 +02:00
20 changed files with 10 additions and 264 deletions
+1 -18
View File
@@ -20,9 +20,6 @@ SECRET_BASIC_AUTH_VERSION=v1
# server is remote
# PROMETHEUS_REMOTE_WRITE_URL=https://prometheus.$DOMAIN/api/v1/write
# Enable container filesystem usage metrics (expensive du-scan, ~30% CPU on large hosts)
# CADVISOR_DISK_USAGE=1
# Enable authenticated scraping of containers that opt in via
# prometheus.io/auth=basic or prometheus.io/auth=bearer labels (used as
# password/bearer token respectively). Insert it with:
@@ -53,13 +50,6 @@ SECRET_BASIC_AUTH_VERSION=v1
# Not needed if you just want to read local log files — use SYSLOG_FILES instead.
# SYSLOG=1
# COMPOSE_FILE="$COMPOSE_FILE:compose.syslog.yml"
#
# Scrape Docker-Events via official docker-cli image using docker socket
# COMPOSE_FILE="$COMPOSE_FILE:compose.docker-events.yml"
# Monitor physical disks health
# COMPOSE_FILE="$COMPOSE_FILE:compose.smartctl.yml"
# Monitoring Server
#
@@ -94,7 +84,7 @@ SECRET_BASIC_AUTH_VERSION=v1
# SECRET_GF_ADMINPASSWD_VERSION=v1
## Grafana's own domain. Defaults to $DOMAIN
## Change the value if you want Grafana on another domain.
# GRAFANA_DOMAIN=$DOMAIN
GRAFANA_DOMAIN=$DOMAIN
#
## Single-Sign-On with OIDC
# COMPOSE_FILE="$COMPOSE_FILE:compose.grafana-oidc.yml"
@@ -133,10 +123,3 @@ SECRET_BASIC_AUTH_VERSION=v1
# Node memory usage alert will trigger when memory usage is above the given number in percent
#ALERT_NODE_MEMORY_USAGE=85
# Tell Traefik to keep access logs (could be very verbose). See Access
# logs on Traefik recipe.
#ALLOY_ACCESS_LOGS=true
#PROMETHEUS_ACCESS_LOGS=true
#LOKI_ACCESS_LOGS=true
#GRAFANA_ACCESS_LOGS=true
-1
View File
@@ -1 +0,0 @@
* @coop-cloud/monitoring-ng-maintainers
-35
View File
@@ -1,35 +0,0 @@
# Monitoring NG Recipe Maintenance
All contributions should be made via a pull request. This is to ensure a
certain quality and consistency, that others can rely on.
## Maintainer Responsibilities
A recipe maintainer has the following responsibilities:
- Respond to pull requests / issues within two weeks
- Make image security updates within a week
- Make image major updates every three months
In order to fullfill these responsibilities a recipe maintainer:
- Has to watch the repository (to get notifications)
- Needs to make sure renovate is configured properly
## Pull Requests
A pull request can be merged if it is approved by at least one maintainer. For
pull requests opened by a maintainer they need to be approved by another
maintainer. Even though it is okay to merge a pull request with one approval, it
is always better if all maintainers looked at the pull request and approved it.
## Become a maintainer
Everyone can apply to be a recipe maintainer:
1. Watch the repository to always get updates
2. Simply add your self to the list in the [README.md](./README.md) and open a new pull request with the change.
3. Once the pull request gets merged you will be added to the
[monitoring-ng maintainers
team](https://git.coopcloud.tech/org/coop-cloud/teams/monitoring-ng-maintainers).
4. Join the room
[#cc-|-monitoring-ng-maintenance:matrix.org](#cc-|-monitoring-ng-maintenance:matrix.org) and chat to other maintainers.
+1 -17
View File
@@ -7,7 +7,7 @@ It's based heavily on the [monitoring-lite](https://git.coopcloud.tech/coop-clou
Metrics and logs are collected by [Grafana Alloy](https://grafana.com/docs/alloy/latest/) and pushed to a central Prometheus/Loki (via `remote_write`/`loki push`). Every `monitoring-ng` instance runs its own Alloy, whether or not it also runs the central stack.
<!-- metadata -->
- **Maintainer**: [@coop-cloud/monitoring-ng-maintainers](https://git.coopcloud.tech/org/coop-cloud/teams/monitoring-ng-maintainers)
- **Category**: Apps
- **Status**: 2, beta
- **Image**: [`grafana/grafana`](https://hub.docker.com/r/grafana/grafana), 4, upstream
@@ -54,14 +54,6 @@ This is what a gathering host pushes into. It also runs its own Alloy, so it mon
## Additional features
### Scrape docker events
Just uncomment the following line in your .env to collect docker events using
the official docker-cli image and the docker socket:
```
COMPOSE_FILE="$COMPOSE_FILE:compose.docker-events.yml"
```
### Discovering metrics from other apps
Alloy auto-discovers and scrapes other Docker Swarm services running on the same host, on the `proxy` network, that opt in via labels. No manual scrape config needed. On the app's `compose.yml`:
@@ -174,11 +166,3 @@ It is possible to enable the following alerts, by uncommenting the corresponding
- node disk space: `ALERT_NODE_DISK_SPACE_LEFT`
- node memory usage: `ALERT_NODE_MEMORY_USAGE`
## smart monitoring
To be able monitor hard drive health data, you need to configure
`smartd` to run on the host system, and also the
`collect-smartctl-json.sh` script provided here (via cronjob or as
a `smartd` hook). This is a limitation on Docker Swarm, which prevents
the `smartctl_exporter` from running on privileged mode.
+1 -1
View File
@@ -10,7 +10,7 @@ export PROMETHEUS_YML_VERSION=v2
export MATRIX_ALERTMANAGER_CONFIG_VERSION=v1
export MATRIX_ALERTMANAGER_ENTRYPOINT_VERSION=v1
export GF_ALERTS_NODE_VERSION=v3
export CONFIG_ALLOY_VERSION=v11
export CONFIG_ALLOY_VERSION=v1
# migrates secrets from old names to new names by reading values from the
# running containers on the server and re-inserting them under the new names.
-6
View File
@@ -1,6 +0,0 @@
[Unit]
Description=Collect SMART data
[Service]
Type=oneshot
ExecStart=/usr/local/bin/collect-smartctl-json.sh
-61
View File
@@ -1,61 +0,0 @@
#! /bin/bash
# Adapted from https://github.com/prometheus-community/smartctl_exporter/blob/master/collect-smartctl-json.sh
# Data directory to dump smartctl output
# This directory will be created if it doesn't exist
data_dir="/var/lib/smartmontools/json"
# The original script used --xall but that doesn't work
# This matches the command in readSMARTctl()
smartctl_args="--json --info --health --attributes --tolerance=verypermissive \
--nocheck=standby --format=brief --log=error"
# Ignore this devices
smartctl_ignore_dev_regex="^(/dev/bus)"
# Determine the json query tool to use
if command -v jq >/dev/null; then
json_tool="jq"
json_args="--raw-output"
elif command -v yq >/dev/null; then
json_tool="yq"
json_args="--unwrapScalar"
else
echo -e "One of 'yq' or 'jq' is required. Please try again after \
installing one of them"
exit 1
fi
if [[ ! "${UID}" -eq 0 ]] && ! command -v sudo >/dev/null; then
# Not root and sudo doesn't exist
echo "sudo does not exist. Please run this as root"
exit 1
fi
SUDO="sudo"
if [[ "${UID}" -eq 0 ]]; then
# Don't use sudo if root
SUDO=""
fi
[[ ! -d "${data_dir}" ]] && mkdir --parents "${data_dir}"
if [[ $# -ne 0 ]]; then
devices="${1}"
else
devices="$(smartctl --scan --json | "${json_tool}" "${json_args}" \
".devices[].name | select(test(\"${smartctl_ignore_dev_regex}\") | not)")"
mapfile -t devices <<< "${devices[@]}"
fi
for device in "${devices[@]}"
do
echo -n "Collecting data for '${device}'..."
# shellcheck disable=SC2086
data="$($SUDO smartctl ${smartctl_args} ${device})"
# Accommodate a smartmontools pre-7.3 bug
data=${data#" Pending defect count:"}
device_name="$(basename "${device}")"
echo -e "\tSaving to ${device_name}.json"
echo "${data}" > "${data_dir}/${device_name}.json"
done
-9
View File
@@ -1,9 +0,0 @@
[Unit]
Description=Collect SMART data
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
-1
View File
@@ -14,4 +14,3 @@ services:
- "traefik.http.routers.${STACK_NAME}-alloy.tls=true"
- "traefik.http.routers.${STACK_NAME}-alloy.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}-alloy.middlewares=basicauth@file"
- "traefik.http.routers.${STACK_NAME}-alloy.observability.accesslogs=${ALLOY_ACCESS_LOGS:-false}"
-20
View File
@@ -1,20 +0,0 @@
version: "3.8"
services:
docker-events:
image: docker:29.8.1-cli
command: >
docker events
--format '{{json .}}'
--filter type=container
--filter event=start
--filter event=die
--filter event=kill
--filter event=oom
--filter event=restart
--filter event=stop
--filter event=health_status
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
deploy:
restart_policy:
condition: on-failure
+2 -3
View File
@@ -2,7 +2,7 @@ version: '3.8'
services:
grafana:
image: grafana/grafana:13.2.3
image: grafana/grafana:13.0.6
volumes:
- grafana-data:/var/lib/grafana:rw
secrets:
@@ -39,11 +39,10 @@ services:
- "traefik.enable=true"
- "traefik.swarm.network=proxy"
- "traefik.http.services.${STACK_NAME}-grafana.loadbalancer.server.port=3000"
- "traefik.http.routers.${STACK_NAME}-grafana.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.${STACK_NAME}-grafana.rule=Host(`${GRAFANA_DOMAIN:-$DOMAIN}`)"
- "traefik.http.routers.${STACK_NAME}-grafana.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}-grafana.tls=true"
- "traefik.http.routers.${STACK_NAME}-grafana.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}-grafana.observability.accesslogs=${GRAFANA_ACCESS_LOGS:-false}"
healthcheck:
test: "wget -q http://localhost:3000/healthz -O/dev/null"
interval: 5s
-1
View File
@@ -34,7 +34,6 @@ services:
- "traefik.http.routers.${STACK_NAME}-loki.tls=true"
- "traefik.http.routers.${STACK_NAME}-loki.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}-loki.middlewares=basicauth@file"
- "traefik.http.routers.${STACK_NAME}-loki.observability.accesslogs=${LOKI_ACCESS_LOGS:-false}"
configs:
+1 -2
View File
@@ -2,7 +2,7 @@ version: '3.8'
services:
prometheus:
image: prom/prometheus:v3.15.0
image: prom/prometheus:v3.12.0
secrets:
- basic_auth
volumes:
@@ -38,7 +38,6 @@ services:
- "traefik.http.routers.${STACK_NAME}-prometheus.tls=true"
- "traefik.http.routers.${STACK_NAME}-prometheus.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}-prometheus.middlewares=basicauth@file"
- "traefik.http.routers.${STACK_NAME}-prometheus.observability.accesslogs=${PROMETHEUS_ACCESS_LOGS:-false}"
configs:
prometheus_yml:
-17
View File
@@ -1,17 +0,0 @@
---
version: "3.8"
services:
smartctl:
image: "prometheuscommunity/smartctl-exporter:v0.14.0"
volumes:
- "/var/lib/smartmontools/json:/debug"
command:
- "--smartctl.fake-data"
- "--smartctl.interval=1h"
networks:
- "proxy"
deploy:
labels:
- "prometheus.io/scrape=true"
- "prometheus.io/port=9633"
- "prometheus.io/path=/metrics"
+4 -4
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
app:
image: grafana/alloy:v1.20.1
image: grafana/alloy:v1.18.1
hostname: "${DOMAIN}"
configs:
- source: config_alloy
@@ -46,14 +46,14 @@ services:
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/localhost/12345 && printf 'GET /-/ready HTTP/1.0\r\nHost: localhost\r\n\r\n' >&3 && head -1 <&3 | grep -q 200"]
interval: 5s
timeout: 10s
retries: 10
start_period: 30s
retries: 3
start_period: 10s
deploy:
restart_policy:
condition: on-failure
labels:
- "backupbot.backup=${ENABLE_BACKUPS:-true}"
- "coop-cloud.${STACK_NAME}.version=2.2.0+v1.18.1"
- "coop-cloud.${STACK_NAME}.version=1.6.0+v1.8.1"
configs:
config_alloy:
template_driver: golang
-57
View File
@@ -14,11 +14,7 @@ discovery.docker "linux" {
{{ if ne (env "PROMETHEUS_REMOTE_WRITE_URL") "" }}
prometheus.exporter.cadvisor "docker" {
docker_only = true
{{ if eq (env "CADVISOR_DISK_USAGE") "1" }}
enabled_metrics = ["cpu", "cpuLoad", "disk", "diskIO", "memory", "network"]
{{ else }}
enabled_metrics = ["cpu", "cpuLoad", "diskIO", "memory", "network"]
{{ end }}
// host-wide totals already come from prometheus.exporter.unix
disable_root_cgroup_stats = true
}
@@ -52,32 +48,7 @@ prometheus.scrape "default" {
prometheus.exporter.cadvisor.docker.targets,
)
forward_to = [prometheus.relabel.container_meta.receiver]
}
prometheus.relabel "container_meta" {
forward_to = [prometheus.remote_write.prometheus.receiver]
// remove sha tail: nginx:1.31.1@sha256:608a... -> nginx:1.31.1
rule {
source_labels = ["image"]
regex = "([^@]+)@sha256:.*"
target_label = "image"
replacement = "$1"
}
// split image in name and tag
rule {
source_labels = ["image"]
regex = "(.+):[^:/]+"
target_label = "image_name"
replacement = "$1"
}
rule {
source_labels = ["image"]
regex = ".+:([^:/]+)"
target_label = "image_tag"
replacement = "$1"
}
}
prometheus.remote_write "prometheus" {
@@ -315,35 +286,7 @@ loki.source.docker "docker" {
loki.source.journal "journal" {
path = "/rootfs/var/log/journal"
labels = { job = "{{ env "DOMAIN" }}" }
relabel_rules = loki.relabel.journal.rules
forward_to = [loki.process.journal.receiver]
}
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
rule {
source_labels = ["__journal_syslog_identifier"]
target_label = "ident"
}
rule {
source_labels = ["__journal_priority_keyword"]
target_label = "priority"
}
}
// drop network db stats (15-20% of kernel logs)
loki.process "journal" {
forward_to = [loki.write.loki.receiver]
stage.drop {
expression = ".*NetworkDB stats.*"
drop_counter_reason = "networkdb_stats"
}
}
{{ end }}
-1
View File
@@ -1 +0,0 @@
alloy: split image label in "image_name" and "image_tag", relabel journald logs and drop NetworkDB stats
-1
View File
@@ -1 +0,0 @@
allow scraping of docker events via new compose.docker-events.yml
-3
View File
@@ -59,6 +59,3 @@ See the README's "Auto-discovering metrics from other apps" section.
The Swarm, Stacks and Traefik dashboards were reworked to show old (pull-model)
and new (Alloy push-model) data as one continuous line, so you don't lose history
across the migration.
The disk usage per container was disabled per default because of the CPU-expensive
filesystem scan (30% cpu increase). Enable by uncommenting env CADVISOR_DISK_USAGE=1
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}