Ein Wartungsnetz für meine Server-Infrastruktur mit PeerVPN und Docker

In meinem Beitrag “Dezentrales HA Servercluster-VPN mit PeerVPN” habe ich euch PeerVPN vorgestellt - einen kleinen Peer-to-Peer VPN-Server, der sich wunderbar eignet, um seine Hosts miteinander zu vernetzen. Ich betreibe selbst mehrere Server an verschiedenen Standorten, die direkt miteinander Daten austauschen. So kommuniziert beispielsweise jeder Host mit einem zentralen Munin-Server, um Statusupdates zu senden. Auch Icinga-Daemons laufen auf den Hosts und prüfen verschiedene lokale Dienste. Das Ergebnis wird an das Icinga-Backend geschickt.

All diese Kommunikation soll für die Öffentlichkeit nicht einsehbar sein, d.h.:

  • Die Existenz von Wartungs- und Verwaltungsdiensten soll verborgen werden
  • Die Verbindung soll verschlüsselt werden: Internet und Hoster kann im Zweifel nicht getraut werden
  • Nur autorisierte Personen oder Maschinen sollen Zugriff auf bestimmte Dienste haben (z.B. Admin, Munin-Frontend)

Ziel ist also ein “Wartungsnetz”, welches nur dem Austausch der Hosts untereinander und dem Administrator dienen soll. Dienste wie Munin-Clients, Icinga-Clients, Docker-Daemons und sonstiges werden fest an die IP-Adresse aus dem Wartungsnetz gebunden und sind damit nicht mehr über die öffentlichen IP-Adressen des jeweiligen Hosts erreichbar. Das bringt drei Vorteile mit sich:

  • Ein aufgeräumtes Setup: Öffentliches kommt ans Internet, Privates in ein eigenes Netz.
  • Sicherheit:
    • Sicherheitskritische Dienste und Dienste, die nicht für die Öffentlichkeit bestimmt sind, sind aus dem Internet nicht erreichbar.
    • Es gibt Client-Server-Software, die nicht für den Betrieb im Internet ausgelegt ist, sondern nur für die Verwendung innerhalb eines Rechenzentrums (z.B. in dedizierten, lokalen Netzen). Auf Verschlüsselung und Authentifizierung wird hier oftmals verzichtet. Diese Software kann in einem solchen Wartungsnetz trotzdem guten Gewissens genutzt werden.
  • Komfort: Software via MySQL, Redis und weitere beherrschen zwar oft die Absicherung der Verbindung in einem Client-Server / Cluster -Setup, doch das Einbinden neuer Nodes ist nicht immer bequem. So beherrscht beispielsweise auch Munin sichere Verbindungen via TLS - dafür muss dann aber eine X.509-CA aufgebaut und gepflegt werden. Das schließt dann auch das umständliche Hantieren mit Zertifikaten ein. In einem dedizierten, sicheren Host-Netz kann auf die Munin-eigene Authentifizierung verzichtet werden.

Heute habe ich die vollständige Vernetzung all meiner Hosts in ein eigenes Wartungsnetz (bzw. virtuelles “lokales” Netz) in Angriff genommen. Sofort war klar, dass ich das Netz mit PeerVPN realisieren wollte, um einen Single Point of Failure zu vermeiden. Bei Installieren der Software auf dem ersten Server ist mir jedoch folgendes aufgefallen:

  • PeerVPN ist nicht kompatibel mit OpenSSL 1.1, welches von allen Hosts verwendet wird. Das Problem lässt sich lösen, indem PeerVPN statisch mit LibreSSL gelinkt wird. Dann bekommen die LibreSSL-Bibliotheken allerdings keine automatischen Updates durch das Betriebssystem. Heißt: Updates für LibreSSL müsste ich dann manuell verfolgen, und auf jedem der Hosts bei Bedarf PeerVPN neu linken.
  • Die Installation von PeerVPN ist nicht besonders zeitintensiv, aber die Einrichtung auf mehreren Hosts artet dann doch in Arbeit aus: Quellcode herunterladen, LibreSSL herunterladen, PeerVPN kompilieren und linken, im System integrieren, und einen Systemd-Service erstellen.

Das muss doch auch bequemer gehen. Und vor allem die Sache mit den Updates wollte ich eleganter gelöst haben.

Eine komfortable Lösung: PeerVPN im Docker-Container

Wenn man eine einzelne Anwendung als Baustein unabhängig vom Hostsystem ausrollen will, bieten sich Linux-Container an. Ich musste sofort an einen Docker-Container denken, den ich auf jedem meiner Hosts starten kann. PeerVPN sollte im Docker-Container laufen und mittels Host-Networking allen anderen Diensten auf demselben Host den Zugriff auf das Wartungsnetz ermöglichen. Glücklicherweise hat sich Markus Juenemann bereits Gedanken zu PeerVPN unter Docker gemacht, sodass ich seine Arbeit aufgreifen konnte. Ein paar kleine Änderungen habe ich schließlich in einen Fork des Projekts eingebracht: https://github.com/ThomasLeister/docker-alpine-peervpn (die meisten Änderungen sind inzwischen via Pull Request zurück in das Originalprojekt geflossen)

VPN Deployment auf den Hosts

Von nun an war die Verknüpfung der Hosts miteinander ein Kinderspiel. Docker und Docker-Compose waren auf den meisten Maschinen schon vorhanden. Für die Integration eines neuen Hosts in das Wartungsnetz waren nur noch 2 Schritte notwendig:

1) Docker-Compose File auf den Host kopieren

version: '3'

services:
    peervpn:
        image: thomasleister/peervpn
        container_name: "peervpn"
        network_mode: "host"
        cap_add:
            - NET_ADMIN
        restart: "always"
        environment:
            NETWORKNAME: srvnet
            INTERFACE: "srvnet0"
            PSK: "meintollerpresharedkey"
            LOCAL: "0.0.0.0"
            PORT: 7000
            INITPEERS: "1.2.3.4 7000"
            IFCONFIG4: "10.8.1.1/24"
            ENABLERELAY: "yes"
            ENABLEIPV6: "no"

(… und anpassen!)

2) PeerVPN starten

docker-compose up -d

Zur Überprüfung der Funktion kann die Ausgabe von PeerVPN beobachtet werden:

docker-compose logs -f peervpn

Nach wenigen Sekunden können sich alle teilnehmenden Hosts über die 10.8.1.x-IP-Adressen erreichen, oder auf dem srvnet0 Interface eigene Dienste über dieses Netz verfügbar machen.

Automatische Updates mit Watchtower

Ein generelles Problem beim Einsatz von Docker sind fehlende, automatische Updates. So würde mein Alpine-Linux-Container mit PeerVPN nach dem ersten Pull des Images immer mit demselben Softwarestand laufen bzw. wieder gestartet werden. LibreSSL-Updates würden meinen Container nicht ohne weiteres Zutun erreichen. Ich müsste ständig selbst nachsehen, ob es Updates für Alpine Linux gibt, dann mein Image ggf. neu bauen und auf allen Hosts dieses neue Image downloaden, um dann meine PeerVPN-Container neu zu starten.

Mein eigenes PeerVPN-Image auf DockerHub kann ich automatisch neu bauen lassen, wenn das Basisimage (Alpine Linux Official) sich verändert. Doch wie kommt das aktuelle PeerVPN-Image auf meine Hosts? Auch hierfür gibt es eine Lösung: Watchtower

Das Tool wird selbst als Container auf den Hosts gestartet und überwacht alle aktiven Container. Ändert sich das Image, auf dem ein Container basiert, erkennt Watchtower das, lädt das neue Image herunter und startet die entsprechenden Container neu.

Neben meinen PeerVPN-Containern habe ich auf jedem der Hosts also auch einen Watchtower-Container gestartet. Der nützt mir nicht nur im Zusammenhang mit PeerVPN, sondern generell bei allen Docker-Containeranwendungen. Der zusätzliche Aufwand fiel für mich daher nicht so sehr ins Gewicht.

version: '3'

services:
    watchtower:
        image: v2tec/watchtower
        container_name: "watchtower"
        restart: "always"
        volumes:
            - "/var/run/docker.sock:/var/run/docker.sock"
        command: "--interval 300 --cleanup"

Watchtower starten:

docker-compose up -d

Wer nur einzelne Container auf Updates überwachen lassen will, kann den “command” Parameter z.B. wie folgt erweitern:

command: "--interval 300 --cleanup meincontainer1 meincontainer2 meincontainer3"

(Übrigens: Watchtower aktualisiert nicht nur andere Container, sondern konsequenterweise auch sich selbst)

Fazit

Meine Hosts sind nun allesamt miteinander sicher vernetzt. Monitoring-Tools und andere verteilte Anwendungen können nun miteinander verknüpft werden, als wären sie in einem gemeinsamen, lokalen Netz. Das nimmt einem nicht nur Arbeit bei der Konfiguration ab, sondern sorgt auch für eine striktere Trennung zwischen Administrator-Diensten und öffentlichen Diensten.

Durch die Kapselung der VPN-Software in einem Docker-Container und die automatischen Updates via Watchtower muss ich mir keine Gedanken mehr um Updates und Kompatibilität mit dem Hostsystem machen. Neue Hosts sind zudem schnell in das bestehende Netz integriert.

Wer ein solches Wartungsnetz auch für seine Infrastruktur nutzen will, kann gerne auf mein Docker-Image und die oben genannten Docker-Compose-Files zurückgreifen.

Übrigens: Die 10.8.1.x-Adressen der Hosts habe ich im DNS verfügbar gemacht. So könnte z.B. 10.8.1.1 auf meinhost.srvnet.domain.tld gemappt sein. Das erleichtert den Umgang mit den IP-Adressen

Update am 11.09.2017: Ggf. starten Dienste nach einem Reboot nicht mehr, weil sie versuchen, Ports auf der srvnet0-Schnittstelle zu belegen, welche zu diesem Zeitpunkt aber noch nicht existiert. Der Docker-Container mit PeerVPN benötigt etwas Zeit, bis er hochgefahren ist. Eine Lösung für dieses Problem stelle ich in diesem Beitrag vor: Systemd: Dienst-Start von Netzwerk-Interface abhängig machen.