Le réseau Ingress Overlay
Pour commencer cette partie, nous allons supprimer le service demo
docker service rm demo
Swarm et les Overlay
Nous venons de déployer une application sur le cluster et l’on a vu que le port du service est disponible sur tous les noeuds.
Cela est possible grâce au réseau par défaut de Swarm, appelé ingress
.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
6b141a0943ca bridge bridge local
bfc621566968 docker_gwbridge bridge local
6dbb4bea0e35 host host local
1b6ek4sxqg9g ingress overlay swarm
797221e77f12 none null local
C’est un réseau overlay installé de base, et nécessaire pour les flux entrant.
Nous pouvons de la même manière que les réseaux bridge
qui ont un scope local, créer un réseau overlay qui à un scope au niveau du cluster.
$ docker network create --driver overlay --subnet 10.10.10.0/24 mon-overlay-demo
Puis exécuter un serveur web simple exposé en dehors du cluster sur le port 8080.
Ce service aura 3 réplicas et sera attaché à notre overlay mon-overlay-demo
$ docker service create --name webapp --replicas=3 --network my-overlay-network -p 8080:80 ghcr.io/zaggash/demo-webapp
Relever l’ID du conteneur qui tourne sur notre noeud actuelle normalement node1
$ docker ps
Puis on entre dans le network namespace du conteneur pour afficher les interfaces.
$ sudo nsenter -n -t $(pidof -s nginx)
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
59: eth0@if60: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:00:00:16 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.22/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
61: eth2@if62: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:06 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet 172.18.0.6/16 brd 172.18.255.255 scope global eth2
valid_lft forever preferred_lft forever
63: eth1@if64: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:0a:0a:18 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 10.10.10.24/24 brd 10.10.10.255 scope global eth1
valid_lft forever preferred_lft forever
On voit qu’il y en a 3, au lieu de une comme lorsque l’on lance un conteneur hors Swarm.
Le conteneur est connecté à mon mon-overlay-demo
au travers de eth1, comme on peux le voir avec l’IP.
Les autres interfaces sont connectées à d’autres réseaux.
eth0 est le ingress
car nous exposons un port vers l’extérieur.
eth2 est le docker_gwbridge
, le bridge local qui permet au conteneur de sortir du cluster.
$ docker network inspect ingress | grep Subnet
"Subnet": "10.0.0.0/24",
$ docker network inspect docker_gwbridge | grep Subnet
"Subnet": "172.18.0.0/16",
Les Overlays
Les réseaux overlay créent un sous réseau qui peut être utilisé par les conteneurs entre plusieurs noeuds dans le cluster.
Les conteneurs situés sur des noeud différents peuvent échanger des paquets sur ce réseau si ils sont attaché à celui-ci.
Par exemple, pour notre webapp
, on voit qu’il y a un conteneur qui tourne sur chaque hôte dans notre cluster.
On le vérifie:
$ docker service ps webapp
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
9yrr2kdwc6t5 webapp.1 ghcr.io/zaggash/demo-webapp:latest node1 Running Running 37 minutes ago
4qyritdeuwhx webapp.2 ghcr.io/zaggash/demo-webapp:latest node3 Running Running 36 minutes ago
eiqh6znlcvus webapp.3 ghcr.io/zaggash/demo-webapp:latest node2 Running Running 36 minutes ago
Se connecter maintenant sur le node2
et essayé de ping le conteneur qui est sur le node1
(node2) | $ nsenter -n -t $(pidof -s nginx)
(node2) | # ping 10.10.10.24
PING 10.10.10.24 (10.10.10.24) 56(84) bytes of data.
64 bytes from 10.10.10.24: icmp_seq=1 ttl=64 time=0.505 ms
64 bytes from 10.10.10.24: icmp_seq=2 ttl=64 time=0.373 ms
64 bytes from 10.10.10.24: icmp_seq=3 ttl=64 time=0.485 ms
VXLAN
Les réseaux overlay de Docker utilisent la technologie VXLAN qui encapsule les trames ethernet (couche 2 du modèle OSI), dans un datagramme UDP (couche 4).
Ceci permet d’étendre un réseau de couche 2 au dessus de réseaux routés.
Les membres de se réseau virtuel peuvent se voir comme s’ils étaient connecté sur un switch.
On identifie un réseau VXLAN par son identifiant VNI (VXLAN Network Identifier).
Celle-ci est codée sur 24 bits, ce qui donne 16777216 possibilités, bien plus intéressant que la limite de 4096 induite par les VLANs.
On peut le voir en prenant une trace sur les noeuds qui font partis de l’overlay.
Regardons la capture du ping entre le conteneur de node2
vers node1
(node2) | $ nsenter -n -t $(pidof -s nginx)
(node2) | # ping 10.10.10.24
(node1) | $ sudo tcpdump -i ens160 udp and port 4789
[sudo] password for user:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens160, link-type EN10MB (Ethernet), capture size 262144 bytes
19:58:03.259691 IP 10.59.72.7.38266 > node1.4789: VXLAN, flags [I] (0x08), vni 4097
IP 10.10.10.26 > 10.10.10.24: ICMP echo request, id 25411, seq 36, length 64
19:58:03.259756 IP node1.59501 > 10.59.72.7.4789: VXLAN, flags [I] (0x08), vni 4097
IP 10.10.10.24 > 10.10.10.26: ICMP echo reply, id 25411, seq 36, length 64
On peut voir dans les trames ci-dessus, que le premier est le paquet du tunnel VXLAN UDP entre les hôtes dans le port 4789.
Et à l’intérieur, on voit le paquet ICMP entre les conteneurs.
Encryption
Le trafic que l’on a vu ci-dessus montre que si l’on peut voir les paquets entre les noeuds, on peut voir le trafic entre les conteneurs qui passe dans l’overlay.
C’est pourquoi Docker a ajouté une option qui permet de crypter avec IPsec le tunnel VXLAN.
Pour cela, il faut ajouter --opt encrypted
lors de la création du réseau.
Répétons les étapes précédentes en utilisant un overlay crypté.
(node1) | $ docker service rm webapp
(node1) | $ docker network rm mon-overlay-demo
(node1) | $ docker network create --driver overlay --opt encrypted --subnet 10.20.20.0/24 mon-overlay-ipsec
(node1) | $ docker service create --name webapp --replicas=3 --network mon-overlay-ipsec -p 8080:80 ghcr.io/zaggash/demo-webapp
(node1) | $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1efc497c5983 ghcr.io/zaggash/demo-webapp:latest "/docker-entrypoint.…" 17 seconds ago Up 13 seconds 80/tcp webapp.1.ptfs32hrkcom2nu7syry31bwb
(node1) | $ docker inspect 1efc497c5983 | grep IPv4Address
"IPv4Address": "10.0.0.26"
"IPv4Address": "10.20.20.3"
(node1) | $ sudo nsenter -n -t $(pidof -s nginx)
(node1) | # ping 10.20.20.4
PING 10.20.20.4 (10.20.20.4) 56(84) bytes of data.
64 bytes from 10.20.20.4: icmp_seq=1 ttl=64 time=0.503 ms
64 bytes from 10.20.20.4: icmp_seq=2 ttl=64 time=0.412 ms
(node1) | $ sudo tcpdump -i ens160 esp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens160, link-type EN10MB (Ethernet), capture size 262144 bytes
20:22:42.338614 IP node1 > 10.59.72.7: ESP(spi=0x1916910d,seq=0x19), length 140
20:22:42.338976 IP 10.59.72.7 > node1: ESP(spi=0x01fcdf39,seq=0x19), length 140
20:22:43.349513 IP node1 > 10.59.72.7: ESP(spi=0x1916910d,seq=0x1a), length 140
20:22:43.349913 IP 10.59.72.7 > node1: ESP(spi=0x01fcdf39,seq=0x1a), length 140
Inspecter un réseau Overlay
De la même manière que les réseaux bridge
, Docker créé une interface bridge pour chaque overlay
.
Ce bridge connecte les interfaces virtuelles du tunnel pour établir les connections du tunnel VXLAN entre les hôtes.
Cependant, ces bridges et interfaces de tunnel VXLAN ne sont pas créés directement sur l’hôte.
Ils sont dans un conteneur séparé que Docker lance pour chaque réseau overlay.
Pour inspecter ces interfaces, nous devons utiliser nsenter pour accèder à leur namespace.
Déjà voyons les interfaces de notre conteneur, nous en avons des nouveau depuis le test du tunnel IPsec:
$ sudo nsenter -n -t $(pidof -s nginx) ifconfig
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
68: eth0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:00:00:1a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.26/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
70: eth2@if71: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet 172.18.0.3/16 brd 172.18.255.255 scope global eth2
valid_lft forever preferred_lft forever
72: eth1@if73: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1424 qdisc noqueue state UP group default
link/ether 02:42:0a:14:14:03 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 10.20.20.3/24 brd 10.20.20.255 scope global eth1
valid_lft forever preferred_lft forever
Voyons le PeerID de la veth eth1 lié à notre overlay.
$ sudo nsenter -n -t $(pidof -s nginx) ethtool -S eth1
NIC statistics:
peer_ifindex: 73
On cherche du coup maintenant la veth avec l’index 73, elle doit être dans le namespace de l’overlay.
$ docker network ls | grep mon-overlay-ipsec
o2v3e4cf5fro mon-overlay-ipsec overlay swarm
$ sudo ls -l /run/docker/netns/
total 0
[...]]
-r--r--r-- 1 root root 0 Jun 16 20:16 1-o2v3e4cf5f
[...]
$ sudo nsenter --net=/run/docker/netns/1-o2v3e4cf5f ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1424 qdisc noqueue state UP group default
link/ether be:86:9b:82:98:53 brd ff:ff:ff:ff:ff:ff
inet 10.20.20.1/24 brd 10.20.20.255 scope global br0
valid_lft forever preferred_lft forever
65: vxlan0@if65: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1424 qdisc noqueue master br0 state UNKNOWN group default
link/ether e2:fb:73:9d:d7:a2 brd ff:ff:ff:ff:ff:ff link-netnsid 0
67: veth0@if66: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1424 qdisc noqueue master br0 state UP group default
link/ether be:86:9b:82:98:53 brd ff:ff:ff:ff:ff:ff link-netnsid 1
73: veth1@if72: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1424 qdisc noqueue master br0 state UP group default
link/ether ea:86:89:7a:9e:f2 brd ff:ff:ff:ff:ff:ff link-netnsid 2
On voit ici notre interface veth avec l’index 73, il s’agit de veth1.
Et nous voyons aussi notre interface VXLAN, vxlan0.
veth0 est l’interface du namespace de la VIP du service.
On peut voir l’ID de notre VXLAN avec la commande suivante, ici 4098
$ sudo nsenter --net=/run/docker/netns/1-o2v3e4cf5f ip -d link show vxlan0
65: vxlan0@if65: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1424 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default
link/ether e2:fb:73:9d:d7:a2 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1
vxlan id 4098 srcport 0 0 dstport 4789 proxy l2miss l3miss ttl inherit ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx
Pour finir, lancer une capture sur l’interface virtuelle veth1 va nous montrer le trafic qui quitte le conteneur.
$ sudo nsenter --net=/run/docker/netns/1-o2v3e4cf5f tcpdump -i veth1 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
23:02:39.989473 IP 10.20.20.3 > 10.20.20.4: ICMP echo request, id 799, seq 61, length 64
23:02:39.989945 IP 10.20.20.4 > 10.20.20.3: ICMP echo reply, id 799, seq 61, length 64
23:02:41.013488 IP 10.20.20.3 > 10.20.20.4: ICMP echo request, id 799, seq 62, length 64
23:02:41.013979 IP 10.20.20.4 > 10.20.20.3: ICMP echo reply, id 799, seq 62, length 64
23:02:42.037832 IP 10.20.20.3 > 10.20.20.4: ICMP echo request, id 799, seq 63, length 64
23:02:42.038311 IP 10.20.20.4 > 10.20.20.3: ICMP echo reply, id 799, seq 63, length 64