Externe IPs für Kubernetes-PODs mittels Pipework

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).

Veröffentlicht unter Linux

Schreibe einen Kommentar

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