Ich habe kürzlich ein bestehendes Web-Hostingsystem auf Kubernetes migriert. Dabei sollte das Management-Tool „Froxlor“ weiterhin im Einsatz bleiben, das ganze jedoch von der bisherigen dedizierten Maschine in einen Kubernetes-POD umziehen. In einem anderen Projekt wurde bereits ein Docker-Container sowie ein Helm-Chart für Froxlor erstellt.
Nun stand ich vor der Herausforderung, dem System eine eigene IP-Adresse, unabhängig vom Kubernetes Ingress zuzuweisen. Ich hatte ein ähnliches Setup im privaten Bereich bereits mittels Multus-CNI realisiert. Dazu ist jedoch die Modifikation des „Cluster Networking“ notwendig – diesen Weg wollte ich bei unseren Produktivsystemen nicht gehen. Nach kurzer Recherche habe ich das Tool „pipework“ als mögliche Lösung gefunden, welche ich bereits für reine Docker-Installationen im Einsatz hatte. Mit kube-pipework existiert auch eine Lösung, welche besser an Kubernetes angepasst ist.
Voraussetzungen
Damit Container externe IPs erhalten können, ist es notwendig, das Netzwerk des jeweiligen Hosts entsprechend zu konfigurieren. Hierbei gibt es mehrere Möglichkeiten, beispielsweise der Einsatz von MacVLAN oder einer Bridge. Ich habe mich der Einfachheit halber für eine Bridge entschieden.
Des Weiteren ist eine zusätzliche IP notwendig, welche auf den Node geroutet wird. Dabei ist speziell beim Setup mit der Bridge wichtig, dass die IP auf eine IP des Servers (und nicht direkt auf das Netzwerkdevice) geroutet wird. Ist dies nicht möglich, muss MacVLAN eingesetzt werden.
Setup der Bridge (Debian und Derivate)
Der Node läuft auf einem Hetzner-Cloud Node, welche bereits mittels DHCP konfiguriert wird. Zunächst habe ich die automatische Netzwerkkonfiguration mittels „Cloud-Init“ deaktiviert, die nötigen Schritte sind im Hetzner-Wiki dokumentiert. Anschließend habe ich die Netwerkkonfiguration unter /etc/network/interfaces angepasst:
# interfaces(5) file used by ifup(8) and ifdown(8) auto lo iface lo inet loopback iface eth0 inet manual auto br0 iface br0 inet dhcp bridge_ports eth0 # add a route for each external IP to be used directly within containers (will be set up via pipework) up route add -host 22.33.44.1 dev br0 up route add -host 22.33.44.2 dev br0 # Import other config source /etc/network/interfaces.d/*
Die Konfiguration von eth0 wurde hierbei deaktiviert (Zeile 6). Zusätzlich wurde eine Bridge „br0“ angelegt (Zeile 7-8), welche die ursprüngliche Konfiguration von eth0 erhält (Zeile 9) in dem Fall lediglich die Aktivierung von DHCP). Das Device eth0 wird der Bridge hinzugefügt (Zeile 10).
Für jede IP, welche in Countainern verwendet werden soll, muss eine statische Route auf die Bridge angelegt werden (Beispiel in Zeilen 12-13).
Wichtig ist, dass das Paket „bridge-utils“ installiert ist, damit die Bridge aktiviert werden kann.
Anschließend kann das alte Interface deaktiviert und das neue aktiviert werden (am einfachsten mittels reboot).
Für eine statische IP ohne DHCP würde die Konfiguration in etwa wie folgt aussehen:
# interfaces(5) file used by ifup(8) and ifdown(8) auto lo iface lo inet loopback iface eth0 inet manual auto br0 iface br0 inet static bridge_ports eth0 # setup IP to be used on host (and for regular POD network port mappings) address 22.33.43.2 netmask 255.255.255.0 broadcast 22.33.43.255 gateway 22.33.43.1 dns-nameservers 8.8.8.8 1.1.1.1 # add a route for each external IP to be used directly within containers (will be set up via pipework) up route add -host 22.33.44.1 dev br0 up route add -host 22.33.44.2 dev br0 # Import other config source /etc/network/interfaces.d/*
Setup der externen IP
In der Hetzner-Cloud kann einfach eine „Floating-IP“ bestellt und auf den Node geroutet werden. Ähnlich ist die Konfiguration bei Netcup, hier muss eine „Zusatz-IP“ bestellt werden.
Setup von Pipework
Kube-pipework kann als Daemonset unter Kubernetes deployed werden. Das folgende Beispiel wurde mittels Helm-Chart generiert:
--- # Source: pipework/templates/daemonset.yaml apiVersion: apps/v1 kind: DaemonSet metadata: name: pipework labels: app.kubernetes.io/name: pipework helm.sh/chart: pipework-0.1.0 app.kubernetes.io/instance: pipework app.kubernetes.io/managed-by: Helm spec: selector: matchLabels: app.kubernetes.io/name: pipework app.kubernetes.io/instance: pipework template: metadata: labels: app.kubernetes.io/name: pipework app.kubernetes.io/instance: pipework spec: hostPID: true hostIPC: true hostNetwork: true containers: - name: pipework image: "kvaps/pipework:v1.0.0" imagePullPolicy: Always securityContext: privileged: true env: - name: run_mode value: "batch,daemon" volumeMounts: - name: docker-sock mountPath: /docker.sock volumes: - name: docker-sock hostPath: path: /var/run/docker.sock nodeSelector: pipework/enabled: "true"
Optional kann das Deployment mittels Nodeselector auf bestimmte Nodes beschränkt werden (Zeile 43).
Deployen von Pods mit eigenständiger IP
Hier ist ein Beispiel eines Pods mit zwei externen IPs:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: pipework_cmd_ip1: "br0 -i ip1 @CONTAINER_NAME@ 22.33.44.1/32@11.22.33.44" pipework_cmd_ip2: "br0 -i ip2 @CONTAINER_NAME@ 22.33.44.2/32" pipework_cmd_route1: "route @CONTAINER_NAME@ add 10.42.0.0/16 via 169.254.1.1" pipework_cmd_route2: "route @CONTAINER_NAME@ add 10.43.0.0/16 via 169.254.1.1" spec: initContainers: - image: kvaps/pipework:v1.0.0 name: pipework command: - /bin/sh - -ce - | pipework --wait -i ip1 pipework --wait -i ip2 containers: - name: nginx image: nginx:alpine strategy: type: Recreate
Die erste IP wird in Zeile 16 konfiguriert. Dabei wird die IP des Nodes (11.22.33.44) als Standard-Gateway eingetragen. Der Node wird dadurch direkt und nicht mehr über das IP-Masquerading von Docker nach außen kommunizieren und die erste IP als Quell-IP für ausgehende Verbindungen verwenden.
Für alle weiteren IPs wird kein Standard-Gateway mehr angegeben (Zeile 17).
Damit der POD mit anderen PODs und Services im Cluster kommunizieren kann, ist es nötig, eine Route auf das POD-Netzwerk (Zeile 18) und auf das Service-Netzwerk (Zeile 19) zu setzen. Die IPs passen für die Standardeinstellungen für Rancher2 und müssen an den jeweiligen Cluster angepasst werden.
Der Init-Container (Zeilen 22-29) wartet beim Start des PODs auf die Verfügbarkeit der Pipework-Devices, bevor die eigentlichen Container gestartet werden.
Wichtig ist noch, eine Redeployment-Strategie zu setzen, welche verhindert, dass mehrere PODs mit gleicher externer IP zur gleichen Zeit gestartet werden. Dies ist beispielsweise über die Recreate-Strategie möglich (Zeile 34).