diff --git a/infrastructure/.env.example b/infrastructure/.env.example new file mode 100644 index 0000000..6a492ce --- /dev/null +++ b/infrastructure/.env.example @@ -0,0 +1,4 @@ +# Beispiel-Konfiguration. Als .env kopieren und anpassen + +# Domainname für das Traefik-Dashboard (und ggf. anderen Infrastruktur-Krempel) +INFRASTRUCTURE_DOMAIN=infra.freifunk-leipzig.de diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore new file mode 100644 index 0000000..af4d511 --- /dev/null +++ b/infrastructure/.gitignore @@ -0,0 +1,2 @@ +/.env +/data diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..87ee4dc --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,6 @@ +# Infrastruktur-Container + +## Traefik + +* TLS-Terminierung +* Routen anhand von Annotations diff --git a/infrastructure/docker-compose.yaml b/infrastructure/docker-compose.yaml new file mode 100644 index 0000000..f37fd61 --- /dev/null +++ b/infrastructure/docker-compose.yaml @@ -0,0 +1,44 @@ +--- + +version: "3.5" + +networks: + services: + name: services + +services: + traefik: + image: library/traefik:v2.4.5 + hostname: traefik + networks: + - services + restart: unless-stopped + command: | + --log.level=INFO + --api.dashboard=true + --providers.docker=true + --providers.docker.exposedbydefault=false + --entrypoints.http.address=:80 + --entrypoints.https.address=:443 + --certificatesresolvers.default.acme.httpchallenge=true + --certificatesresolvers.default.acme.httpchallenge.entrypoint=http + --certificatesresolvers.default.acme.email= + --certificatesresolvers.default.acme.storage=/data/acme.json + ports: + - "80:80" + - "443:443" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "./data/traefik:/data" + labels: + traefik.enable: "true" + traefik.http.routers.dashboard.rule: Host(`${INFRASTRUCTURE_DOMAIN:?INFRASTRUCTURE_DOMAIN is required}`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`)) + traefik.http.routers.dashboard.service: api@internal + traefik.http.routers.dashboard.tls.certresolver: default + + # http -> https + traefik.http.routers.http2https.entrypoints: http + traefik.http.routers.http2https.rule: PathPrefix(`/`) + traefik.http.routers.http2https.priority: "900" + traefik.http.routers.http2https.middlewares: http2https + traefik.http.middlewares.http2https.redirectscheme.scheme: https diff --git a/mapserver/envfile.example b/mapserver/.env.example similarity index 85% rename from mapserver/envfile.example rename to mapserver/.env.example index 33b86b1..c6c9466 100644 --- a/mapserver/envfile.example +++ b/mapserver/.env.example @@ -1,4 +1,7 @@ -# Example configuration. Copy to "envfile" and modify to your needs +# Example configuration. Copy to ".env" and modify to your needs + +# Domain (required) +MAPSERVER_DOMAIN=map.freifunk-leipzig.de # Fastd settings FASTD_MTU=1426 diff --git a/mapserver/.gitignore b/mapserver/.gitignore index 59e5298..af4d511 100644 --- a/mapserver/.gitignore +++ b/mapserver/.gitignore @@ -1,2 +1,2 @@ -/envfile +/.env /data diff --git a/mapserver/README.md b/mapserver/README.md index e228473..a42781b 100644 --- a/mapserver/README.md +++ b/mapserver/README.md @@ -27,6 +27,15 @@ Umgebungsvariablen: * keine +### meshviewer-collector + +Ein kleines Programm (https://github.com/genofire/meshviewer-collector), welches weitere Karten einsammelt und an Yanic weiterleitet. Wird verwendet, um Libremesh-Nodes in die Karte einzubinden. + +Umgebungsvariablen: + +* keine +* TODO: externe Karten konfigurierbar machen + ### meshviewer Der Meshviewer-Container stellt die Meshviewer-Anwendung aus https://git.dezentrale.cloud/Freifunk-Leipzig/meshviewer/src/branch/ffle sowie die zugehörigen Meshviewer-Daten bereit. @@ -43,3 +52,8 @@ Umgebungsvariablen: * GF_SECURITY_ADMIN_USER: initialier Admin-Benutzer für Grafana * GF_SECURITY_ADMIN_PASSWORD: initiales Admin-Passwort (sollte beim ersten Login unbedingt geändert werden!) + +### renderer / renderer-proxy + +Da Grafana Bilder sehr langsam rendert, wird prometheus-png als Renderer eingesetzt. Die Charts werden direkt aus den Victoriametrics-Daten erzeugt. Vor dem Renderer ist NGinx als Proxy, um die URLs mit den PromQL-Queries korrekt zusammenzubauen. + diff --git a/mapserver/docker-compose.yaml b/mapserver/docker-compose.yaml index 2acad89..26b1b2a 100644 --- a/mapserver/docker-compose.yaml +++ b/mapserver/docker-compose.yaml @@ -1,29 +1,25 @@ --- -version: '3.4' +version: '3.5' + +networks: + services: + external: + name: services + services: fastd: build: ./fastd - env_file: envfile + env_file: .env privileged: true # required to create the tap device sysctls: net.ipv6.conf.all.disable_ipv6: 0 # enable ipv6 withn container net.ipv6.conf.all.forwarding: 1 stop_grace_period: 0s - - yanic: - build: ./yanic - network_mode: "service:fastd" - stop_grace_period: 10s - volumes: - - ./data/yanic:/data - meshviewer: - build: ./meshviewer - ports: - - 80:80 - volumes: - - ./data/yanic/meshviewer:/usr/share/nginx/html/data - + restart: unless-stopped + networks: + - services + victoriametrics: image: victoriametrics/victoria-metrics:v1.69.0 command: | @@ -31,6 +27,42 @@ services: -selfScrapeInterval=30s volumes: - ./data/victoriametrics:/victoria-metrics-data + networks: + - services + restart: unless-stopped + + yanic: + build: ./yanic + network_mode: "service:fastd" + stop_grace_period: 10s + volumes: + - ./data/yanic:/data + depends_on: + - fastd + - victoriametrics + restart: unless-stopped + + meshviewer-collector: + build: ./meshviewer-collector + network_mode: "service:fastd" + stop_grace_period: 10s + volumes: + - ./data/meshviewer-collector:/data + depends_on: + - fastd + restart: unless-stopped + + meshviewer: + build: ./meshviewer + volumes: + - ./data/yanic/meshviewer:/usr/share/nginx/html/data + labels: + traefik.enable: "true" + traefik.http.routers.meshviewer.rule: Host(`${MAPSERVER_DOMAIN:?MAPSERVER_DOMAIN is required}`) + traefik.http.routers.meshviewer.tls.certresolver: default + networks: + - services + restart: unless-stopped grafana: image: grafana/grafana:8.2.3 @@ -39,12 +71,12 @@ services: # https://blog.56k.cloud/provisioning-grafana-datasources-and-dashboards-automagically/ - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards - env_file: envfile + env_file: .env environment: GF_LOG_MODE: console GF_AUTH_ANONYMOUS_ENABLED: "true" - ports: - - 81:3000 + GF_SERVER_ROOT_URL: "%(protocol)s://%(domain)s:%(http_port)s/grafana/" + GF_SERVER_SERVE_FROM_SUB_PATH: "true" user: root entrypoint: - /bin/sh @@ -52,3 +84,34 @@ services: - | chown grafana /var/lib/grafana exec su grafana -s /bin/sh -c /run.sh + labels: + traefik.enable: "true" + traefik.http.routers.grafana.rule: Host(`${MAPSERVER_DOMAIN:?MAPSERVER_DOMAIN is required}`) && PathPrefix(`/grafana`) + traefik.http.routers.grafana.tls.certresolver: default + networks: + - services + restart: unless-stopped + + renderer-proxy: + image: nginx:1.21-alpine + volumes: + - ./renderer-proxy:/etc/nginx/conf.d/:ro + labels: + traefik.enable: "true" + traefik.http.routers.renderer.rule: Host(`${MAPSERVER_DOMAIN:?MAPSERVER_DOMAIN is required}`) && PathPrefix(`/render`) + traefik.http.routers.renderer.tls.certresolver: default + networks: + - services + restart: unless-stopped + + renderer: + image: lomik/prometheus-png:v0.5.0 + network_mode: "service:renderer-proxy" + command: + - -prometheus + - http://victoriametrics:8428 + depends_on: + - renderer-proxy + restart: unless-stopped + + # 650x350 \ No newline at end of file diff --git a/mapserver/grafana/provisioning/dashboards/nodes_public.json b/mapserver/grafana/provisioning/dashboards/nodes_public.json index db5b4eb..30de5af 100644 --- a/mapserver/grafana/provisioning/dashboards/nodes_public.json +++ b/mapserver/grafana/provisioning/dashboards/nodes_public.json @@ -1,215 +1,302 @@ { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": null, - "graphTooltip": 0, - "id": 1, - "iteration": 1636893346139, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": null, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] }, - "gridPos": { - "h": 3, - "w": 12, - "x": 0, - "y": 0 - }, - "id": 4, - "interval": null, - "maxDataPoints": 1, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "name" - }, - "pluginVersion": "8.2.3", - "targets": [ - { - "exemplar": false, - "expr": "node_clients.total{nodeid=\"$node_id\"}", - "format": "time_series", - "instant": true, - "interval": "", - "legendFormat": "{{hostname}}", - "refId": "A" - } - ], - "timeFrom": "1y", - "timeShift": null, - "title": "hostname", - "type": "stat" - }, - { - "datasource": null, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 3 - }, - "id": 2, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "exemplar": true, - "expr": "node_clients.total{nodeid=\"$node_id\"}", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Connected clients", - "type": "timeseries" + "type": "dashboard" } - ], - "schemaVersion": 31, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "datasource": null, - "definition": "label_values(nodeid)", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": null, - "multi": false, - "name": "node_id", - "options": [], - "query": { - "query": "label_values(nodeid)", - "refId": "StandardVariableQuery" + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": null, + "graphTooltip": 0, + "iteration": 1637104630864, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "interval": null, + "maxDataPoints": 1, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.3", + "targets": [ + { + "exemplar": false, + "expr": "node_clients.total{nodeid=\"$node_id\"}[1y]", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{hostname}}", + "refId": "A" } - ] + ], + "title": "hostname", + "type": "stat" }, - "time": { - "from": "now-6h", - "to": "now" + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 3 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "max_over_time(node_clients.total{nodeid=\"$node_id\"}[10m])", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Connected clients", + "type": "timeseries" }, - "timepicker": {}, - "timezone": "", - "title": "Node (public)", - "uid": "KoKOqJc7k", - "version": 3 - } \ No newline at end of file + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "irate(node_traffic.rx.bytes{nodeid=\"$node_id\"})", + "interval": "", + "legendFormat": "Traffic (in)", + "refId": "A" + }, + { + "exemplar": true, + "expr": "irate(node_traffic.tx.bytes{nodeid=\"$node_id\"})", + "hide": false, + "interval": "", + "legendFormat": "Traffic (out)", + "refId": "B" + } + ], + "title": "Traffic", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 31, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": false, + "text": "14cc202b86c0", + "value": "14cc202b86c0" + }, + "datasource": null, + "definition": "label_values(nodeid)", + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "node_id", + "options": [], + "query": { + "query": "label_values(nodeid)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Node (public)", + "uid": "KoKOqJc7k", + "version": 9 +} \ No newline at end of file diff --git a/mapserver/meshviewer-collector/Dockerfile b/mapserver/meshviewer-collector/Dockerfile new file mode 100644 index 0000000..f28ba95 --- /dev/null +++ b/mapserver/meshviewer-collector/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.16-alpine as builder + +RUN go get -v -u github.com/genofire/meshviewer-collector@45acc9a25f6d7a57c89309d657a346d7167c2df7 + +FROM alpine:3.14 + +COPY --from=builder /go/bin/meshviewer-collector /bin/meshviewer-collector + +RUN apk add --update --no-cache bash + +ADD entrypoint.sh /entrypoint.sh + +VOLUME /data + +CMD /entrypoint.sh diff --git a/mapserver/meshviewer-collector/entrypoint.sh b/mapserver/meshviewer-collector/entrypoint.sh new file mode 100755 index 0000000..dc97437 --- /dev/null +++ b/mapserver/meshviewer-collector/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +mkdir -p /config /data +cat << EOF > "/config/config.toml" + +run_every = "1m" + +ignore_meshviewer = "6d" +ignore_node = "6h" + +status_json = "/data/status.json" + +[dataPaths] +"http://db.leipzig.freifunk.net/uptime/meshviewer.json" = "meshkit" + +[yanic_connection] +type = "udp6" +address = "localhost:10011" +EOF + +exec /bin/meshviewer-collector collect --config /config/config.toml diff --git a/mapserver/prometheus-png/Dockerfile b/mapserver/prometheus-png/Dockerfile new file mode 100644 index 0000000..87e21fa --- /dev/null +++ b/mapserver/prometheus-png/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.16-alpine as builder + +RUN go get -v -u github.com/itskoko/prometheus-renderer@v0.0.5 + +FROM alpine:3.14 + +COPY --from=builder /go/bin/renderd /bin/renderd + +ENTRYPOINT ["/bin/renderd"] diff --git a/mapserver/renderer-proxy/default.conf b/mapserver/renderer-proxy/default.conf new file mode 100644 index 0000000..17369a9 --- /dev/null +++ b/mapserver/renderer-proxy/default.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name localhost; + + location ~ /render/clients/(.*) { + proxy_pass "http://127.0.0.1:8080/?g0.expr=max_over_time(node_clients.total%7Bnodeid=%22$1%22%7D[30m])&width=650&height=350&g0.legend=Clients%20(total)"; + } + + location ~ /render/traffic/(.*) { + proxy_pass "http://127.0.0.1:8080/?g0.expr=irate(node_traffic.tx.bytes%7Bnodeid=%22$1%22%7D)&g1.expr=irate(node_traffic.rx.bytes%7Bnodeid=%22$1%22%7D)&width=650&height=350&g0.legend=Traffic%20(out)&g1.legend=Traffic%20(in)"; + } + +} diff --git a/mapserver/yanic/entrypoint.sh b/mapserver/yanic/entrypoint.sh index 8dad328..ee0e0de 100755 --- a/mapserver/yanic/entrypoint.sh +++ b/mapserver/yanic/entrypoint.sh @@ -27,6 +27,12 @@ multicast_address = "ff02::2:1001" ifname = "bat0" multicast_address = "ff05::2:1001" +[[respondd.interfaces]] +ifname = "lo" +ip_address = "::1" +send_no_request = true +port = 10011 + [webserver] enable = false