Docker-Container mit IPv6 anbinden

Die aktuelle Verbreitung von IPv6 liegt laut Google in der Schweiz bei 27.4%. Da sollte man sich als Betreiber einer Webseite oder eines Cloud-Dienstes schon langsam Gedanken darüber machen, wie man seine Services auch per IPv6 an seine Kunden bringt.

Ich zeige in diesem Artikel auf, wie man Docker vorbereitet und dann seinen Containern eine IPv6-Adresse verpasst. Alle gezeigten Befehle sollten auf einem Ubuntu 16.04 funktionieren. Bei anderen Distributionen und Versionen können die zu editierenden Dateien und auszuführenden Befehle natürlich unterschiedlich sein!

Voraussetzungen

Server mit geroutetem IPv6-Netzwerk

Natürlich muss man zuerst einmal einen Server in Betrieb haben, der schon per IPv6 angebunden ist. Das heisst, einerseits muss der Server-Provider das unterstützen und andererseits muss der Dual-Stack-Betrieb im Betriebssystem aktiviert sein.

Am einfachsten findet man das mit ifconfig heraus. Wenn bei der Ausgabe etwas Ähnliches wie hier zu sehen ist, dann scheint alles bereit zu sein:

Wichtig ist hier die Adresse mit dem Scope Global. Adressen, die mit fe80 oder fd00 beginnen oder den Scope Link haben, sind lokale Adressen, mit denen man nicht vom Internet aus erreichbar ist. Im obigen Beispiel sehen wir, dass wir ein ganzes /64-Netz zur Verfügung haben, nämlich das 2a01:4f8:a:b::/64.

Ob die Adresse auch korrekt funktioniert, findet man am einfachsten mit einem Ping heraus:

Aktuelle Docker-Version

Es muss mindestens die Version 1.10 von Docker installiert sein. Erst diese unterstützt es, eigene IP-Adressen beim Erstellen eines Containers anzugeben.

Docker vorbereiten

Folgende Schritte müssen wir durchführen, damit die Docker Engine was mit IPv6 anfangen kann:

  • Route für Interface docker0 hinzufügen:

    • Wir routen damit allen Traffic, der über IPv6 an das Netz 2a01:4f8:a:b::/64 kommt
      an das Interface docker0 weiter
  • Forwarding von IPv6-Paketen im Kernel  aktivieren:
  • Beim Docker-Daemon IPv6 aktivieren und ihm das Subnetz zuweisen. Dafür editiert man die Datei /etc/default/docker
    wie folgt:
  • Restart des Docker-Daemon:
  • Ein Docker-Netzwerk erstellen, das IPv6 aktiviert hat:

    • Damit erstellen wir ein Docker-Netzwerk mit dem Namen ipv6 und dem Subnetz 2a01:4f8:a:b:c::/80
    • Eventuell wollen wir mehrere Docker-Netzwerke mit unterschiedlichen Subnetzen machen. Darum wählen wir eine
      Netzlänge von /80 und den Suffix c für dieses Subnetz. Für weitere Netze
      würden wir einfach einen anderen Suffix benutzen.
    • Den Teil c können wir frei wählen. Theoretisch können wir auch noch weitere Bytes des Subnetzes
      bestimmen, dann muss aber auch die Länge nach dem / angepasst werden

Sofern das alles geklappt hat, sollte der Befehl docker network inspect nun etwas in der Art liefern:

Container dem Netzwerk hinzufügen

Zum Schluss können wir einen laufenden Container dem soeben erstellten Netzwerk hinzufügen:

Damit wird der Container mit dem Namen [container_name] unter der Adresse 2a01:4f8:a:b:c::5 erreichbar sein.

Die Voraussetzung dafür ist natürlich, dass die Software im Container auch mit IPv6 umgehen kann. Diese muss nämlich auch über IPv6 auf dem Interface zuhören. Falls man eine Listen-Address konfigurieren muss, dann gibt man dort [::] ein, was das IPv6-Äquivalent zu 0.0.0.0 ist. Am besten konsultiert man die Dokumentation des Services, den man laufen lassen möchte. Oft ist es auch nur eine Einstellung in einer Konfigurationsdatei.

Muss ein Container nicht über IPv4 erreichbar und damit nicht im default-Netzwerk hinzugefügt sein, kann man auch direkt beim Start des Containers das Netzwerk konfigurieren:

Um zu prüfen, ob das Hinzufügen geklappt hat, kann man wiederum den Befehl docker network inspect benutzen.

Ob der Service danach auch wirklich über IPv6 erreichbar ist, prüft man am besten mit einem Tool wie ipv6-test.com

Edit (20.06.2017):

Wie im Kommentar von Roland korrekt erwähnt wurde, ergeben sich durch IPv6 mit Docker neue Herausforderungen, was Security und Wartbarkeit betrifft.

Der Hauptgrund dafür ist das Fehlen des NAT-Supports für IPv6 in Docker. Beziehungsweise ist ja NAT nur eine Krücke, um das Problem der beschränkten Anzahl öffentlicher Adressen in IPv4 einzuschränken. Deshalb wird NAT möglicherweise gar nie nativ in Docker eingebaut werden für IPv6 (siehe dazu dieses Issue auf Github).

Was bedeutet das Fehlen von IPv6-NAT nun für meine Container?

  • Bisher ist man sich von Docker gewohnt, dass nur die Ports, die man ganz explizit (also mit -p xxx:xxx oder -P) freischaltet/weiterleitet auch von aussen erreichbar sind. Bei IPv6 ist dies nicht so. Gibt man einem Container eine öffentliche IPv6-Adresse, dann sind standardmässig alle (!!!) Ports, die der Container öffnet und an diese Adresse bindet, von aussen erreichbar. Also nicht nur die, die im EXPOSE-Statement des Dockerfiles angegeben sind, sondern alle, die der Prozess öffnet.
    Will man also die gleiche Sicherheit wie NAT, dann muss man noch explizit Firewall-Rules definieren, welche die gleiche Isolation des Containers bewirken.
  • Ein weiterer Nachteil gegenüber NAT ist das Handling der DNS-Einträge. Bisher war es einem relativ egal, welche Adresse ein Container intern genau gekriegt hatte. Im DNS trug man sowieso einfach die öffentliche IP des Docker-Hosts ein. Da mit v6 aber jeder Container eine öffentliche Adresse kriegt, muss diese im DNS eingetragen werden. Und damit sie nicht bei jedem Neuanlegen des Containers ändert, will man sie dem Container statisch zuordnen. Das heisst, man muss sich auch da wieder deutlich mehr Gedanken machen und es funktioniert nicht einfach alles „out of the box“ wie man es sich bisher gewohnt war.

Wie genau diese Herausforderungen am besten angegangen werden, wird sich zeigen. Es gibt beispielsweise Projekte, die versuchen die gleiche NAT-Funktionalität für IPv6 zu implementieren.

2 Kommentare

  • Roland, 16. Juni 2017

    Ganz so einfach ist es leider nicht im praktischen Betrieb: Das IPv4-Legacy-Networking von Docker etabliert eine „Port-Forwarding“ Kultur mittels NAT: Nur Ports, die vom Container auf den Host gemapped werden, sind von aussen erreichbar.

    Der native IPv6-Weg von Docker sorgt aber dafür, dass jeder Container per IPv6 von aussen ansprechbar ist, unabhängig vom Portmapping. Hier muss also zwangsläufig noch eine Firewall davor implementiert werden (bzw manuelles netfilter-scripting) um Container nicht versehentlich zu zweit zu öffnen. Auch Failover bzw das Neuanlegen von Containern wird schwer (DNS change notwendig oder downtime), sofern man nicht wieder einen Proxy davor hat.

    Technisch gesehen ist NAT schlimm und die IPv6-Lösung eigentlich der bessere Weg, in der Docker-Welt muss man hier aber zweigleisig fahren oder IPv6-NAT-Lösungen Dritter einsetzen, siehe auch https://github.com/moby/moby/issues/25407 und https://github.com/robbertkl/docker-ipv6nat

  • Oliver Gugger, 16. Juni 2017

    Hallo Roland

    Vielen Dank für deinen Kommentar!
    Ich gebe dir absolut recht. Genau diese zwei Punkte (fehlender NAT-Support und DNS-Administration wegen einer IP pro Container) sind mir auch aufgefallen bei meinem Test-Setup.
    Da ich selbst auch keine Lösung/Vorgehensweise dafür habe, habe ich nichts dazu geschrieben. Aber wenigstens auf die Problematik hätte ich hinweisen können. Ich werde den Post bei Gelegenheit anpassen.

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *