himpler.com himpler.com

Reverse Proxy für Docker mit Traefik

8 Min.

Wie im vorherigen Beitrag bereits angedeutet, war es etwas aufwendiger auf relevante Dienste meines Smarthome-Stacks auch außerhalb des heimischen Netzwerks zugreifen zu können. In erster Linie ging es um den Zugriff auf HomeAssistant. Hier kam der Reverse Proxy Traefik ins Spiel.

Gerade im Smarthome-Bereich ist es wichtig, auch unterwegs auf die Anwendungen im privaten Netzwerk zugreifen zu können. Es gibt mehrere Möglichkeiten, diesen Zugriff zu ermöglichen.

  • VPN Zugriff: Über einen eingerichtetes VPN (Virtual-Private-Network) kann man aus einem externen Netz auf das entfernte, private Netzwerk zugreifen. Ist der Zugang eingerichtet und aktiv, ist es so als wäre man vor Ort. Jedoch muss die Verbindung manuell aufgebaut werden.
  • Portfreigaben: Über die Einrichtung einer Portfreigabe lassen sich die Dienste extern aufrufen. Grundsätzlich muss für jeden Dienst, der extern erreichbar sein soll, eine Portfreigabe erfolgen.
  • Reverse Proxy: Ein Reverse Proxy benötigt, sofern er Zuhause betrieben wird, ebenfalls eine Portfreigabe. Allerdings ist hier nur die Freigabe von wenigen Ports (in der Regel nur die Ports 80 und 443) notwendig. Der Reverse Proxy routet die Anfrage des externen Clients dann über verschiedene Parameter an einen oder mehrere Server und liefert das Ergebnis (hier konkret das Frontend einer Anwendung) an den Client.

Während lange Zeit häufig NGINX als ReverseProxy genutzt wurde, taucht im Zusammenhang mit Docker immer häufiger Traefik als Anwendung auf. Traefik ist gegenüber NGINX gerade im Zusammenhang mit Docker leichter zu konfigurieren. Zum Beispiel lassen sich bei der Containerdefinition in der docker-compose.yaml einige Labels ergänzen, die verschiedene Parameter an Traefik übermitteln. Anhand der Parameter (z.B. dem Host) entscheidet Traefik an welchen Container die Anfrage geroutet wird. Dazu aber später mehr.

Externe Erreichbarkeit des privaten Netzwerks

Zunächst muss sichergestellt sein, dass unser Netzwerk von außen erreichbar ist. Neben der oben erwähnten Portfreigabe im Router benötigt man, sofern der eigene Anschluss keine feste IP-Adresse hat, einen DynDNS Dienst und eine Domain. Viele Router können dann so konfiguriert werden, dass die jeweils aktuelle IP-Adresse an den DynDNS-Dienst übermittelt wird.

Duckdns.org - Es stehen verschieden Dienste zur Anmeldung zur Verfügung

Sowohl DynDNS als auch eine Subdomain gibt es kostenlos bei DuckDNS. Dort kann man sich über verschiedene Dienste einen kostenlosen Account samt einer Subdomain anlegen. Im Anschluss muss dafür gesorgt werden, dass die jeweils aktuelle IP-Adresse an DuckDNS übertragen wird. In der FritzBox findet man die Einstellung im Menü unter Internet - Freigaben unter dem Tab DynDNS. Neben dem obligatorischen Haken im Feld DynDNS benutzen muss ein Benutzerdefinierter Dienst angegeben werden. Für DuckDNS muss die folgende URL eingegeben werden:

https://www.duckdns.org/update?domains=<domain>&token=<pass>&ip=<ipaddr>&ipv6=<ip6addr>

Konfiguration des DynDNS Dienstes am Beispiel einer FritzBox.

Domain, Benutzername und Kennwort (der Token) lassen sich der DuckDNS Übersicht entnehmen. Die FritzBox ersetzt die Platzhalter in der URL mit den in den Feldern angegeben Werten und fügt sowohl die klassische IP-Adresse und auch die neue IPV6 Adresse hinzu. Bei jeder Änderung der öffentlichen IP-Adresse wird die neue Adresse jetzt automatisch an DuckDNS übertragen.

Wo einmal die Einstellungen des Routers offen sind, können wir auch gleich die benötigten Ports freischalten. Unter dem gleichem Menüpunkt lässt sich der Tab Portfreigaben finden. Dort lassen sich Geräte für Freigaben hinzufügen. Nach der Auswahl des Gerätes im Dropdown-Menü können über Neue Freigabe die Ports freigeben werden. Dazu wählt man die Einstellung wie im Screenshot gezeigt.

Zunächst muss bei der FritzBox das Gerät für die Freigabe ausgewählt werden...

Anschließend werdend die Ports freigegeben. Die Freigabe von Port 80 erfolgt analog zu Freigabe wie im Screenshot.

Konfiguration von Traefik und HomeAssistant

Nachdem die Verbindung von außen vorbereitet ist, müssen wir unserem Smarthome noch beibringen was es bei einem Aufruf zu tun hat. Dazu fügen wir als erstes unserer docker-compose.yaml den Traefik Container hinzu.

traefik:
  container_name: traefik
  image: traefik:v2.2
  restart: unless-stopped
  ports:
    - 80:80
    - 443:443
    - 8080:8080
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
    - ./volumes/traefik/traefik.yml:/etc/traefik/traefik.yml
    - ./volumes/traefik/acme.json:/acme.json

Und der Eintrag von HomeAssistant wird um folgende Labels ergänzt.

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.homeassistant.rule=Host(`ha.subdomain.duckdns.org`)"
  - "traefik.http.routers.homeassistant.entrypoints=websecure"
  - "traefik.http.routers.homeassistant.tls=true"
  - "traefik.http.routers.homeassistant.tls.certresolver=letsencrypt"

Zum Stand des Posts verwenden wir die erst kürzlich erschiene Version 2.2. Wie man der Konfiguration entnehmen kann, benötigen wir jetzt noch zwei Dateien, die wir beide unter dem angegebenen Pfad erstellen. Die traefik.yml dient der Festlegung von Grundeinstellungen. Diese können zwar auch über CLI-Parameter beim Starten des Containers festgelegt werden, ich empfinde eine weitere Datei aber als übersichtlicher.

Die zum HomeAssistant-Eintrag hinzugefügten Labels konfigurieren die Route für HomeAssistant in Traefik. Hier muss der Host individuell auf eure Domain angepasst werden. Zahlreiche Möglichkeiten samt Beispielen findet man in der Traefik-Dokumentation.

Wer in HomeAssistant die Homekit Integration nutzen möchte, muss ebenfalls noch den Port 51827 mit 51827:51872 zum Port-Mapping hinzufügen.

# traefik.yml

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

api:
  dashboard: true
  insecure: true

providers:
  docker:
    exposedByDefault: false
    network: pi_default

certificatesResolvers:
  letsencrypt:
    acme:
      email: mail@example.com
      storage: acme.json
      httpChallenge:
        entryPoint: web

Für einen Überblick schauen wir uns kurz die gewählten Einstellungen an.

  • Die entryPoints sind die Schnittstellen nach außen. Auf diesen Ports lauscht Traefik nach eingehenden Verbindungen. Hier wird auch direkt für eine sichere Verbindung gesorgt. Alle Verbindungen von extern über den Port 80 werden automatisch auf den Port 443 umgeleitet.
  • Die api Konfiguration erzeugt ein Dashboard, auf welchem man die aktiven Routen von Traefik bequem einsehen kann. Da das ganze nur lokal genutzt wird, setzen wir insecure auf true.
  • Als Provider nutzen wir Docker, das bedeutet, dass Traefik automatisch die vorhanden Docker Container überwacht. Damit nicht alle Container automatisch eine externe Verbindung erhalten, wird exposedByDefault auf false gesetzt. Das Netzwerk muss dem Netzwerk der Container entsprechen.
  • Die certificatesResolvers sorgen mit Let’s Encrypt für ein kostenloses SSL-Zertifkat. Traefik nimmt einem sowohl die Beantragung als auch die regelmäßige Erneuerung des Zertifikates ab. Bei diesem Eintrag muss zwingend die E-Mail Adresse geändert werden.

Die acme.json enthält später die Zertifikate für die SSL-Verschlüsselung unserer Verbindung. Hier ist wichtig, dass die Berechtigungen richtig gesetzt werden. Die Datei benötigt zwingend die Rechte 600. Diese setzen wir mit

sudo chmod 600 ~/volumes/traefik/acme.json

Jetzt starten wir mit docker-compose up -d --build homeassistant zuerst HomeAssistant neu. Der Parameter --build sorgt dafür, dass der Container mit den Labels neu gebaut wird. Der Befehl docker-compose up -d traefik lädt dann das Traefik Image herunter und startet den Container im Hintergrund.

Traefik Dashboard

Traefik bietet ein Dashboard auf welchem sich die erkannten Routen einsehen lassen. Gerade zu Beginn kann das eine hilfreiche Übersicht sein.

Das Traefik Dashboard für einen schnellen Überblick

Neben einigen internen Routen für die API und das Dashboard sollte jetzt auch HomeAssistant aufgeführt und von außen über die Host-URL erreichbar sein.

Die berücksichtigten Routen unter dem Menüpunkt HTTP.

Homekit trotz Docker Container

Es wäre natürlich zu schön, wenn jetzt bereits alles funktionieren würde. Allerdings macht die Homekit Integration von HomeAssistant trotz Portmapping noch einen Strich durch die Rechnung. Ursache ist, das der Homekit Dienst so noch nicht über den Avahi-Daemon im lokalen Netzwerk bekannt gemacht wird. Nach etwas Recherche bin ich in der HomeAssistant-Community auf die Lösung gestoßen.

Für eine funktionierende Homekit-Integration in HomeAssistant ergänzt man auf dem Raspberry Pi unter /etc/avahi/services die Datei homeassistant.service mit folgendem Inhalt:

<!-- homeassistant.service -->
<service-group>
  <name>Home Assistant Bridge</name>
  <service>
    <type>_hap._tcp</type>
    <port>51827</port>
    <txt-record>md=HA Bridge</txt-record>         <!-- friendly name                 -->

    <!-- the following appear to be mandatory -->
    <txt-record>pv=1.0</txt-record>               <!-- HAP version                   -->
    <txt-record>id=AA:BB:CC:DD:EE:FF</txt-record> <!-- MAC (from `.homekit.state`)   -->
    <txt-record>c#=2</txt-record>                 <!-- config version                -->

    <!-- the following appear to be optional -->
    <txt-record>s#=1</txt-record>                 <!-- accessory state               -->
    <txt-record>ff=0</txt-record>                 <!-- unimportant                   -->
    <txt-record>ci=2</txt-record>                 <!-- accessory category (2=bridge) -->
    <txt-record>sf=1</txt-record>                 <!-- 0=not paired, 1=paired        -->
    <txt-record>sh=UaTxqQ==</txt-record>          <!-- setup hash (used for pairing) -->
  </service>
</service-group>

Drei wichtige Punkte müssen richtig sein:

  • HAP-Version (aktuell 1.0)
  • MAC-Adresse von Homekit. Diese findet ihr in den Konfigurationsdateien von HomeAssistant unter .storage. Die Datei heißt homekit.<xyz>.state wobei xyz eine zufällige Zeichenfolge ist.
  • Config Version: Ebenfalls in der .homekit.state zu finden.

Ist der Service angelegt und gespeichert, muss Avahi mit sudo service avahi-daemon restart neu gestartet werden. HomeAssistant sollte dann nun auch als Bridge zu Homekit hinzugefügt werden können.

Die oben genannten Befehle sind für die Verwendung auf einem Raspberry Pi ausgelegt. Die Anleitung funktioniert analog aber auf fast jedem Linux, also auch auf einer Synology Diskstation.

Andere Dienste freigeben

Natürlich kann Traefik mehr als nur einen Container nach außen freigeben. Sonst könnte man abgesehen von der SSL-Verschlüsselung ja auf eine normale Portfreigabe zurückgreifen. Um weitere Dienste von außen zu erreichen, ergänzt man an den jeweiligen Container-Definitionen die Labels wie im Beispiel zu HomeAssistant. Für Node-RED würde das ganze dann in etwa so aussehen:

nodered:
  container_name: nodered
  image: nodered/node-red
  restart: unless-stopped
  user: "0"
  privileged: true
  environment:
    TZ: Europe/Berlin
  ports:
    - 1880:1880
  volumes:
    - ./volumes/nodered/data:/data
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.homeassistant.rule=Host(`nodered.subdomain.duckdns.org`)"
    - "traefik.http.routers.homeassistant.entrypoints=websecure"
    - "traefik.http.routers.homeassistant.tls=true"
    - "traefik.http.routers.homeassistant.tls.certresolver=letsencrypt"

Wie auch zuvor muss die Domain angepasst werden.

Achtung! Alle Container, an die Traefik routet, sind über den Hostnamen im Internet offen zugänglich. Daher sollten alle Dienste vernünftig abgesichert sein und mindestens mit einen Passwort geschützt sein.

Bringt die Anwendung keinen Passwortschutz mit sich, kann in Traefik eine zusätzliche Middleware mit einer HTTP-Basic-Authentifizierung aktiviert werden. Weitere Information gibt es in der Traefik Dokumentation.

Optional: Einem Container eine eigene IP zuweisen

Manchmal ist es sinnvoll, dass der Container eine eigene IP-Adresse hat. Damit wird er wie ein physisches Gerät im Netzwerk behandelt und alle Ports sind nutzbar.

Zunächst muss ein neues Docker-Netzwerk erstellt werden.

docker network create -d macvlan -o parent=eth0 --subnet 192.168.178.0/24 --gateway 192.168.178.1 --ip-range 192.168.178.224/27 --aux-address 'host=192.168.178.254' macvlan0

Anschließend kümmern wir uns um den Rest.

sudo ip link add macvlan-shim link eth0 type macvlan mode bridge
sudo ip addr add 192.168.178.254/32 dev macvlan-shim
sudo ip link set macvlan-shim up
sudo ip route add 192.168.178.224/27 dev macvlan-shim

tl;dr

Die Einrichtung von Traefik sieht zwar auf den ersten Blick kompliziert aus, ist sie aber dem Grunde nach nicht. Wer sich an diese kleine Anleitung hält und ein wenig Zeit investiert, erhält ein flexibles System, um interne Anwendungen schnell und sicher außerhalb des eigenen Netzwerks verfügbar zu machen. Zudem ist die Lösung vollkommen kostenfrei und benötigt abgesehen von einem DynDNS Dienst keine externen Lösungen.

In einem der letzten Posts habe ich ein kleines Tool vorgestellt, welches einem die Installation von Docker und die Anlage der Docker-Compose Datei abnimmt. Auf einem Raspberry Pi muss dazu auf der Kommandozeile nur der Befehl curl -ssL https://himpler.com/smarthome-setup | sh ausgeführt werden. Das Script wurde nun um Traefik und HomeAssistant ergänzt. Die benötigten Dateien aus dieser Anleitung müssen selbst angelegt werden.

Share this post!

Das könnte dir auch gefallen