Traefik und Portainer

Traefik und Portainer
Photo by Timelab / Unsplash

Container Management mit Portainer hinter dem Reverse-Proxy Traefik mit automatischer Zertifikatsvergabe von Let's Encrypt.

Traefik

Traefik ist ein moderner HTTP-Reverse-Proxy und Load-Balancer, der die Bereitstellung von Containern in verschiedenen Infrastrukturen wie Docker, Docker Swarm und Kubernetes vereinfacht. Dabei wird Traefik selbst als Container installiert und über statische und dynamische Konfigurationen eingerichtet. Traefik verwaltet als Reverse-Proxy automatisch die Zertifikate für die angebotenen Dienste über Zertifizierungsstellen, wie z.B. von Let's Encrypt.

Traefik, The Cloud Native Application Proxy | Traefik Labs
Traefik is the leading open-source reverse proxy and load balancer for HTTP and TCP-based applications that is easy, dynamic and full-featured.

Portainer

Portainer ist eine Web-GUI für Docker, welche selbst in Docker Containern läuft. Dabei unterstützt Portainer nicht nur Docker als Plattform, sondern auch Docker Swarm und Kubernetes. Während Docker auch über die Kommandozeile verwaltet werden kann, ist es besonders bei komplexeren Befehlen, die über mehrere Zeilen gehen, schwierig den Überblick zu behalten und Fehler zu vermeiden. Aus diesem Grund kann eine grafische Benutzeroberfläche wie Portainer eine große Hilfe sein, um die Verwaltung von Docker Containern zu vereinfachen und auch weniger erfahrenen Anwendern den Umgang damit zu erleichtern.

Portainer ist in zwei Versionen verfügbar - Community Edition (CE) und Business Edition (EE). Die Business Edition ist zur Zeit für drei Nodes und für ein Jahr als kostenlose Lizenz zu bekommen.

Portainer: Docker and Kubernetes Management Platform
Portainer is your container management platform to deploy, troubleshoot, and secure applications across cloud, datacenter, and Industrial IoT use cases.

Einrichtung von Domainnamen

Für den Einsatz von Traefik als Reverse-Proxy werden Domainnamen zur Zuordnung der einzelnen Dienste über den HTTP-Host-Header benötigt. Für mein HomeLab nutze ich das kostenlose Angebot der Firma Digineo aus Bremen, um sowohl ipv4, als auch ipv6 Adressen dynamisch zu registrieren.

Free dynamic DNS for IPv6

Installation von Traefik mit Portainer

Für die Einrichtung meiner HomeLab Umgebung verwende ich "docker compose" zur einfachen Bereitstellung der Basisumgebung. Als Basis dient dazu mein Post zu "Docker unter Debian 12".

Für die Umgebung richte ich einen Basis Ordner für die Docker Konfigurationsdatei "docker-compose.yml" und die Datei mit den Umgebungsvariablen ".env" für "docker compose" ein. Zudem einen Ordner "traefik" für die Speicherung der Zertifikate in der Datei "acme.json" und einen Unterordner "rules" für spätere dynamische Konfigurationsdateien von Traefik. Diese Ordnerstruktur erstelle ich wie folgt im Dateisystem unter "/opt".

$ mkdir -p /opt/containers/basis/traefik/rules
$ touch /opt/containers/basis/traefik/acme.json
$ chmod 600 /opt/containers/basis/traefik/acme.json

In den Umgebungsvariablen sind die Domainnamen für Traefik und Portainer anzugeben. Das Dashboard von Traefik sichere ich mit der Basisauthentifizierung ab, wozu Traefik eine Zeichenkette aus Benutzername und Passwordhash benötigt. Diese Zeichenkette kann mit Hilfe von "htpasswd" erstellt werden.

$ echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g

Zur Nutzung von Let's Encrypt über das ACME Protokoll erwartet der Dienst eine gültige E-Mail Adresse, an die auch Meldungen über ablaufende Zertifikate verschickt werden.

Beispielhafter Inhalt der Datei /opt/containers/basis/.env

TRAEFIK_DASHBOARD_DOMAINNAME=traefik.example.com
TRAEFIK_DASHBOARD_AUTH=view:$$hjezstfkjgldp$$qdVT/l3SI.GtFiqoi13cf1
TRAEFIK_ACME_EMAIL=webmaster@example.com
PORTAINER_FRONTEND_DOMAINNAME=portainer.example.com
PORTAINER_EDGE_DOMAINNAME=edge.example.com

Für Traefik wird das externe Docker Netzwerk "proxy" definiert. Traefik soll alle Anfragen auf den Ports 80 (web) und 443 (websecure) und jeden eingerichteten Domainnamen im HTTP-Host-Header ein Zertifikat von Let's Encrypt ("leresolver") beziehen. Dabei werden alle Anfragen ("http-catchall") auf Port 80 nach Port 443 umgeleitet ("redirect-to-https").

Unter der Adresse "https://traefik.example.com/dashboard/" bietet Traefik nach der Benutzeranmeldung einen Überblick der Konfiguration im Web-Dashboard an.

Portainer soll seine Daten in einem Docker Volume "portainer_data" speichern. Die Anwendung hört intern auf den Port 9000, ist aber über Traefik und dem Domainnamen "https://portainer.example.com" mit einem Let's Encrypt Zertifikat von außen erreichbar. Als erster Schritt muss für Portainer ein Administratorkonto vergeben werden.

Wird die Business Edition verwendet, ist zusätzlich der per E-Mail versandte Lizenzschlüssel anzugeben.

Um Traefik und Portainer zu installieren, werden durch folgenden Docker Befehl gemäß der "docker-compose.yml" die Images geladen und gestartet. Mit der Option "-d" laufen die Anwendungen im Hintergrund, zeigen die Debug Meldungen aber nicht interaktiv an.

$ docker compose up -d

Inhalt der Datei /opt/containers/basis/docker-compose.yml

version: "3.3"

services:
  traefik:
    image: "traefik:latest"
    restart: unless-stopped
    command:
      # Globals
      - --global.sendAnonymousUsage=false
      - --log.level=DEBUG
      - --api.dashboard=true
      # Entrypoints
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      # Providers
      - --providers.docker
      - --providers.docker.exposedbydefault=false
      - --providers.file.directory=/etc/traefik/rules
      - --providers.file.watch=true
      # LetsEncrypt
      - --certificatesresolvers.leresolver.acme.httpchallenge=true
      - --certificatesresolvers.leresolver.acme.email=${TRAEFIK_ACME_EMAIL}
      - --certificatesresolvers.leresolver.acme.storage=/etc/traefik/acme.json
      - --certificatesresolvers.leresolver.acme.httpchallenge.entrypoint=web
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "/etc/localtime:/etc/localtime:ro"
      - "./traefik/acme.json:/etc/traefik/acme.json"
      - "./traefik/rules:/etc/traefik/rules"
    labels:
      # Dashboard
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DASHBOARD_DOMAINNAME}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.tls.certresolver=leresolver"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}"
      # Containers
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"

  portainer:
    image: portainer/portainer-ee:latest
    restart: unless-stopped
    command: -H unix:///var/run/docker.sock
    networks:
      - proxy
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/etc/localtime:/etc/localtime:ro"
      - portainer_data:/data
    labels:
      # Frontend
      - "traefik.enable=true"
      - "traefik.http.routers.frontend.rule=Host(`${PORTAINER_FRONTEND_DOMAINNAME}`)"
      - "traefik.http.routers.frontend.entrypoints=websecure"
      - "traefik.http.services.frontend.loadbalancer.server.port=9000"
      - "traefik.http.routers.frontend.service=frontend"
      - "traefik.http.routers.frontend.tls.certresolver=leresolver"
      # Edge
      - "traefik.http.routers.edge.rule=Host(`${PORTAINER_EDGE_DOMAINNAME}`)"
      - "traefik.http.routers.edge.entrypoints=websecure"
      - "traefik.http.services.edge.loadbalancer.server.port=8000"
      - "traefik.http.routers.edge.service=edge"
      - "traefik.http.routers.edge.tls.certresolver=leresolver"

volumes:
  portainer_data:

networks:
  proxy:
    name: proxy