This commit is contained in:
nemunaire 2015-10-29 05:45:40 +01:00 committed by Pierre-Olivier Mercier
parent d3281c2d51
commit 6256e0eb0c
10 changed files with 564 additions and 0 deletions

19
tutorial/3/Makefile Normal file
View file

@ -0,0 +1,19 @@
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 \
--normalize \
--number-sections \
-M lang=frenchb \
-M fontsize=12pt \
-M papersize=a4paper \
--template=${TEMPLATE}
all: tutorial.pdf
tutorial.pdf: ${SOURCES}
pandoc ${PANDOCOPTS} -o $@ $+
clean::
rm tutorial.pdf

36
tutorial/3/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.

18
tutorial/3/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`.

85
tutorial/3/first.md Normal file
View file

@ -0,0 +1,85 @@
\newpage
# Premiers pas
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.
## Les caches
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.
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é.
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`.
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).
## `apt-get`
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 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).
## `RUN` ou script ?
### InfluxDB
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.
### Grafana
Une fois InfluxDB configuré, nous allons avoir la même réflexion avec
Grafana.
De la même manière, téléchargez, installez et supprimez le paquet.
Lors de vos tests, sachez que vous pouvez vous connecter sur grafana avec
l'utilisateur *admin*, mot de passe *admin*.

214
tutorial/3/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

@ -0,0 +1,31 @@
\newpage
# Installation
## `docker-compose`
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 :
```
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
```
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 :
```
42sh$ docker-compose --version
docker-compose version: 1.3.3
```
Si vous obtenez une réponse similaire, c'est que vous êtes prêt à commencer le
TP ! Alors n'attendons pas, partons à l'aventure !

63
tutorial/3/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/3/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 !

20
tutorial/3/tutorial.md Normal file
View file

@ -0,0 +1,20 @@
% Virtualisation légère -- TP n^o^3
% Pierre-Olivier *Nemunaire* Mercier
% Jeudi 29 octobre 2015
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 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.
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
vous devriez m'envoyer vos rendus signés avec votre clef PGP. Pensez à
[me](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x842807A84573CC96) faire
signer votre clef et n'hésitez pas à
[faire signer votre clef](http://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/).
\hypersetup{linkcolor=black}
\tableofcontents

18
tutorial/3/what.md Normal file
View file

@ -0,0 +1,18 @@
\newpage
# But du TP
Aujourd'hui, nous allons terminer notre système de monitoring commencé lors du
premier TP.
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
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.