Files
sultan/docs/community.md
T
2026-10-03 13:22:51 +02:00

12 KiB
Raw Blame History

Sultan im Eigenbetrieb (Community)

Anleitung für alle, die Sultan selbst betreiben: ein Ingress-Host mit Traefik und sultan server, dazu beliebig viele Docker-Hosts mit sultan agent. Das betrifft Homelab, kleine Firmen und einen Hoster in eigener Sache.

Besucher ──▶ Traefik ──(pollt alle 3 s)──▶ sultan server ◀──(TLS, ausgehend)── sultan agent ── Docker
                │                                                                  │
                └──────────────── HTTP zum veröffentlichten Port ──────────────────┘

Zwei Verbindungen müssen funktionieren:

  1. Agent → Server, TCP 8443 (TLS 1.3). Nur ausgehend vom Agent, funktioniert also auch hinter NAT/CGNAT.
  2. Traefik → Backend, auf den veröffentlichten Port des Containers, z. B. 9080 bei ports: "9080:80". Das ist normales HTTP. Der Ingress muss den Backend-Host also erreichen können: per LAN, öffentlicher IP oder VPN (z. B. NetBird).

1. Image holen

Server und Agent nutzen dasselbe Image. Die Rolle bestimmt der Befehl.

docker pull zot.chmln.de/sultan:<version>     # z. B. v0.1.1; :latest = letztes Release
docker run --rm zot.chmln.de/sultan:<version> sultan version

Auf Servern eine feste Version in die .env schreiben (SULTAN_IMAGE=…:v0.1.1). latest wird bei docker compose up nicht neu geladen; für ein Update ist immer docker compose pull nötig.

Update später:

$EDITOR .env                                   # SULTAN_IMAGE auf neue Version
docker compose pull && docker compose up -d
docker compose exec <dienst> sultan version

2. Server (Ingress-Host, neben Traefik)

Vorlage: deploy/compose/server.

2.1 Wie läuft Traefik bei dir?

Davon hängt ab, wie Sultan neben Traefik läuft:

Traefik läuft mit … Sultan-Server Traefik erreicht Sultan unter
network_mode: host Variante A: ebenfalls network_mode: host (Vorlage) http://127.0.0.1:8080
eigenem Docker-Netz (z. B. traefik-public) Variante B: im selben bzw. einem gemeinsamen internen Netz http://sultan-server:8080

Sicherheitsregel für beide Varianten: Port 8080 (Traefik-Konfiguration, Checkmk) und 8081 (Wartungsseiten) sind Klartext und ohne Anmeldung. Sie dürfen nur für Traefik erreichbar sein, nie aus dem Internet und nicht für fremde Container. Öffentlich ist nur 8443.

2.2 Variante A: network_mode: host (empfohlen, Vorlage)

mkdir -p /docker/sultan-server && cd /docker/sultan-server
cp <repo>/deploy/compose/server/{compose.yaml,.env.example,traefik-provider.yml} .
cp .env.example .env && $EDITOR .env
install -d -m 0700 -o 65532 -g 65532 suldata   # nonroot-User im Image

In der .env mindestens:

SULTAN_IMAGE=zot.chmln.de/sultan:v0.1.1
SULTAN_PUBLIC_URL=https://ingress.example.de:8443   # so erreichen Agents den Server; landet in jeder Einladung

Ports prüfen: Traefiks API/Dashboard belegt oft schon 8080.

ss -ltnp | grep -E ':(8443|8080|8081)\b'
# belegt → in .env z. B.:
# SULTAN_PROVIDER_ADDR=127.0.0.1:8085
# SULTAN_LANDER_ADDR=127.0.0.1:8086

Starten und prüfen:

docker compose up -d
docker compose logs sultan-server               # "Sultan-Server gestartet" + Fingerprint
docker compose exec sultan-server sultan version  # Version + Zeitzone
curl -s http://127.0.0.1:8080/healthz           # ok

2.3 Variante B: Traefik in einem Docker-Netz

Sultan muss hier auf allen Interfaces im Container lauschen und Traefik den Lander unter dem Containernamen nennen. Am besten legt man ein eigenes internes Netz nur für Traefik und Sultan an, statt traefik-public mitzunutzen: Sonst könnte jeder Container in traefik-public die Konfiguration (Backend-Adressen) lesen.

# compose.yaml (Variante B) – statt network_mode: host
services:
  sultan-server:
    image: ${SULTAN_IMAGE}
    container_name: sultan-server
    restart: unless-stopped
    command: ["sultan", "server", "--data-dir=/suldata"]
    ports:
      - "8443:8443"                     # nur der Agent-Port nach außen
    environment:
      SULTAN_AGENT_ADDR: ":8443"
      SULTAN_PROVIDER_ADDR: ":8080"     # nur im Netz sultan-internal erreichbar
      SULTAN_LANDER_ADDR: ":8081"
      SULTAN_INTERCEPTOR_URL: "http://sultan-server:8081"   # so erreicht Traefik die Wartungsseiten
      SULTAN_PUBLIC_URL: ${SULTAN_PUBLIC_URL}
      TZ: Europe/Berlin
    volumes:
      - ./suldata:/suldata
    networks: [sultan-internal]

networks:
  sultan-internal:
    name: sultan-internal               # Traefik zusätzlich an dieses Netz hängen

Im Traefik-Compose: networks: [traefik-public, sultan-internal] (plus sultan-internal: {external: true}).

Unterschiede zu Variante A:

  • curl vom Host aus geht nicht. Prüfen per docker compose exec traefik wget -qO- http://sultan-server:8080/healthz.
  • Die Client-IP der Agents (Rate-Limit, node list) ist meist korrekt. Bei IPv6 oder aktivem userland-proxy kann dort aber die Docker-Gateway-IP stehen.

2.4 Traefik anbinden

In die statische Konfiguration (traefik.yml), zusätzlich zu den vorhandenen Providern:

providers:
  http:
    endpoint: "http://127.0.0.1:8080/v1/traefik-config"   # Variante B: http://sultan-server:8080/…
    pollInterval: "3s"

Danach Traefik neu starten (statische Konfiguration). Im Dashboard taucht der Provider http auf. Routen von Sultan heißen dort <mandant>__<knoten>__<router>@http.

Wichtig: Die Labels an den Diensten verweisen auf Namen aus deiner Traefik-Konfiguration: Entrypoints (web, websecure) und Certresolver (le). Sultan prüft nicht, ob es sie gibt; Traefik meldet Tippfehler im eigenen Log.

2.5 Firewall

  • TCP 8443 eingehend von den Agent-Hosts (bzw. aus dem Internet, wenn Agents von überall kommen).
  • 8080/8081 nicht öffnen.
  • Ausgehend vom Ingress zu den Backend-Ports (siehe 3.4).

2.6 Mandant und Einladung

Auch im Eigenbetrieb gehört jeder Knoten zu einem Mandanten. Der Mandant legt fest, welche Domains seine Dienste beanspruchen dürfen.

docker compose exec sultan-server sultan token tenant add \
  --id homelab --name "Mein Homelab" --email ich@example.de \
  --domains "example.de,*.example.de" --max-routes -1
docker compose exec sultan-server sultan token invite --tenant homelab   # 24 h gültig, einmalig
  • --tier ist im Eigenbetrieb community (Default): kein Mahnwesen, kein Ablauf.
  • --max-routes -1 = unbegrenzt (Default 5).
  • Domains: example.de deckt nur genau diese Domain. *.example.de deckt alle Subdomains, aber nicht example.de selbst; für beides braucht es beide Einträge.

Die Ausgabe (sltn_enroll_…) ist die Einladung. Sie enthält Server-Adresse und Zertifikats-Fingerprint, ist also alles, was der Agent braucht. Sie ist geheim bis zur Benutzung und danach wertlos.

2.7 Sichern

suldata/ enthält Datenbank, TLS-Schlüssel und Master-Key. Ohne diese Dateien müssen sich alle Agents neu anmelden.

tar -C /docker/sultan-server -czf sultan-backup-$(date +%F).tgz suldata   # verschlüsselt ablegen!

3. Agent (Docker-Host)

Vorlage: deploy/compose/agent.

3.1 Warum network_mode: host

Der Agent läuft immer mit network_mode: host. Ein Traefik-Netz braucht er nicht, denn er spricht nur ausgehend mit dem Server. Das host-Netz brauchen wir aus zwei Gründen:

  • Er meldet als Backend-Adresse die IP des Hosts, über die er den Server erreicht. In einem Bridge-Netz wäre das die nutzlose Container-IP 172.x.
  • Die Knoten-ID kommt aus dem Hostnamen des Hosts statt aus der zufälligen Container-ID.

3.2 Einrichten

mkdir -p /docker/sultan-agent && cd /docker/sultan-agent
cp <repo>/deploy/compose/agent/{compose.yaml,.env.example} .
cp .env.example .env && $EDITOR .env
install -d -m 0700 -o 65532 -g 65532 suldata

.env:

SULTAN_IMAGE=zot.chmln.de/sultan:v0.1.1
SULTAN_TOKEN=sltn_enroll_…                 # Einladung aus 2.6; nach dem ersten Start egal
DOCKER_GID=999                             # getent group docker | cut -d: -f3
# SULTAN_NODE_ID=dockweb11                 # optional, sonst aus dem Hostnamen
# SULTAN_BACKEND_INTERFACE=wt0             # optional: Traefik erreicht den Host über NetBird
# SULTAN_BACKEND_HOST=192.168.1.20         # optional: Adresse fest vorgeben
docker compose up -d
docker compose logs -f sultan-agent
# Registriert node=… tenant=…
# Agent gestartet … backend=<IP>      ← diese IP muss Traefik erreichen können!
# Mit Server verbunden / Lease erhalten

Danach suldata/ behalten. Darin liegt der Schlüssel des Knotens (node.key); er verlässt den Host nie. Wer suldata/ löscht, braucht eine neue Einladung.

3.3 Dienste freigeben: Labels am Container

Es sind dieselben Labels wie bei Traefiks Docker-Provider:

services:
  web:
    image: nginx
    ports:
      - "9080:80"                      # muss veröffentlicht sein – nicht nur 127.0.0.1:9080:80
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.meinweb.rule=Host(`www.example.de`) || Host(`example.de`)"
      - "traefik.http.routers.meinweb.entrypoints=websecure"
      - "traefik.http.routers.meinweb.tls.certresolver=le"
      - "traefik.http.services.meinweb.loadbalancer.server.port=80"   # Port IM Container
  • Der Agent rechnet den Container-Port 80 selbst auf den veröffentlichten Port 9080 um.
  • Fehlt das Port-Label und ist genau ein Port veröffentlicht, nimmt er diesen.
  • Änderungen (Start, Stop, neue Labels) kommen innerhalb einer Sekunde bei Traefik an; Neustarts sind nicht nötig.
  • Nicht übernommene Container stehen mit Grund im Agent-Log („Container nicht übernommen“). Fremde Domains landen im Server-Log („Policy-Verstoß“).

3.4 Erreichbarkeit Traefik → Backend

Der Agent meldet http://<backend-ip>:<veröffentlichter Port>. Prüfen vom Ingress aus:

curl -sI http://<backend-ip>:9080 -H 'Host: www.example.de'

Klappt das nicht: Firewall des Backend-Hosts (Port nur für die Ingress-IP freigeben), falsche Adresse (dann SULTAN_BACKEND_HOST/SULTAN_BACKEND_INTERFACE) oder IPv6. Der Agent nimmt die Adresse, über die er den Server erreicht, das kann eine IPv6-Adresse sein. Dann muss auch Traefik per IPv6 dorthin kommen.

3.5 Ohne Docker (/etc/sultan.d)

Für Bare-Metal-Dienste beschreibt man die Dienste als YAML in /etc/sultan.d/*.yaml (Format siehe README). Im Compose dafür --config-dir=none aus command entfernen und /etc/sultan.d:/etc/sultan.d:ro mounten.

Als systemd-Dienst ohne Docker ist der Agent vorgesehen, aber noch nicht getestet.


4. Kontrolle

# Server:
docker compose exec sultan-server sultan token node list                # online, Lease bis …
curl -s 'http://127.0.0.1:8080/v1/traefik-config?format=yaml'           # was Traefik bekommt
curl -s http://127.0.0.1:8080/metrics/checkmk                           # Monitoring
# von außen:
curl -sI https://www.example.de

Typische Fehler:

Symptom Ursache
Route fehlt in traefik-config Container nicht traefik.enable=true, Port nicht veröffentlicht, Domain nicht in --domains (Server-Log „Policy-Verstoß“)
Route da, Traefik meldet 502/504 Traefik erreicht <backend-ip>:<port> nicht (Firewall, IPv6)
Kein Zertifikat DNS zeigt nicht auf den Ingress, Certresolver-Name falsch
404 statt Wartungsseite Container wurde entfernt statt gestoppt, siehe Wartung

Wartung, Neustarts und Updates: docs/wartung.md.