TUTOS.EU

Docker : introduction et prise en main


Le but ici est de prendre en main docker.

Pour cela j'ai installé une Ubuntu version server sous Virtual Box.

Comme indiqué sur https://docs.docker.com/engine/install/ubuntu/, il faut installer une de ces versions de Ubuntu :

  • Ubuntu Focal 20.04 (LTS)
  • Ubuntu Bionic 18.04 (LTS)
  • Ubuntu Xenial 16.04 (LTS)

 

Ensuite, j'ai suivi les tutos vidéo dédiés de Xavki que l'on trouve sur sa playlist, cad https://www.youtube.com/playlist?list=PLn6POgpklwWq0iz59-px2z-qjDdZKEvWd

Donc dans une console sous Linux, installer Docker avec

sudo apt-get install docker.io
Lien vers le fichier : cliquez ici

Quand Docker est installé, cette commande donne une liste de commandes possibles

docker
Lien vers le fichier : cliquez ici

On peut valider que l'installation fonctionne correctement avec

sudo docker run hello-world
Lien vers le fichier : cliquez ici

Docket va chercher le conteneur hello-world et va l'installer si nécessaire

Lister les conteneurs se fait avec la commande suivante.
Le 'a' veut dire all, cad que l'on affichera tout.

docker ps -a
Lien vers le fichier : cliquez ici

Commande Utilité
docker image ls Lister les images existantes
docker ps Lister tous les conteneurs démarrés
docker ps -a Lister les conteneurs (a pour all)
docker run alpine:latest Lancer ou créer le conteneur alpine
docker run -di --name alpinetest alpine:latest Lancer ou créer le conteneur alpine, le nommer alpinetest et le laisser fonctionner (mode Detach Interactive)
docker run -di --name alpinetest alpine:latest sleep infinity Comme au dessus mais en plus on conserve un process qui tourne sur le conteneur
docker run -tid -p 8080:80 --name monnginx nginx:latest Lancer le conteneur nginx, le nommer monnginx, le laisser fonctionner. Rediriger son port 80 sur le 8080 de notre machine locale
docker run -tid --name conteneur2 --link conteneur1 alpine Lancer un conteneur alpine que l'on nomme conteneur2, le linker avec conteneur1 (conteneur2 pourra pinguer conteneur1)
docker exec -ti monnginx sh Se connecter au conteneur nommé monnginx avec le shell et le bach
docker stop <nom du conteneur> Arrêter un conteneur
docker container kill $(docker ps -q) Tuer tous les conteneurs
docker start <nom du conteneur> Démarrer un conteneur
docker restart <nom du conteneur> Redémarrer un conteneur
docker rm -f <nom du conteneur> Supprimer un conteneur
docker rm $(docker ps -a -q) Supprimer tous les conteneurs
docker inspect <nom du conteneur> Obtenir les infos sur un conteneur
docker inspect -f "{{.NetworkSettings.IPAddress}}" <nom du conteneur> Obtenir l'ip d'un conteneur
--no-cache Demande à ne pas utiliser le cache. Exemple : RUN apk --no-cache vim. Attention toutes les commandes après seront sans le cache

Par défaut le docker hub est installé.
On peut voir les packages dispos via https://hub.docker.com

On peut commencer à jouer en installant un conteneur minimaliste nommé Alpine.
La commande pour le lancer est donnée sur le site.

Donc la commande est

 docker pull alpine
Lien vers le fichier : cliquez ici

Ceci fait, lancez le avec

 docker run alpine:latest
Lien vers le fichier : cliquez ici

Si vous listez les conteneurs, vous verrez qu'Alpine s'est lancé et qu'il s'est arrêté tout de suite car il n'avait rien à faire.

docker ps -a
Lien vers le fichier : cliquez ici

On voit que son état est Exited

Si on avait lancé le conteneur Alpine en ajoutant la commande di pour Detach Interractive, alors il aurait continué de fonctionner.
Notez qu'on peut nommer le conteneur, ici avec alpinetest, pour éviter que Docker ne choisisse un nom par défaut.

docker run -di --name alpinetest alpine:latest
Lien vers le fichier : cliquez ici

Là si on refait un docker ps, on voit qu'il est lancé et qu'il tourne

Pour s'y connecter, utiliser docker exec avec les options tid suivi du nom du conteneur et de sh pour avoir le shell et le bach

docker exec -tid alpinetest sh
Lien vers le fichier : cliquez ici

Ensuite si on execute la commande ps, on voit les process qui tournent dans ce conteneur

ps
Lien vers le fichier : cliquez ici

Pour arrêter un conteneur, utilise la commande

docker stop <nom du conteneur>
Lien vers le fichier : cliquez ici

Et pour le supprimer :
Le 'f' permet de forcer, cad qu'il sera arrêté si nécessaire.

docker rm -f <nom du conteneur>
Lien vers le fichier : cliquez ici

Autre exemple où on lance un conteneur nginx en redirigeant son port 80 sur le 8080 de notre machine locale

docker run -tid -p 8080:80 --name monnginx nginx:latest
Lien vers le fichier : cliquez ici

Un docker inspect d'un conteneur permet d'afficher toutes les infos sur ce dernier.
Exemple

docker inspect monnginx
Lien vers le fichier : cliquez ici

Par exemple on voit ici l'ip du conteneur

Une remarque sur docker inspect, c'est qu'on peut filtrer le contenu qui serait du json si j'en crois https://youtu.be/RX96EugUNDk?t=211

Pour récupérer uniquement l'ip, on pourrait pu faire

docker inspect -f "{{.NetworkSettings.IPAddress}}" monnginx
Lien vers le fichier : cliquez ici

Pour lancer un conteneur tout en présentant un volume persistant, on peut utiliser l'option -v (on verra juste après qu'il y a mieux).
Le répertoire local /srv/dockerdata/nginx de notre machine permettra d'héberger les données du répertoire /usr/share/nginx/html du conteneur

Attention du coup comme /srv/dockerdata/nginx est vide, et bien /usr/share/nginx/html aussi, et il faudrait créer un index.php par exemple dans /srv/dockerdata/nginx

docker rm -f monnginx
mkdir -p /srv/dockerdata/nginx
chmod o+rwx /srv/dockerdata/nginx
docker run -tid -p 80:80 -v /srv/dockerdata/nginx/:/usr/share/nginx/html/ --name monnginx nginx:latest
Lien vers le fichier : cliquez ici

De mon côté, dans un premier temps, la commande a généré une erreur du type

docker: Error response from daemon: error while creating mount source path '/srv/dockerdata/nginx': mkdir /srv/dockerdata: read-only file system.

 

Je l'ai résolu en mettant à jour Docker

Voici l'erreur que j'ai eu

Notez que si vous n'avez pas la commande ps de disponible quand vous être connecté à un conteneur,
vous pouvez l'obtenir en tapant

apt-get update && apt-get install -y procps
Lien vers le fichier : cliquez ici

Maintenant, au lieu d'utiliser l'option v pour utiliser un volume persistant, il y a

docker volume
Lien vers le fichier : cliquez ici

Pour créer un volume de base nommé monvolume, taper

docker volume create monvolume
Lien vers le fichier : cliquez ici

Et pour lancer un conteneur qui l'utilise, taper

docker rm -f monnginx
docker run -tid -p 80:80 --mount source=monvolume,target=/usr/share/nginx/html --name monnginx nginx:latest
Lien vers le fichier : cliquez ici

Maintenant qu'un conteneur utilise ce volume, si on inspecte le volume

docker volume inspect monvolume
Lien vers le fichier : cliquez ici

On voit qu'il pointe localement sur

/var/snap/docker/common/var-lib-docker/volumes/monvolume/_data

Et si on se rend dans ce répertoire, on retrouve les fichiers utilisés par le conteneur


On va utiliser le conteneur alpine, nommé ici alpinetest, pour montrer qu'on peut passer une variable d'environnement en paramètre.
On utilise pour cela l'option --env

Exemple :

docker rm -f alpinetest
docker run -tid --name alpinetest --env MYVAR="pouet" alpine
docker exec -ti alpinetest sh
Lien vers le fichier : cliquez ici

Une fois connecté au conteneur, on voit avec la commande ENV que notre variable MYVAR a bien été passée

On peut aussi passer les différentes variables via un fichier.
On commence pour cela par créer un fichier nommé ici mesvars.txt

cd
nano mesvars.txt
Lien vers le fichier : cliquez ici

On y place nos variables

Puis on peut détruire et relancer le conteneur en lui disant de lire les variables contenues dans le fichier

docker rm -f alpinetest
docker run -tid --name alpinetest --env-file mesvars.txt alpine
docker exec -ti alpinetest sh
Lien vers le fichier : cliquez ici

Si vous voulez sauvegarder l'état d'un conteneur, vous pouvez.
Pour l'exemple ci-dessous on va créer un conteneur nommé myubuntu qui contient la dernière version de Ubuntu.

Ensuite on s'y connecte, on installe nano qui n'y est pas de base et on quitte le conteneur.

docker rm -f myubuntu
docker run -tid --name myubuntu ubuntu:latest
docker exec -ti myubuntu sh
apt-get update
apt-get install nano
exit
Lien vers le fichier : cliquez ici

Afficher les conteneurs qui tournent avec

docker ps
Lien vers le fichier : cliquez ici

Relever le container id du conteneur que vous venez de modifier

Sauvegardez le avec la commande docker commit.
Respectez pour cela ce formalisme :

docker commit -m "Mon commentaire" <container id> nom:version
Lien vers le fichier : cliquez ici

Exemple

On peut ensuite afficher les images qui existent avec

docker image ls
Lien vers le fichier : cliquez ici

On retrouve bien l'image qu'on vient de créer

A noter qu'avec la commande docker diff, on peut voir tout ce qui a été modifié sur un conteneur depuis son téléchargement.
Avec notre image nommée test, cela donne donc

docker diff test
Lien vers le fichier : cliquez ici

On peut maintenant lancer un conteneur en utilisant cette image.
Pour cela il faut indiquer son nom et son tag/version

Dans notre exemple cela donne

docker run -tid --name test myubuntu:V1.0
docker exec -ti test sh
Lien vers le fichier : cliquez ici

On voit qu'on s'est bien connecté sur ce conteneur généré à partir d'une image de Ubuntu que l'on avait sauvegardé, image à laquelle on avait ajouté nano, et qui est toujours présent

On peut effacer l'image qu'on vient de créer.
Pour cela il faut supprimer dans un premier temps les conteneurs qu'on a généré avec cette image, puis supprimer l'image.

A noter que dans notre cas on avait nommé le conteneur test, donc les commandes seront :

docker rm -f test
docker image rm -f myubuntu:V1.0
Lien vers le fichier : cliquez ici

Pour effacer une image avec son id, taper

docker image rm <image id>
Lien vers le fichier : cliquez ici

Pour avoir la séquence de commandes qui ont été exécutées pour créer une image, taper

docker history <container id>
Lien vers le fichier : cliquez ici

On peut aussi créer une image avec docker file.
C'est un fichier de configuration qui permet de créer une image avec une séquence de commandes.

Exemple :

cd
nano dockerfile
Lien vers le fichier : cliquez ici

Y coller ce contenu.
Il indique de faire une image avec la dernière ubuntu,
dire que la personne en charge est roberto,
faire un apt-get update,
installe nano et git
faire un clean et effacer des répertoires.

FROM ubuntu:latest
MAINTAINER roberto
RUN apt-get update \
&& apt-get install -y nano git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
Lien vers le fichier : cliquez ici

De là on peut créer une image se basant sur le dockerfile qu'on vient de créer avec la commande docker build.
Le point à la fin symbolise le dockerfile

docker build -t image:version .
Lien vers le fichier : cliquez ici

Exemple de build. Vous remarquerez qu'on donne ici un numéro de version à l'image avec ici V1.0 :

docker build -t imagetest:v1.0 .
Lien vers le fichier : cliquez ici

Ce qui donne visuellement

docker image ls confirme que notre image de test est bien créée

Pour indiquer à cette image V1.0 que c'est la dernière version en date (latest), on peut lui ajouter un tag.

docker tag imagetest:v1.0 imagetest:latest
Lien vers le fichier : cliquez ici

On peut sortir l'historique de toutes les commandes qui ont permises de créer une image avec docker history

Exemple :

docker history imagetest:v1.0
Lien vers le fichier : cliquez ici

Il existe d'autres commandes pour dockerfile avec par exemple

Commande Utilité
env Variables d'environnement
expose 80 Exposition de ports, ici le 80
volume pour créer des volumes
workdir /app Définir le répertoire /app comme répertoire de travail, pour un copy par exemple
copy . /app pour copier des fichiers présents dans le répertoire courant du host dans le répertoire /app du conteneur
entrypoint Définition du processus maître

Comme indiqué sur cette vidéo de xavki, par défaut les conteneurs tournent avec le compte root de l'hôte.
Donc les process ou les montages fonctionnent en tant que root, et çà ce n'est pas top niveau sécurité.

 

Pour éviter cela on utilise le remap de user qui prévu de base dans Docker.

Les opérations à réaliser sont reprises dans le script de xavki, cad https://bit.ly/2Bj6GmE.
Dans ce script, deux groupes/comptes sont créés avec un shell nul pour plus de sécurité (/bin/false). 
Sur ces 2 comptes, dockremap sera utilisé par docker.
On force l'utilisation d'un uid élevé, en dehors des uid classiques, pour qu'un attaquant n'ai aucune chance d'avoir des droits autre que celui de cet uid élevé.

 

Ensuite la commande dockremap sera ajouté au fichier subuid et subgid.
A ce niveau la commande dockremap:500000:65536 fait qu'un fichier appartenant à l'utilisateur 6 dans le conteneur appartiendra à l'utilisateur 500006 sur la machine hôte.
N'importe quel utilisateur de la machine hôte qui fait un docker exec sera "root" dans le  conteneur via le user dockremap.

 

Enfin dans la configuration de docker, le fichier /etc/docker/daemon.json sera édité
Docker l'utilise quand il est renseigné.
On va y indiquer qu'il faut utiliser le remap de user par défaut.

 

La documentation officielle du remap est sur https://docs.docker.com/engine/security/userns-remap/#enable-userns-remap-on-the-daemon.

#!/bin/bash

###############################################################
#  AUTEUR:   Xavier
#
#  DESCRIPTION:  création d'un user spécific pour docker
###############################################################


groupadd -g 500000 dockremap && 
groupadd -g 501000 dockremap-user && 
useradd -u 500000 -g dockremap -s /bin/false dockremap && 
useradd -u 501000 -g dockremap-user -s /bin/false dockremap-user

echo "dockremap:500000:65536" >> /etc/subuid && 
echo "dockremap:500000:65536" >>/etc/subgid

echo "
  {
   \"userns-remap\": \"default\"
  }
" > /etc/docker/daemon.json

systemctl daemon-reload && systemctl restart docker
Lien vers le fichier : cliquez ici