Ce tutoriel a pour but de découvrir la solution de conteneur Docker et son écosystème.

Les fichiers utilisés dans ce tutoriel sont disponibles dans le dépot git suivant : https://gitlab.in2p3.fr/cavet/tp-docker-obs

Pour récupérer les fichiers vous pouvez cloner le dépot git :

$ git clone https://gitlab.in2p3.fr/cavet/tp-docker-obs.git

Dans ce tutoriel, nous allons utiliser différents OS pour les exécuter les conteneurs : CentOS 7, Ubuntu, Alpine…​

Installation de Docker

Utilisation de Docker version 19.03.5-ce, disponible ici : Docker Website.

$ yum install -y yum-utils device-mapper-persistent-data lvm2
$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ yum install -y docker-ce docker-ce-cli containerd.io
$ systemctl start docker
$ apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ apt-get install docker-ce docker-ce-cli containerd.io
  • Docker Compose (v1.24.1) :

$ curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
$ ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
  • Docker Machine (v0.16.2) :

$ curl -L https://github.com/docker/machine/releases/download/v0.16.2/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine &&
    chmod +x /tmp/docker-machine &&
    sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
 $ ln -s /usr/local/bin/docker-machine /usr/bin/docker-machine

Pour tester :

$ docker version
$ docker-compose version
$ docker-machine version

L’aide de Docker est accessible via la commande suivante :

$ docker help

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
      --config string      Location of client config files (default "/root/.docker")
  -c, --context string     Name of the context to use to connect to the daemon (overrides DOCKER_HOST env
                           var and default context set with "docker context use")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket(s) to connect to
  -l, --log-level string   Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default "/root/.docker/ca.pem")
      --tlscert string     Path to TLS certificate file (default "/root/.docker/cert.pem")
      --tlskey string      Path to TLS key file (default "/root/.docker/key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

Management Commands:
  builder     Manage builds
  config      Manage Docker configs
  container   Manage containers
  context     Manage contexts
  engine      Manage the docker engine
  image       Manage images
  network     Manage networks
  node        Manage Swarm nodes
  plugin      Manage plugins
  secret      Manage Docker secrets
  service     Manage services
  stack       Manage Docker stacks
  swarm       Manage Swarm
  system      Manage Docker
  trust       Manage trust on Docker images
  volume      Manage volumes

Commands:
  attach      Attach local standard input, output, and error streams to a running container
  build       Build an image from a Dockerfile
  commit      Create a new image from a container's changes
  cp          Copy files/folders between a container and the local filesystem
  create      Create a new container
  diff        Inspect changes to files or directories on a container's filesystem
  events      Get real time events from the server
  exec        Run a command in a running container
  export      Export a container's filesystem as a tar archive
  history     Show the history of an image
  images      List images
  import      Import the contents from a tarball to create a filesystem image
  info        Display system-wide information
  inspect     Return low-level information on Docker objects
  kill        Kill one or more running containers
  load        Load an image from a tar archive or STDIN
  login       Log in to a Docker registry
  logout      Log out from a Docker registry
  logs        Fetch the logs of a container
  pause       Pause all processes within one or more containers
  port        List port mappings or a specific mapping for the container
  ps          List containers
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rename      Rename a container
  restart     Restart one or more containers
  rm          Remove one or more containers
  rmi         Remove one or more images
  run         Run a command in a new container
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  search      Search the Docker Hub for images
  start       Start one or more stopped containers
  stats       Display a live stream of container(s) resource usage statistics
  stop        Stop one or more running containers
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
  top         Display the running processes of a container
  unpause     Unpause all processes within one or more containers
  update      Update configuration of one or more containers
  version     Show the Docker version information
  wait        Block until one or more containers stop, then print their exit codes

Run 'docker COMMAND --help' for more information on a command.

Instructions globales pour le tutoriel

  • root@id : super utilisateur dans le conteneur avec l’identifiant id = CONTAINER ID = bb9720…​

  • Toutes les instructions (noms, commandes) sont en minuscule sauf le Dockerfile.

  • Commande : précédé d’un $.

  • Output d’une commande : sans $.

  • Instruction sur la ligne suivante : \.

  • Attention au chemin local : $ pwd.

Mes premiers conteneurs

Exécution du premier conteneur

Lancer un conteneur basique avec le client Docker :

  • Récupérer la dernière version de l’image CentOS depuis le Docker Hub (Docker Store) et l’éxécuter en ouvrant un shell bash.

  • Options : i - STDIN open, t - pseudo-tty terminal, rm - remove container after use.

Terminal #1 :
$ docker pull centos:latest
$ docker run -it --name mycontainer --rm centos
[root@id /]$  cat /etc/redhat-release
CentOS Linux release 8.0.1905 (Core)
[root@id /]$ yum install -y nano
[root@id /]$ exit
Terminal #2 :
$ docker ps
$ docker images
$ docker logs mycontainer
$ docker inspect mycontainer
$ docker ps -a
Le client Docker permet de lancer des conteneurs de deux manières :
$ docker run ...
$ docker container run ...

Quelques commandes pour gérer le système, nettoyer les conteneurs et les images

Ne pas supprimer l’image Jupyter avec : $ docker rmi $(docker images -q)
 $ docker system df
 $ docker system prune
 $ docker rm -f id
 $ docker rm -f $(docker ps -a -q)
 $ docker rmi id
 $ docker rmi $(docker images -q)

Exécution du second conteneur

Lancer un conteneur avec Python :

  • Récupérer et lancer un conteneur utilisant une image avec le shell Python pré-installé puis le même conteneur en ouvrant le shell bash.

  • Choisir le tag de l’image : 2.7 ou 3.7.

Terminal #1 :
$ export tag=3.7
$ docker run -it --name mycontainer --rm python:$tag
Python XXX
>>> print('Hello Python')
Hello Python
>>> exit()

$ docker run -it --name mycontainer --rm python:$tag /bin/bash
[root@id /]$ python
Python XXX
Terminal #2 :
$ docker ps -a

Mes premières applications

On distingue deux types d’applications : micro-service ou application scientifique.

Exécution de la première application

Lancer une application micro-service permettant d’exécuter des Notebooks Python :

  • Récupérer l’image Jupyter Scipy Notebook qui est basée sur Ubuntu et qui contient le Jupyter Notebook serveur, Scipy et JupyterLab (vous pouvez aussi utiliser une image de taille plus petite, la Jupyter Base Notebook : base-notebook). Attention : la taille compressée de Jupyter Scipy Notebook est de 2 GB (4,39 GB décompréssée), le temps de téléchargement avoisine ∼10min.

  • Lancer l’image en mode détaché et avec la publication des ports réseaux (association des ports conteneur/hote).

  • Options : d - detach processus, p - network port.

Terminal #1 :
$ docker pull jupyter/scipy-notebook
 Already exists
 Download complete
 Extracting
 Pull complete
$ docker run -d --name myjupyter -p 8888:8888 jupyter/scipy-notebook
$ docker ps

Se connecter à l’interface Jupyter :

  • Vérifier que l’utilisateur est jovyan et récupérer son jeton de connexion.

Terminal #1 :
$ docker logs myjupyter
http://127.0.0.1:8888/?token=...
$ docker exec myjupyter jupyter notebook list
$ docker exec myjupyter pwd
 /home/jovyan
Navigateur :
http://127.0.0.1:8888/?token=...

Copier un fichier stocké localement dans le conteneur pour l’exécuter :

  • Utiliser un Notebook Python ou récupérer celui du détecteur d’ondes gravitationnelles LIGO, mettre le Notebook dans un répertoire local et le copier dans le conteneur :

Terminal #1 :
$ mkdir local_work && cd local_work
$ wget https://losc.ligo.org/s/events/LOSC_Event_tutorial.ipynb
$ docker cp LOSC_Event_tutorial.ipynb myjupyter:/home/jovyan/work
Navigateur :
> Run LOSC_Event_tutorial.ipynb
  ModuleNotFoundError: No module named 'readligo'

Exécution de la deuxième application

Lancer une deuxième instance du serveur Jupyter afin d’exécuter le Notebook permettant d’analyser les données d’un évènement de LIGO :

  • Récupérer l’ensembles des données de LIGO.

  • Exporter le chemin local dans une variable d’environnement.

  • Changer le port du conteneur redirigé localement.

  • Monter le répertoire local local_work dans le conteneur.

  • Options : v - mount volume.

Terminal #1 :
$ wget https://losc.ligo.org/s/events/LOSC_Event_tutorial.zip && unzip LOSC_Event_tutorial.zip
$ export user_path=`pwd`
$ docker run -d --name myjupyter2 -p 8889:8888 -v $user_path/LOSC_Event_tutorial:/home/jovyan/work jupyter/scipy-notebook
Navigateur :
> Run LOSC_Event_tutorial.ipynb

Tuer les deux instances Jupyter.

Création d’images

Création de la première image

Construire l’image d’une application echo et la lancer dans un conteneur :

  • Copier le Dockerfile (recette de construction d’image) et le fichier .dockerignore (sélectionne les fichiers copiés dans le conteneur lors du build) dans un répertoire local_build.

  • Construire une image de l’application et la lancer.

  • Options : t - tag name

Dockerfile :
FROM centos:7.7.1908
MAINTAINER Cecile Cavet "ccavet@apc.in2p3.fr"

RUN yum install -y nano

ENTRYPOINT ["echo"]
CMD ["Le runscript est la commande par défaut du conteneur !"]
dockerignore :
#.dockerignore
.DS_Store
.git
Dockerfile*
Terminal #1 :
$ mkdir local_build && cd local_build
$ ls -a
 .dockerignore    Dockerfile
$ docker build -t myapp .
$ docker images
myapp      latest              208c47af0787        3 days ago          918MB
$ docker run myapp
 Le runscript est ...
$ docker run myapp hello world
 hello world

Création de la deuxième image

Construire une image d’une application scientifique en installant des paquets spécifiques et la lancer dans un conteneur :

  • Copier le Dockerfile dans un fichier Dockerfile.app dans le répertoire local_build.

  • Copier les fichiers requirements.txt et LOSC_Event_tutorial.py dans le répertoire local_build.

  • Modifier le Dockerfile.app pour qu’il puisse exécuter le script Python LOSC_Event_tutorial.py (modifier/rajouter les instructions COPY, ENTRYPOINT et COMMAND).

  • Construire une image de l’application et la lancer avec le volume des données LIGO monté dans le conteneur.

requirements.txt :
numpy==1.17.2
scipy==1.3.1
h5py==2.9.0
simplejson==3.16.0
matplotlib==3.1.1
ipython==7.8.0
ipython-genutils==0.2.0
Dockerfile.app :
WORKDIR /app
COPY . /app/

RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["python"]
CMD ["LOSC_Event_tutorial.py"]
Terminal #1 :
$ cp ../local_work/LOSC_Event_tutorial/LOSC_Event_tutorial.py .
$ docker build -t myapp -f Dockerfile.app .
$ docker images
myapp        latest              208c47af0787        3 days ago          1.15GB
$ docker run -v $user_path/LOSC_Event_tutorial:/app myapp
$ ls -lrt ../local_work/LOSC_Event_tutorial
GW150914_strain.png ...

Gestion des images

Docker Hub

Explorer le Docker Hub (Store, https://hub.docker.com) dans le but de trouver l’image Python utilisée précédemment et son Dockerfile.

Docker Registry

Mettre en place un registre local et pousser une image dans ce gestionnaire d’image :

  • Récupérer l’image Registry et lancer le service correspondant.

  • Tagger l’image myapp et la pousser sur le registre.

  • Options : restart always - always restart container in case of failure.

Terminal #1 :
$ docker run -d -p 5000:5000 --restart always --name registry registry:2
$ docker tag myapp localhost:5000/myapp
$ docker images
localhost:5000/myapp   latest     88280d4a85ec        9 days ago          1.15GB
$ docker push localhost:5000/myapp
$ docker exec registry ls /var/lib/registry/docker/registry/v2/repositories

GitLab-CI

Réutiliser le projet en Python myapp et utiliser l’intégration continue pour permettre la construction automatique d’une image Docker accessible via le registre GitLab :

  • Copier le fichier d’intégration continue .gitlab-ci.yaml.example dans local_build/.gitlab-ci.yaml

  • Pousser le contenu du répertoire local_build dans un projet de GitLab.

  • Vérifier l’éxécution du pipeline de CI sur l’interface graphique de GitLab (/projet/pipelines).

  • Vérifier la construction de l’image Docker sur l’interface graphique de GitLab (/projet/container_registry).

gitlab-ci.yaml.example :
variables:
  DOCKER_DRIVER: overlay2

stages:
  - build

build:
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  stage: build
  script:
    - export IMAGE_TAG=$(echo -en $CI_BUILD_REF_NAME | tr -c '[:alnum:]_.-' '-')
    - docker build --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" .
    - docker push "$CI_REGISTRY_IMAGE:$IMAGE_TAG"
    - echo 'pushed on registry'
Terminal #1 :
$ ls -la
.dockerignore .gitlab-ci.yml Dockerfile LOSC_Event_tutorial.py requirements.txt
$ git init
$ git remote add origin git@gitlab.fr:projet/myapp.git
$ git add .
$ git commit -m "Initial commit"
$ git push -u origin master

Utiliser la nouvelle image Docker de votre projet qui a été construite automatiquement :

Terminal #1 :
$ docker login https://gitlab-registry.fr
$ cat ~/.docker/config.json
$ docker pull gitlab-registry.fr/user/projet:latest
$ docker run -v $user_path/LOSC_Event_tutorial:/app gitlab-registry.fr/user/projet:latest

Déploiement de conteneurs avec Docker Compose

Permet de lancer plusieurs conteneurs en interaction avec une seule commande.

Ma première application composée

Lancer une application de micro-service via Docker Compose :

  • Copier le fichier docker-compose.yml qui permet de lancer un seul service (le Jupyter serveur).

  • Exporter la variable d’environnement LOCAL_PATH.

  • Exécuter le fichier YAML avec Docker Compose.

  • Options : le mot clé version 3 correspond à la dernière version de Docker Compose compatible avec la version 2 et Docker Swarm.

docker-compose.yml
version: "3"
services:
  jupyter:
    image: jupyter/scipy-notebook
    container_name: jupyter
    volumes:
      - $LOCAL_PATH:/home/jovyan/work/local
    ports:
      - "8888:8888"

volumes:
  workspace:
Terminal #1 :
$ export LOCAL_PATH=`pwd`
$ docker-compose up -d
Creating jupyter ... done
 $ docker-compose ps
   Name     Command  State    Ports
 ------------------------------------------------
 jupyter    tini...   Up  0.0.0.0:8888->8888/tcp
$ docker-compose logs jupyter
$ docker-compose exec jupyter
$ docker-compose down

Création de Machines Virtuelles avec Docker Machine

Permet de créer des MV locales ou sur le cloud avec Docker installé et configuré.

Lancer une MV via Docker Machine :

  • en local si vous avez VirtualBox installé.

  • sur une infrastructure de cloud computing (OpenStack) si vous avez un compte sur ce type de ressources. Il est nécessaire de sourcer d’abord les identifiants du compte.

VirtualBox :
$ docker-machine create --driver virtualbox --virtualbox-memory 2048 --virtualbox-cpu-count "2" --virtualbox-disk-size "2000" default
$ docker-machine ls
$ docker-machine ssh default
$ docker-machine rm -f default
OpenStack :
$ source ~/.novacreds/novarc.sh
$ docker-machine create --driver openstack --openstack-flavor-name "m1.small" --openstack-image-name centos-7 --openstack-domain-name default --openstack-net-name cloud-net --openstack-floatingip-pool ext-net --openstack-keypair-name cloudkey --openstack-private-key-file ~/.novacreds/cloudkey --openstack-ssh-user centos test