Docker
Les notes de cettes pages sont prises à partir de la documentation de docker ;: https://docs.docker.com/get-started/ ;
Concernant le débuguage https://vsupalov.com/debug-docker-container/
Memento - Pull une image, lancer un conteneur et utiliser sa ligne de commande
[justine@localhost ~]$ docker pull robertdebock/docker-ansible-2.4 [justine@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 8 months ago 1.84kB robertdebock/docker-ansible-2.4 latest 706707922ec6 21 months ago 119MB [justine@localhost ~]$ docker run -i -t robertdebock/docker-ansible-2.4 #Je suis alors envoyée "dans" le conteneur. On peut ajouter -d pour le lancer en arrière plan. [justine@localhost ~]$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b4689ee4a632 robertdebock/docker-ansible-2.4 "/bin/sh" 58 seconds ago Up 57 seconds practical_swanson [justine@localhost ~]$ docker exec -it b4689ee4a632 /bin/bash
Sur Ubuntu 18.10 et dans mon cas, il faut pas utiliser le snap de docker mais ;: sudo apt install docker
Puis aller sur https://github.com/docker/machine/releases/, télécharger docker-machine, puis:
- faire un chmod +x sur le fichier
- Le copier dans /snap/bin ;:
sudo cp docker-machine-Linux-x86_64 /snap/bin/docker-machine
;
;
Présentation de Docker
Docker est une plateforme qui permet de développer, déployer et faire tourner des applications en conteneurs. L'usage de conteneur s'appelle la conteneurisation. Les conteneurs sont:
- Flexibles ;: Tout peut être conteneurisé
- Légers
- Interchangeables
- Portables
- Scalables
- Empilables
Un conteneur est lancé en faisant tourner une image ;: une image est un exécutable qui inclut tout ce dont a besoin le programme, et le conteneur est l'environnement d'exécution qui permet à cette image de fonctionner. Une fois lancé le conteneur est un processus utilisateur, que l'on peut lister avec docker ps. Les conteneurs partagent entre eux les ressources système et le noyau Linux. ;
À la différence d'une VM, un conteneur peut tourner nativement sur n'importe quelle machine Linux; une même machine Linux peut utiliser plusieurs conteneurs, cette technologie est plus économe que les VM.
Préparer l'environnement Docker
On va commencer par installer Docker.
https://docs.docker.com/install/
Le versioning fonctionne sur le modèle ;: Année.Mois.Patch
Une fois installé, on peut tester avec docker --version
.
On a plus d'informations avec docker info:
justine@Justine-pc:~$ docker info
Containers: 0
;Running: 0
;Paused: 0
;Stopped: 0
Images: 1
Server Version: 18.09.0
Storage Driver: overlay2
;Backing Filesystem: extfs
;Supports d_type: true
;Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
;Volume: local
;Network: bridge host macvlan null overlay
;Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: d6de12e2f362cb9dc49ad957911996d3de59b338
runc version: 4fc53a81fb7c994640722ac585fa9ca548971871
init version: fec3683
Security Options:
;apparmor
;seccomp
; Profile: default
Kernel Version: 4.18.0-10-generic
Operating System: Ubuntu 18.10
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.59GiB
Name: ***
ID: EN3D:SYGK:2WYA:TZM5:MHRP:FBOH:GQVD:VFKW:SDG6:V3FW:XSKK:5MAI
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
;127.0.0.0/8
Live Restore Enabled: false
Product License: Community Engine
WARNING: No swap limit support
On peut ensuite ajouter son utilisateur au groupe docker:
sudo adduser user docker
;
Tester l'installation de Docker
Le test le plus simple consiste à faire tourner l'image hello-world ;:
justine@Justine-pc:~$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
;1. The Docker client contacted the Docker daemon.
;2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
; ; ; (amd64)
;3. The Docker daemon created a new container from that image which runs the
; ; ; executable that produces the output you are currently reading.
;4. The Docker daemon streamed that output to the Docker client, which sent it
; ; ; to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
;$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
;https://hub.docker.com/
For more examples and ideas, visit:
;https://docs.docker.com/get-started/
;
Hello-world est une image simple qui permet de valider l'installation.
On peut lister les images télechargées avec docker image ls
...et lister les conteneurs avec docker container ls --all
Récapitulatif des commandes issu de la documentation Docker :
List Docker CLI commands
docker
docker container --help
;
Display Docker version and info
docker --version
docker version
docker info
;
Execute Docker image
docker run hello-world
;
List Docker images
docker image ls
;
List Docker containers (running, all, all in quiet mode)
docker container ls
docker container ls --all
docker container ls -aq
;
;
Construire une app
Nous allons commencer en bas de la hiérarchie de Docker pour remonter. La hiérarchie Docker est constituée comme suit ;:
-Stack ;: Définit les interactions entre les services
-Services ;: Définit comment les containers se comportent en production
-Conteneur ;: On y est.
Classiquement, lorsque l'on construit une application en python par exemple, on commence par télécharger un environnement Python pour ensuite coder par dessus. Avec Docker, on peut se contenter de récupérer une image contenant une instance de Python sans installation. Ensuite notre build contiendra l'image de Python avec toutes ses dépendances, ainsi que notre application; tout sera "packagé" ensemble, et on sera sûrs que tout fonctionne. Cela évite d'avoir à prendre en compte des différences entre l'environnement de développement et celui de production.
Ces images portables sont définies par un fichier de configuration appellé Dockerfile.
;
Définir un container avec Dockerfile
Le Dockerfile définit tout ce qui va aller dans l'environnement à l'intérieur du conteneur; les ressources comme les interfaces réseau sont virtualisées dans cet environnement isolé, alors il va falloir mapper les ports vers le monde extérieur, et être spécifique quant aux fichiers que l'on veut copier dans l'environnement. Après ça, le conteneur devrait fonctionner partout.
On va commencer avec Python. Il faut créer un nouveau dossier, s'y rendre, créer un fichier appellé "Dockerfile" et y coller ce texte:
#Use an official Python runtime as a parent image FROM python:2.7-slim #Set the working directory to /app WORKDIR /app #Copy the current directory contents into the container at /app COPY . /app #Install any needed packages specified in requirements.txt RUN pip install --trusted-host pypi.python.org -r requirements.txt #Make port 80 available to the world outside this container EXPOSE 80 #Define environment variable ENV NAME World #Run app.py when the container launches CMD ["python", "app.py"]
Ce fichier fait référence à deux fichiers encore inexistants, app.py et requirements.txt. On va les créer.
;
;
;
L'application proprement dite
Dans le dossier lui-même, créer les deux fichiers.
Notre app sera alors complète: puisque que le Dockerfile contient la commande COPY, app.py et requirements.txt seront pris en compte à la création de l'image. La sortie de l'application sera dispo via http grâce au EXPOSE, qui permet d'accéder à la sortie depuis le "monde extérieur" sur le port 80.
requirements.txt:
Redis
app.py:
from flask import Flask
from redis import Redis, RedisError
import os
import socket
Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
; ; ; try:
; ; ; ; ; ; ; visits = redis.incr("counter")
; ; ; except RedisError:
; ; ; ; ; ; ; visits = "cannot connect to Redis, counter disabled" ; ; ; html = "
Hello {name}!
" \
; ; ; ; ; ; ; ; ; ; "Hostname: {hostname}
" \
; ; ; ; ; ; ; ; ; ; "Visits: {visits}"
; ; ; return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)if __name__ == "__main__":
; ; ; app.run(host='0.0.0.0', port=80)
;
;Ici le pip install -r requirements.txt
installe les bibliothèques Flask et Redis pour Python. L'application affiche aussi la variable d'environnement NAME et la sortie d'un appel à socket.gethostname(). Ici, Redis ne devrait pas fonctionner puisqu'on a fait qu'installer sa bibliothèque, et pas Redis lui-même.
NB ;: Redis est un système de BDD "anti-SQL".
NB2 ;: demander le hostname depuis un conteneur renvoie le container ID, qui est comme le PID d'un exécutable.
Construire l'application
On est prêts à construire notre application ;! Il faut s'assurer que l'on est dans notre dossier et qu'il contient bien nos trois fichiers (Dockerfile, requirements.txt et app.py). On va lancer la construction ;:
L'argument -t permet de taguer mon image pour qu'elle ait un nom reconnaissable (ici "montest"); ne pas oublier le . qui renvoie au dossier actuel.
Docker va alors exécuter le Dockerfile; installer requirements.txt dans l'image en partant de l'image Python, etc...
Une fois le processus terminé, mon dossier n'as pas changé; mais l'image est bien apparu dans ma liste d'images, ce que je peux voir avec un docker image ls.
;
;
Problèmes de proxy/dns:
Un Proxy peut être indiqué dans le dockerfile avec:
- Set proxy server, replace host:port with values for your servers
ENV http_proxy host:port
ENV https_proxy host:port
Un DNS peut être indiqué à Docker en modifiant/créant le fichier /etc/docker/daemon.json et en y mettant:
{
; "dns": ["your_dns_address", "8.8.8.8"]
}
Il faut ensuite sauvegarder le fichier et redémarrer le service docker.
Tester l'application
Notre image est créée, il est temps de créer ce conteneur et de tester ;!
il suffit d'un ;:
L'argument -p 4000:80 mappe le port 4000 de ma machine au port 80 dans le conteneur ;: j'ai alors accès à mon application en visitant l'url suivante ;: 127.0.0.1:4000 . ;
J'y vois la sortie de mon application. Ca marche aussi avec un curl http://localhost:4000
On peut aussi lancer l'application en arrière-plan (detached) ;:
J'ai alors l'identifiant de conteneur et je suis renvoyée au terminal ;: le conteneur est en arrière-plan. Je le vois avec docker container ls ;: je vois que le container ID (en abrégé) correspond à ce que renvoie l'app.
Je peux stopper le conteneur avec ;:
Partager l'image
On peut uploader cette image et la faire tourner ailleurs.
En effet il va être nécessaire de savoir comment push des registres lors de la mise en prod. Un registre est une collection de dépôts, et un dépôt est une collection d'images. Un peu comme Github, mais le code est déjà construit. On peut créer plusieurs dépôts sur un registre avec un seul compte; la ligne de commande docker utilise le registre public de docker par défaut. Il en existe d'autres.
On peut même créer son propre registre ;!
Se logger
Il va falloir se logger avec son compte docker (qu'on peut créer sur hub.docker.com) et noter son identifiant.
Une fois que c'est fait, je peux me connecter sur le site et créer un repo.
Je peux ensuite logger via le terminal avec docker login
.
Tagger l'image
La notation pour associer une image a un dépôt est nomutilisateur/repo:tag.
Le tag est optionnel mais recommandé, car c'est c'est le mécanisme utilisé par DOcker pour donner des numéros de versions. Il faut donc que les noms aient du sens.
Je vais donc faire ici ;:
docker tag montest justinep/monrepotest:letest #montest est l'image, justinep mon identifiant Docker, letest est le tag que je donne.
Un docker image ls
me permet de voir mon image tagguée.
Publier l'image
Je peux uploader l'image avec ;:
Récupérer l'image depuis le dépôt et la faire tourner (Pull and Run)
Désormais, je peux faire un ;:
pour faire tourner l'image ;: si elle n'est pas sur ma machine locale, elle est téléchargée et lancée.
Peu importe où je suis, Docker récupère l'image avec Python et tout le reste, et fait tourner le code; tout ça dans un paquet bien ficelé, sans qu'il n'y ai rien de plus à installer.
Les Services
Nous allons scaler nos applications et active le load balancing. Pour cela, On va monter d'un niveau dans la hiérarchie pour voir les services.
Dans une application distribuée, les différentes parties de l'application sont appellées "services". Par exemple, si l'on prend un site web de streaming vidéo, l'un des services servirait à à stocker les données dans une BDD; un autre à encoder les vidéos; etc.
Les services ne sont finalement que des conteneurs en prod. Un service fait tourner une image, mais il codify la façon dont elle tourne ;: quels ports utiliser, combien de répliques du conteneur, etc. Le scaling d'un service change le nombre d'instances d'un conteneur seront lancées pour un même logiciel, afin d'assigner plus de ressources matérielles au processus. Les services peuvent être définis, lancés et scalés grâce au fichier docker-compose.yaml.
Notre premier docker-compose.yml
Ce fichier définit la façon dont les conteneurs doivent se comporter en production.
Une foit que l'on s'est assuré que notre image a bien été push, on peut créer le fichier n'importe où et y coller le contenu suivant ;:
version: "3"
services:
; web:
; ; ; # replace username/repo:tag with your name and image details
; ; ; image: username/repo:tag
; ; ; deploy:
; ; ; ; ; replicas: 5
; ; ; ; ; resources:
; ; ; ; ; ; ; limits:
; ; ; ; ; ; ; ; ; cpus: "0.1"
; ; ; ; ; ; ; ; ; memory: 50M
; ; ; ; ; restart_policy:
; ; ; ; ; ; ; condition: on-failure
; ; ; ports:
; ; ; ; ; - "4000:80"
; ; ; networks:
; ; ; ; ; - webnet
networks:
; webnet:
;
Ce fichier accomplit les choses suivantes ;:
- Faire un pull de l'image
- Lancer 5 instances de l'image en tant qu'un service appellé "web", chacun pouvant utiliser maximum 10% du CPU (distribué sur tous les coeurs) et 50 Mo de RAM
- Relancer immédiatement un conteneur qui plante
- Mapper le port 4000 de l'hôte au port 80 de web
- Dire aux conteneur de web de se partager le port 80 via un réseau avec load-balancing appellé webnet
- Définir le réseau webnet avec les paramètres par défaut (un réseau avec load-balancing)
Lancer notre nouvelle application
On va d'abord utiliser la commande ;:
docker swarm init
Le fonctionnement de cette commande sera expliqué plus loin.
NB: Un swarm est un outil de clustering et de scheduling servant à administrer un cluster de noeuds Docker comme si c'était un seul système virtuel.
Si j'ai plusieurs adresses sur mon interface réseau (IPv6...) je peux en préciser une en ajoutant --advertise-addr @IP
Je peux ensuite créer l'application ;:
docker stack deploy docker-compose.yml monservicetest
Je peux remplacer "monservicetest" par n'importe quel nom.
Désormais, notre service fait tourner 5 réplique de notre image sur le même hôte. On peut faire un docker service ls et constater que celui-ci est bien présent ;:
justine@Justine-pc:~/Dockertest$ docker service ls
ID ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; NAME ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; MODE ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; REPLICAS ; ; ; ; ; ; ; ; ; ; ; IMAGE ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; PORTS
c3jwhxzgvjj6 ; ; ; ; ; ; ; monservicetest_web ; ; replicated ; ; ; ; ; ; ; ; ; 5/5 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; justinep/monrepotest:letest ; ; *:4000->80/tcp
;On a un certain nombre d'infos ;: un ID de service, le nom avec le suffixe _web, les répliques, etc.
Un seul conteneur tournant au sein d'un service est appellé une tâche (task). Les tâches ont un ID unique qui s'incrémente avec le nombre de répliques. Je peux les lister ;:
n4kztr59mt8o ; ; ; ; ; ; ; monservicetest_web.1 ; ; justinep/monrepotest:letest ; ; Justine-pc ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 4 minutes ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
by570g9v7nzn ; ; ; ; ; ; ; monservicetest_web.2 ; ; justinep/monrepotest:letest ; ; Justine-pc ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 4 minutes ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
k8mdckf6m8fg ; ; ; ; ; ; ; monservicetest_web.3 ; ; justinep/monrepotest:letest ; ; Justine-pc ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 4 minutes ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
84vxmgbbe1s0 ; ; ; ; ; ; ; monservicetest_web.4 ; ; justinep/monrepotest:letest ; ; Justine-pc ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 4 minutes ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
jigcz7c22hdh ; ; ; ; ; ; ; monservicetest_web.5 ; ; justinep/monrepotest:letest ; ; Justine-pc ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 4 minutes ago
On peut aussi les voir avec docker container ls, mais sans indications sur le service.
Si je vais voir mon 127.0.0.1:4000 et que je rafraichis la page plusieurs fois, je vois que le hostname renvoyé change, grâce au load balancing.
Faire du scaling de notre application
Il suffit de changer de changer le nombre de répliques dans le fichier docker-compose.yml, et de relancer le déploiement. Le service sera mis à jour, pas besoin de le couper avant ;: on peut rajouter des répliques.
;
Arrêter le swarm et l'application
On peut arrêter l'app avec:
docker stack rm monservicetest
Couper le swarm:
docker swarm leave --force
Note ;: les fichiers composés ainsi servent à définir des applications Docker, et peuvent être uploadés sur le cloud quand on utiliser Docker édition entreprise.
Résumé des commandes ;:
docker stack deploy -c <composefile> <appname> ; # Run the specified Compose file
docker service ls ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # List running services associated with an app
docker service ps <service> ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # List tasks associated with an app
docker inspect <task or container> ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # Inspect task or container
docker container ls -q ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # List container IDs
docker stack rm <appname> ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # Tear down an application
docker swarm leave --force ; ; ; ; ; # Take down a single node swarm from the manager
;
Les swarms (essaims)
Un swarm est un groupe de machines qui font tourner docker et qui ont rejoint un cluster. Une fois que ce cluster est en place, on peut continuer à utiliser les commandes docker que l'on connait, mais elles sont exécutées sur le cluster par un swarm manager. Les machines d'un swarm peuvent être physiques ou virtuelles. Après avoir rejoint un swam, elles sont appellées noeuds.
Les swarm managers peuvent utiliser différentes stratégies pour faire tourner des conteneurs, telles que "le noeud le plus vide" (emptiest node) qui remplit les machines les moins sollicitées, ou "global" qui s'assure que chaque machine a exactement une instance du conteneur spécifié. Ces instructions sont données dans le fichier de composition, comme celui utilisé précédemment.
Les swarm managers sont les seules machines du swarm qui peuvent exécuter des commandes, ou autoriser d'autres machines à rejoindre le swarm comme travailleurs. Les travailleurs ne font que fournir de la capacité de travail ; sans avoir aucune autorité sur les autres machines.
Jusqu'à présent nous avons utilisé Docker en mode single-host, cependant Docker peut être basculé en mode swarm. Passer dans le mode swarm fait automatiquement de la machine actuelle un swarm manager. À partir de là, Docker exécute les commandes sur tout le swarm et non plus sur notre seule machine.
Préparer le swarm
Un swarm est composé de noeuds, qu'ils soient physiques ou virtuels. Le concept de base est simplement d'utiliser la commande docker swarm init
pour activer le mode swarm faire de notre machine un manager, et de lancer docker swarm join
sur les autres machines pour qu'elles rejoignent le swarm comme travailleurs.
Nous allons utiliser des VMs pour la suite; à partir d'ici, j'utiliserais des VMs locales sur Linux à l'aide de VirtualBox.
Nous allons créer les deux vm avec docker-machine:
docker-machine create --driver virtualbox myvm1
docker-machine create --driver virtualbox myvm2
(En cas de soucis avec docker-machine, cf en haut de la page ;!)
Lister les VMs et avoir leurs IP
Tout simplement ;:
docker-machine ls
NAME ; ; ; ACTIVE ; ; DRIVER ; ; ; ; ; ; STATE ; ; ; ; URL ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; SWARM ; ; DOCKER ; ; ; ; ERRORS
myvm1 ; ; - ; ; ; ; ; ; ; virtualbox ; ; Running ; ; tcp://192.168.99.100:2376 ; ; ; ; ; ; ; ; ; ; v18.09.0 ; ;
myvm2 ; ; - ; ; ; ; ; ; ; virtualbox ; ; Running ; ; tcp://192.168.99.101:2376 ; ; ; ; ; ; ; ; ; ; v18.09.0 ; ;
;
Initialiser le swarm et ajouter des noeuds
La première machine agit en tant que manager et exécute les commandes; elle authentifie les travailleurs qui rejoignent le swarm. La seconde est un travailleur.
On peut envoyer des commandes aux machines avec la commande docker-machine ssh. On va dire à myvm1 de devenir swarm manager et regarder la sortie:
docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.100"
- La sortie ;:
Swarm initialized: current node (qcdqotcpeohwk9yi7dp4fvpcq) is now a manager.
To add a worker to this swarm, run the following command:
; ; ; docker swarm join --token SWMTKN-1-3ngct9c9o6a0j78qm49aivrjgo9i5hnk84ba823e2lcp2tv7bj-073c8yp54wekct3xzz1ory8om 192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
;Il faut toujours lancer docker swarm init et docker swarm join avec le port 2377 (port de gestion de swarm) ou ne rien mettre pour avoir le port par défaut.
docker-machine ls renvoie le port 2376, qui est le port du daemon Docker; il ne faut pas l'utiliser.
Si on veut utiliser le client ssh de notre propre machine, on peut ajouter l'argument --native-ssh.
On peut voir que le swarm init renvoie une commande de swarm join à donner aux autres machines pour qu'elles rejoignent le swarm comme travailleurs. On va l'envoyer à myvm2.
This node joined a swarm as a worker.
;Et voilà ;! Notre premier swarm est actif. On peut voir les noeuds depuis myvm1 qui est manager ;:
ID ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; HOSTNAME ; ; ; ; ; ; ; ; ; ; ; STATUS ; ; ; ; ; ; ; ; ; ; ; ; ; AVAILABILITY ; ; ; ; ; ; ; MANAGER STATUS ; ; ; ; ; ENGINE VERSION
qcdqotcpeohwk9yi7dp4fvpcq * ; ; myvm1 ; ; ; ; ; ; ; ; ; ; ; ; ; ; Ready ; ; ; ; ; ; ; ; ; ; ; ; ; ; Active ; ; ; ; ; ; ; ; ; ; ; ; ; Leader ; ; ; ; ; ; ; ; ; ; ; ; ; 18.09.0
1mtumwa9zqmk09xj9mih5nt8c ; ; ; ; myvm2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; Ready ; ; ; ; ; ; ; ; ; ; ; ; ; ; Active ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
;Si l'on désire quitter le swarm, il suffit d'utiliser la commande docker swarm leave
sur un des noeuds.
Déployer notre application sur le cluster
Jusque ici, nous avons utilisé docker-machine ssh pour envoyer des commandes vers nos machines. Une autre option est d'utiliser la commande docker-machine env <machine> pour lancer une commande qui configure l'interpréteur de commandes actuel pour qu'il parle au daemon Docker sur la VM. C'est la meilleure méthode pour ce qui va suivre car elle permet d'utiliser un fichier docker-compose.yml local pour déployer les applications "à distance" sans avoir à le copier où que ce soit.
On va utiliser la commande docker-machine env myvm1 pour faire en sorte que notre shell parle à la VM1, ce sera utile pour la suite.
- $ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/justine/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell:
# eval $(docker-machine env myvm1)
-
-
- On nous invite à lancer une commande, ce que l'on fait ;:
-
- $ eval $(docker-machine env myvm1)
-
-
- Je vais vérifier que myvm1 est bien la machine active (regarder l'astérisque)
-
- ~$ docker-machine ls
NAME ; ; ; ACTIVE ; ; DRIVER ; ; ; ; ; ; STATE ; ; ; ; URL ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; SWARM ; ; DOCKER ; ; ; ; ERRORS
myvm1 ; ; * ; ; ; ; ; ; ; virtualbox ; ; Running ; ; tcp://192.168.99.100:2376 ; ; ; ; ; ; ; ; ; ; v18.09.0 ; ;
myvm2 ; ; - ; ; ; ; ; ; ; virtualbox ; ; Running ; ; tcp://192.168.99.101:2376 ; ; ; ; ; ; ; ; ; ; v18.09.0 ; ;
Il est à noter que:
- Pour utiliser le shell sur une autre machine, il suffit d'utiliser à nouveau la commande docker-machine env dans le même ou un autre shell, puis d'utiliser la commande suivante sur la nouvelle machine à contrôler. Ces commandes sont toujours spécifiques au shell en cours.
- On peut aussi utiliser docker-machine ssh, mais on a alors pas d'accès immédiat au fichier sur la machine locale.
- On peut aussi utiliser docker-machine scp <fichier> <machine>:~ pour copier des fichiers depuis la machine locale.
Déployer l'application sur le swarm manager
Maintenant que l'on contrôle myvm1, on peut utiliser ses pouvoirs de swarm manager pour déployer notre app avec la même commande docker stack deploy
que l'on a utilisé auparavant, ainsi que notre copie locale de docker-compose.yml. La commande prend quelques secondes à s'accomplir, et le déploiement prend du temps avant d'être disponible. On peut ensuite utiliser la commande docker service ps <nom_du_service> sur le swarm manager pour vérifier que tout fonctionne:
justine@Justine-pc:~/Dockertest$ docker swarm init --advertise-addr 192.168.99.100
Swarm initialized: current node (jno762f6q1jnzfpn9evw3md9r) is now a manager.
To add a worker to this swarm, run the following command:
; ; ; docker swarm join --token SWMTKN-1-5etyayjtyceujmmsakct446qf6lshz1tjvh41fj2hrrclla8xr-1tbbbgxsodbu4cj508lq9erm7 192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
justine@Justine-pc:~/Dockertest$ docker-machine ssh myvm2 "docker swarm join --token SWMTKN-1-5etyayjtyceujmmsakct446qf6lshz1tjvh41fj2hrrclla8xr-1tbbbgxsodbu4cj508lq9erm7 192.168.99.100:2377"
This node joined a swarm as a worker.
-
-
- Je lance le service
-
justine@Justine-pc:~/Dockertest$ docker stack deploy -c docker-compose.yml letest
Creating network letest_webnet
Creating service letest_web
-
-
- Je vérifie
-
justine@Justine-pc:~/Dockertest$ docker service ps letest_web
ID ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; NAME ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; IMAGE ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; NODE ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; DESIRED STATE ; ; ; ; ; ; CURRENT STATE ; ; ; ; ; ; ; ; ; ; ; ERROR ; ; ; ; ; ; ; ; ; ; ; ; ; ; PORTS
ie2uwhz2ci0q ; ; ; ; ; ; ; letest_web.1 ; ; ; ; ; ; ; justinep/monrepotest:letest ; ; myvm1 ; ; ; ; ; ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 31 seconds ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
o59l1s79q7fh ; ; ; ; ; ; ; letest_web.2 ; ; ; ; ; ; ; justinep/monrepotest:letest ; ; myvm2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 42 seconds ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
m0o5ejudaqku ; ; ; ; ; ; ; letest_web.3 ; ; ; ; ; ; ; justinep/monrepotest:letest ; ; myvm1 ; ; ; ; ; ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 31 seconds ago ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
lxfbcszozwxs ; ; ; ; ; ; ; letest_web.4 ; ; ; ; ; ; ; justinep/monrepotest:letest ; ; myvm2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; Running ; ; ; ; ; ; ; ; ; ; ; ; Running 42 seconds ago ;
À noter ;: si mon docker-compose.yml prévoit de faire 4 répliques du service, ces 4 répliques sont réparties sur les deux vms.
;
;
;
Accéder à mon application
Il suffit d'utiliser à l'ip de l'une des deux machines via un navigateur. Les machines sont reliées entre elles via un réseau IP ;:
En cas de problème de connexion, il faut s'assurer que les ports 7946 TCP/UDP et 4789 UDP sont ouverts entre les noeuds du swarm.
Itérer et scaler l'application
À partir d'ici, on peut faire tout ce que l'on a vu précédemment. On peut changer le comportement de l'application en éditant le code, puis en reconstruisant et pushant la nouvelle image. Dans tous les cas, la commande docker stack deploys est utilisée pour déployer les changements.
Pour rajouter des machines il suffit d'utiliser la même commande que sur myvm2; ensuite il suffit de réutiliser docker stack deploy pour prendre en compte les nouveaux changements.
Quelques commandes pour interagir avec les VMs et le swarm:
docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux)
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10
docker-machine env myvm1 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # View basic information about your node
docker-machine ssh myvm1 "docker node ls" ; ; ; ; ; ; ; ; # List the nodes in your swarm
docker-machine ssh myvm1 "docker node inspect <node ID>" ; ; ; ; ; ; ; # Inspect a node
docker-machine ssh myvm1 "docker swarm join-token -q worker" ; ; # View join token
docker-machine ssh myvm1 ; ; # Open an SSH session with the VM; type "exit" to end
docker node ls ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; # View nodes in swarm (while logged on to manager)
docker-machine ssh myvm2 "docker swarm leave" ; # Make the worker leave the swarm
docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm
docker-machine ls # list VMs, asterisk shows which VM this shell is talking to
docker-machine start myvm1 ; ; ; ; ; ; ; ; ; ; ; # Start a VM that is currently not running
docker-machine env myvm1 ; ; ; ; ; # show environment variables and command for myvm1
eval $(docker-machine env myvm1) ; ; ; ; ; ; ; ; # Mac command to connect shell to myvm1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression ; ; # Windows command to connect shell to myvm1
docker stack deploy -c <file> <app> ; # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file
docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app)
docker-machine ssh myvm1 "docker stack deploy -c <file> <app>" ; ; # Deploy an app using ssh (you must have first copied the Compose file to myvm1)
eval $(docker-machine env -u) ; ; ; ; # Disconnect shell from VMs, use native docker
docker-machine stop $(docker-machine ls -q) ; ; ; ; ; ; ; ; ; ; ; ; ; ; # Stop all running VMs
docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images
;
;
;
Annexe: Faire un site web en container (et en faire une image)
Le but ici sera de faire un site web en local dans un conteneur, et une fois que notre site sera fonctionnel, de le commit et de le sauvegarder sur notre dépôt.
On va commencer par créer un dossier quelque part pour stocker notre site web, et y mettre une page web.
justine@Justine-pc:~$ mkdir Website
justine@Justine-pc:~$ cd Website/
justine@Justine-pc:~/Website$ vim index.html
##Le contenu de index.html, avec des écureuils ;!
<h1><strong>Bienvenue sur l'entreprise des écureuils ;!<br /></strong></h1> <p> ;</p> <p><strong>Ici, on apprécie les noisettes.</strong></p> <p> ;</p> <p>Toutes les noisettes. Parce que les noisettes, c'est bon.</p> <p> ;</p> <p><img src="https://media.npr.org/assets/img/2017/04/25/istock-115796521-fcf434f36d3d0865301cdcb9c996cfd80578ca99-s800-c85.jpg" alt="" width="800" height="599" /></p> <p><strong> ;</strong></p>
;
Une fois qu'on a notre site web, on va partir d'un conteneur d'apache (2.4, ici). Il s'agit en fait d'httpd, un peu différent du Apache2 habituel (les dossiers ne sont pas au même endroit), puisque httpd est en fait le daemon d'Apache; pas d'hôtes virtuels ici, mais peu importe. On commence par lancer le conteneur, en reliant à son dossier de site web notre dossier créé juste avant ;:
docker run -dit --name ecureuilweb -p 8080:80 -v /home/justine/Website/:/usr/local/apache2/htdocs/ httpd:2.4
Quelques explications ;:
- -d pour tourner en arrière plan
- -i pour interactive
- -t pour alouer un pseudo-shell au conteneur
- --name pour lui donner un nom, plus pratique
- -p 8080:80 ;: Le port 80 du conteneur est relié au port 8080 de ma machine
- -v /home/justine/Website/:/usr/local/apache2/htdocs/ ;: On relie mon dossier local au dossier de site web du conteneur. Si mon conteneur tourne et que je modifie le contenu du dossier local, la modification sera effectuée sur le conteneur aussi ;! Le volume est partagé.
- httpd:2.4 ;: docker devrait rechercher tout seul l'image depuis les dépôts.
Une fois que c'est fait, mon conteneur tourne (je peux vérifier avec un docker container ps
). Je peux vérifier que j'y ai accès en entrant 127.0.0.1:8080 dans mon navigateur, et aussi modifier mon site web, etc...
ATTENTION : Les changements que je fais dans mon volume partagé NE SERONT PAS transférés lors du push. Cela ne peut arriver, semble-t'il, que grâce à un Dockerfile. Cependant on peut continuer à utiliser le volume partagé. Docker est fait pour utiliser des volumes partagés.
Une fois que je suis satisfaite de mon site, il est temps d'en faire une image que je mettrais sur mon dépôt personnel. Pour cela:
##Je commence par me logger
justine@Justine-pc:~/Website$ docker login
###On va effectuer un commit. On a déjà donné un nom au conteneur, pas besoin de le taguer ;!
justine@Justine-pc:~/Website$ docker commit 109fd8b0c033 justinep/monrepotest:squirrelweb
###C'est fait: Docker me renvoie un checksum. Plus qu'a uploader l'image sur mon dépôt personnel ;:
docker push justinep/monrepotest:squirrelweb
Par défaut, un commit met le conteneur en pause. On peut désactiver cette option en rajoutant --pause=false
juste après la commande commit ;: docker commit --pause=false 109fd8b0c033 justinep/monrepotest:squirrelweb
.
Annexe2 : Autres commandes utiles
--net=host : Avec cette option, le conteneur utilise le réseau de l'hôte (il n'est plus isolé). Si mon conteneur écoute sur son port 80, ce sera relié au port 80 de l'hôte. Un exemple avec un serveur dhcp:
/etc/dhcp/dhcpd.conf:/etc/dhcp/dhcpd.conf --net=host --name=dhcp --restart=always sirferdek/isc-dhcp-server eno1
--restart=always : signifie que le conteneur sera toujours redémarré, même lors d'un reboot, même si j'essaie de l'arrêter. Peut aussi être no, on-failure, unless-stopped.
- Effacer tout ce qui n'est pas utilisé:
docker system prune
- ... la même chose, mais on efface vraiment tout
docker system prune -a
Construire une image docker (version 2, plus claire !)
Je commence par créer un dossier dédié. Dans celui-ci se trouveront plusieurs fichiers:
- Un fichier nommé Dockerfile, qui définit l'image
- Un fichier nommé .dockerignore, qui permet de dire quels fichiers doivent être ignorés
- D'éventuels fichiers que je voudrais intégrer à l'image (ici, index.htm, ma page web)
Il me faut ensuite une image de laquelle partir. Pour partir d'une image Debian:
docker pull debian
Je peux ensuite la voir avec
docker image ls
Mon Dockerfile détaillé
#De quelle image je pars FROM debian:stretch #RUN permet de lancer des commandes #Lancer de longues commandes permet d'alléger l'image finale RUN apt-get update\ && apt-get install -y apache2 && /etc/init.d/apache2 start; exit 0 #WORKDIR: équivalent d'un cd dans le conteneur WORKDIR /var/www/html #J'enlève la page html de base RUN mv index.htm* index.html.bkp #Pour la démo, je vais juste ajouter une simple page html #Mais cela pourrait être un script ou autre #ADD : ajoute un fichier local vers le dossier du conteneur #COPY : Pareil mais en mieux? COPY --chown=www-data:www-data ./index.htm /var/www/html RUN /etc/init.d/apache2 restart #On expose le port 80 du serveur avec EXPOSE EXPOSE 80 #On expose le dossier de logs avec VOLUME VOLUME /var/log #On précise une commande qui va tourner au lancement du conteneur grâce à CMD #Un conteneur qui ne fait rien tourner s'arrête ! #C'est pour ça qu'on fait un tail infini : pour que notre conteneur ne s'arrête pas. CMD tail -f /dev/null
.dockerignore
Ici je n'en ai pas besoin, mais son contenu pourrait ressembler à :
.git photodemonchat.jpeg
Construction et lancement
Je lance la construction de mon image :
docker build -t nomdelimage .
Et lancer un container : <syntaxhighlight lang='bash'> [justine@argonaut Docker]$ docker run -d -p 80:80 -it test6 2a2e57d86eef84594aad628d2e67b84bd42363c6162bcd093e79346c1795f993 [justine@argonaut Docker]$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a2e57d86eef test6 "/bin/sh -c 'tail -f…" 5 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp flamboyant_blackburn
</syntaxhighlight> Ici, -p mappe le port 4000 local vers le port 80 du conteneur et -d le lance en arrière-plan. Attention à bien mettre le tag / nom de l'image en dernier !
Debug
Si le conteneur n'as pas l'air de se lancer, plusieurs choses :
- Lancer le conteneur avec --verbose est important. Cela permet de connaitre un éventuel souci.
- docker ps -a permet de voir les conteneurs arrêtés.
- Pour lancer un conteneur avec un bash alors qu'il s'arrête tout seul sans rien dire :
docker run -it --entrypoint /bin/bash $IMAGE_NAME -s
Autres commandes utiles
SSH dans un conteneur
docker exec -it ABCDEF123 /bin/bash
SCP dans un conteneur
docker cp foo.txt mycontainer:/foo.txt
Sortir d'un conteneur sans l'arrêter
Pour sortir d'un conteneur sans l'arrêter, on peut utiliser le raccourci suivant : Ctrl+p puis Ctrl+q
Dockerhub
Push une image
On commence par se créer un compte sur le dockerhub, puis créer un repo. Pour ma part je vais créer squi/sqnotes pour mon application de prise de notes.
Ensuite, sur ma machine docker, il va falloir construire mon image et la tagger avec mon user/image:
- Soit lors du build:
docker build -t <hub-user>/<repo-name>[:<tag>]
- Soit retagger une image existante:
docker tag <existing-image> <hub-user>/<repo-name>[:<tag>]
- Soit avec un commit:
docker commit <existing-container> <hub-user>/<repo-name>[:<tag>]
Plugins
Il existe divers plugins pour Docker : Une liste non exhaustive.
Installation rootless
Par exigence de sécurité, on peut vouloir installer docker en mode rootless (soit, qui ne tourne pas en tant que root...).
La doc recommande d'utiliser Ubuntu, ce que je vais faire. D'ailleurs, la version rootless implique certaines limitations; en bref : lire la doc. Ce paragraphe a seulement pour but de clarifier. Je vais utiliser ici la méthode "no packages". Je pars d'une machine tout juste installée : je n'ai pas encore fait l'utilisateur dédié à docker, j'ai simplement le mien qui as les droits sudo.
Etapes préalables:
- Créer un utilisateur dédié avec un mdp. Il n'as pas besoin d'avoir les droits sudo.
- Installer les paquets : iptables, dbus-user-session, uidmap
- Vérifier dans /etc/subuidmap et /etc/subgidmap que l'utilisateur a bien au moins 65536 UID et GID subordonnés. Un cat de /etc/subuidmap devrait par exemple donner:
monuser:231072:65536
(c'est le dernier chiffre qui est important).
- Ensuite il faut passer sur l'utilisateur créé, mais attention : pas avec sudo su -. Le mieux est de faire un logout et de se loger sur la console, ou en ssh (quand bien même on est déjà sur la machine, on peut faire un ssh monuser@localhost). Cela permet de se loger en utilisant PAM_UID.
- Installer docker avec:
curl -fsSL https://get.docker.com/rootless | sh
- Vérifier l'uid de mon utilisateur en faisant un ls /run/user par exemple (je ne vois alors que mon propre uid). Pour la suite de cet exemple, disons que mon uid est 1001.
- Ajouter les lignes suivantes de le .bashrc du user:
export PATH=/home/monuser/bin:$PATH export DOCKER_HOST=unix:///run/user/1001/docker.sock
- Sourcer le .bashrc
source .bashrc
- Activer et démarrer docker
systemctl --user enable docker systemctl --user start docker
- C'est bon
Avoir accès aux port privilégiés
Un user classique n'as pas accès aux port statiques, ce qui est normal. Du coup, en rootless, on ne pourra pas utiliser le port 443 par exemple.
La doc mentionne:
sudo setcap cap_net_bind_service=ep $(which rootlesskit) systemctl --user restart docker
...mais leur commande est fausse, visiblement.
La solution est, en root, de rajouter:
net.ipv4.ip_unprivileged_port_start=0
en bas de /etc/sysctl.conf, suivi de
sysctl --system