Kürzlich konnte ich meine eigenen Online-Services nicht erreichen – scheinbar weil ich in einem IPv6-only Netz „unterwegs“ war und so IPv4-Dienste nicht funktionierten. So gewann ich dein Eindruck, es könnte sich lohnen, mal ein wenig IPv6 lernen. Bei mir läuft extern alles via kong – also musste ich kong auf docker via IPv6 erreichbar machen.
Vorab ein „Disclaimer“: Ich kann (wie eigentlich immer) nicht behaupten, alles vollständig verstanden zu haben. Dies sind meine Beobachtungen und Notizen, wie ich es zum Laufen bekommen habe:
Basics: 3 Arten Container via IPv6 erreichbar zu machen
Es gibt 3 Wege, Container via IPv6 erreichbar zu machen (alle 3 sind auch hier genauer beschrieben):
- Über den „userland proxy“ (docker-proxy), der bei Docker-Port-Freigaben automatisch konfiguriert wird. Er wird z.B. auf
0.0.0.0
bzw.[::]
hören, wenn in docker-compose bei den Ports z.B.- 80:80
steht. Das ist ja bei IPv4 der „ganz normale“ Weg.
Großer Nachteil in meinen Augen: Bei IPv6 kommen aus Sicht des Containers dann aber alle Requestst von der IP des docker-proxy. -
Über einen Hilfs-Container aus
robbertkl/ipv6nat
: Das konfiguriertiptables -6
genauso als NAT, wie man das auch von IPv4 kennt.
Aber auch das gefällt mir nicht: Erstens brauch ich dann immer noch einen zusätzlichen Container, von dessen Maintainer ich abhängig bin und zweitens ist NAT nicht sehr „IPv6-like“. -
Routing via Host direkt zum Container.
Daher habe ich mich für den letzgenannten Weg entscheiden:
1. Docker Host mit IPv6 versorgen
Als erstes muss natürlich der Host selber eine IPv6 bekommen. Von meinem Hoster habe ich ein 64er Prefix erhalten (2a03:2222:3333:4444::/64
). Darin habe ich mir eine IP ausgesucht, die die IP des Servers sein soll.
- Auf meinem Ubuntu-Server ging das so: In der
/etc/netplan/01-netcfg.yaml
:... ens3: dhcp4: no addresses: - 1.2.3.4/22 - 2a03:2222:3333:4444::2/64 gateway4: 1.2.3.1 gateway6: fe80::1 ...
netplan apply
- Beim Hoster den Reverse Lookup eintragen.
2. Docker IPv6 beibringen
Docker verwaltet ja „jede Menge“ eigene Netze, in denen bisher nur IPv4 gesprochen wurde. Die sollen jetzt grundsätzlich auch IPv6 können.
- In der
/etc/docker/daemon.json
:Hier kann man sich entscheiden, ob man als Default-IPv6 schon die öffentlichen einträgt oder „interne“, nicht routbare. Ich habe mich gegen die öffentlichen entschieden, weil die allermeisten meiner Docker-Netze nicht öffentlich erreichbar sein sollen. Nur Kong wird öffentlich erreichbar, indem ich ein spezielles öffentliches Netz „ipv6_exposed“ definiere (s.u.). So habe in einen Beitrag zum Schutz vor versehentlichem Veröffentlichen von internen Services.
... "ipv6": true, "fixed-cidr-v6": "fd00::/80" # oder: "2a03:2222:3333:4444:300::/72", ...
systemctl reload docker
3. Öffentliches Docker-Netz definieren
Wie schon geschrieben, habe ich mich entschieden, nur genau eins meiner Docker-Netze öffentlich zu machen, damit ich bei jedem Docker-Service entscheiden kann und muss, ob ich ihn bewusst in dieses Netz lege, um ihn dadurch öffentlich zu machen.
docker network create --ipv6 --subnet=2a03:2222:3333:4444:300::/80 ipv6_exposed
Dieses Netz wird hiermit als 80er-Netz definiert und erzeugt. Es bleibt persistent bestehen und muss bei Bedarf explizit gelöscht werden.
4. Docker-Netz extern erreichbar machen
Ich nutze den Neighbor Discovery Protocol Proxy Daemon (ndppd), um mein eigenes, gerade angelegtes öffentliches Netz von extern erreichbar zu machen. Dieser Dämon beantwortet Nachbar-Anfragen (Neighbor Solicitations) mit den entsprechenden Antworten (Neighbor Advertisements). So erfährt und speichert der Router des Hosters z.B. welche anderen Netze über meinen Host als Router erreichbar sind.
apt install ndppd
- In der
/etc/ndppd.conf
:proxy ens3 { rule 2a03:2222:3333:4444:300::/80 { auto } }
Damit deklariere ich das Netz als über meinen Host erreichbar. Der ndppd macht das bei seinen Nachbarn bekannt. ens3
ist das Netz, worüber der Host von außen erreichbar ist und wo also der ndppd aktiv sein soll.
5. Container dem öffentlichen Docker-Netz zuordnen
In der docker-compose.yml
definiere ich ein internes (IPv4 only) Netz und außerdem das öffentliche ipv6_exposed
, das ich oben erzeugt habe:
...
networks:
kong-net:
driver: bridge
enable_ipv6: false
ipam:
config:
- subnet: 172.99.0.0/16
gateway: 172.99.0.1
ipv6_exposed:
external:
name: ipv6_exposed
...
Das Keyword external
gibt an, dass das Netz außerhalb des Compose-Files erzeugt wurde – also schon existieren muss.
6. Kong IPv6 beibringen
Da Kong nun direkt selber erreichbar ist und kein Docker mehr eine Port-Umsetzung macht (wie bei es bei IPv4 üblich ist), muss er auch selber direkt auf die Ports 80 und 443 hören. Dazu…
- braucht Kong die entsprechenden Rechte (Capabilities):
Ich geb sie ihm imDockerfile
, in dem ich mir auch diverse Kong-Plugins installiere:FROM kong:2.3 USER root # install some plugins ... # see https://github.com/Kong/docker-kong/blob/60626098f2f32fe1528eb4ffacff13fd1c3e919f/alpine/Dockerfile # https://blog.container-solutions.com/linux-capabilities-in-practice RUN setcap 'cap_net_bind_service+eip' /usr/local/openresty/nginx/sbin/nginx USER kong
- Nun kann er auf privilegierte Ports (unter 1024) selber hören. Im
docker-compose.yml
:... kong: build: ./kong-build restart: always networks: kong-net: ipv4_address: 172.99.0.100 ipv6_exposed: ipv6_address: ${KONG_BIND_IPV6:?err} cap_add: - NET_BIND_SERVICE environment: KONG_PG_HOST: kong-database KONG_PG_DATABASE: kong KONG_PG_USER: kong KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-kong} KONG_PROXY_LISTEN: 0.0.0.0:80, 0.0.0.0:443 ssl, [::]:443 ssl, [::]:80 KONG_ADMIN_LISTEN: 0.0.0.0:8001 KONG_PROXY_ACCESS_LOG: /dev/stdout KONG_ADMIN_ACCESS_LOG: /dev/stdout KONG_PROXY_ERROR_LOG: /dev/stderr KONG_ADMIN_ERROR_LOG: /dev/stderr depends_on: - kong-migration - kong-database volumes: - ${KONG_DATA_DIR:?err}/logs:/var/log healthcheck: test: ["CMD", "curl", "-f", "http://kong:8001"] interval: 50s timeout: 2s retries: 15 ports: - ${KONG_BIND_IP:?err}:80:80 - ${KONG_BIND_IP:?err}:443:443
Beachte: Die
KONG_BIND_IPV6
ist nicht unterports
zu finden. Dies würde den Userland-Proxy konfigureren. Das mach ich nicht; der Container hat ja seine eigene IPv6 und hört selber auf die entsprechenden Ports (siehe oben).
DieKONG_BIND_IP
undKONG_BIND_IPV6
stehen im.env
:... KONG_BIND_IP=1.2.3.4 KONG_BIND_IPV6=2a03:4000:43:60:300::8100
- Nun nur noch im DNS den AAAA-Record
2a03:2222:3333:4444:300::8100
eintragen.
7. Forwarding und Kernel-Firewall (iptables/ufw)
-
Außerdem muss IP-Forwarding für IPv6 aktiviert sein. Bei mir war/ist das bereits. Ich weiß nicht, ob Docker das vielleicht aktiviert.
# sysctl net.ipv6.conf.all.forwarding net.ipv6.conf.all.forwarding = 1
Ggf. in
/etc/sysctl.d/99-sysctl.conf
einkommentieren. -
Dementsprechend ist logisch, dass die Pakete zu den Containern über die Forward-Tables geroutet werden und z.B. ufw so eingestellt werden muss:
# ufw status ... 443/tcp ALLOW FWD Anywhere 443/tcp (v6) ALLOW FWD Anywhere (v6) ...
Fazit
Das ganze läuft bei mir nun seit ein paar Monaten stabil und ohne dass ich nochmal „nachjustieren“ musst. Auch ein Docker-Versionsupgrade hat das ganze schon „überlebt“.
Anhang: Links
- Zu Kong aus diesem Blog:
-
Super Artikel zu IPv6 bei Docker: https://medium.com/@skleeschulte/how-to-enable-ipv6-for-docker-containers-on-ubuntu-18-04-c68394a219a2
- zu Capabilities: https://blog.container-solutions.com/linux-capabilities-in-practice
- IPv6 bei netcup mit docker: https://www.falk-online.eu/author/sascha/, https://www.netcup-wiki.de/wiki/Zus%C3%A4tzliche_IP_Adresse_konfigurieren und https://forum.netcup.de/anwendung/scp-server-control-panel/10447-ipv6-einrichten/
- IPv6 im Docker-Daemon: https://docs.docker.com/config/daemon/ipv6/
- Netz-Konfig in docker-compose: https://docs.docker.com/compose/compose-file/compose-file-v2/#network-configuration-reference
Vielen vielen Dank! Dieser Eintrag hat mir sehr weitergeholfen!!!