Docker : y faire tourner une appli graphique
Docker s’est rapidement imposé comme étant un outil incontournable dans les équipes de développement. Il est possible d’y exécuter très rapidement un grand nombre d’applications virtualisées, depuis un serveur MySQL, un Apache, Tomcat et beaucoup d’autres. Le problème se pose quand on souhaite exécuter une application graphique dans un container Docker.
Effectivement, dans un certain nombre de cas, il est possible d’être amené à devoir exécuter une application graphique dans un container. Par exemple :
- Exécuter une version spécifique d’une application différente de celle installée sur son PC
- Faire une image que l’on souhaite partager entre différentes personnes et/ou PC
- Isoler une application du système
Néanmoins, faire tourner un tel programme est un peu plus complexe qu’un simple docker run -it ubuntu
Un exemple : Firefox
Nous partons de ce Dockerfile :
FROM ubuntu:wily RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install firefox CMD ["/usr/bin/firefox"]
Puis le build avec Docker :
$ docker build -t firefox .
Enfin, un test d’exécution :
$ docker run --rm -it firefox Error: no display specified
Que se passe-t-il ?
L’erreur que nous rencontrons ici parlera à beaucoup de Linuxiens qui ont un jour tenter de démarrer une application graphique dans un serveur pourvu uniquement d’une console. Il n’y a pas d’interface graphique disponible, et toute la difficulté va consister à faire en sorte que notre Firefox dans notre container puisse utiliser le serveur graphique hôte de la machine.
En effet, tenter d’installer un serveur X dans le container Docker ne servira strictement à rien car il ne verra aucune carte graphique : nous ne sommes pas dans un système de virtualisation complet comme Virtualbox où tout le système est émulé mais dans un système containérisé qui partage les ressources du système hôte.
Partage du serveur X
Pour partager le serveur X, cela se passe en 2 étapes :
- Modification de notre Dockerfile afin que Firefox ne s’exécute pas en root mais avec un utilisateur ayant un uid = 1000
- Exécution du container avec les bons paramètres pour partager le serveur X de l’hôte
Adaptation de notre Dockerfile
Notre Dockerfile initial se voit maintenant ajouter 2 instructions :
FROM ubuntu:wily RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install firefox RUN useradd --create-home -u 1000 -g 1000 user USER user CMD ["/usr/bin/firefox"]
En effet, pour pouvoir partager le serveur X avec une application tournant dans un container Docker, celle-ci ne doit pas s’exécuter en root. Or l’utilisateur par défaut dans un container Docker est root. Pour remédier à ce problème :
- Nous ajoutons un nouvel utilisateur via la commande useradd –create-home -u 1000 user. Ici l’utilisateur s’appellera simplement « user », mais le nom importe peu car pour le serveur X de la machine hôte c’est l’UID/GID qui compte. Il doit être le même que celui de l’utilisateur connecté.
- Ensuite, nous demandons à Docker d’exécuter l’ensemble des commandes du Dockerfile avec l’utilisateur « user »
Un peu de détails et de tests :
Sous Linux (et Unix en général), chaque utilisateur a un UID (identifiant utilisateur numérique unique) et un GID. Par défaut, l’utilisateur non-root sous Debian/Ubuntu a la valeur 1000 pour le couple UID/GID. La commande id me permet de le savoir mon UID/GID :
$ id uid=1000(vdavy) gid=1000(vdavy)
Ayant un UID/GID de 1000, le serveur X du container Docker pourra s’y connecter. Si l’uid/gid n’est pas 1000, il faut modifier les valeurs dans le Dockerfile.
Enfin, testons dans le container Docker si l’UID/GID est identique à celui de notre PC hôte :
$ docker run --rm -it firefox id uid=1000(user) gid=1000(user) groups=1000(user)
Cela est correct. Nous pouvons passer à l’exécution du container.
Exécution de l’image dans un container Docker
Notre image est prête à être lancée dans un container. Encore faut-il que ce dernier ait les bons paramètres :
$ docker run --rm -it -v /tmp/.X11-unix:/tmp/.X11-unix:ro -v ~/.Xauthority:/home/user/.Xauthority:ro -e DISPLAY firefox
Le détail des paramètres :
Nous partageons les éléments nécessaires avec le container pour que ce dernier puisque venir se connecter au serveur X local :
- -v /tmp/.X11-unix:/tmp/.X11-unix:ro : ici, nous partageons via un volume la socket de connexion au serveur X local – cela permet à l’application graphique d’afficher sa fenêtre. En spécifiant l’option « ro » à la fin, le partage est accessible uniquement en lecture seule.
- -v ~/.Xauthority:/home/user/.Xauthority:ro : le serveur X n’autorise pas n’importe quelle application à venir s’y connecter : le partage du fichier .Xautority dans le répertoire personnel de l’utilisateur permet de donner cette autorisation
- -e DISPLAY : Firefox cherche au travers de cette variable d’environnement le nom de l’écran et du serveur X auquel se connecter
Ainsi, Firefox peut tourner dans un container Docker.
Un peu plus loin avec docker-compose
Le problème est que la ligne de commande pour démarrer mon container est vraiment longue. Un outil nommé « docker-compose » permet de s’affranchir de cette étape fastidieuse en décrivant les paramètres de lancement d’un (ou plusieurs) container dans un fichier YML.
En utilisant ce fichier, il est très simple de démarrer notre container :
firefox: build: . volumes: - /tmp/.X11-unix:/tmp/.X11-unix:ro - ~/.Xauthority:/home/user/.Xauthority:ro environment: - DISPLAY
Il suffit de créer 2 fichiers dans le même répertoire :
- Dockerfile
- docker-compose.yml
Ensuite, docker-compose up et le tour est joué.
Référence
Docker :
Docker-compose :
Bonus :
Atom.io est un éditeur open-source codé par Github permettant d’éditer bon nombre de fichier texte et y compris les fichiers YML et Dockerfile. Il est à télécharger ici.