From a64d78142e00e16d0cede710648409187a7782ad Mon Sep 17 00:00:00 2001 From: Jonas Thelemann Date: Thu, 22 Oct 2026 17:52:31 +0200 Subject: [PATCH 1/4] feat: add the swarm scheduler exporter --- artifacts/docs/README.md | 6 +++- .../prometheus/configurations/prometheus.yaml | 4 +++ .../swarm-scheduler-exporter/compose.yaml | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/production/swarm-scheduler-exporter/compose.yaml diff --git a/artifacts/docs/README.md b/artifacts/docs/README.md index daad8ffe..903c2162 100644 --- a/artifacts/docs/README.md +++ b/artifacts/docs/README.md @@ -8,7 +8,7 @@ Profiles group services so you can deploy subsets on demand. Activate with `darg ### analytics -Services: cadvisor, grafana, node-exporter, postgres, prometheus, sqitch, traefik +Services: cadvisor, grafana, node-exporter, postgres, prometheus, sqitch, swarm-scheduler-exporter, traefik ### default @@ -156,6 +156,10 @@ You can access the event streaming platform's ui at [redpanda.app.localhost](htt You cannot access the database migrations directly. +### swarm-scheduler-exporter *(production only)* + +The exporter that reports the swarm's state, its nodes and each service's tasks to the metrics monitoring. + ### traefik You can access the reverse proxy's dashboard at [traefik.app.localhost](https://traefik.app.localhost/). diff --git a/src/development/prometheus/configurations/prometheus.yaml b/src/development/prometheus/configurations/prometheus.yaml index 5e60ae2b..996cd1b8 100644 --- a/src/development/prometheus/configurations/prometheus.yaml +++ b/src/development/prometheus/configurations/prometheus.yaml @@ -33,3 +33,7 @@ scrape_configs: - targets: - redpanda:9644 metrics_path: /public_metrics + + - job_name: "swarm-scheduler-exporter" + static_configs: + - targets: ["swarm-scheduler-exporter:8888"] diff --git a/src/production/swarm-scheduler-exporter/compose.yaml b/src/production/swarm-scheduler-exporter/compose.yaml new file mode 100644 index 00000000..064eede4 --- /dev/null +++ b/src/production/swarm-scheduler-exporter/compose.yaml @@ -0,0 +1,28 @@ +services: + swarm-scheduler-exporter: + # The exporter that reports the swarm's state, its nodes and each service's tasks to the metrics monitoring. + deploy: + labels: + - dargstack.profiles=analytics + placement: + constraints: + # Only a manager's docker daemon serves the cluster-wide data the exporter reads. + - node.role == manager + resources: + limits: + memory: 128m + reservations: + memory: 64m + update_config: + order: start-first + group_add: + # The image runs unprivileged and needs the manager node's docker group to read the socket. + - "998" + image: ghcr.io/leinardi/swarm-scheduler-exporter:v1.3.2 + logging: + driver: json-file + options: + max-file: "3" + max-size: 20m + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro From d0737942d6dda44fd518827f5849ae3ba4735eec Mon Sep 17 00:00:00 2001 From: Jonas Thelemann Date: Thu, 22 Oct 2026 18:35:11 +0200 Subject: [PATCH 2/4] feat: read the docker api through an allowlisted socket proxy --- artifacts/docs/README.md | 6 ++- .../docker-socket-proxy/compose.yaml | 40 +++++++++++++++++++ .../configurations/allowlist.env | 39 ++++++++++++++++++ .../swarm-scheduler-exporter/compose.yaml | 23 ++++++++--- 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 src/production/docker-socket-proxy/compose.yaml create mode 100644 src/production/docker-socket-proxy/configurations/allowlist.env diff --git a/artifacts/docs/README.md b/artifacts/docs/README.md index 903c2162..640c3053 100644 --- a/artifacts/docs/README.md +++ b/artifacts/docs/README.md @@ -8,7 +8,7 @@ Profiles group services so you can deploy subsets on demand. Activate with `darg ### analytics -Services: cadvisor, grafana, node-exporter, postgres, prometheus, sqitch, swarm-scheduler-exporter, traefik +Services: cadvisor, docker-socket-proxy, grafana, node-exporter, postgres, prometheus, sqitch, swarm-scheduler-exporter, traefik ### default @@ -65,6 +65,10 @@ You can see how changes in the database end up in the event stream using `redpan You can check the database connector's setup logs using `portainer`. +### docker-socket-proxy *(production only)* + +The allowlisted proxy in front of the manager's docker socket, for the swarm scheduler exporter. + ### elasticsearch You cannot access the search engine via a web interface. diff --git a/src/production/docker-socket-proxy/compose.yaml b/src/production/docker-socket-proxy/compose.yaml new file mode 100644 index 00000000..c2110c52 --- /dev/null +++ b/src/production/docker-socket-proxy/compose.yaml @@ -0,0 +1,40 @@ +networks: + docker-socket-proxy: + # Internal: nothing outside the exporter can reach the proxy. + driver: overlay + driver_opts: + com.docker.network.driver.mtu: "1400" + internal: true +services: + docker-socket-proxy: + # The allowlisted proxy in front of the manager's docker socket, for the swarm scheduler exporter. + cap_drop: + - ALL + deploy: + labels: + - dargstack.profiles=analytics + placement: + constraints: + # Only a manager's docker daemon answers the cluster-wide service, node and task calls. + - node.role == manager + resources: + limits: + memory: 64m + reservations: + memory: 32m + update_config: + order: start-first + env_file: + - ~~/src/production/docker-socket-proxy/configurations/allowlist.env + image: ghcr.io/tecnativa/docker-socket-proxy:v0.5.0 + logging: + driver: json-file + options: + max-file: "3" + max-size: 20m + networks: + - docker-socket-proxy + security_opt: + - no-new-privileges:true + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/src/production/docker-socket-proxy/configurations/allowlist.env b/src/production/docker-socket-proxy/configurations/allowlist.env new file mode 100644 index 00000000..42eaa606 --- /dev/null +++ b/src/production/docker-socket-proxy/configurations/allowlist.env @@ -0,0 +1,39 @@ +# The Docker API sections the proxy forwards to the daemon, for the swarm scheduler exporter. +# Every ACL variable of the image is set explicitly, relying on no default: the image enables EVENTS and VERSION by default. + +# Needed by the exporter. PING is how the client negotiates the API version, so VERSION stays off. +PING=1 +EVENTS=1 +SERVICES=1 +NODES=1 +TASKS=1 +# CONTAINERS: only for the exporter's container metrics, which the stack does not use. +CONTAINERS=0 + +# Not needed: denied. +ALLOW_RESTARTS=0 +ALLOW_START=0 +ALLOW_STOP=0 +AUTH=0 +BUILD=0 +COMMIT=0 +CONFIGS=0 +DISTRIBUTION=0 +EXEC=0 +GRPC=0 +IMAGES=0 +INFO=0 +NETWORKS=0 +PLUGINS=0 +# POST: 0 denies every method but GET (and HEAD), whatever the section. +POST=0 +SECRETS=0 +SESSION=0 +SWARM=0 +SYSTEM=0 +VERSION=0 +VOLUMES=0 + +# Referenced by the image's haproxy.cfg but not given a default in its environment: set to 0 anyway. +ALLOW_PAUSE=0 +ALLOW_UNPAUSE=0 diff --git a/src/production/swarm-scheduler-exporter/compose.yaml b/src/production/swarm-scheduler-exporter/compose.yaml index 064eede4..dade302d 100644 --- a/src/production/swarm-scheduler-exporter/compose.yaml +++ b/src/production/swarm-scheduler-exporter/compose.yaml @@ -1,6 +1,8 @@ services: swarm-scheduler-exporter: # The exporter that reports the swarm's state, its nodes and each service's tasks to the metrics monitoring. + cap_drop: + - ALL deploy: labels: - dargstack.profiles=analytics @@ -15,14 +17,25 @@ services: memory: 64m update_config: order: start-first - group_add: - # The image runs unprivileged and needs the manager node's docker group to read the socket. - - "998" + environment: + # The exporter reads the Docker API through the allowlisted socket proxy instead of the socket itself. + - DOCKER_HOST=tcp://docker-socket-proxy:2375 + healthcheck: + # The image is distroless and has no shell, so the binary probes its own readiness endpoint. + test: ["CMD", "/usr/local/bin/swarm-scheduler-exporter", "-healthcheck"] + interval: 30s + timeout: 5s + start_period: 90s + retries: 3 image: ghcr.io/leinardi/swarm-scheduler-exporter:v1.3.2 logging: driver: json-file options: max-file: "3" max-size: 20m - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - default + - docker-socket-proxy + read_only: true + security_opt: + - no-new-privileges:true From 071fdeb87b24d6e8cf73a87227ee5dc0303f6bbe Mon Sep 17 00:00:00 2001 From: Jonas Thelemann Date: Thu, 22 Oct 2026 20:25:15 +0200 Subject: [PATCH 3/4] fix(security): remove the ineffective no-new-privileges options docker stack deploy silently ignores security_opt, so the no-new-privileges restriction was never applied to either service --- src/production/docker-socket-proxy/compose.yaml | 2 -- src/production/swarm-scheduler-exporter/compose.yaml | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/production/docker-socket-proxy/compose.yaml b/src/production/docker-socket-proxy/compose.yaml index c2110c52..6d1166d7 100644 --- a/src/production/docker-socket-proxy/compose.yaml +++ b/src/production/docker-socket-proxy/compose.yaml @@ -34,7 +34,5 @@ services: max-size: 20m networks: - docker-socket-proxy - security_opt: - - no-new-privileges:true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/src/production/swarm-scheduler-exporter/compose.yaml b/src/production/swarm-scheduler-exporter/compose.yaml index dade302d..cb7c019f 100644 --- a/src/production/swarm-scheduler-exporter/compose.yaml +++ b/src/production/swarm-scheduler-exporter/compose.yaml @@ -37,5 +37,3 @@ services: - default - docker-socket-proxy read_only: true - security_opt: - - no-new-privileges:true From dca2c8d56730adb63bdd1e8d200c358d8fc330a7 Mon Sep 17 00:00:00 2001 From: Jonas Thelemann Date: Thu, 22 Oct 2026 21:03:02 +0200 Subject: [PATCH 4/4] chore(dargstack): resolve the allowlist env file relative to the compose dargstack 4.20.0 resolves dot-relative env_file entries against the declaring compose's dir, so the stack root prefix workaround comes out. The compatibility floor rises to 4.20.0, so a node running an older CLI refuses the stack until it updates. --- dargstack.yaml | 2 +- src/production/docker-socket-proxy/compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dargstack.yaml b/dargstack.yaml index 1d1d23b4..c862a40a 100644 --- a/dargstack.yaml +++ b/dargstack.yaml @@ -14,7 +14,7 @@ environment: # tag: latest # optional, defaults to "latest" metadata: - compatibility: ">=4.13.0-0 <5.0.0" + compatibility: ">=4.20.0-0 <5.0.0" name: "vibetype" source: name: "maevsi" diff --git a/src/production/docker-socket-proxy/compose.yaml b/src/production/docker-socket-proxy/compose.yaml index 6d1166d7..15540175 100644 --- a/src/production/docker-socket-proxy/compose.yaml +++ b/src/production/docker-socket-proxy/compose.yaml @@ -25,7 +25,7 @@ services: update_config: order: start-first env_file: - - ~~/src/production/docker-socket-proxy/configurations/allowlist.env + - ./configurations/allowlist.env image: ghcr.io/tecnativa/docker-socket-proxy:v0.5.0 logging: driver: json-file