Bridge Networking Deep Dive
Bridge Networking Deep Dive
Le réseau bridge
représente le réseau docker0
présent dans toutes les installations Docker.
Sauf indication contraire par le biais de l’option docker run --network=<NETWORK>
, le démon Docker connecte les containers
à ce réseau par défaut.
Note: Il existe quatre concepts importants concernant la mise en réseau
bridge
:
- Docker0 Bridge
- Network Namespace
- Veth Pair
- External Communication
Le bridge Docker0
Version Docker pour ce lab:
$ docker version
Client: Docker Engine - Community
Version: 24.0.1
API version: 1.43
Go version: go1.20.4
Git commit: 6802122
Built: Fri May 19 18:06:34 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.1
API version: 1.43 (minimum version 1.12)
Go version: go1.20.4
Git commit: 463850e
Built: Fri May 19 18:06:34 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.21
GitCommit: 3dce8eb055cbb6872793272b4f20ed16117344f8
runc:
Version: 1.1.7
GitCommit: v1.1.7-0-g860f061
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Via la commande docker network
on obtient plus d’informations concernant le bridge docker0
$ docker network ls
NETWORK ID NAME DRIVER
32b93b141bae bridge bridge
c363d9a92877 host host
88077db743a8 none null
Il n’y a pas de container connecté bridge
.
$ docker network inspect 32b93b141bae
[
{
"Name": "bridge",
"Id": "32b93b141baeeac8bbf01382ec594c23515719c0d13febd8583553d70b4ecdba",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Containers": {},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
Vous pouvez également voir ce pont comme faisant partie de la pile réseau d’un hôte
à l’aide de la commande ifconfig/ip
sur l’hôte.
$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 06:95:4a:1f:08:7f brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:d6:23:e6:18 brd ff:ff:ff:ff:ff:ff
Comme il n’y a pas de container attaché à ce bridge docker0
, il est down.
On peut aussi utilisaer la commande brctl
pour obtenir plus d’informations sur le bridge docker0
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242d623e618 no veth6a5ae6f
Note: Si vous ne trouvez pas la commande
brctl
, vous devrez l’installer. Pour les distributionRedHat Like
, utilisezsudo yum install bridge-utils
. Pour les distributionDebian Like
, il faut utiliserapt-get install bridge-utils
Veth Pair
Nous allons créer un container centos7:
$ docker run -d --name test1 centos:7 /bin/bash -c "while true; do sleep 3600; done"
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4fea95f2e979 centos:7 "/bin/bash -c 'while " 6 minutes ago Up 6 minutes test1
Nous pouvons vérifier l’interface IP sur l’hôte docker.
$ ip li
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 06:95:4a:1f:08:7f brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:d6:23:e6:18 brd ff:ff:ff:ff:ff:ff
15: vethae2abb8@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether e6:97:43:5c:33:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 0
Le bridge docker0
est up, et il y a une paire veth
créée, une est sur le localhost
, et l’autre dans le nampespace
du container.
ESpace de nom réseau
Si on ajoute un nouvel espace de nom réseau à partie de la ligne de commande
$ sudo ip netns add demo
$ ip netns list
demo
$ ls /var/run/netns
demo
$ sudo ip netns exec demo ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Mais à partir de la commande ip netns list
, nous ne pouvons pas obtenir l’espace de noms réseau du container.
Docker a supprimé tous les informations des espaces de noms réseau des container de /var/run/netns
.
On peux récupérer tous les espaces de noms réseau à partir de /var/run/docker/netns
.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4fea95f2e979 centos:7 "/bin/bash -c 'while " 2 hours ago Up About an hour test1
$ sudo ls -l /var/run/docker/netns
total 0
-rw-r--r--. 1 root root 0 Nov 28 05:51 572d8e7abcb2
Comment récupérer toutes les informations(comme veth) concernant l’espace de nom réseau du container ?
Nous devons récupérer dans un premier temps le pid
du process du container, et récupérer tous les espaces de noms de ce container.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4fea95f2e979 centos:7 "/bin/bash -c 'while " 2 hours ago Up 2 hours test1
$ docker inspect --format '{{.State.Pid}}' 4f
3090
$ sudo ls -l /proc/3090/ns
total 0
lrwxrwxrwx. 1 root root 0 Nov 28 05:52 ipc -> ipc:[4026532156]
lrwxrwxrwx. 1 root root 0 Nov 28 05:52 mnt -> mnt:[4026532154]
lrwxrwxrwx. 1 root root 0 Nov 28 05:51 net -> net:[4026532159]
lrwxrwxrwx. 1 root root 0 Nov 28 05:52 pid -> pid:[4026532157]
lrwxrwxrwx. 1 root root 0 Nov 28 08:02 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Nov 28 05:52 uts -> uts:[4026532155]
Ensuite on restaure l’espace de nom réseau:
$ sudo ln -s /proc/3090/ns/net /var/run/netns/3090
$ ip netns list
3090
demo
$ sudo ip netns exec 3090 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
26: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
Ensuite c’est bon, il faut supprimer /var/run/netns/3090
.
Communication Externe
Tous les containers connectés au bridge docker0
peuvent communiqués avec le réseau externe ou d’autres containers connectés au même bridge.
Démarrons deux containers:
$ docker run -d --name test2 centos:7 /bin/bash -c "while true; do sleep 3600; done"
8975cb01d142271d463ec8dac43ea7586f509735d4648203319d28d46365af2f
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8975cb01d142 centos:7 "/bin/bash -c 'while " 4 seconds ago Up 4 seconds test2
4fea95f2e979 centos:7 "/bin/bash -c 'while " 27 hours ago Up 26 hours test1
Et à partir du bridge docker0
, on peux voire deux inrterfaces connectées.
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242d623e618 no veth6a5ae6f
vethc16e6c8
$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 06:95:4a:1f:08:7f brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:d6:23:e6:18 brd ff:ff:ff:ff:ff:ff
27: veth6a5ae6f@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether 02:7d:eb:4e:85:99 brd ff:ff:ff:ff:ff:ff link-netnsid 0
31: vethc16e6c8@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether d2:9f:2e:ca:22:a5 brd ff:ff:ff:ff:ff:ff link-netnsid 1
Les deux containers peuvent communiquer
$ docker inspect --format '{{.NetworkSettings.IPAddress}}' test1
172.17.0.2
$ docker inspect --format '{{.NetworkSettings.IPAddress}}' test2
172.17.0.3
$ docker exec test1 bash -c 'ping 172.17.0.3'
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.053 ms
^C
Le réseau de base doit être comme ci-dessous:
CNM
Pour comprendre comment le conteneur obtient son adresse IP, vous devez comprendre qu’est-ce que CNM (Container Network Model)1.
Libnetwork implémente Container Network Model (CNM) qui formalise les étapes nécessaires pour fournir une mise en réseau pour les conteneurs tout en fournissant un abstraction pouvant être utilisée pour prendre en charge plusieurs pilotes réseau.
Au cours du cycle de vie du réseau et des terminaux, le modèle CNM contrôle Attribution d’adresse IP pour les interfaces réseau et terminal via l’IPAM conducteur(s)2.
Lors de la création du pont docker0
, libnetwork fera une requête vers le pilote IPAM,
quelque chose comme une passerelle réseau, un pool d’adresses. Lors de la création
un conteneur, dans le bac à sable du réseau, et un point de terminaison a été créé,
libnetwork demandera une adresse IPv4 au pool IPv4 et l’attribuera
à l’adresse IPv4 de l’interface du point de terminaison.
NAT
Les containers avec leur réseau en mode bridge peuvent accéder au réseau externe au travers du NAT
qui est configuré via iptables
.
Dans le container:
# ping www.google.com
PING www.google.com (172.217.27.100) 56(84) bytes of data.
64 bytes from sin11s04-in-f4.1e100.net (172.217.27.100): icmp_seq=1 ttl=61 time=99.0 ms
64 bytes from sin11s04-in-f4.1e100.net (172.217.27.100): icmp_seq=2 ttl=61 time=108 ms
64 bytes from sin11s04-in-f4.1e100.net (172.217.27.100): icmp_seq=3 ttl=61 time=110 ms
^C
--- www.google.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 99.073/106.064/110.400/4.990 ms
A partir du server docker, on peut voir:
$ sudo iptables --list -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 anywhere
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
Pour le NAT au travers d’iptables, on peux se référer34