k3s graceful shutdown im Cluster Betrieb – oder wie keine Pods mehr in der Schwebe hängen bleiben

Um einen eigenen Playground zu haben, steht bei mir nun schon seit einer Weile ein Proxmox Cluster, bestehend aus mehreren Intel NUCs der meine lokalen Services hosted. Unter anderem läuft darauf auch ein k3s cluster mit mehreren Nodes.

Da Kubernetes / k8s eigentlich ein “selbstheilendes” System ist, bin ich bisher davon ausgegangen, dass man ohne Probleme einzelne Nodes aus dem Cluster nehmen kann, ohne dass man sonderlich lange Downtimes hat. Das ist auch korrekt, wenn man Services mit mehreren Instanzen hat, wird jedoch schwer, wenn Services nur aus einzelnen Instanzen bestehen. Mir ist bewusst, dass es dann natürlich kein HA (High Availability) Betrieb ist, aber das ist bei mir lokal auch nicht notwendig. Für mich ist nur wichtig, dass wenn einer der NUCs mal aussteigen sollte, dass meine Services sich selbst heilen und dann auf einer anderen Node weiter laufen.

Beim experimentieren mit diesen Anforderungen habe ich dann schnell gelernt, dass k8s bzw. k3s sich nicht so verhält wie von mir erwartet. Das gewollte Verhalten ist nämlich, dass wenn eine Node für den Cluster nicht erreichbar ist, erstmal für 5 Minuten nichts passiert. Die Hoffnung des Systems ist, dass es sich nur um einen kurzen Restart handelt oder aber z.b. Upgrades gemacht werden. Da k8s ja eine Orchestrierung ist, ist es möglich, dass nur der k8s Service selbst heruntergefahren / neu gestartet wird, die Container/Pods aber einfach weiter laufen.

Bis hierhin macht das für mich auch Sinn, aber das Verhalten wird komisch, wenn man die Node mittels “reboot” neu startet oder per “shutdown” herunterfährt. K8s/k3s verhält sich an dieser Stelle nämlich genauso. Obwohl es wissen sollte, dass ein Shutdown ansteht und somit die Pods auch gestoppt werden, teilt es dies dem restlichen Cluster nicht mit. Und somit wartet der restliche Cluster 5 Minuten, ehe er irgendwas macht.

Wenn die Node als nach 5 Minuten nicht wieder da ist, dann fangen die anderen Nodes an, die Pods zu verschieben bzw. neue Instanzen hochzufahren. Das ist erstmal eine gute Idee, aber im k8s Kontext sind die “alten” Instanzen noch da. Und hier wird es tricky, sobald man Container hat, die z.B. Volumes verwenden. In meinem Fall sind es Volumes innerhalb eines Ceph Clusters, aber das gleiche wird auch bei anderen Formen von Volumes passieren: der Claim auf diese Volumes erlaubt nur einen einzelnen Zugriff, und keinen Zugriff von mehreren Pods gleichzeitig. In meinem speziellen Fall blockiert also der Controller, der die Ceph Volumes bereit stellt, dass der neue Pod hochfahren kann, da er nicht weiß, was mit dem “alten” Pod ist. So lange also die eben heruntergefahrene k3s Node nicht wieder für den Cluster erreichbar ist, verharrt der Pod in diesem Zustand. Das zum Thema selbstheilend.

Ich habe das Problem für mich nun so gelöst, dass ich auf jeder k3s Node einen Cleanup Service neben dem eigentlichen k3s Service laufen lasse. Dieser Service startet NACH dem k3s Service, wird aber VOR der Beendigung des k3s Services auch ordentlich beendet.

Der Service sieht so aus (%H ist der Hostname, meine Nodes heißen jeweils wie ihr Hostname):

[Unit]
Description=k3s node drain on shutdown
After=k3s.service
Before=shutdown.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/k3s-enable-node.sh %H
RemainAfterExit=true
ExecStop=/usr/local/bin/k3s-drain-node.sh %H
[Install]
WantedBy=multi-user.target

Die beiden Scripte haben diesen Inhalt:

k3s-enable-node.sh

#!/bin/bash
kubectl uncordon $1

k3s-drain-node.sh

#!/bin/bash
node_name=$1
kubectl drain $node_name
sleep 5
set +e
kubectl get pods --all-namespaces --field-selector spec.nodeName=$node_name | tail +2 | grep -v "csi-rbdplugin" | grep -v "svclb-traefik" | awk '{print $2 " --namespace=" $1}' | xargs -n 2 kubectl delete pod -o name
sleep 20

Die sleep Befehle habe ich zur Sicherheit drin, weil kubectl Commands ja nicht immer zu 100% synchron ablaufen. Kann sein, dass das übertrieben ist. Das “set +e” ist drin, weil das Stoppen einzelner Pods evtl Fehler wirft und in diesem Fall das ganze Script failen würde. Da ich hier aber nur versuche noch zu retten was zu retten ist, ignoriere ich die Fehler und versuche so viele Pods wie möglich zu verschieben.

Wenn ich nun einen shutdown oder reboot command auf einer meiner k3s Nodes ausführe, dann wird zuerst das k3s-drain-node.sh Script ausgeführt. Es markiert die k3s Node, dass auf dieser keine neuen Pods mehr gestartet werden dürfen und auch kein Processing mehr passiert. Anschließend lasse ich mir eine Liste mit allen Pods, die auf dieser Node laufen ausgeben, formatierte die etwas um, ignoriere einzelne Pods die ohnehin fest an diese Node gepinnt sind und nirgendwo anders laufen und lösche dann alle Pods, die übrig bleiben. Da die aktuelle Node keine neuen Pods mehr starten darf, werden diese ordnungsgemäß beendet und auf den anderen Nodes im Cluster neu gestartet. Erst wenn das erledigt ist fährt sich die eigentliche Node herunter. Somit habe ich natürlich eine kleine Downtime, aber innerhalb kürzester Zeit ist der jeweilige Pod ja dann wieder erreichbar und ich habe keine permanenten Ausfälle mehr. Ohne diese Scripts würde es also mindestens 5 Minuten dauern, ehe der Pod woanders hin verschoben werden würde, und selbst dann ist aufgrund der Volume Problematik nicht sicher, ob er überhaupt neu gestartet werden kann.

nip.io – deine dynamische Domain für beliebige IP Adressen

Ein Kollege hat mir den Dienst nip.io gezeigt, der extrem praktisch ist, wenn man schnell und unkompliziert eine Domain benötigt, die auf eine spezielle IP zeigt.

Das Prinzip ist relativ einfach, denn bei nip.io ist die Ziel IP direkt ein Teil der Domain:

optionale-subdomain.192.168.1.10.nip.io

Den Teil optionale-subdomain kann man beliebig nutzen/ändern oder auch einfach weg lassen, der Rest erklärt sich glaube ich relativ leicht selbst 🙂

Eine bestehende Flux Installation upgraden

Wenn man z.B. beim bootstrap von Flux die Services für image-reflector-controller und image-automation-controller vergessen hat und diese nachinstallieren möchte, dann wird man zumindest in der offiziellen Doku nicht so recht darauf hingewiesen.

Die Lösung ist dann doch relativ einfach: in eurem Flux Ordner folgendes (natürlich für euer System angepasst) ausführen und anschließend per git commiten/pushen:

flux install \
--components-extra image-reflector-controller,image-automation-controller \
--export > ./clusters/my-cluster/flux-system/gotk-components.yaml

[Quicktip] Auf dem Ubiquity Edge Router X einen Wildcard DNS Eintrag anlegen

Auf dem Ubiquity Edge Router X kann man ja wunderbar unter Wizards –> DNS Host Names statische DNS Einträge mit “Hostname –> IP” setzen. Allerdings eben nur einzeln. Wenn man einen Wildcard DNS Eintrag anlegen will, der “*.bytelude.intern” auf die IP “192.168.1.10” weiterleitet, dann geht das so:

  1. Edge Router X UI aufrufen
  2. Menüpunkt “Config Tree” öffnen
  3. service –> dns –> forwarding auswählen
  4. auf der rechten seite bei “options” auf “+Add” klicken und dann in das Textfeld folgendes schreiben:
    address=/.bytelude.intern/192.168.1.10
  5. Auf Preview/Speichern drücken und der Eintrag ist sofort aktiv. Nun könnt ihr auf eurem Rechner test.bytelude.intern anpingen und das DNS Ergebnis sollte die IP 192.168.1.10 sein.

Wichtig ist der Punkt vor der Domain, damit es als Wildcard funktioniert!

Defektes Teasi One 3 extend wiederherstellen

Mein Schwiegervater hat ein Fischer E-Bike und zumindest eine Zeit lang hatten die als Fahrradcomputer ein Tahuna Teasi One 3 extend mit dabei. Das Teil ist ein Windows CE PocketPC mit Touch Screen und da drauf läuft Tahuna, was quasi ein Open Streetmap Client mit Spezialisierung auf Fahrrad fahren ist. Der Hersteller dieses Gerätes ist vor einer Zeit insolvent gegangen und es gab noch ein letztes Update für das System, wo dann wohl auch alle Länderkarten freigeschaltet wurden (die vorher Geld gekostet haben). Und dieses Update funktionierte wohl noch bis Ende 2023 und damit zu spät für mich. Da noch ein zweites Teasi mit einer neueren Software vorhanden war, dachte ich, dass ich das System einfach rüber kopieren kann (das Gerät verhält sich wie ein USB Stick am Rechner). Dummerweise ging das zwar, allerdings wollte das Tahuna dann einen Lizenzschlüssel, der natürlich nicht vorhanden war. Und ganz blöderweise ging in diesem Moment der USB Stick Modus nicht mehr, sodass das Gerät quasi festgefahren war.

Weder möglicher Support Dienstleister noch das was von der Firma übrig war konnten mir weiterhelfen bzw. waren überhaupt erreichbar. Auf den noch online verfügbaren Seiten des Herstellers war die Rede von einem Wiederherstellungsimage, welches man auf eine Micro SD Karte kopieren konnte und damit das Gerät auf Werkseinstellungen bekommen konnte. Ja, aber leider waren die Links bereits tot. Also klapperte ich diverse Blogs und Foren ab, um dann irgendwann beim Forum von mtb-news.de zu landen. Dort fand ich einen Thread und ein User namens Holger hatte genau das gleiche Teasi wie ich und sprach auch von dem Wiederherstellungs-Image. Also kontaktierte ich ihn, er meldete sich, fand noch die alte Micro-SD Karte von seiner Wiederherstellung und konnte dank einer Anleitung von mir eine Kopie der Karte als Image anfertigen. Nachdem er mir die Datei dann zugeschickt hatte, spielte ich sie auf die Karte und es funktionierte direkt auf Anhieb – sie formatierte das Teasi komplett und nach einem Neustart ging es direkt in den USB Stick Modus.

Und GLÜCKLICHERWEISE hatte ich vor meiner Herumkopiererrei eine komplette Sicherheitskopie des Systems gezogen, welche ich anschließend wieder auf das Gerät kopieren konnte – und es funktionierte.

Da ich vielleicht nicht der letzte mit diesem Problem bin, möchte ich die Datei zur Verfügung stellen und hoffe jemandem damit genauso zu helfen wie Holger es mit mir getan hat.

WICHTIG: dieses Image formatiert mehr oder weniger einfach nur den internen Speicher des Teasi One 3 Extend (ich vermute es geht auch mit den anderen Versionen, kann das aber nicht verifizieren!). Das bedeutet, nachdem diese SD Karte in das Gerät eingelegt wird beginnt SOFORT die Formatierung, alle Daten auf dem Teasi sind direkt gelöscht! Wenn das Gerät nicht eingeschaltet ist dann passiert die Formatierung erst nach dem Einschalten mit der eingelegten Karte.

ACHTUNG!!!! ACHTUNG!!!! ACHTUNG!!!!

Die Teasi Update Server sind offline, ihr könnt das original System NICHT wiederherstellen!!!!

HABT IHR ALSO KEINE KOMPLETTE SICHERHEITSKOPIE EURES “BikeNav” ORDNERS VOM TEASI, DANN LEGT KEINESFALLS!!!!! EINE MICRO-SD MIT DIESEM IMAGE IN DAS GERÄT EIN!!!!!!

Dieses Image bringt euch nur etwas, wenn ihr eine original Kopie des BikeNav Ordners habt!

Nach dieser Warnung, hier nun das Image: Teasi One 3 Extend Recovery Image

Wie man Jenkins Credentials wieder auslesen kann

Wie in so vielen CI/CD Tools kennt auch Jenkins das Prinzip von Secrets. Dabei kann man Benutzername/Passwort, nur Passwort, SSH Keys usw. im System hinterlegen und dann innerhalb von Build Jobs darauf zugreifen. Sollte der Inhalt des Secrets irgendwo im Build Log ausgegeben werden, ist Jenkins sogar so schlau das ganze mit **** zu maskieren. An sich also ein sehr praktisches System. Kommt man nun aber wie ich an den Punkt, dass man Secrets in Jenkins hat, aber sie dummerweise nirgendwo anders gespeichert hat, dann ist das ein Problem.

Aber es gibt eine Lösung, die ich hier gefunden habe: https://stackoverflow.com/questions/34795050/how-do-i-list-all-of-my-jenkins-credentials-in-the-script-console

Man öffnet im Jenkins einfach die Script Konsole ([JENKINS_URL]/manage/script) und fügt dort folgendes Script ein, und lässt es dann ausführen:

import java.nio.charset.StandardCharsets;
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
      com.cloudbees.plugins.credentials.Credentials.class
)
for (c in creds) {
  println(c.id)
  if (c.properties.description) {
    println("   description: " + c.description)
  }
  if (c.properties.username) {
    println("   username: " + c.username)
  }
  if (c.properties.password) {
    println("   password: " + c.password)
  }
  if (c.properties.passphrase) {
    println("   passphrase: " + c.passphrase)
  }
  if (c.properties.secret) {
    println("   secret: " + c.secret)
  }
  if (c.properties.secretBytes) {
    println("    secretBytes: ")
    println("\n" + new String(c.secretBytes.getPlainData(), StandardCharsets.UTF_8))
    println("")
  }
  if (c.properties.privateKeySource) {
    println("   privateKey: " + c.getPrivateKey())
  }
  if (c.properties.apiToken) {
    println("   apiToken: " + c.apiToken)
  }
  if (c.properties.token) {
    println("   token: " + c.token)
  }
  if (c.properties.subscriptionId) {
    println("   subscriptionId: " + c.subscriptionId)
  }
  if (c.properties.clientId) {
    println("   clientId: " + c.clientId)
  }
  if (c.properties.tenant) {
    println("   tenant: " + c.tenant)
  }
  if (c.properties.clientSecret) {
    println("   clientSecret: " + c.clientSecret)
  }
  if (c.properties.plainClientSecret) {
    println("   plainClientSecret: " + c.plainClientSecret)
  }
  println("")
}

Anschließend werden in der Ausgabe alle Secrets mit ihrem hinterlegten Namen und ihren Inhalten ausgegeben.

Influxdb auf dem Raspberry Pi scheitert an der Compaction der Daten mit “cannot allocate memory”

Influxdb wird bei sehr vielen IoT Systemen verwendet, weil es die perfekte Datenbank für die Erfassung von Metriken ist. Wenn man dann im Sekundentakt Daten rein pumpt, dann füllt sich so eine Datenbank natürlich auch irgendwann. Und dafür gibt es dann den Mechanismus der Compaction, welcher in regelmäßigen (konfigurierbaren) Abständen die bisherigen Daten zusammenfasst, optimiert und ggf. auch reduziert.

In sehr vielen Fällen kommt als Server ein Raspberry Pi zum Einsatz, der in den meisten Fällen auf einem 32 Bit Betriebssystem betrieben wird. Und hier ist Influxdb (mittlerweile wird von denen auch 32Bit gar nicht mehr supported) eine mehr oder weniger tickende Zeitbombe. So auch bei mir. In den letzten Tagen ist die Last auf dem System auf durchschnittlich 20 und darüber gegangen, obwohl der Raspberry sonst nur eine Load von ca. 1-2 hatte.

Im Log konnte ich dann sehen, dass Influxdb alle 15 Sekunden versucht hat, die Daten zu compacten, aber immer wieder scheiterte – weil der RAM nicht ausreichte. “cannot allocate memory” konnte ich dann immer wieder im Log lesen, und der Spaß ging von vorne los.

Das Problem hier ist: Influxdb lädt alle Daten erstmal in den RAM, um sie dann zu compacten. Es benötigt für seine Arbeiten dann ungefähr das 2-3 fache der Datenbankgrö0e an RAM. Sobald man diese Grenze einmal überschreitet, passiert der Fehler wie bei mir und ist nicht wirklich zu lösen.

Ein User hat sich dann hingesetzt und das Problem analysiert und auch einen Fix gebastelt, der aber leider nicht gemerged wurde. Aber man kann den Fix selbst einbauen oder dankenswerterweise direkt das fertige Docker Image mit dem Patch nutzen:

https://github.com/terjesannum/docker-influxdb-arm32/pkgs/container/influxdb-arm32

docker pull ghcr.io/terjesannum/influxdb-arm32:1.8.10-1

Zusätzlich solltest du in deiner Influxdb Config folgendes einstellen:

[data]

index-version = “tsi1”
tsm-use-seek = true

Der zweite Parameter sollte nur gesetzt werden, wenn deine Influx DB auf einer SSD liegt, da im Prinzip auf in-Memory Sachen verzichtet wird und stattdessen die Platte genutzt wird. Das ist bei Low-Memory Systemen wie derm Raspberry Pi vorteilhafter. Wenn du den Raspberry Pi allerdings nur mit der SD Karte betreibst, dann schalte das keineswegs an!

Wenn du nun Influxdb startest, dann sollte die Compaction der Datenbank ohne Probleme durchlaufen.

Jenkins Jobs laufen unendlich und lassen sich nicht abbrechen

Wenn du einen Jenkins Job (z.B. innerhalb einer Pipeline) haben solltest, welcher einfach unendlich lange läuft und sich auch nicht über die UI abbrechen/stoppen lässt, dann geh auf

[JENKINS_URL]/manage/script

und lass da folgendes Script laufen:

Jenkins.instance.getItemByFullName("[NAME_DES_JOBS]")
                .getBuildByNumber([NUMMER_DES_BUILDS])
                .finish(
                        hudson.model.Result.ABORTED,
                        new java.io.IOException("Aborting build")
                );

Dann einfach auf “ausführen” klicken und schon sollte der Job beendet sein.

WordPress Bilder Upload schlägt immer wieder fehl – Die Verarbeitung des Bildes ist fehlgeschlagen

Wenn du seit dem Upgrade auf WordPress 6.2 beim Upload mehrerer Bilder diesen Fehler siehst:

“Die Verarbeitung des Bildes ist fehlgeschlagen. Der Server ist möglicherweise ausgelastet oder hat nicht genügend Ressourcen zur Verfügung. Eventuell hilft es, wenn du ein kleineres Bild hochlädst. Die vorgeschlagene Maximalgröße ist 2500 Pixel.”

dann kann das Problem realtiv einfach gelöst werden. In der Regel liegt es daran, dass php eine zu kurze maximale Laufzeit eingestellt bekommen hat. Du kannst den Wert für “max_execution_time” einfach höher setzen (ich habe mal 300 genommen), und damit erledigt sich das Problem.

Das kannst du entweder direkt über die php.ini Datei machen, oder aber (beim Einsatz von Apache2 als Webserver) per htaccess, evtl sogar direkt in der wp-config.php. Der maximale Wert wird allerdings von deinem Hoster festgelegt. Evtl musst du also mit dem Support Kontakt aufnehmen.

Falls du das offizielle WordPress Docker Image verwendest, dann geht es relativ einfach:

Lege eine datei wordpress.ini mit den Anpassungen an, die du in der php.ini gerne machen möchtest:

file_uploads = On
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
max_input_time = 1000

Das mal als Beispiel.

Beim Docker run command mountest du diese Datei dann einfach in den Pfad “/usr/local/etc/php/conf.d/” innerhalb des Containers, also z.B. so:

docker run -d -p 8080:80 \
-v ./wordpress.ini:/usr/local/etc/php/conf.d/wordpress.ini \
-e WORDPRESS_DB_HOST="db:3306" \
-e WORDPRESS_DB_PASSWORD="P@ssw0rd2" \
wordpress

Diese direktiven in der ini Datei überschreiben dann die Default-Werte, die in der php.ini gesetzt wurden.

Warum du deine MTU Einstellungen prüfen solltest, wenn du Docker Swarm auf virtualisierter (OpenStack oder Qemu) Hardware (z.B. Hetzner Cloud) betreibst

Es gibt so Momente in der Karierre als Entwickler/DevOp, bei denen man sich fragt, ob man nicht mehr ganz Dicht ist und überhaupt keine Ahnung hat, was man eigentlich tut.

Genau so einen Moment hatte ich diese Woche mit einem Kollegen, als unser Docker Swarm Stack ein sehr komisches Verhalten zeigte. Wir haben eine simple Webapp mit einer extra API, Datenbanken, Storage usw. dahinter und lassen die Systeme mittels Nginx Reverse Proxy miteinander reden. All das funktionierte wunderbar, aber aus unerfindlichen Gründen wurde von der React Website nur der HTML Teil ausgeliefert, aber das Laden der komprimierten CSS und JS schlug fehl. Natürlich schießt einem da gleich in den Kopf, dass es vielleicht ein Permission Problem ist, oder dass die Dateien in einem Unterordner liegen. Vielleicht ist Nginx hinter einem anderen Nginx ein Problem usw. Wir haben sogar den Webserver ausgetauscht weil uns die Lösung nicht einfallen wollte.

Irgendwann kam ich auf die Idee, die funktionierende index.html Datei einfach in den Dateinamen der komprimierten CSS sowie der komprimierten JS umzubenennen und auf einmal konnte ich beide Dateien ohne Probleme abrufen. Sprich, es konnte nicht am Dateityp oder Pfad liegen, sondern es war relativ schnell klar, dass die Größe der Datei scheinbar einen Einfluss auf das Verhalten hat.

Nach etwas Google Magic hatten wir dann herausgefunden, dass scheinbar die MTU der Netzwerkinterfaces das Problem verursacht.

Kurzer Exkurs:
Die MTU ist eine Abkürzung für “Maximum Transmission Unit” und steht für die maximale Größe eines Pakets, das über ein Netzwerk gesendet werden kann, ohne fragmentiert zu werden. In einfacheren Worten bedeutet dies, dass MTU die maximale Größe eines Datenpakets ist, das über das Netzwerk gesendet werden kann, ohne in kleinere Teile aufgeteilt zu werden. Wenn ein Paket größer als die MTU-Einstellung ist, muss es in kleinere Pakete aufgeteilt werden, um über das Netzwerk gesendet zu werden. Zum Beispiel beträgt die Standard-MTU für Ethernet 1500 Bytes. Wenn ein Paket größer als 1500 Bytes ist, wird es in kleinere Pakete aufgeteilt, um über das Netzwerk gesendet zu werden.

Problematisch wird es, wenn Netzwerkinterfaces unterschiedliche MTU verwenden, in unserem speziellen Fall die virtuellen Netzwerk Interfaces von Docker (Swarm) sowie die Netzwerkschnittstellen des Servers selbst. Denn bei virtualisierter Hardware, wie sie viele Cloud Anbieter nutzen (speziell wenn OpenStack oder auch Qemu zum Einsatz kommt), kann es sein, dass die MTU dieser virtuellen Netzwerkschnittstellen geringer als die Default 1500 Bytes ist. Ich habe es so verstanden dass die virtualisierung sich hier einfach 50 Bytes nimmt und diese für Verschlüsselung usw verwendet.

Prüfen kann man das einfach mittels

ip link

auf der Linux Shell und bekommt dann folgende Ausgabe:

1: lo:  mtu 16436 qdisc noqueue
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0:  mtu 1400 qdisc pfifo_fast qlen 1000
link/ether 00:0f:ea:91:04:07 brd ff:ff:ff:ff:ff:ff
3: docker0:  mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 00:0f:ea:91:04:08 brd ff:ff:ff:ff:ff:ff

Wenn ihr so eine Konstellation vorfindet, also dass die MTU eures Netzwerkinterfaces (eth0 in diesem Fall) KLEINER ist als der Wert, der bei docker0 steht, dann könnte ein Problem bestehen.

Da das virtuelle Docker Interface auf der “echten” Hardware aufsetzt, muss dessen MTU gleich oder kleiner der MTU des Netzwerk-Interfaces sein.

Man kann nun einfach versuchen, die MTU mittels

ip link set dev eth0 mtu 1500

setzen und das Problem ist gelöst. In meinem Fall ging das aber nicht, da das virtuelle Netzwerkinterface keinen höheren Wert erlaubte.

Also mussten wir anders heran gehen und Docker dazu zwingen, nicht seinen Default Wert von 1500 zu nehmen, sondern einen geringeren Wert. Dazu gibt es zwei Punkte, an denen man ansetzen kann.

Zum einen kann man in der Datei /etc/docker/daemon.json den Wert

{
"mtu": 1400
}

setzen, den Docker Service neu starten und wird dann sehen, dass die MTU von docker0 sich entsprechend geändert hat. Dies bringt aber nur etwas, wenn man Docker als Single Node betreibt.

Wenn man aber Docker Swarm einsetzt (evtl passiert das auch im Kubernetes Umfeld, da bin ich nicht ganz sicher), dann reicht das nicht aus. Denn dann verwendet Docker virtuelle Netzwerke, die jeweils eigene virtuelle Interfaces anlegen. Ausserdem sind zwei Netzwerke standardmäßig auf allen Nodes vorhanden:

ingress
docker_gwbridge

Wir haben es folgendermaßen gelöst:
Bevor eine Node dem Docker Swarm beitritt, löschen wir ihr docker_gwbridge und setzen es so neu auf:

docker network create \
--subnet 10.11.0.0/16 \
--opt com.docker.network.bridge.name=docker_gwbridge \
--opt com.docker.network.bridge.enable_icc=false \
--opt com.docker.network.bridge.enable_ip_masquerade=true \
docker_gwbridge

Wichtig: “docker_gwbridge” wird zwar vom Typ lokal angezeigt, es ist aber DAS Netzwerk über welches die Swarm Nodes/Docker Daemons miteinander sprechen!

Anschließend lassen wir die Nodes dem Docker Swarm beitreten. Nachdem alle Nodes drin sind, haben wir auf der Swarm Manage Node das Ingress Netzwerk gelöscht (wahrscheinlich reicht es auch, dass der Swarm erstmal initialisiert wurde) und dann mittels folgendem Befehl neu angelegt:

docker network create -d overlay --ingress --opt com.docker.network.driver.mtu=1400 ingress

Dieses neue Ingress Netzwerk ist dann erstmal nur auf der Master Node des Swarms sichtbar, wird auf den anderen Nodes aber dymanisch erzeugt, sobald der erste Container darauf deployed wird. Wenn aktuell kein Container aus dem Swarm auf dieser Node läuft, verschwindet das Ingress Netzwerk wieder von der Node bzw. wird von ihr gelöscht!

Anschließend haben wir den Swarm Stack deployed und schon konnten alle Services wieder miteinander sprechen und auch größere Datein konnten ohne Probleme ausgetauscht werden.