h.rvé.netContactSe connecter
  • Contact

  • Waterfall scrum »

Le(s) réseau(x) de Docker Swarm, deep dive

Posted by herve on 12 Sep 2021 in Linux et logiciel libre

Petite investigation sur le réseau Swarm.

Introduction

Il y a pas mal de littérature sur le réseau avec Docker et Swarm, notamment:

  • L'utilisation des réseaux Overlay avec Docker dans la doc officielle
  • Des articles avec des schémas pour creuser un peu plus comme celui-ci de Nigel Poulton


Mais concrètement, comment observer tout ça sur mes serveurs, dans mon terminal ? J'ai trouvé un très bon article ici mais je voulais mieux comprendre:

  • Le load balancing par IPVS
  • Où est effectué le port forwarding

Je vais donc explorer ça ici !

Setup

Prenons un cluster de 3 nodes swarm (parce que 1 ça n'est pas redondant, 2 ça n'est pas robuste au split, et plus de 3 c'est overkill :D).

Sur lequel on a démarré créé un service web tout simple dont voici le composefile:

---
version: '3.8'

services:
  web:
    image: httpd
    ports:
      - 8091:80
    deploy:
      replicas: 2
    networks:
      - testnet

networks:
  testnet:
    attachable: true

Pour démarrer cette stack:

docker stack deploy -c docker-compose.yml webtest

 

Check

On vérifie que c'est bien déployé, et que ça répond:

[herve@node0 test]$ docker service ls
ID             NAME                MODE         REPLICAS   IMAGE                     PORTS
ox7io3soug7o   webtest_web         replicated   2/2        httpd:latest              *:8091->80/tcp
[herve@node0 test]$ docker service ps webtest_web 
ID             NAME            IMAGE          NODE          DESIRED STATE   CURRENT STATE    ERROR   PORTS
mjyuczobcrb4   webtest_web.1   httpd:latest   node0         Running         Running 13 minutes ago             
klj0q37g2k22   webtest_web.2   httpd:latest   node1         Running         Running 13 minutes ago             
[herve@node0 test]$ curl 127.0.0.1:8091
<html><body><h1>It works!</h1></body></html>
[herve@node0 test]$

Exploration

Les réseaux utilisés par un container:

# pour avoir l'outil "ip"
herve@node0:~$ docker exec -ti webtest_web.2.klj0q37g2k22a1ar9f6sp7wai \
bash -c "apt-get update && apt-get install -y iproute2"
[snip]
herve@node0:~$ docker exec -ti webtest_web.2.klj0q37g2k22a1ar9f6sp7wai 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
25274: eth0@if25275: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 02:42:0a:00:00:c5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.0.197/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
25276: eth2@if25277: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:12:00:0c brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet 172.18.0.12/16 brd 172.18.255.255 scope global eth2
       valid_lft forever preferred_lft forever
25278: eth1@if25279: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 02:42:0a:00:05:04 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet 10.0.5.4/24 brd 10.0.5.255 scope global eth1
       valid_lft forever preferred_lft forever
herve@node0:~$

On a donc 3 IPs, sur 3 réseaux:

  • 10.0.0.197/24 sur eth0
  • 10.0.5.4/24 sur eth1
  • 172.18.0.12/16 sur eth2

Sur l'autre container:

  • 10.0.0.196/24 sur eth0
  • 10.0.5.3/24 sur eth1
  • 172.18.0.6/16 sur eth2

Comparons ça aux réseaux docker:

herve@node0:~$ docker network ls
NETWORK ID     NAME                   DRIVER    SCOPE
a815e8c6b042   bridge                 bridge    local
040fa5ce7313   docker_gwbridge        bridge    local
918497e0a927   host                   host      local
yewz22nd9rf7   ingress                overlay   swarm
c53d64081620   none                   null      local
i0ylvjdz6sqg   webtest_testnet        overlay   swarm
herve@node0:~$ docker network inspect --format '{{ json .Containers  }}' docker_gwbridge | jq '.'
{
  "1b2ffd90e4f770b1f6db42bd48eef81d0c9c3edfe3d6298df1e4213a54b9e43c": {
    "Name": "gateway_161c8a8cd5e7",
    "EndpointID": "8cdadbc8543d4dc343ca6b3882e26d7a12251cb16c3b651bfe7ac636b673eef6",
    "MacAddress": "02:42:ac:12:00:06",
    "IPv4Address": "172.18.0.6/16",
    "IPv6Address": ""
  },
  "7d05626b82efc45c7d7834eafd55f025f8098b3e1dabe4ffd6e55754a8522e45": {
    "Name": "gateway_b5b17964f1aa",
    "EndpointID": "b5ab1d4a5f4b05a564653e3cf7132313487d74c1b6b9fb31535baf263dc8b1c5",
    "MacAddress": "02:42:ac:12:00:0c",
    "IPv4Address": "172.18.0.12/16",
    "IPv6Address": ""
  }, 
  "ingress-sbox": {
    "Name": "gateway_ingress-sbox",
    "EndpointID": "6a6974e33a38e0c97eb35f63688dda840731a4f74da6e7fc32f2202a2281bc17",
    "MacAddress": "02:42:ac:12:00:02",
    "IPv4Address": "172.18.0.2/16",
    "IPv6Address": ""
  }
}
herve@node0:~$ docker network inspect --format '{{ json .Containers  }}' ingress | jq '.'
{
  "7d05626b82efc45c7d7834eafd55f025f8098b3e1dabe4ffd6e55754a8522e45": {
    "Name": "webtest_web.2.klj0q37g2k22a1ar9f6sp7wai",
    "EndpointID": "65009589cd3e3083228c27c907c8dc956dc4e58e36b168eeb6b3955ee9174e4c",
    "MacAddress": "02:42:0a:00:00:c5",
    "IPv4Address": "10.0.0.197/24",
    "IPv6Address": ""
  },
  "ingress-sbox": {
    "Name": "ingress-endpoint",
    "EndpointID": "898b76a4aeb2144a60a7bd3066113e647974b0f639bb76934ac4c4ee43da68f5",
    "MacAddress": "02:42:0a:00:00:04",
    "IPv4Address": "10.0.0.4/24",
    "IPv6Address": ""
  }
}
herve@node0:~$ docker network inspect --format '{{ json .Containers  }}' webtest_testnet | jq '.'
{
  "7d05626b82efc45c7d7834eafd55f025f8098b3e1dabe4ffd6e55754a8522e45": {
    "Name": "webtest_web.2.klj0q37g2k22a1ar9f6sp7wai",
    "EndpointID": "619345cf52266f5b1c810ff1ccd3d8299952c226689234a13625ee62432e8eb9",
    "MacAddress": "02:42:0a:00:05:04",
    "IPv4Address": "10.0.5.4/24",
    "IPv6Address": ""
  },
  "lb-webtest_testnet": {
    "Name": "webtest_testnet-endpoint",
    "EndpointID": "3b3736e50d02ef8fb8abedaf721ef57b7f58f16a17cb8fa16fe0dd98cfb5aba2",
    "MacAddress": "02:42:0a:00:05:05",
    "IPv4Address": "10.0.5.5/24",
    "IPv6Address": ""
  }
}
herve@node0:~$

Décryptage:

Le réseau webtest_testnet (10.0.5.0/24) est un réseau overlay spécifique à mon service. Et je n'y vois que les containers présents sur le node.

Le réseau ingress (10.0.0.0/24) est un réseau overlay commun à tous mes services (D'autres containers de services Swarn sur le même host y apparaîtraient). Je n'y vois que les containers présents sur le node, référencés par leur nom.

Le réseau docker_gwbridge (172.18.0.0/16) est un réseau bridge (= qui fait le "pont" avec le host) commun à tous mes services. Et j'y voir des containers hébergés sur les hosts voisins. Par contre les noms "gateway_xxxx" ne sont pas associables directement à un container.

Un peu de sondage avec curl

herve@node0:~$ curl 172.18.0.1:8091
<html><body><h1>It works!</h1></body></html>
herve@node0:~$ curl 172.18.0.2:8091
<html><body><h1>It works!</h1></body></html>
herve@node0:~$ curl 172.18.0.6:8091
curl: (7) Failed to connect to 172.18.0.6 port 8091: Connection refused
herve@node0:~$ curl 172.18.0.12:8091
curl: (7) Failed to connect to 172.18.0.12 port 8091: Connection refused
herve@node0:~$ curl 172.18.0.6:80
curl: (7) Failed to connect to 172.18.0.6 port 80: Connection refused
herve@node0:~$ curl 172.18.0.12:80
<html><body><h1>It works!</h1></body></html>
herve@node0:~$
  • 172.18.0.1 est une IP portée par le host (interface docker_gwbridge). Elle permet d'accéder à mon service.
  • Via 172.18.0.2, l'IP de la gateway_ingress-sbox du réseau docker docker_gwbridge, ça marche aussi.
  • Par contre avec 172.18.0.6 et 172.18.0.12, les IPs des containers, ça ne marche plus sur le port exposé sur le host (8091)
  • Mais sur 172.18.0.12, l'ip du container qui tourne sur le host en cours, je peux interroger le port exposé dans le container (80).

Il semble se passer des choses intéressantes dans ce réseau &#58;&#68;

iptables et namespaces réseau

Commencons par le début, ce qui arrive sur notre host

herve@node0:~$ sudo iptables -nv -L PREROUTING -t nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in  out  source         destination         
 222M   20G DOCKER-INGRESS  all  --  *   *  0.0.0.0/0   0.0.0.0/0      ADDRTYPE match dst-type LOCAL
 173M   17G DOCKER     all  --  *   *    0.0.0.0/0      0.0.0.0/0      ADDRTYPE match dst-type LOCAL
herve@node0:~$

PREROUTING (et OUTPUT) passent les paquets dans les tables DOCKER-INGRESS et DOCKER. DOCKER pour les containers standard Docker, DOCKER-INGRESS pour Swarm.
Intéressons-nous à DOCKER-INGRESS

herve@node0:~$ sudo iptables -nv -L DOCKER-INGRESS -t nat | grep 8091
    4   240 DNAT       tcp  --  *    *     0.0.0.0/0      0.0.0.0/0       tcp dpt:8091 to:172.18.0.2:8091
herve@node0:~$ 

Le port 8091 va être redirigé vers 172.18.0.2.
Tout traffic entrant sur mon host sur le port 8091 est redirigé vers le réseau docker_gwbridge.
Nickel.

La suite se passe dans un autre namespace réseau, nommé ingress_sbox

Commençons par regarder les namespaces réseau:

herve@node0:~$ sudo ls -l /run/docker/netns
total 0
-r--r--r-- 1 root root 0 Sep 11 08:01 1-i0ylvjdz6s
-r--r--r-- 1 root root 0 Jul 16 13:15 1-yewz22nd9r
-r--r--r-- 1 root root 0 Sep 11 08:01 b5b17964f1aa
-r--r--r-- 1 root root 0 Jul 16 13:15 ingress_sbox
-r--r--r-- 1 root root 0 Sep 11 08:01 lb_i0ylvjdz6
herve@node0:~$ 

Inventaire:

  • 1-xxx correspond à un réseau overlay
  • 1-i0ylvjdz6s pour le réseau docker i0ylvjdz6sqg - webtest_testnet
  • 1-yewz22nd9r pour le réseau docker yewz22nd9rf7 - ingress
  • lb_i0ylvjdz6 pour le load balancer du réseau i0ylvjdz6sqg - webtest_testnet
  • ingress_sbox - au moins lui il a un nom parlant !
  • b5b17964f1aa pour finir: spécifique à notre container. (il n'y en a qu'un, car l'autre container est sur un autre host).

On peuc retrouver l'ID du netns de notre container en l'inspectant:

herve@node0:~$ docker inspect \
--format '{{ json .NetworkSettings.SandboxKey  }}' webtest_web.2.klj0q37g2k22a1ar9f6sp7wai \
| jq '.'
"/var/run/docker/netns/b5b17964f1aa"
herve@node0:~$ 

Exploration du namespace ingress_sbox

herve@node0:~$ sudo nsenter --net=/run/docker/netns/ingress_sbox \
iptables -t mangle -L PREROUTING -vn | head -n 2
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in   out  source            destination         
herve@node0:~$ sudo nsenter --net=/run/docker/netns/ingress_sbox \
iptables -t mangle -L PREROUTING -vn | grep 8091
   42  2792 MARK       tcp  --  *    *    0.0.0.0/0         0.0.0.0/0      tcp dpt:8091 MARK set 0x27f2
herve@node0:~$ sudo nsenter --net=/run/docker/netns/ingress_sbox \
iptables -t mangle -L INPUT -vn | grep 27f2
    0     0 MARK       all  --  *    *    0.0.0.0/0         10.0.0.195     MARK set 0x27f2
herve@node0:~$ # 0x27f2 = 10226 en base 10
herve@node0:~$ sudo nsenter --net=/run/docker/netns/ingress_sbox \
ipvsadm | grep -A 3 10226
FWM  10226 rr
  -> 10.0.0.196:0                 Masq    1      0          0         
  -> 10.0.0.197:0                 Masq    1      0          0         
herve@node0:~$

On voit ici que les paquets à destination de TCP:8091 sont taggés "0x27f2", ainsi que tous ceux à destination de 10.0.0.195.

Ensuite, IPVS a des règles de routage pour les paquets taggés "0x27f2", indiquant qu'il faut faire du round robin entre 10.0.0.196 et 10.0.0.197.

Les paquets sont donc transmis à chaque container du service à tour de rôle, tout en gardant le même port pour le moment (8091)

Port-forwarding

Pour terminer, le port forwarding se passe dans le namespace spécifique à chaque container:

herve@node0:~$ sudo nsenter --net=/var/run/docker/netns/b5b17964f1aa iptables -L PREROUTING -t nat -nv
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out   source          destination         
   86  5160 REDIRECT   tcp  --  *      *     0.0.0.0/0       10.0.0.197      tcp dpt:8091 redir ports 80
herve@node0:~$ 

Conclusion

On a vu:

  • Le routage des paquets extérieurs, du réseau host vers le docker_gwbridge
  • Leur tagging vers pour utilisation sur un load-balancer IPVS
  • la redirection du load-balancer vers une IP privée sur le réseau ingress
  • la redirection du port exposé par Docker vers le port exposé par le service dans le container

On n'a pas vu:

  • les vxlans, ou comment abstraire le fait que les containers sont sur différents hosts
  • le lien entre réseaux docker et namespaces réseau
  • les communications d'un container à un autre
  • ...et plein d'autres choses passionnantes...

Personnellement, c'est un truc que je trouve génial avec Docker: ça repose énormément sur des mécanismes système, présents bien avant Docker.

C'est donc possible d'explorer tout ça sans lire le code source, juste en observant les serveurs.

À vous de continuer à explorer &#58;&#68; et vivement qu'on se croise à un meetup pour discuter de tout ça &#59;&#41;

Versions utilisées

  • Version de Docker: 20.10.7
  • Hosts: Debian 10
Tags: dockeriptablesipvsswarm

Commentaires en attente de modération

Ce post a 1 réaction en attente de modération...


Formulaire en cours de chargement...

Février 2023
Lun Mar Mer Jeu Ven Sam Dim
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28          
 << <   > >>

Rechercher

Catégories

Blog h.rvé

  • Linux et logiciel libre
  • Musique
  • Non catégorisé
  • Vie du blog
  • Vie sociale et citoyenne

Flux XML

  • RSS 2.0: Posts, Commentaires
  • Atom: Posts, Commentaires
What is RSS?

Posts récents

  • Le(s) réseau(x) de Docker Swarm, deep dive
  • Waterfall scrum
  • Retour sur les 24HSeries au Castelet
  • [docker] premiers pas avec Moby et LinuxKit
  • Fabrication et pose d'une fibre optique sous-marine
  • Docker et les Go Templates
  • Megaprocessor - un processeur dont on peut voir chaque transistor
  • Keynote: State of the Linux Kernel, Greg Kroah-Hartman
  • L'art de se débarrasser ce qui est inutile.
  • La magie de l'Internet des Objets: quand "sécurité" est un gros mot

Commentaires récents

  • afix le Docker: volumes et dépendances croisées
  • afix le Docker: volumes et dépendances croisées
  • Richard le Les empreintes digitales pour remplacer les mots de passe: bad idea!
  • Henri le Megaprocessor - un processeur dont on peut voir chaque transistor
  • Henri le L'art de se débarrasser ce qui est inutile.
  • herve le L'art de se débarrasser ce qui est inutile.
  • Henri le L'art de se débarrasser ce qui est inutile.
  • Joel le Unikernels et Docker: une explication claire.
  • Henri le "The End of the Internet Dream", ou pas.
  • Agnès le Haken, rock progressif
  • herve le Une imprimante 3D pour $60
  • Agnès le Une imprimante 3D pour $60
  • Henri le Une imprimante 3D pour $60
  • Agnès le Bienvenue!
  • herve le Bienvenue!
  • Agnès le Bienvenue!
  • Agnès le Site parlement-et-citoyens.fr
  • Agnès le Bienvenue!
  • herve le Bienvenue!
  • Fabien le Bienvenue!

Licence Creative Commons
Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International. • Contact • Aide • Online manual generator

Website builder

Cookies are required to enable core site functionality.