Hinweis perev. : Der Autor des Originalartikels, Nicolas Leiva, ist ein Lösungsarchitekt von Cisco, der beschlossen hat, seinen Kollegen, Netzwerkingenieuren, die Funktionsweise des Kubernetes-Netzwerks von innen mitzuteilen. Zu diesem Zweck untersucht er die einfachste Konfiguration im Cluster und nutzt dabei aktiv den gesunden Menschenverstand, sein Wissen über Netzwerke und Standard-Linux / Kubernetes-Dienstprogramme. Es stellte sich voluminös heraus, aber sehr deutlich.
Zusätzlich zu der Tatsache, dass Kelsey Hightowers
Kubernetes The Hard Way- Handbuch nur funktioniert (
sogar unter AWS! ),
Gefiel mir, dass das Netzwerk sauber und einfach gehalten wurde. Dies ist eine großartige Gelegenheit, um beispielsweise die Rolle der Container Network Interface (
CNI ) zu verstehen. Trotzdem möchte ich hinzufügen, dass das Kubernetes-Netzwerk eigentlich nicht sehr intuitiv ist, insbesondere für Anfänger ... und vergessen Sie auch nicht, dass es
einfach kein Netzwerk für Container gibt.
Obwohl es zu diesem Thema bereits gute Materialien gibt (siehe Links
hier ), konnte ich kein solches Beispiel finden, dass ich alles Notwendige mit den Schlussfolgerungen der Teams kombinieren würde, die Netzwerktechniker lieben und hassen, um zu demonstrieren, was tatsächlich hinter den Kulissen passiert. Aus diesem Grund habe ich beschlossen, Informationen aus vielen Quellen zu sammeln. Ich hoffe, dies hilft und Sie verstehen besser, wie alles miteinander verbunden ist. Dieses Wissen ist nicht nur wichtig, um sich selbst zu testen, sondern auch, um die Diagnose von Problemen zu vereinfachen. Sie können dem Beispiel in Ihrem Cluster von
Kubernetes The Hard Way folgen: Alle IP-Adressen und Einstellungen werden von dort übernommen (Stand der Commits für Mai 2018, bevor
Nabla-Container verwendet werden ).
Und wir werden am Ende beginnen, wenn wir drei Controller und drei Arbeitsknoten haben:

Sie können feststellen, dass es hier auch mindestens drei private Subnetze gibt! Ein wenig Geduld, und sie werden alle berücksichtigt. Denken Sie daran, dass wir uns zwar auf sehr spezifische IP-Präfixe beziehen, diese jedoch einfach aus
Kubernetes The Hard Way stammen , sodass sie nur lokale Bedeutung haben und Sie gemäß
RFC 1918 einen anderen Adressblock für Ihre Umgebung auswählen können. Für den Fall von IPv6 wird es einen separaten Blog-Artikel geben.
Host-Netzwerk (10.240.0.0/24)
Dies ist ein internes Netzwerk, zu dem alle Knoten gehören. Definiert durch das
--private-network-ip
in
GCP oder die Option
--private-ip-address
in
AWS beim
--private-ip-address
Computerressourcen.
Initialisieren von Controller-Knoten in GCP
for i in 0 1 2; do gcloud compute instances create controller-${i} \
(
controllers_gcp.sh
)
Initialisieren von Controller-Knoten in AWS
for i in 0 1 2; do declare controller_id${i}=`aws ec2 run-instances \
(
controllers_aws.sh
)

Jede Instanz hat zwei IP-Adressen: privat vom Host-Netzwerk (Controller -
10.240.0.1${i}/24
, Worker -
10.240.0.2${i}/24
) und eine öffentliche, vom Cloud-Anbieter festgelegte, über die wir später sprechen werden wie man zu
NodePorts
.
Gcp
$ gcloud compute instances list NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS controller-0 us-west1-c n1-standard-1 10.240.0.10 35.231.XXX.XXX RUNNING worker-1 us-west1-c n1-standard-1 10.240.0.21 35.231.XX.XXX RUNNING ...
Aws
$ aws ec2 describe-instances --query 'Reservations[].Instances[].[Tags[?Key==`Name`].Value[],PrivateIpAddress,PublicIpAddress]' --output text | sed '$!N;s/\n/ /' 10.240.0.10 34.228.XX.XXX controller-0 10.240.0.21 34.173.XXX.XX worker-1 ...
Alle Knoten müssen in der Lage sein, sich gegenseitig zu pingen, wenn die
Sicherheitsrichtlinien korrekt sind (und wenn
ping
auf dem Host installiert
ping
).
Herdnetzwerk (10.200.0.0/16)
Dies ist das Netzwerk, in dem die Pods leben. Jeder Arbeitsknoten verwendet ein Subnetz dieses Netzwerks. In unserem Fall ist
POD_CIDR=10.200.${i}.0/24
für
worker-${i}
.

Um zu verstehen, wie alles konfiguriert ist, treten Sie einen Schritt zurück und sehen Sie sich
das Kubernetes-Netzwerkmodell an , für das Folgendes erforderlich ist:
- Alle Container können ohne Verwendung von NAT mit anderen Containern kommunizieren.
- Alle Knoten können mit allen Containern kommunizieren (und umgekehrt), ohne NAT zu verwenden.
- Die IP, die der Container sieht, muss mit der IP-Adresse übereinstimmen, die andere sehen.
All dies kann auf viele Arten implementiert werden, und Kubernetes übergibt das Netzwerk-Setup an das
CNI-Plugin .
„Das CNI-Plugin ist dafür verantwortlich, dem Netzwerk-Namespace des Containers eine Netzwerkschnittstelle hinzuzufügen (z. B. ein Ende eines veth-Paares ) und die erforderlichen Änderungen auf dem Host vorzunehmen (z. B. das zweite Ende von veth mit einer Bridge zu verbinden). Dann muss er eine IP-Schnittstelle zuweisen und die Routen gemäß dem Abschnitt IP-Adressverwaltung konfigurieren, indem er das gewünschte IPAM-Plugin aufruft. “ (aus der Spezifikation der Containernetzwerkschnittstelle )

Netzwerk-Namespace
„Der Namespace verpackt die globale Systemressource in eine Abstraktion, die für Prozesse in diesem Namespace so sichtbar ist, dass sie über eine eigene isolierte Instanz der globalen Ressource verfügen. Änderungen in der globalen Ressource sind für andere in diesem Namespace enthaltene Prozesse sichtbar, für andere Prozesse jedoch nicht. “ ( von der Namespaces-Manpage )
Linux bietet sieben verschiedene Namespaces (
Cgroup
,
IPC
,
Network
,
Mount
,
PID
,
User
,
UTS
). Netzwerk-Namespaces (
CLONE_NEWNET
) definieren die Netzwerkressourcen, die dem Prozess zur Verfügung stehen: „Jeder Netzwerk-Namespace verfügt über eigene Netzwerkgeräte, IP-Adressen, IP-Routing-Tabellen,
/proc/net
Verzeichnis, Portnummern usw.“
( aus dem Artikel „ Namespaces in Betrieb “) .
Virtuelle Ethernet-Geräte (Veth)
„Ein virtuelles Netzwerkpaar (veth) bietet eine Abstraktion in Form einer„ Pipe “, mit der Tunnel zwischen Netzwerk-Namespaces oder eine Brücke zu einem physischen Netzwerkgerät in einem anderen Netzwerkbereich erstellt werden können. Wenn der Namespace freigegeben wird, werden alle darin enthaltenen Geräte zerstört. “ (von der Manpage der Netzwerk-Namespaces )
Gehen Sie zu Boden und sehen Sie, wie sich alles auf den Cluster bezieht. Erstens sind
Netzwerk-Plugins in Kubernetes vielfältig, und CNI-Plugins sind eines davon (
warum nicht CNM? ).
Kubelet auf jedem Knoten teilt der Container-
Laufzeit mit, welches
Netzwerk-Plug-In verwendet werden soll. Die Container Network Interface (
CNI ) befindet sich zwischen der Container-Laufzeit und der Netzwerkimplementierung. Und schon baut das CNI-Plugin das Netzwerk auf.
„Das CNI-Plugin wird ausgewählt, indem die Befehlszeilenoption --network-plugin=cni
an Kubelet übergeben wird. Kubelet liest die Datei aus --cni-conf-dir
(der Standardwert ist /etc/cni/net.d
) und verwendet die CNI-Konfiguration aus dieser Datei, um das Netzwerk für jede Datei zu konfigurieren. " (aus den Anforderungen des Netzwerk-Plugins )
Die realen Binärdateien des CNI-Plugins befinden sich in
-- cni-bin-dir
(der Standardwert ist
/opt/cni/bin
).
Bitte beachten Sie, dass die
kubelet.service
von
--network-plugin=cni
:
[Service] ExecStart=/usr/local/bin/kubelet \\ --config=/var/lib/kubelet/kubelet-config.yaml \\ --network-plugin=cni \\ ...
Zunächst erstellt Kubernetes einen Netzwerk-Namespace für den Herd, noch bevor Plugins aufgerufen werden. Dies wird mithilfe des speziellen
pause
implementiert, der „als„ übergeordneter Container “für alle Herdcontainer dient“
(aus dem Artikel „ Der allmächtige Pausencontainer “) . Kubernetes führt dann das CNI-Plugin aus, um den
pause
an das Netzwerk anzuhängen. Alle Pod-Container verwenden den
netns
dieses
netns
.
{ "cniVersion": "0.3.1", "name": "bridge", "type": "bridge", "bridge": "cnio0", "isGateway": true, "ipMasq": true, "ipam": { "type": "host-local", "ranges": [ [{"subnet": "${POD_CIDR}"}] ], "routes": [{"dst": "0.0.0.0/0"}] } }
Die verwendete
CNI-Konfiguration gibt die Verwendung des
bridge
Plugins zum Konfigurieren der Linux (L2) -Softwarebrücke im Root-Namespace
cnio0
(der
Standardname ist
cni0
) an, der als Gateway fungiert (
"isGateway": true
).

Ein veth-Paar wird ebenfalls konfiguriert, um den Herd mit der neu erstellten Brücke zu verbinden:

Um L3-Informationen wie IP-Adressen zuzuweisen, wird das
IPAM-Plugin (
ipam
) aufgerufen. In diesem Fall wird der
host-local
Typ verwendet, "der den Status lokal im Hostdateisystem speichert, wodurch die Eindeutigkeit der IP-Adressen auf einem Host sichergestellt wird"
(aus der host-local
) . Das IPAM-Plugin gibt diese Informationen an das vorherige Plugin (
bridge
) zurück, sodass alle in der Konfiguration angegebenen Routen konfiguriert werden können (
"routes": [{"dst": "0.0.0.0/0"}]
). Wenn
gw
nicht angegeben ist,
wird es aus dem Subnetz übernommen . Die Standardroute wird auch im Netzwerk-Namespace des Herdes konfiguriert und zeigt auf die Bridge (die als erstes IP-Subnetz des Herdes konfiguriert ist).
Und das letzte wichtige Detail: Wir haben Maskerading (
"ipMasq": true
) für den Datenverkehr aus dem
"ipMasq": true
angefordert. Wir brauchen NAT hier nicht wirklich, aber dies ist die Konfiguration in
Kubernetes The Hard Way . Der Vollständigkeit halber muss ich daher erwähnen, dass die Einträge in den
iptables
bridge
Plugins für dieses spezielle Beispiel konfiguriert sind. Alle Pakete vom Herd, deren Empfänger nicht im Bereich
224.0.0.0/4
, befinden
sich hinter NAT , was die Anforderung "Alle Container können mit jedem anderen Container ohne Verwendung von NAT kommunizieren" nicht ganz erfüllt. Nun, wir werden beweisen, warum NAT nicht benötigt wird ...

Herdführung
Jetzt können wir die Pods anpassen. Schauen wir uns alle Netzwerkbereiche der Namen eines der Arbeitsknoten an und analysieren Sie einen davon, nachdem Sie
von hier aus die Bereitstellung von
nginx
haben . Wir werden
lsns
mit der Option
-t
, um den gewünschten Typ des Namespace (d. H.
net
) auszuwählen:
ubuntu@worker-0:~$ sudo lsns -t net NS TYPE NPROCS PID USER COMMAND 4026532089 net 113 1 root /sbin/init 4026532280 net 2 8046 root /pause 4026532352 net 4 16455 root /pause 4026532426 net 3 27255 root /pause
Mit der Option
-i
zu
ls
können wir ihre Inode-Nummern finden:
ubuntu@worker-0:~$ ls -1i /var/run/netns 4026532352 cni-1d85bb0c-7c61-fd9f-2adc-f6e98f7a58af 4026532280 cni-7cec0838-f50c-416a-3b45-628a4237c55c 4026532426 cni-912bcc63-712d-1c84-89a7-9e10510808a0
Sie können auch alle Netzwerk-Namespaces mit
ip netns
:
ubuntu@worker-0:~$ ip netns cni-912bcc63-712d-1c84-89a7-9e10510808a0 (id: 2) cni-1d85bb0c-7c61-fd9f-2adc-f6e98f7a58af (id: 1) cni-7cec0838-f50c-416a-3b45-628a4237c55c (id: 0)
Um alle Prozesse
cni-912bcc63–712d-1c84–89a7–9e10510808a0
, die im Netzwerkbereich
cni-912bcc63–712d-1c84–89a7–9e10510808a0
(
4026532426
) ausgeführt werden, können Sie beispielsweise den folgenden Befehl ausführen:
ubuntu@worker-0:~$ sudo ls -l /proc/[1-9]*/ns/net | grep 4026532426 | cut -f3 -d"/" | xargs ps -p PID TTY STAT TIME COMMAND 27255 ? Ss 0:00 /pause 27331 ? Ss 0:00 nginx: master process nginx -g daemon off; 27355 ? S 0:00 nginx: worker process
Es ist zu sehen, dass wir zusätzlich zur
pause
in diesem Pod Nginx gestartet haben. Der
pause
teilt die
net
und
ipc
Namespaces mit allen anderen Pod-Containern. Erinnern Sie sich an die PID aus
pause
- 27255; wir werden darauf zurückkommen.
Nun wollen wir sehen, was
kubectl
über diesen Pod erzählt:
$ kubectl get pods -o wide | grep nginx nginx-65899c769f-wxdx6 1/1 Running 0 5d 10.200.0.4 worker-0
Weitere Details:
$ kubectl describe pods nginx-65899c769f-wxdx6
Name: nginx-65899c769f-wxdx6 Namespace: default Node: worker-0/10.240.0.20 Start Time: Thu, 05 Jul 2018 14:20:06 -0400 Labels: pod-template-hash=2145573259 run=nginx Annotations: <none> Status: Running IP: 10.200.0.4 Controlled By: ReplicaSet/nginx-65899c769f Containers: nginx: Container ID: containerd://4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 Image: nginx ...
Wir sehen den Namen des Pods -
nginx-65899c769f-wxdx6
- und die ID eines seiner Container (
nginx
), aber über die
pause
nichts gesagt. Graben Sie einen tieferen Arbeitsknoten, um alle Daten abzugleichen. Denken Sie daran, dass
Kubernetes The Hard Way Docker nicht verwendet. Einzelheiten zum Container finden Sie im Konsolen-Dienstprogramm
Containerd - ctr
(siehe auch Artikel " Integration von Containerd mit Kubernetes, Ersetzen von Docker, produktionsbereit " - ca. Übertragung ). ::
ubuntu@worker-0:~$ sudo ctr namespaces ls NAME LABELS k8s.io
Wenn Sie den Containerd-
k8s.io
(
k8s.io
) kennen, können Sie die
nginx
Container-ID
k8s.io
:
ubuntu@worker-0:~$ sudo ctr -n k8s.io containers ls | grep nginx 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 docker.io/library/nginx:latest io.containerd.runtime.v1.linux
... und auch
pause
:
ubuntu@worker-0:~$ sudo ctr -n k8s.io containers ls | grep pause 0866803b612f2f55e7b6b83836bde09bd6530246239b7bde1e49c04c7038e43a k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux 21640aea0210b320fd637c22ff93b7e21473178de0073b05de83f3b116fc8834 k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux
Die
nginx
Container-ID mit der Endung
…983c7
stimmt mit der von
kubectl
. Mal sehen, ob wir herausfinden können, welcher
pause
zum
nginx
Pod gehört:
ubuntu@worker-0:~$ sudo ctr -n k8s.io task ls TASK PID STATUS ... d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 27255 RUNNING 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 27331 RUNNING
Denken Sie daran, dass Prozesse mit PID 27331 und 27355 im Netzwerk-Namespace
cni-912bcc63–712d-1c84–89a7–9e10510808a0
werden.
ubuntu@worker-0:~$ sudo ctr -n k8s.io containers info d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6 { "ID": "d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6", "Labels": { "io.cri-containerd.kind": "sandbox", "io.kubernetes.pod.name": "nginx-65899c769f-wxdx6", "io.kubernetes.pod.namespace": "default", "io.kubernetes.pod.uid": "0b35e956-8080-11e8-8aa9-0a12b8818382", "pod-template-hash": "2145573259", "run": "nginx" }, "Image": "k8s.gcr.io/pause:3.1", ...
... und:
ubuntu@worker-0:~$ sudo ctr -n k8s.io containers info 4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7 { "ID": "4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7", "Labels": { "io.cri-containerd.kind": "container", "io.kubernetes.container.name": "nginx", "io.kubernetes.pod.name": "nginx-65899c769f-wxdx6", "io.kubernetes.pod.namespace": "default", "io.kubernetes.pod.uid": "0b35e956-8080-11e8-8aa9-0a12b8818382" }, "Image": "docker.io/library/nginx:latest", ...
Jetzt wissen wir sicher, welche Container in diesem Pod (
nginx-65899c769f-wxdx6
) und im Netzwerk-Namespace (
cni-912bcc63–712d-1c84–89a7–9e10510808a0
) ausgeführt werden:
- nginx (ID:
4c0bd2e2e5c0b17c637af83376879c38f2fb11852921b12413c54ba49d6983c7
); - Pause (ID:
d19b1b1c92f7cc90764d4f385e8935d121bca66ba8982bae65baff1bc2841da6
).

Wie ist dies unter (
nginx-65899c769f-wxdx6
) mit dem Netzwerk verbunden? Wir verwenden die zuvor von
pause
empfangene PID 27255, um Befehle in ihrem Netzwerk-Namespace
cni-912bcc63–712d-1c84–89a7–9e10510808a0
(
cni-912bcc63–712d-1c84–89a7–9e10510808a0
):
ubuntu@worker-0:~$ sudo ip netns identify 27255 cni-912bcc63-712d-1c84-89a7-9e10510808a0
Für diese Zwecke verwenden wir
nsenter
mit der Option
-t
, die die Ziel-PID definiert, und
-n
ohne eine Datei anzugeben, um in den Netzwerk-Namespace des
nsenter
zu gelangen (27255). Hier ist, was
ip link show
sagen wird:
ubuntu@worker-0:~$ sudo nsenter -t 27255 -n ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default link/ether 0a:58:0a:c8:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0
... und
ifconfig eth0
:
ubuntu@worker-0:~$ sudo nsenter -t 27255 -n ifconfig eth0 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 10.200.0.4 netmask 255.255.255.0 broadcast 0.0.0.0 inet6 fe80::2097:51ff:fe39:ec21 prefixlen 64 scopeid 0x20<link> ether 0a:58:0a:c8:00:04 txqueuelen 0 (Ethernet) RX packets 540 bytes 42247 (42.2 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 177 bytes 16530 (16.5 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Dies bestätigt, dass die zuvor über
kubectl get pod
erhaltene IP-Adresse auf der
eth0
Schnittstelle konfiguriert ist. Diese Schnittstelle ist Teil eines
Veth-Paares , von dem sich ein Ende im Herd und das andere im Root-Namespace befindet. Um die Schnittstelle des zweiten Endes herauszufinden, verwenden wir
ethtool
:
ubuntu@worker-0:~$ sudo ip netns exec cni-912bcc63-712d-1c84-89a7-9e10510808a0 ethtool -S eth0 NIC statistics: peer_ifindex: 7
Wir sehen, dass der
ifindex
Festes 7 ist. Überprüfen Sie, ob er sich im Root-Namespace befindet. Dies kann über den
ip link
:
ubuntu@worker-0:~$ ip link | grep '^7:' 7: veth71f7d238@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cnio0 state UP mode DEFAULT group default
Um sicherzugehen, lassen Sie uns sehen:
ubuntu@worker-0:~$ sudo cat /sys/class/net/veth71f7d238/ifindex 7
Großartig, jetzt ist mit dem virtuellen Link alles klar.
brctl
Sie uns mit
brctl
sehen, wer noch mit der Linux-Bridge verbunden ist:
ubuntu@worker-0:~$ brctl show cnio0 bridge name bridge id STP enabled interfaces cnio0 8000.0a580ac80001 no veth71f7d238 veth73f35410 vethf273b35f
Das Bild sieht also wie folgt aus:

Routing-Prüfung
Wie leiten wir den Verkehr tatsächlich weiter? Schauen wir uns die Routing-Tabelle im Netzwerk-Namespace-Pod an:
ubuntu@worker-0:~$ sudo ip netns exec cni-912bcc63-712d-1c84-89a7-9e10510808a0 ip route show default via 10.200.0.1 dev eth0 10.200.0.0/24 dev eth0 proto kernel scope link src 10.200.0.4
Zumindest wissen wir, wie man zum Root-Namespace kommt (
default via 10.200.0.1
). Nun sehen wir uns die Host-Routing-Tabelle an:
ubuntu@worker-0:~$ ip route list default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.20 metric 100 10.200.0.0/24 dev cnio0 proto kernel scope link src 10.200.0.1 10.240.0.0/24 dev eth0 proto kernel scope link src 10.240.0.20 10.240.0.1 dev eth0 proto dhcp scope link src 10.240.0.20 metric 100
Wir wissen, wie Pakete an einen VPC-Router weitergeleitet werden (VPC
verfügt über einen „impliziten“ Router, der
normalerweise eine zweite Adresse aus dem Haupt-IP-Adressraum des Subnetzes hat). Jetzt: Weiß der VPC-Router, wie er zum Netzwerk jedes Herdes gelangt? Nein, das tut er nicht. Daher wird davon ausgegangen, dass die Routen vom CNI-Plug-In oder
manuell (wie im Handbuch) konfiguriert werden. Anscheinend macht das
AWS CNI-Plugin genau das für uns bei AWS. Denken Sie daran, dass es
viele CNI-Plugins gibt , und wir betrachten ein Beispiel für eine
einfache Netzwerkkonfiguration :

Tiefes Eintauchen in NAT
kubectl create -f busybox.yaml
zwei identische
busybox
Container mit Replication Controller:
apiVersion: v1 kind: ReplicationController metadata: name: busybox0 labels: app: busybox0 spec: replicas: 2 selector: app: busybox0 template: metadata: name: busybox0 labels: app: busybox0 spec: containers: - image: busybox command: - sleep - "3600" imagePullPolicy: IfNotPresent name: busybox restartPolicy: Always
(
busybox.yaml
)
Wir bekommen:
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE busybox0-g6pww 1/1 Running 0 4s 10.200.1.15 worker-1 busybox0-rw89s 1/1 Running 0 4s 10.200.0.21 worker-0 ...
Pings von einem Container zum anderen sollten erfolgreich sein:
$ kubectl exec -it busybox0-rw89s -- ping -c 2 10.200.1.15 PING 10.200.1.15 (10.200.1.15): 56 data bytes 64 bytes from 10.200.1.15: seq=0 ttl=62 time=0.528 ms 64 bytes from 10.200.1.15: seq=1 ttl=62 time=0.440 ms --- 10.200.1.15 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.440/0.484/0.528 ms
Um die Bewegung des Datenverkehrs zu verstehen, können Sie Pakete mit
tcpdump
oder
conntrack
:
ubuntu@worker-0:~$ sudo conntrack -L | grep 10.200.1.15 icmp 1 29 src=10.200.0.21 dst=10.200.1.15 type=8 code=0 id=1280 src=10.200.1.15 dst=10.240.0.20 type=0 code=0 id=1280 mark=0 use=1
Die Quell-IP von Pod 10.200.0.21 wird in die IP-Adresse des Hosts 10.240.0.20 übersetzt.
ubuntu@worker-1:~$ sudo conntrack -L | grep 10.200.1.15 icmp 1 28 src=10.240.0.20 dst=10.200.1.15 type=8 code=0 id=1280 src=10.200.1.15 dst=10.240.0.20 type=0 code=0 id=1280 mark=0 use=1
In iptables können Sie sehen, dass die Anzahl steigt:
ubuntu@worker-0:~$ sudo iptables -t nat -Z POSTROUTING -L -v Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination ... 5 324 CNI-be726a77f15ea47ff32947a3 all -- any any 10.200.0.0/24 anywhere /* name: "bridge" id: "631cab5de5565cc432a3beca0e2aece0cef9285482b11f3eb0b46c134e457854" */ Zeroing chain `POSTROUTING'
Wenn Sie andererseits
"ipMasq": true
aus der CNI-Plugin-Konfiguration entfernen, sehen Sie Folgendes (dieser Vorgang wird ausschließlich zu Bildungszwecken ausgeführt - wir empfehlen, die Konfiguration in einem funktionierenden Cluster nicht zu ändern!):
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE busybox0-2btxn 1/1 Running 0 16s 10.200.0.15 worker-0 busybox0-dhpx8 1/1 Running 0 16s 10.200.1.13 worker-1 ...
Ping sollte noch bestehen:
$ kubectl exec -it busybox0-2btxn -- ping -c 2 10.200.1.13 PING 10.200.1.6 (10.200.1.6): 56 data bytes 64 bytes from 10.200.1.6: seq=0 ttl=62 time=0.515 ms 64 bytes from 10.200.1.6: seq=1 ttl=62 time=0.427 ms --- 10.200.1.6 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.427/0.471/0.515 ms
Und in diesem Fall - ohne NAT:
ubuntu@worker-0:~$ sudo conntrack -L | grep 10.200.1.13 icmp 1 29 src=10.200.0.15 dst=10.200.1.13 type=8 code=0 id=1792 src=10.200.1.13 dst=10.200.0.15 type=0 code=0 id=1792 mark=0 use=1
Daher haben wir überprüft, dass "alle Container ohne Verwendung von NAT mit anderen Containern kommunizieren können".
ubuntu@worker-1:~$ sudo conntrack -L | grep 10.200.1.13 icmp 1 27 src=10.200.0.15 dst=10.200.1.13 type=8 code=0 id=1792 src=10.200.1.13 dst=10.200.0.15 type=0 code=0 id=1792 mark=0 use=1
Clusternetzwerk (10.32.0.0/24)
Möglicherweise haben Sie im
busybox
Beispiel
busybox
dass die der
busybox
zugewiesenen IP-Adressen jeweils
busybox
waren. Was wäre, wenn wir diese Container für die Kommunikation von anderen Herden zur Verfügung stellen wollten? Man könnte die aktuellen IP-Adressen des Pods nehmen, aber sie werden sich ändern. Aus diesem Grund müssen Sie die Serviceressource konfigurieren, die Anfragen an viele kurzlebige Herde weiterleitet.
"Service in Kubernetes ist eine Abstraktion, die den logischen Satz von Herden und die Richtlinien definiert, über die auf sie zugegriffen werden kann." (aus der Dokumentation zu Kubernetes Services )
Es gibt verschiedene Möglichkeiten, einen Dienst zu veröffentlichen. Der Standardtyp ist
ClusterIP
, der die IP-Adresse aus dem CIDR-Block des Clusters festlegt (d. h. nur über den Cluster zugänglich). Ein solches Beispiel ist das in Kubernetes The Hard Way konfigurierte DNS-Cluster-Add-on.
# ... apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: "KubeDNS" spec: selector: k8s-app: kube-dns clusterIP: 10.32.0.10 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP # ...
(
kube-dns.yaml
)
kubectl
zeigt, dass der
Service
Endpunkte
kubectl
und übersetzt:
$ kubectl -n kube-system describe services ... Selector: k8s-app=kube-dns Type: ClusterIP IP: 10.32.0.10 Port: dns 53/UDP TargetPort: 53/UDP Endpoints: 10.200.0.27:53 Port: dns-tcp 53/TCP TargetPort: 53/TCP Endpoints: 10.200.0.27:53 ...
Wie genau? .. wieder
iptables
. Lassen Sie uns die für dieses Beispiel erstellten Regeln durchgehen. Ihre vollständige Liste kann mit dem Befehl
iptables-save
angezeigt werden.
Sobald Pakete vom Prozess erstellt werden (
OUTPUT
) oder auf der Netzwerkschnittstelle ankommen (
PREROUTING
), durchlaufen sie die folgenden
iptables
Ketten:
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
Die folgenden Ziele entsprechen TCP-Paketen, die um 10.32.0.10 an den 53. Port gesendet und mit dem 53. Port an den Empfänger 10.200.0.27 übertragen werden:
-A KUBE-SERVICES -d 10.32.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4 -A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-SEP-32LPCMGYG6ODGN3H -A KUBE-SEP-32LPCMGYG6ODGN3H -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 10.200.0.27:53
Gleiches gilt für UDP-Pakete (Empfänger 10.32.0.10:53 → 10.200.0.27:53):
-A KUBE-SERVICES -d 10.32.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU -A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-LRUTK6XRXU43VLIG -A KUBE-SEP-LRUTK6XRXU43VLIG -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.200.0.27:53
Es gibt andere Arten von
Services
in Kubernetes. Insbesondere
NodePort
Kubernetes The Hard Way über
NodePort
- siehe
Smoke Test: Services .
kubectl expose deployment nginx --port 80 --type NodePort
NodePort
veröffentlicht den Dienst unter der IP-Adresse jedes Knotens und platziert ihn an einem statischen Port (er heißt
NodePort
).
NodePort
kann
NodePort
von außerhalb des Clusters zugegriffen werden. Sie können den dedizierten Port (in diesem Fall - 31088) mit
kubectl
:
$ kubectl describe services nginx ... Type: NodePort IP: 10.32.0.53 Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 31088/TCP Endpoints: 10.200.1.18:80 ...
Under ist jetzt im Internet unter
http://${EXTERNAL_IP}:31088/
. Hier ist
EXTERNAL_IP
die öffentliche IP-Adresse
einer Arbeitsinstanz . In diesem Beispiel habe ich die öffentliche IP-Adresse von
worker-0 verwendet . Die Anforderung wird von einem Host mit einer internen IP-Adresse von 10.240.0.20 empfangen (der Cloud-Anbieter ist an öffentlichem NAT beteiligt). Der Dienst wird jedoch tatsächlich auf einem anderen Host gestartet (
Worker-1 , erkennbar an der IP-Adresse des Endpunkts - 10.200.1.18):
ubuntu@worker-0:~$ sudo conntrack -L | grep 31088 tcp 6 86397 ESTABLISHED src=173.38.XXX.XXX dst=10.240.0.20 sport=30303 dport=31088 src=10.200.1.18 dst=10.240.0.20 sport=80 dport=30303 [ASSURED] mark=0 use=1
Das Paket wird von
Worker-0 an
Worker-1 gesendet, wo es seinen Empfänger findet:
ubuntu@worker-1:~$ sudo conntrack -L | grep 80 tcp 6 86392 ESTABLISHED src=10.240.0.20 dst=10.200.1.18 sport=14802 dport=80 src=10.200.1.18 dst=10.240.0.20 sport=80 dport=14802 [ASSURED] mark=0 use=1
Ist eine solche Schaltung ideal? Vielleicht nicht, aber es funktioniert. In diesem Fall lauten die programmierten
iptables
Regeln wie folgt:
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx:" -m tcp --dport 31088 -j KUBE-SVC-4N57TFCL4MD7ZTDA -A KUBE-SVC-4N57TFCL4MD7ZTDA -m comment --comment "default/nginx:" -j KUBE-SEP-UGTFMET44DQG7H7H -A KUBE-SEP-UGTFMET44DQG7H7H -p tcp -m comment --comment "default/nginx:" -m tcp -j DNAT --to-destination 10.200.1.18:80
Mit anderen Worten, die Adresse für den Empfänger von Paketen mit Port 31088 wird am 10.200.1.18 gesendet. Der Port sendet auch von 31088 bis 80.
Wir haben keinen anderen
LoadBalancer
-
LoadBalancer
- der den Dienst mithilfe eines Load Balancers eines Cloud-Anbieters öffentlich verfügbar macht, aber der Artikel hat sich bereits als umfangreich herausgestellt.
Fazit
Es scheint, dass es viele Informationen gibt, aber wir haben nur die Spitze des Eisbergs berührt. In Zukunft werde ich über IPv6, IPVS, eBPF und einige interessante aktuelle CNI-Plugins sprechen.
PS vom Übersetzer
Lesen Sie auch in unserem Blog: