Communication client / serveur

Dans cet article nous allons parler des différents protocoles de communication entre le client Docker et le daemon.

Communication via une socket unix par défaut

Lorsque l’on suit par exemple la procédure d’Installation de Docker CE sur Ubuntu, le Docker daemon est géré par systemd. On peut facilement voir les options fournies au lancement du daemon:

$ ps aux | grep dockerd
root      5470  0.2  6.6 782564 66644 ?        Ssl  13:02   0:00 /usr/bin/dockerd -H fd://

Le résultat de cette commande nous dit simplement que le daemon écoute sur une socket mise en place par systemd.

$ sudo systemctl status docker
- docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-01-17 13:02:19 UTC; 18min ago
     Docs: https://docs.docker.com
 Main PID: 5470 (dockerd)
    Tasks: 10
   CGroup: /system.slice/docker.service
           └─5470 /usr/bin/dockerd -H fd://
           ...

Si nous regardons d’un peu plus près la définition du service.docker, nous pouvons voir qu’il dépend de docker.socket.

$ cat /lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
BindsTo=containerd.service
After=network-online.target firewalld.service
Wants=network-online.target
Requires=docker.socket

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd://
...

Le fichier de configuration de docker.socket montre que la socket mise en place par systemd est /var/run/docker.sock

cat /lib/systemd/system/docker.socket
[Unit]
Description=Docker Socket for the API
PartOf=docker.service

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target

On voit donc ici que le daemon Docker écoute sur la socket /var/run/docker.sock, c’est la socket par défaut. Celle-ci ne permet qu’une communication locale, c’est à dire enter le client docker et le daemon dockerd installés sur la machine. Le client docker communique sur cette socket par défaut.

Communication sécurisée via un réseau TCP

La commande ci-dessous montre l’exemple d’un daemon Docker exposé de façon sécurisée sur un réseau:

$ ps aux | grep dockerd
$ root      2601  0.4  7.0 354148 71688 ?        Sl   03:51   1:49 dockerd --data-root /var/lib/docker -H unix:// --label provider=virtualbox -H tcp://0.0.0.0:2376 --tlsverify --tlscacert=/var/lib/boot2docker/ca.pem --tlskey=/var/lib/boot2docker/server-key.pem --tlscert=/var/lib/boot2docker/server.pem --storage-driver overlay2 --pidfile /var/run/docker.pid

Il y a 2 flags qui nous intéressent ici, ceux-ci permettant la communication entre un client Docker et le daemon:

  • -H unix:// fait référence à la socket unix que l’on a vu plus haut (le path n’est pas précisé car c’est /var/run/docker.sock par défaut). Cette socket est utilisée par le client local pour communiquer avec le daemon

  • -H tcp://0.0.0.0:2376 permet d’exposer le daemon via le port 2376 sur n’importe quelle interface réseau de la machine. Les différents flags commençant par –tls permettent de sécuriser cette connection avec TLS et également de vérifier l’authenticité du client en s’assurant que le certificat TLS du client a bien été signé par l’authorité de certification du daemon.

Pour qu’un client puisse communiquer avec le daemon via la socket TCP il faut donc:

  • que le port 2376 soit ouvert et accessible depuis l’extérieur
  • d’avoir accès au certificat du daemon
  • de créer un couple clé privée / clé publique pour le client et le signer avec le certificat du daemon

Il faut également que le client précise, sur la ligne de commande avec le flag -H ou via la variable d’environnement DOCKER_HOST, l’URL du daemon:

  • en utilisant le flag -H:
docker -H IP:2376 info
  • en utilisant la variable d’environnement DOCKER_HOST:
DOCKER_HOST=IP:2376
docker info
ℹ️

Il est possible, mais déconseillé, d’exposer le daemon via tcp sans sécuriser la communication. Dans ce cas, la commande de lancement aurait la forme suivante:

dockerd -H unix:// -H tcp://0.0.0.0:2375

Il y a différentes choses à noter ici:

  • le flag -H unix:// est obligatoire si l’on souhaite continuer à pouvoir communiquer avec le daemon en local. Si il n’est pas précisé, seule la communication via le réseau sera possible

  • le port 2375 est utilisé par convention dans le cas d’une communication non sécurisée (port 2376 pour une communication sécurisée)

Communication via SSH

Le protocole SSH est largement utilisé et est souvent l’un des seuls protocoles autorisés par défaut pour accéder à une machine. La release 18.09 de Docker permet de comuniquer avec le daemon directement via SSH.

Nous supposons dans cet exemple que nous avons un machine virtuelle node1 basée sur Ubuntu et sur laquelle Docker a été installé. Son IP est 192.168.33.10 et elle contient un utilisateur nommé luc qui peut se connecter en SSH soit avec son mot de passe soit avec la clé privée ~/.ssh/node1_private_key.

Depuis Docker 18.09, Il est possible de s’adresser au docker daemon avec la commande suivante:

$ docker -H ssh://luc@192.168.33.10 version
luc@192.168.33.10's password:
Client: Docker Engine - Community
 Version:           18.09.1
 API version:       1.39
 Go version:        go1.10.6
 Git commit:        4c52b90
 Built:             Wed Jan  9 19:33:12 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.1
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       4c52b90
  Built:            Wed Jan  9 19:02:44 2019
  OS/Arch:          linux/amd64
  Experimental:     false

Il y a plusieurs choses à modifier ici de façon à simplifier la commande:

  • ajouter la clé privée de l’utilisateur dans la liste des identités (ssh-add ~/.ssh/node1node1_private_key), cela permettra de ne pas avoir à renseigner le mot de passe de l’utilisateur à chaque commande.

  • utiliser la variable d’environnement DOCKER_HOST (export DOCKER_HOST="ssh://vagrant@192.168.33.10" ), cela permettra de ne pas avoir à utiliser le flag -H pour chaque commande.

Il est alors possible de lancer la commande suivante, elle retournera le même résultat que précédemment.

docker version

En résumé

Nous avons vu ici différentes façons pour le client docker de communiquer avec un hôte, local ou bien distant. La possibilité d’utiliser une connection via SSH permet de simplifier ces intéractions.