Ready for j1

This commit is contained in:
nemunaire 2016-09-08 03:44:20 +02:00
parent 150f5a8834
commit dc8544c0c9
31 changed files with 688 additions and 544 deletions

View file

@ -1,4 +1,4 @@
SOURCES = tutorial.md installation.md what.md first.md dockerfile.md volumes.md linking.md
SOURCES = tutorial.md installation.md what.md first.md supervisor.md goodpractices.md compose.md project.md
TEMPLATE = ../../template.tex
PANDOCOPTS = --latex-engine=xelatex \
--standalone \

36
tutorial/2/compose.md Normal file
View file

@ -0,0 +1,36 @@
\newpage
# Compose
Avec notre conteneur utilisant `supervisor`, nous ne respectons pas
bien cette dernière bonne pratique d'un seul processus par conteneur
:-(
L'intérêt est de permettre à chaque conteneur d'effectuer une tâche
générique, de manière à pouvoir être réutilisé pour d'autres projet
dans le futur. Par exemple, notre conteneur InfluxDB pourra être
utilisé pour stocker des relevés de métriques systèmes ou des logs.
Grafana peut également afficher davantage d'informations ou combiner
les informations de plusieurs bases distinctes.
## Séparer le `Dockerfile`
Commençons par séparer notre `Dockerfile` en deux : dans une partie
nous allons garder la partie InfluxDB, de l'autre la partie Grafana.
Il va vous falloir créer deux dossiers distincts, il en faut un par
`Dockerfile`.
Profitez en pour rajouter les Data Volume Container.
## Automatiser le lancement
Commencez par lancer tous vos conteneurs à la main pour voir les
étapes que vous allez devoir automatiser.
Au lieu de faire un script pour construire et lancer tous vos
conteneurs, définissez à la racine de votre projet un fichier
`docker-compose.yml` qui contiendra les méthodes de construction et
les paramètres d'exécution.

View file

@ -1,167 +0,0 @@
\newpage
# `Dockerfile`
## Mon premier conteneur ... par `Dockerfile`
Pour construire une image, vous n'êtes pas obligé de passer par une série de
commits. Docker dispose d'un mécanisme permettant d'automatiser la construction
de nouvelles images. Vous pouvez arriver au même résultat que ce que l'on a
réussi à faire précédemment en utilisant le Docker file suivant :
```
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y nano
```
La syntaxe d'un `Dockerfile` est simple, le premier mot de chaque ligne est
l'intitulé d'une instruction (que l'on écrit généralement en majuscule), elle
est suivie de ses arguments.
Dans notre exemple, nous utilisons `FROM` qui indique une image de départ à
utiliser ; `RUN` est une commande qui sera exécutée dans le conteneur, dans le
but de le construire.
Pour lancer la construction de la nouvelle image, créer un nouveau dossier ne
contenant que votre fichier `Dockerfile`, placez-vous dedans, puis utilisez la
commande `build` :
```
docker build --tag=my_editor .
```
Une fois la construction de l'image terminée, vous pouvez la lancer et
constater l'existence de notre éditeur favori :
```
docker run -it my_editor /bin/bash
```
## `RUN` dans le `Dockerfile`
Chaque ligne est exécutée indépendamment des autres ;
cela signifie que l'exemple suivant **ne fonctionne pas** :
```
COPY db.sql /db.sql
RUN service mysqld start
RUN mysql -u root -p toor virli < /db.sql
```
Cet exemple ne fonctionne pas car le serveur MySQL est lancé dans le premier
RUN, n'est plus lancé au moment du deuxième RUN. En effet, chaque commande du
`Dockerfile` a pour but de modifier le système de fichiers.
Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
```
COPY db.sql /db.sql
RUN service mysqld start && mysql -u root -p toor virli < /db.sql
```
Après le `RUN`, MySQL sera de nouveau arrêté, si on veut l'utiliser dans le
conteneur, il ne faudra pas oublier de lancer le processus.
## Exposer des ports
Construisons maintenant un conteneur avec un serveur web :
```
FROM my_editor
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
```
L'instruction `EXPOSE` sera traité plus tard par le client Docker
(équivalent à l'argument `--expose`). Il s'agit de préciser les ports
sur lesquels votre image écoute.
En utilisant l'option `-P` du `run`, vous allez pouvoir assigner une
redirection de port aléatoire sur la machine hôte vers votre
conteneur :
```
docker build --tag=my_webserver .
docker run -it -P my_webserver /bin/bash
service nginx start
```
Dans un autre terminal, lancer un `docker ps` et consulter la colonne
*PORTS* pour connaître le port choisit par Docker pour effectuer la
redirection.
Rendez-vous ensuite dans votre navigateur sur <http://localhost:49153/>.
À vous de jouer : utilisez l'instruction `COPY` pour afficher votre
propre `index.html` remplaçant celui installé de base par nginx.
## Lancement de commande automatique
Vous pouvez placer dans un `Dockerfile` une instruction `CMD` qui sera
exécutée si aucune commande n'est passée lors du `run`, par exemple :
```
CMD nginx -g "daemon off;"
```
```
docker build --tag=my_nginx .
docker run -d -P my_nginx
```
L'option `-d` passée au `run` lance le conteneur en tâche de
fond. Si vous constatez via un `docker ps` que le conteneur s'arrête
directement, retirer cette option pour voir ce qui ne va pas, ou
utilisez la commande `docker logs`.
## D'autres instructions ?
Consultez <https://docs.docker.com/reference/builder/> pour la liste complète
des instructions reconnues.
## Rendu
### Exercice
Rendez le fichier `Dockerfile` et son contexte (`index.html`, fichiers de conf
éventuels, ...) que vous avez utiliser pour réaliser votre image
`my_webserver`.
### Questions
1. De combien de couches de systèmes de fichiers est composé votre image
`my_webserver` ? Comment pourriez-vous en avoir moins ?
1. On a vu comment créer une nouvelle image à partir d'une image existante
(`FROM`). Mais comment sont créés les images de bases comme debian ou ubuntu
(quelle(s) commande(s) et quels type(s) de fichier(s)) ?
1. Quels sont les avantages de ce `RUN` :
```
RUN apt-get update && \
apt-get install -y \
nginx \
php5-fpm \
php5-mysql \
php5-gd \
&& apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
```
par rapport aux précédents exemples :
```
RUN apt-get update
RUN apt-get install -y nginx php5-fpm
```

18
tutorial/2/entrypoint.md Normal file
View file

@ -0,0 +1,18 @@
\newpage
# Entrypoint
Jusque là, à chaque redémarrage d'InfluxDB, il est nécessaire de reconfigurer
Grafana pour lui indiquer la nouvelle IP du conteneur. En effet, le data
container préserve les données, mais un changement d'IP n'est pas
répercuté. Pour cela, il nous fait un script d'initialisation, qui va écrire
l'ip de notre conteneur Docker dans la table `data_source` :
Petit indice, les requêtes SQL sont les suivantes :
```
DELETE FROM "data_source";
INSERT INTO "data_source" VALUES(1,1,0,'influxdb','influx','direct','http://${}:8086/','user','pass','metrics',0,'','',0,'null','2015-10-29 09:00:00','2015-10-29 09:05:00');
```
La base se trouve dans `/var/lib/grafana/grafana.db`.

View file

@ -1,182 +1,85 @@
\newpage
# Mon premier conteneur
# Premiers pas
Afin de tester la bonne marche de votre installation, exécutons la commande :
```
docker run hello-world
```
Cette commande va automatiquement exécuter une série de commandes pour nous,
comme indiqué dans le message affiché en retour :
D'abord, le daemon va rechercher s'il possède localement l'image
*hello-world*. Si ce n'est pas le cas, il va aller la récupérer sur
<hub.docker.com>. Ce site met à disposition un grand nombre d'images : des
systèmes de base comme Ubuntu, Debian, Centos, etc. jusqu'à des conteneurs
prêts à l'emploi : le serveur web nginx, la base de données MySQL, un serveur
node.js, etc.
Nous pouvons directement utiliser le client pour rechercher une image sur le
hub, en utilisant la commande `search` :
```
docker search mariadb
```
Il est possible de mettre à jour les images locales ou simplement
pré-télécharger des images depuis le hub en utilisant la commande `pull` :
```
docker pull ubuntu
```
Pour consulter la liste des images dont nous disposons localement (soit parce
qu'on les a téléchargées, soit parce que nous les avons créées nous-même), on
utilise la commande `images` :
```
docker images
```
Nous devrions constater la présence de deux images « Ubuntu », ayant un *TAG*
différent. Souvent, il existe plusieurs versions d'une même image. Pour Ubuntu
par exemple, nous avons la possibilité de lancer la version `vivid`, `trusty`
ou `precise`.
Chaque image est identifiable par son *Image ID* unique ; les noms d'images
ainsi que leurs tags sont, comme les tags Git, une manière humainement plus
simple de faire référence aux identifiants.
Chaque nom d'image possède au moins un tag associé : *latest*. C'est le tag qui
est automatiquement recherché lorsque l'on ne le précisez pas en lançant
l'image.
Dans un premier temps, nous allons créer une image Docker comme si
l'on réalisait l'installation sur une machine classique : en suivant
une recette. La machine (notre première image Docker) contient tout le
nécessaire pour faire fonctionner notre service.
## Exécuter un programme dans un conteneur
## Les caches
Maintenant que nous avons à notre disposition l'image d'un conteneur Ubuntu,
lançons-la !
Nous avons vu que chaque instruction de notre `Dockerfile` génère une
couche. Chaque couche sert de cache d'une construction de conteneur à
l'autre. Ainsi, lorsque vous modifiez une instruction dans votre
`Dockerfile`, les instructions précédentes ne sont pas réexécutées
mais sont ressorties du cache.
La commande `run` de Docker prend comme derniers arguments le programme à
lancer dans le conteneur ainsi que ses éventuels arguments. Essayons d'afficher
un Hello World :
Le cache se basant principalement sur le contenu de chaque instruction
dans le `Dockerfile` (pour les `COPY` et `ADD`, il va aussi regarder
la date de dernière modification de fichier copié ou ajouté). Donc
tant qu'une instruction n'est pas modifiée dans le `Dockerfile`, le
cache sera utilisé.
```
docker run ubuntu /bin/echo "Hello World"
```
Il est possible de ne pas utiliser le cache et de relancer toutes les
étapes du `Dockerfile` en ajoutant l'option `--no-cache` au moment du
`docker build`.
Dans notre exemple, c'est bien le `/bin/echo` présent dans le conteneur qui est
appelé. Ce n'est pas le programme `/bin/echo` de la machine hôte qui a été
transféré dans le conteneur.
Les couches du cache peuvent être partagées entre plusieurs conteneur,
c'est ainsi que vous pouvez partager facilement une plus grosse partie
du système de fichier (afin de profiter du cache du système de
fichiers au moment de l'exécution du conteneur).
## Modifier un conteneur
## `apt-get`
À chaque fois que nous lançons un `run`, un nouveau conteneur est créé à partir
de l'image que l'on a précisé (via un mécanisme de Copy-On-Write, c'est donc
très rapide et ne consomme pas beaucoup d'espace disque). Cela signifie que
lorsque nous exécutons une commande modifiant les fichiers d'un conteneur, cela
ne modifie pas l'image de base, mais crée une nouvelle image. Que nous pouvons
ensuite utiliser comme image de base.
Pour profiter du cache, il faut donc placer les étapes les plus
génériques (qui seraient susceptibles d'apparaître dans plusieurs
conteneur), en haut du `Dockerfile`.
Commençons par entrer dans un nouveau conteneur pour modifier l'image :
```
docker run -it ubuntu /bin/bash
```
Nous voilà maintenant dans le conteneur ! Il est assez épuré, il n'y a rien de
superflu : il n'y a même pas d'éditeur de texte : ni vim, ni emacs, même pas
`vi` !
La première chose à faire est de télécharger la liste des paquets. En effet,
afin de ne pas livrer de superflu, la liste des paquets et son cache ne sont
pas inclues dans le conteneur.
```
apt-get update
```
Il peut arriver que des paquets présents dans l'image ne soient pas à
jour. Afin de garder un environnement cohérent, il est recommandé de ne pas
utiliser le gestionnaire de paquets pour mettre à jour les paquets présent de
base, mais plutôt de contacter le mainteneur de l'image pour qu'il la mette à
jour.
Installons maintenant un programme :
```
apt-get install nano
```
En attendant la fin de l'installation, jetons un œil à la commande dans un
autre terminal :
```
docker ps
```
Cette commande liste les conteneurs actifs. Notez le *Container ID* ainsi que
le *NAMES* du conteneur du conteneur actuellement en cours d'installation de
`nano`.
Lorsque l'installation de `nano` est terminée, quittez l'image en tapant
`exit`.
Sauvegardez votre image modifiée avec la commande `commit` pour pouvoir
commencer directement de votre image avec `nano` :
```
docker commit CONTAINER my_nano
```
En remplaçant `CONTAINER` par le nom ou l'identifiant de votre
container. `my_nano` est le nom que vous voudrez utiliser à la place
d'`ubuntu` :
```
docker run -it my_nano /bin/bash
```
Vous constatez cette fois que vous pouvez lancer `nano`, alors que vous ne
pouvez toujours pas le faire dans un conteneur issue d'une image `ubuntu` !
Commençons donc notre `Dockerfile` : choisissez une image de base pour
votre `FROM`, et indiquez votre nom avec l'instruction `MAINTAINER`,
pour indiquez que c'est vous qui maintenez ce conteneur (si d'autres
gens ont besoin qu'il faut le mettre à jour par exemple).
## Métadonnées
## `RUN` ou script ?
Les images et les conteneurs sont composés d'un ensemble de couches de système
de fichiers et de métadonnées. Ces métadonnées stockent de nombreux paramètres
tels que la commande à lancer par défaut pour une image, ou le PID du premier
programme lancé pour un conteneur.
### InfluxDB
Vous pouvez affichez ces métadonnées avec la commande `docker inspect
hello-world`.
Ensuite viens l'installation d'InfluxDB. Le paquet n'est pas
disponible dans les dépôts. La
[https://influxdb.com/docs/v0.9/introduction/installation.html](procédure
décrite sur le site) incite à télécharger le paquet mis à disposition
puis à l'installer via `dpkg -i`.
Deux solutions s'offrent à nous : télécharger le paquet hors du
conteneur, le copier, puis l'installer. Ou faire un `RUN` avec toutes
ces opérations (sans oublier l'installation de `wget`/`curl`).
La copie étant définitive (supprimer le fichier ne le supprimera pas
des couches où il a pu exister), donc la seconde solution semble
préférable (mais `wget` restera en déchet).
Écrivez une commande `RUN` qui va télécharger la dernière version
d'InfluxDB, qui va l'installer et supprimer le fichier.
\vspace{1em}
À ce stade, nous pouvons déjà terminer le conteneur et tester
qu'InfluxDB est bien utilisable : `EXPOSE`, `CMD`, ... Il est possible
que vous ayez à écraser le fichier de configuration via un
`COPY`. Garder la ligne qui vous permet de lancer votre serveur web
dans un coin, en attendant la partie suivante.
## Rendu
### Grafana
### Questions
Une fois InfluxDB configuré, nous allons avoir la même réflexion avec
Grafana.
1. Comment limiter la quantité maximale de mémoire qu'un conteneur pourra
utiliser ?
De la même manière, téléchargez, installez et supprimez le paquet.
1. Un conteneur Docker se détache-t-il de tous les *namespaces* ? Si non,
pourquoi ?
### Exercice
Réalisez un script permettant de rejoindre les namespaces d'un conteneur Docker
actif. Vous pouvez réutiliser votre exécutable `setns` du premier TP. Vous
allez sans doute avoir besoin d'utiliser `docker inspect`, n'hésitez pas à
consulter son aide : `docker inspect --help`.
```
host# ./docker-attach romantic_archimedes /bin/bash
inCntnr# _
```
`romantic_archimedes` correspond au nom du conteneur Docker auquel on veut
s'attacher et `/bin/bash`, la première commande que l'on va exécuter après le
`clone`.
Lors de vos tests, sachez que vous pouvez vous connecter sur grafana avec
l'utilisateur *admin*, mot de passe *admin*.

214
tutorial/2/goodpractices.md Normal file
View file

@ -0,0 +1,214 @@
\newpage
# Retour sur les bonnes pratiques
http://docs.docker.com/articles/dockerfile_best-practices/
## Utilisez le fichier `.dockerignore`
Dans la plupart des cas, vos Dockerfile seront dans des dossiers contenant
beaucoup de fichiers qui ne sont pas nécessaire à la construction de votre
conteneur (par exemple, vous pouvez avoir un `Dockerfile` placé à la racine
d'un dépôt git : il va avoir besoin des binaires compilés, mais pas des
sources).
Afin d'améliorer les performances lors de la construction, vous pouvez exclure
les fichiers et dossiers inutiles au conteneur en ajoutant un fichier
`.dockerignore` dans le répertoire de votre `Dockerfile`.
Ce fichier fonctionne de la même manière que le `.gitignore` : vous pouvez
utiliser du globing.
Pour plus d'informations, vous pouvez consulter la documentation accessible à
<http://docs.docker.com/reference/builder/#dockerignore-file>.
## N'installez rien de superflu
Afin de réduire la quantité de dépendances à installer, n'installez pas de
paquets dont vous n'avez pas vraiment l'utilité : il n'y a pas de raison par
exemple d'avoir un éditeur de texte dans un environnement qui sera utilisé
comme serveur web. Un autre conteneur pourra contenir cet éditeur de texte dans
les cas où vous avez besoin de modifier des données.
En plus, cela réduira le temps de build et la taille des images produites !
## Minimisez le nombre de couches
Vous devez trouver l'équilibre idéal entre la lisibilité de votre `Dockerfile`
(qui assure la maintenabilité sur le long-terme) et le nombre de couches
créées. Le nombre de couches idéal devrait être égal au nombre de branches
distincte partant d'une image de base, afin d'utiliser au mieux le cache du
système de fichiers.
## Ordonnez vos lignes de commandes complexes
### Allez à la ligne pour séparer les longues lignes de commandes complexes
Aérez vos `Dockerfile` !
N'hésitez pas à commenter et séparer les blocs logiques ensemble, comme lorsque
vous codez.
Lorsqu'une ligne devient complexe, allez à la ligne :
```
RUN apt-get update && apt-get install -y \
nginx \
php5-fpm
```
Notez les backslashs à la fin des lignes, indiquant qu'elle n'est pas terminée.
### Triez les arguments par ordre alphabétique
Lorsque c'est possible, ordonnez vos lignes suivant un ordre logique. Par
exemple :
```
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
```
## Profitez du système de cache
Le processus de construction de votre image Docker va lire les informations de
votre Dockerfile dans l'ordre. Pour chaque instruction, Docker va essayer de
trouver si une image n'est pas déjà disponible dans le cache (plutôt que de
créer une nouvelle image identique).
Il y a un certain nombre de règles à connaître pour bien utiliser ce mécanisme :
- En démarrant d'une image de base déjà présente dans le cache (`docker
images`), l'instruction suivante est comparée avec toutes les autres images
existantes qui en dérivent directement. Si aucune image correspondant n'est
trouvé pour l'instruction, le cache est invalidé pour le reste de cette
construction.
- Dans la plupart des cas, Docker va simplement comparer l'instruction lue avec
le(s) différente(s) image(s) qui dérive(nt) de la commande précédente. Si
aucune commande correspondante n'est trouvé, le cache se retrouve invalidé
pour les instructions suivantes.
- Pour les instructions `ADD` et `COPY`, en plus de la comparaison précédente,
la somme de contrôle du fichier est ajoutée. Si le fichier a été modifié, le
cache se retrouve invalidé.
- Une fois que le cache est invalidé, toutes les commandes restantes à exécuter
dans le `Dockerfile` vont être exécutées.
## Concevez des conteneur éphémères
Les conteneurs que vous générez doivent aussi éphémères que possible : ils
devraient pouvoir être arrêtés, détruits et recréés sans nécessité d'étape de
reconfiguration. La configuration devrait se faire au lancement du conteneur ou
lors de sa construction.
## Cas d'`apt-get` et des gestionnaires de paquets
- N'exécutez pas `apt-get update` seul sur une ligne. Cela risque de poser des
problèmes de cache, car la ligne ne va jamais changer et le cache sera
toujours utilisé. Vous risquez de récupérer des paquets qui ne sont pas à
jour.
- Évitez de mettre à jour le système fourni (via `apt-get upgrade` ou `apt-get
update`). Si l'image n'est pas à jour, contactez son mainteneur. Si vous avez
besoin d'une version à jour d'un paquet distribué avec l'image, préférez
l'utilisation d'`apt-get install -y foo` qui mettra à jour exclusivement le
paquet `foo`, sans altérer le reste du système.
- Pour assurer une bonne gestion du cache, n'hésitez pas à indiquer les
versions des programmes que vous voulez installer sur votre ligne de commande
`apt-get`.
## Exposez les ports standards
La commande `EXPOSE` vous permet d'indiquer les ports sur lesquels votre
conteneur s'attend à recevoir des paquets venant de l'extérieur. Ces ports ne
sont pas partagés avec l'hôte ou les autres conteneur, donc vous n'avez pas de
raison de ne pas utiliser les ports standards.
Si vous faites cela, il y a de forte chance qu'il n'y ait pas besoin de
modifier la configuration des autres logiciels contenu dans d'autres conteneurs
puis qu'ils sont généralement configurés pour se connecter aux ports standards.
S'il y a un conflit sur la machine hôte, il sera toujours temps de créer une
redirection à ce moment là.
## La bonne utilisation de l'`ENTRYPOINT`
L'entrypoint peut être utilisé de deux manières différentes :
- Vous pouvez l'utiliser de telle sorte que la commande passée au `docker run`,
après le nom de l'image, corresponde aux arguments attendu par le programme
indiqué dans l'entrypoint. Par exemple pour nginx :
```
ENTRYPOINT ["nginx"]
CMD ["-g daemon off;"]
```
- Vous pouvez aussi utiliser un script qui servira à faire les initialisations
ou les configurations nécessaire au bon fonctionnement du conteneur
(rappelez-vous, il doit être éphémère !). Par exemple, le `Dockerfile` pour
l'image de PostgreSQL possède cet entrypoint :
```
#!/bin/bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
exec gosu postgres "$@"
fi
exec "$@"
```
## `[""]`, `'` et sans `[]`
Les instructions `ENTRYPOINT` et `CMD` peuvent prendre deux formes :
- `["cmd", "arg1", "arg2"]` : ici, un simple `exexve` sera effectué avec ces
arguments. Si d'éventuels variables se trouve dans les arguments, elles ne
seront pas remplacées.
- `cmd arg1 arg2` : ici l'exécution se fera au sein d'un `sh -c`, donc les
variables seront remplacés et étendues.
Les commandes sous forme de tableau étant parsées par un parser JSON, vous ne
pouvez pas utiliser les simple quotes.
## Volumes
L'instruction `VOLUME` doit être utilisée pour exposer tous les espaces de
stockage
## Réduisez les privilèges
Utilisez l'instruction `USER` dès que vous le pouvez, lorsqu'un service ne
réclame pas de privilège particulier.
Il vous faudra sans doute créer l'utilisateur et son groupe dans le Dockerfile.
## Exécutez un seul processus par conteneur
Dans la majorité des cas, vous ne devriez jamais lancer plus d'un seul
processus par conteneur. Il est préférable de répartir chaque application dans
un conteneur distinct qui n'effectue que le travail pour lequel il est
chargé. Les options de liaison entre conteneur sont à votre disposition pour
vous aider à cette tâche.

View file

@ -2,118 +2,30 @@
# Installation
## Par le gestionnaire de paquets
## `docker-compose`
Sous Debian et ses dérivés (Ubuntu, Mint, ...) le paquet et la commande ont été
nommés `docker.io`. Vous pouvez vous créer un alias `alias docker=docker.io` si
celui-ci n'a pas déjà été défini.
Sous les autres distributions, `docker` correspond a priori bien à la solution
de virtualisation légère que l'on va utiliser.
### Debian Jessie
`docker` se trouve dans les backports, ajouter-les à votre `/etc/apt/sources.list` :
L'équipe en charge de Docker compose met à disposition un binaire contenant
tous les scripts. Nous pouvons l'installer en suivant la procédure suivante :
```
deb http://ftp.debian.org/debian/ jessie-backports main non-free contrib
curl -L https://github.com/docker/compose/releases/download/1.3.3/docker-compose-Linux-x86_64 > /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose
```
Puis :
Le projet étant écrit en Python, il est également disponible via `pip`, si vous
préférez cette méthode. N'oubliez pas de préciser une version compatible avec
votre version de Docker.
### Vérification du fonctionnement
Comme avec Docker, nous pouvons vérifier le bon fonctionnement de
`docker-compose` en exécutant la commande :
```
apt-get update
apt-get install docker.io
42sh$ docker-compose --version
docker-compose version: 1.3.3
```
### Debian Wheezy
Il vous faut utiliser le dépôt de paquets fourni par Docker :
```
apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
```
Ajoutez cette ligne dans votre `/etc/apt/sources.list` :
```
deb https://apt.dockerproject.org/repo debian-wheezy main
```
Puis :
```
apt-get update
apt-get install docker-engine
```
## Manuellement
L'équipe en charge de Docker met à disposition un script pour installer Docker
sur n'importe quel système :
```sh
curl -sSL https://get.docker.com/ | sh
```
## Vérifier la bonne marche de l'installation
Vous devriez maintenant être capable de lancer la commande suivante :
```
docker version
```
Une sortie similaire au bloc suivant devrait apparaître sur votre écran :
```
Client version: 1.3.3
Client API version: 1.15
Go version (client): go1.3.3
Git commit (client): d344625
OS/Arch (client): linux/amd64
Server version: 1.3.3
Server API version: 1.15
Go version (server): go1.3.3
Git commit (server): d344625
```
### `no such file or directory`?
Si vous avez cette erreur : `dial unix /var/run/docker.sock: no such file or
directory.`, le deamon n'est sans doute pas lancé. Lancez-le :
```
sudo service docker restart
```
### `permission denied`?
Si vous avez cette erreur : `dial unix /var/run/docker.sock: permission
denied.`, ajoutez votre utilisateur au groupe `docker` et **relancer votre
session** :
```
sudo gpasswd -a $USER docker
```
**Attention :** cette action n'est pas anodine d'un point de vue sécurité :
<https://docs.docker.com/articles/security/#docker-daemon-attack-surface>
## Rendu
### Questions
1. Dans quel langage Docker a-t-il été écrit ? Docker utilise la `libcontainer`
afin d'avoir une couche d'abstraction des *namespaces* et des
*cgroups*. Dois-je installer cette bibliothèque avant de recopier sur une
nouvelle machine le binaire `docker` (sans passer par le gestionnaire de
paquets) ? Pourquoi ?
1. Décrivez une méthode permettant à un utilisateur (non-root),
présent dans le groupe `docker`, d'effectuer une action
privilégiée impactant la machine hôte.
Si vous obtenez une réponse similaire, c'est que vous êtes prêt à commencer le
TP ! Alors n'attendons pas, partons à l'aventure !

View file

@ -1,35 +0,0 @@
\newpage
## Lier les conteneurs
En plus de vouloir partager des répertoires entre deux conteneurs, il est
souvent nécessaire de partager des ports.
Pour automatiser le partage d'informations sur les IP et ports exposés, la
commande `run` possède l'option `--link` qui permet de définir dans les
variables d'environnement du conteneur que l'on va lancer.
Le détail des variables ajoutées dans cette situation est disponible à
<https://docs.docker.com/userguide/dockerlinks/#environment-variables>.
On utilise généralement cette liaison pour fournir au conteneur hébergeant un
site web dynamique l'IP et le port où trouver la base de données :
```
docker run -e MYSQL_ROOT_PASSWORD=mysecretpassword -d --name db1 mysql
docker run --link db1 my_nginx
```
### Ambasador
Afin d'abstraire le plus possible l'infrastructure sous-jacente et d'autoriser
les migrations de conteneurs, on utilise le modèle *ambassador*.
On lancera systématiquement un conteneur entre deux conteneurs que l'on veut
lier : l'ambassadeur. Celui-ci s'occupera de router correctement le trafic. En
cas de changement de route (si l'un des conteneurs change de machine hôte par
exemple), on a simplement à redémarrer l'ambassadeur plutôt que le conteneur
principal.
La documentation officielle pour ce modèle est disponible à
<https://docs.docker.com/articles/ambassador_pattern_linking/>.

63
tutorial/2/project.md Normal file
View file

@ -0,0 +1,63 @@
\newpage
# Rendu
## TP
Rendez le contenu de votre dossier à la dernière étape du TP : avec le
`docker-compose.yml`, ainsi que vos `Dockerfile` et les éventuels fichiers
annexes.
## Projet
De la même manière que nous avons réaliser un groupe de conteneurs utilisant
`grafana` et `InfluxDB`, qui permet d'afficher facilement des métriques sous
forme de graphiques, vous allez réaliser, à l'aide des images Docker
présentent sur le hub, une interface web de recherche et de visualisation de
logs, utilisant
[Kibana](https://www.digitalocean.com/community/tutorials/how-to-install-elasticsearch-logstash-and-kibana-4-on-ubuntu-14-04).
Toutes la chaîne d'image Docker est déjà présente sur le hub :
[logstash](https://hub.docker.com/_/logstash/),
[elasticsearch](https://hub.docker.com/_/kibana/),
[kibana](https://hub.docker.com/_/kibana/).
Le but du projet est donc de réaliser un `docker-compose.yml` permettant
d'avoir un système de centralisation de logs fonctionnels. Vous aurez sans
doute à faire quelques adaptations au niveau des images Docker, au moins pour
des fichiers de configuration, donc il n'y aura sans doute pas que ce fichier à
rendre.
Vous pouvez utiliser comme source de logs les conteneurs du TP, grâce aux
options `log-driver=gelf` et `log-opt=gelf-address=udp://host:port`, passées
aux `docker run` (ou dans le `docker-compose`).
Côté `logstash`, votre configuration devrait ressembler à ça :
```
input {
tcp {
port => 4242
}
udp {
port => 4242
}
}
output {
elasticsearch { }
}
```
Vous pourrez ainsi envoyez les logs de Docker sur le port 4242. Ou directement
vos logs syslog :
```
netcat localhost 4242 < /var/log/syslog
```
N'oubliez pas de lire les README associés aux images Docker du hub, ils vous
indiqueront comment utiliser les images et comment leur passer des paramètres.
Bon courage !

60
tutorial/2/supervisor.md Normal file
View file

@ -0,0 +1,60 @@
\newpage
# Plusieurs daemons dans un conteneur
## Script d'init
Lors du dernier TP, nous avons vu que les conteneurs étaient détruits
dès que le premier processus du conteneur (celui qui a le PID 1, à la
place d'`init`) terminer son exécution, quelque soit le statut de ses
éventuels fils.
Pour lancer tous nos daemon,
## Autorestart
L'avantage de détruire le conteneur à la mort du père, est que s'il
s'agit de notre processus principal et qu'il est seul (par exemple
`nginx` pour un conteneur qui délivre des pages web), il va être
possible de redémarrer le conteneur automatiquement grâce à la
*restart policy* que l'on peut définir au moment du `docker run` :
```
docker run -d -p 80:80 --restart=on-failure nginx
```
Il existe trois règles de redémarrage différentes :
- **`no` :** il s'agit de la règle par défaut. Lorsque l'exécution du
conteneur se termine, il n'est pas redémarré.
- **`on-failure[:max-retries]` :** redémarre uniquement si le code de
sortie du conteneur n'est pas 0. Il est possible de préciser pour
cette option le nombre maximum de redémarrage qui sera tenté.
- **`always` :** redémarre le conteneur dans tous les cas, quelque
soit son code de sortie et indéfiniment.
Le script d'init que vous avez réalisé ne tient sans doute pas compte
de cela. Mais plein de gens ont cette problématique et `supervisor`
répond parfaitement à notre problématique !
## `supervisor`
Première étape : installer `supervisor`, le paquet se trouve dans les
dépôts.
L'étape suivante consiste à remplir puis copier le fichier de
configuration dans le conteneur. Vous allez devoir écraser dans votre
conteneur le fichier `/etc/supervisord.conf` pour démarrer à la fois
`grafana` et `influxdb`.
Vous pouvez vous aider de la documentation disponible à :
<http://supervisord.org/configuration.html>
## C'est parti !
Votre conteneur doit maintenant être parfaitement fonctionnel : vous
devriez pouvoir lancer votre script de monitoring et voir apparaître
vos données dans Grafana !

View file

@ -1,2 +0,0 @@
* entrypoint
* parler du contexte du build

View file

@ -1,11 +1,11 @@
% Virtualisation légère -- TP n^o^ 2
% Virtualisation légère -- TP n^o^3
% Pierre-Olivier *Nemunaire* Mercier
% Jeudi 22 octobre 2015
% Jeudi 29 octobre 2015
Durant ce deuxième TP, nous allons apprendre à utiliser Docker !
Durant ce troisième TP, nous allons approfondir l'utilisation de Docker !
Tous les éléments de ce TP (exercices et questions) sont à rendre à
<virli@nemunai.re> au plus tard le mercredi 28 octobre 2015 à 23 h 42. Consultez la
<virli@nemunai.re> au plus tard le jeudi 12 novembre 2015 à 23 h 42. Consultez la
dernière section de chaque partie pour plus d'information sur les éléments à
rendre. Vous pouvez placer les réponses aux questions dans le corps du courriel
ou dans un fichier joint.

View file

@ -1,114 +0,0 @@
\newpage
## Volumes
Il est possible de partager des répertoires entre plusieurs conteneurs. Pour ce
faire, il faut déclarer dans le `Dockerfile` une ou plusieurs instructions
`VOLUME` avec le chemin du répertoire à considérer comme volume (il est
également possible de le faire via l'option `--volume` du client). Ces deux
lignes sont équivalentes :
```
VOLUME /var/log/nginx
```
```
docker run -v /var/log/nginx my_nginx
```
Pour monter les volumes dans un autre conteneur, on utilise l'argument
`--volume-from` du client, en indiquant le nom du conteneur avec lequel on
souhaite partager les volumes :
```
docker run -it --volume-from romantic_archimedes ubuntu /bin/bash
```
Vous constaterez que le répertoire `/var/log/nginx` est partagé entre
`romantic_archimedes` et le dernier conteneur lancé.
\newline
Le concept principal de Docker est de concevoir des conteneurs applicatifs : on
va préférer assigner un unique rôle à un conteneur (donc géralement on ne va
lancer qu'une seule application par conteneur) et concevoir un service complet
en créant un groupe de conteneur, partageant des données entre-eux par des
volumes.
Une lecture intéressante sur ce sujet est sans doute [cet article de Michael
Crosby](http://crosbymichael.com/advanced-docker-volumes.html).
### Partage avec la machine hôte
Il est possible de monter un répertoire de la machine hôte dans un
conteneur. L'intérêt reste plutôt limité à des fins de facilité ou de test, par
exemple si vous voulez partager des fichiers avec votre voisin, en passant par
le protocole HTTP, mais sans se casser la tête à installer et configurer un
serveur web :
```
docker run --rm -p 80:80 -v ~/Downloads:/usr/share/nginx/html:ro -d nginx
```
Une fois cette commande lancée, votre voisin pourra accéder à votre dossier
Downloads en renseignant l'IP de votre machine dans son navigateur favori !
### Data Volume Container
Dans de nombreuses situations, il est intéressant de séparer les données de
l'application, et donc d'avoir un conteneur exécutant l'application et un
second stockant les données.
Cela est particulièrement utile dans le cas d'une base de données : on veut
pouvoir mettre à jour le conteneur exécutant le serveur, sans pour autant
perdre les données.
L'idée derrière le concept de `Data Volume Container` est de partager un volume
avec un conteneur dont le seul rôle est de stocker les données.
Il est parfaitement possible de partager un volume avec un conteneur qui n'est
plus lancé. En effet, tant que vous n'avez pas demandé explicitement à un
conteneur d'être supprimé, il est préservé dans un coin en attendant des jours
meilleurs.
Voici comment on pourrait lancer un conteneur exécutant une base de données :
```
docker run -v /var/lib/mysql --name dbdata busybox
docker run --volume-from dbdata -e MYSQL_ROOT_PASSWORD=mysecretpassword -d mysql
```
Le premier conteneur, sans commande passée, va s'arrêter dès son
lancement. Busybox est l'une des plus petites images possédant tous les outils
de base (il est possible d'obtenir un shell en cas de besoin). Il expose un
volume qui sera utiliser comme stockage persistant.
Le second conteneur va lancer le serveur MySQL et utiliser le répertoire
partagé pour stocker les données.
Lorsqu'il y aura besoin de mettre à jour le conteneur MySQL, les données ne
seront pas perdues (et s'il y avait besoin de migrer les données entre les deux
versions des conteneurs, un conteneur intermédiaire pourrait parfaitement s'en
charger).
Cela facile également les sauvegardes, qui peuvent s'exécuter dans un conteneur
distinct, dédié à la tâche de sauvegarde.
## Rendu
### Exercice
Modifiez le `Dockerfile` de l'exercice précédent pour que votre application web
(ok, c'est juste un `index.html` ...) soit contenue dans un *data volume
container*.
Choisissez le nom de votre volume judicieusement, cela peut vous faciliter la
vie !
Écrivez un script shell qui reprend les commandes que vous avez tapé dans votre
terminal pour créer le *data volume container*, construire l'image à partir du
Dockerfile et lancer le conteneur `my_webserver` lié.
Rendre le Dockerfile, son contexte et le script de construction/lancement.

View file

@ -1,68 +1,18 @@
\newpage
# Composition de Docker
# But du TP
Docker est une suite d'outils de haut niveau, permettant d'utiliser très
facilement les conteneurs.
Aujourd'hui, nous allons terminer notre système de monitoring commencé lors du
premier TP.
Docker est composé d'un daemon lancé au démarrage de votre machine, avec lequel
vous interagissez via un client (le programme `docker`) qui se connecte au
daemon au moyen d'une socket. Le client peut donc potentiellement être sur une
machine distincte du daemon où s'exécutent les conteneurs.
Le résultat attendu d'ici la fin du TP, est un groupe de conteneurs
indépendants les uns des autres, réutilisables en fonction des besoins.
TODO image de graphana
## Les images Docker
Une image Docker est un système de fichiers en lecture seule. Il est formé d'un
ensemble de couches, agrégées par un UnionFS.
Par exemple, une image peut être un système Ubuntu complet ou juste busybox ou
encore un serveur web et votre application web, prêt à l'emploi.
Les images sont utilisées pour créer des conteneurs.
Il y a deux méthodes pour obtenir des images Docker : soit les construire avec
les outils fournis, soit les récupérer depuis un registre.
## Les conteneurs Docker
Alors que les images constituent la partie immuable de Docker, les conteneurs
sont sa partie vivante. Chaque conteneur est créé à partir d'une image : à
chaque fois que vous lancez un conteneur, une couche lecture/écriture est
ajoutée au dessus de l'image. Cette couche est propre au conteneur et est
temporaire : l'image n'est pas modifié par l'exécution d'un conteneur.
Chaque conteneur s'exécute dans un environnement restreint (namespaces,
cgroups, capabilities, ...).
## Les registres Docker (*Docker registries*)
Les registres sont des plates-formes de stockage, publiques ou privées,
contenant des images. Ils permettent de récupérer des images, mais également
d'en réceptionner.
Le registre utilisé de base est le [Docker Hub](https://hub.docker.com/) : il
contient à la fois des images officielles (ubuntu, debian, nginx, ...) et des
images crées par des utilisateurs.
## Outils annexes
En plus du Docker-engine, le daemon et client que nous allons utiliser
aujourd'hui, Docker développe également Docker-machine : qui permet d'installer
et configurer le daemon rapidement sur plusieurs machines (afin de les utiliser
au sein d'un cluster) et Docker-compose : qui permet de lancer un ensemble de
conteneurs dépend les uns des autres (par exemple un serveur web et sa base de
données).
## Rendu
1. À quoi correspondent les différents modes réseau utilisables dans Docker : à
quel type de réseau LXC (VLAN, MACVLAN-VEPA, veth, phys, ...)
correspondent-ils ? comment sont utilisés les *namespaces Network* ?
1. Quels sont les différents *storage drivers* de Docker ? décrivez-les en
quelques mots.
Nous reprendrons le script de monitoring que vous avez rendu au premier TP. Les
données collectées seront envoyés vers [https://influxdb.com/](InfluxDB), puis
elles seront affichées sous forme de graphique dans
[http://grafana.org/](Grafana). L'interface sera servie par un reverse-proxy
qui vous permettra de n'ouvrir que le port 80 ou 443, pour accéder à
l'interface d'administration d'InfluxDB et à l'interface de Grafana.