Dans cette mise en pratique, nous allons étudier la mise en réseau des containers Docker.
Nous verrons notamment les networks qui sont crées par défaut lors de l’installation de la plateforme et nous passerons en revue différents drivers qui sont disponibles avec une installation standard.
Prequis : jq (sudo apt update && sudo apt install jq)
Lors de l’installation de la plateforme Docker, plusieurs networks sont créés.
Listez ces networks avec la commande suivante:
$ docker network lsVous obtiendrez un résultat similaire à celui ci-dessous (aux identifiants près):
NETWORK ID NAME DRIVER SCOPE
78600647cf6b bridge bridge local
f68b62cc1152 host host local
5a4a4afa414b none null localLorsqu’un container est créé, nous pourrons spécifier le network auquel il sera attaché:
-
un container attaché au network none n’aura pas de connectivité externe. Cela peut-être utile par exemple pour un container servant pour du debug.
-
un container attaché au network host bénéficiera de la stack network de la machine hôte
-
un container attaché au network bridge pourra communiquer avec les autres containers attaché à ce network.
Il faut cependant noter que ce type de network permet seulement une connectivité entre containers tournant sur la même machine.
Avec la commande suivante, listez les interfaces de la machine hôte:
$ ip aVous obtiendrez un résultat similaire à celui ci-dessous (les noms de certaines interfaces pourront être différentes):
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group
default qlen 1000link/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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
group default qlen 1000
link/ether 02:4d:82:c4:d5:87 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
valid_lft 85937sec preferred_lft 85937sec
inet6 fe80::4d:82ff:fec4:d587/64 scope link
valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
group default qlen 1000
link/ether 08:00:27:68:30:03 brd ff:ff:ff:ff:ff:ff
inet 192.168.33.10/24 brd 192.168.33.255 scope global enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe68:3003/64 scope link
valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state
DOWN group default
link/ether 02:42:ac:d1:34:9b brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft foreverLa chose importante à noter ici est l’existance d’une interface nommée docker0. C'est un bridge Linux qui a été créée lors de l’installation de la plateforme.
Par défaut, si l’option –-network n’est pas spécifiée au lancement d’un container, celui-ci est attaché au network nommé bridge.
Utilisez la commande suivante pour lancer un container nommé cnt1 basé sur l’image alpine.
$ docker container run -d --name cnt1 alpine:3.8 sleep 10000Note: nous spécifions sleep 10000 dans la commande de façon à ce que le processus de PID 1 de ce container tourne quelques temps.
Avec la commande suivante, récupérer la configuration réseau de ce container (nous utilisons la notation Go template pour récupérer directement le champ qui nous intéresse (.NetworkdSetings.Networks).
$ docker container inspect -f "{{ json .NetworkSettings.Networks }}" cnt1 | jq .La sortie de cette commande nous donne l’ID du network auquel ce container est attaché, via la clé NetworkID. Nous pouvons alors voir que cette valeur correspond à l’ID du network bridge. Nous obtenons également d’autres information comme l’IP du container et celle de la passerelle.
{
"bridge": {
"Aliases": null,
"DriverOpts": null,
"EndpointID":
"1968a1143dba15cad49dc84b06839b83e42d249a5ca6a83c06092840ad205364",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAMConfig": null,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"Links": null,
"MacAddress": "02:42:ac:11:00:02",
"NetworkID":
"78600647cf6b67dbe6fcc0dcc9b06a59a0b5c36033fa088c030490959901ee16"
}
}Avec la commande suivante, inspectez le network bridge.
Note: cette commande utilise le formalisme Go template pour extraire la clé Containers afin d’obtenir la liste des containers attachés à ce network.
$ docker network inspect -f "{{ json .Containers }}" bridge | jq .Vous devriez obtenir un résultat proche de celui ci-dessous, dans lequel le container cnt1 est listé. On voit donc que cnt1 est attaché au network.
{
"3f41f1295700be13435e82df1e98f1575f4380cfdcb8b315e4b275485e4c2470": {
"EndpointID":
"1968a1143dba15cad49dc84b06839b83e42d249a5ca6a83c06092840ad205364",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": "",
"MacAddress": "02:42:ac:11:00:02",
"Name": "cnt1"}
}Supprimez le container cnt1.
$ docker rm -f cnt1Avec la commande suivante, listez les interfaces réseau existantes sur la machine hôte.
$ ip link showVous devriez obtenir un résultat proche de celui ci-dessous.
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state
DOWN
link/ether 02:42:4a:a3:69:00 brd ff:ff:ff:ff:ff:ff
50947: eth0@if50948: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc
noqueue state UP
link/ether 02:42:0a:00:a7:03 brd ff:ff:ff:ff:ff:ff
50949: eth1@if50950: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc
noqueue state UP
link/ether 02:42:ac:12:00:9d brd ff:ff:ff:ff:ff:ffLancez alors un shell interactif dans un container basé sur l’image alpine. Spécifiez l’option --network=host de façon à ce que ce container utilise la stack network de la machine hôte.
$ docker container run -ti --name cnt1 --network=host alpine:3.8 shUne fois dans le container, listez les interfaces réseau.
# ip link showLa liste des interfaces devrait être la même que celle obtenue directement depuis la machine hôte. Sortez du container et supprimez le.
# exit
$ docker rm cnt1Lancez à présent un shell interactif dans un container basé sur l’image alpine mais cette fois en utilisant l’option *--network=none- de façon à ne pas donner au container de connectivité vers l’extérieur.
$ docker container run -ti --name cnt1 --network none alpine:3.8 shListez les interfaces réseau du container.
# ip a showVous devriez constater que seule lo (l’interface locale) est disponible.
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
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 foreverDepuis ce container, essayez de lancer un ping sur le DNS de Google (8.8.8.8).
# ping 8.8.8.8Vous devriez obtenir le message d'erreur suivant, car ce container n'a pas de connection avec l’extérieur.
PING 8.8.8.8 (8.8.8.8): 56 data bytesping: sendto: Network unreachableSortez du container et supprimez le.
# exit
$ docker rm cnt1Le réseau bridge créé par défaut permet à des containers tournant sur la même machine de communiquer entre eux comme nous allons le voir maintenant.
Dans cette partie, nous allons lancer 2 containers, chacun étant attaché au network bridge.
Il n’est pas nécessaire de spécifier l’option --network car un container est attaché à ce network par défaut.
Utilisez la commande suivante pour lancer un premier container, nommé cnt1.
$ docker container run -d --name cnt1 alpine:3.8 sleep 10000Avec la commande suivante, récupérer l'adresse IP du container.
$ docker container inspect -f "{{ .NetworkSettings.IPAddress }}" cnt1Exemple de retour de cette commande:
172.17.0.2Note: l’adresse IP obtenue sur votre environnement pourra être différente.
Utilisez la commande suivante pour lancer un shell interactif dans un second container basé sur l’image alpine, et nommé cnt2.
$ docker container run -ti --name cnt2 alpine:3.8 shDepuis ce shell, essayez de lancer un ping sur le container cnt1 en utilisant l’adresse IP de ce dernier.
Note: lancez la commande suivante en utilisant l’IP du container cnt1 obtenue précédemment.
# ping -c 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.135 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.098 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.110 ms
--- 172.17.0.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.098/0.114/0.135 msCette commande fonctionne correctement, le container cnt2 est capable de pinguer le container cnt1 en utilisant l’adresse IP de ce dernier.
Toujours depuis le shell obtenu dans le container cnt2, lancez un ping vers cnt1 mais cette fois-ci en utilisant le nom cnt1 au lieu de son adresse IP.
# ping -c 3 cnt1Vous devriez obtenir le message suivant:
ping: bad address 'cnt1'Il n’est pas possible, pour des containers attachés au network bridge de communiquer via leur nom. Nous verrons que cela est par contre possible si nous définissons notre propre network de type bridge.
Sortez du container cnt2 et supprimez cnt1 et cnt2.
# exit
$ docker rm -f cnt1 cnt2Nous avons vu que, par défaut, 3 networks sont définis (ceux-ci sont nommés bridge, host et none). Il est également possible d’en créer d’autres, c’est ce que nous allons voir dans les sections suivantes.
Utilisez la commande suivante afin de créer un nouveau network de type bridge nommé bnet.
$ docker network create --driver bridge bnetNote: nous spécifions ici l’option --driver bridge pour l’illustration même si ce n’est pas nécessaire car c’est le driver utilisé par défaut.
Utilisez la commande suivante pour lancer un container, nommé cnt1 et attaché au network bnet.
$ docker container run -d --name cnt1 --network bnet alpine:3.8 sleep 10000Comme précédemment, recupérez l’adresse IP de ce container.
Note: cette fois, nous utilisons la clé .NetworkSettings.Networks.bnet.IPAddress afin de récupérer l’IP alouée sur le réseau bnet.
$ docker container inspect -f "{{ json .NetworkSettings.Networks.bnet.IPAddress
}}" cnt1Exemple de résultat obtenu:
172.18.0.2Note: l’adresse IP obtenue sur votre environnement peux être différente.
Lancez un shell interactif dans un second container, nommé cnt2, et également attaché au network bnet.
$ docker container run -ti --name cnt2 --network bnet alpine:3.8 shDepuis cnt2, lancez un ping sur l'adress IP de cnt1.
# ping -c 3 172.18.0.2Vous devriez obtenir un résultat similaire à celui ci-dessous:
PING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=1.557 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.093 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.088 ms
--- 172.18.0.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.088/0.579/1.557 msNous pouvons constater que, comme c’est le cas pour le network bridge par défaut, les containers attachés au network bnet peuvent communiquer via leur adresse IP.
Comme précédemment, essayez de lancer un ping sur cnt1 en utilisant le nom du container au lieu de son adresse IP.
# ping -c 3 cnt1Vous devriez obtenir un résultat similaire à celui ci-dessous:
PING cnt1 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.096 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.095 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.089 ms
--- cnt1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.089/0.093/0.096 msNous pouvons constater que, contrairement au cas du network bridge par défaut, les containers attachés au network bnet peuvent communiquer via leur nom, il y a une résolution de noms qui s'opère via un serveur DNS embarqué.
Sortez du container cnt2 et supprimez cnt1 et cnt2.
# exit
$ docker rm -f cnt1 cnt2