Ready for j1

This commit is contained in:
nemunaire 2016-09-08 03:44:20 +02:00
commit dc8544c0c9
31 changed files with 690 additions and 546 deletions

View file

@ -1,13 +1,16 @@
SOURCES = tutorial.md installation.md lxc.md cgroups.md namespaces.md
TEMPLATE = ../../template.tex
SOURCES = tutorial.md installation.md what.md first.md dockerfile.md volumes.md linking.md cleaning.md rendu.md
PANDOCOPTS = --latex-engine=xelatex \
--standalone \
--normalize \
--number-sections \
--smart \
-M lang=frenchb \
-M fontsize=12pt \
-M papersize=a4paper \
--template=${TEMPLATE}
-M mainfont="Linux Libertine O" \
-M monofont="Inconsolata" \
-M sansfont="Linux Biolinum O" \
--include-in-header=../header.tex
all: tutorial.pdf

View file

@ -1,195 +0,0 @@
\newpage
# Utiliser les *cgroup*s
Les *cgroup*s (pour *Control Group*s) permettent de collecter des statistiques
sur des groupes de processus (appelés tâches) et de leur attribuer des
propriétés, comme par exemple pour leur imposer des limitations d'utilisation
de ressources ou altérer leurs priorités.
## Premiers tests
Nous allons commencer par faire quelques tests avec le *cgroup* freezer, qui
permet d'interrompre l'exécution d'un groupe de processus et de la reprendre.
### Montage du *cgroup*
En fonction de la configuration de votre système, il est possible que les
*cgroup*s ne soient pas montés au démarrage dans `/sys/fs/cgroup/`. Si vous n'avez
pas de dossier `freezer` ou si celui-ci est vide, monter-le en suivant la
procédure suivante :
```
mkdir /sys/fs/cgroup/freezer/
mount -t cgroup -o freezer none /sys/fs/cgroup/freezer/
```
Cette dernière commande monte le groupe de processus racine, pour le *cgroup*
freezer. Tous les dossiers contenu dans cette racine sont des sous-groupes de
cette dernière.
### Création d'un nouveau groupe
La première étape dans l'utilisation d'un *cgroup* est de créer un nouveau
groupe.
Pour créer un groupe, il suffit de créer un nouveau dossier dans un groupe
existant, par exemple la racine :
```
mkdir /sys/fs/cgroup/freezer/virli/
ls /sys/fs/cgroup/freezer/virli/
```
Vous avez maintenant un nouveau groupe de processus `virli` dans le *cgroup*
Freezer. Comme il s'agit d'une hiérarchie, le groupe `virli` hérite des
propriétés de son (ses) père(s).
### Rattachement de processus
Pour le moment, ce nouveau groupe ne contient aucune tâche.
Ouvrons un nouveau terminal (c'est lui que l'on va freezer), et récupérons son
PID : `echo $$`.
La liste des processus rattachés à un *cgroup* se trouve dans le fichier `task`
du groupe. Pour ajouter une tâche à ce groupe, cela se passe de cette manière :
```
echo $PID > /sys/fs/cgroup/freezer/virli/tasks
```
Il faut ici remplacer `$PID` par le PID du shell que l'on a relevé juste avant.
En validant cette commande, vous avez déplacé le processus dans ce groupe, il
n'est alors plus dans aucun autre groupe (dans ce *cgroup*, il ne bouge pas
dans les autres *cgroup*s).
### Consultation de l'état
En affichant le contenu du dossier `virli`, nous avons pu constater que
celui-ci contenait déjà un certain nombre de fichiers. Certain d'entre-eux sont
en lecture seule et permettent de lire des statistiques instantanées sur le
groupe ; tandis que d'autres sont des propriétés que vous pouvez modifier.
Nous pouvons consulter l'état de gel du groupe en affichant le contenu du
fichier\newline `/sys/fs/cgroup/freezer/virli/freezer.state`.
Pour plus d'information sur les différents fichiers présents dans ce *cgroup*,
consulter la documentation, accessible ici :
<https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt>
### Changement d'état
Faisons exécuter à notre interpréteur une commande pour voir effectivement
l'exécution s'arrêter. Si vous manquez d'inspiration, utilisez :
```
for i in $(seq 9999); do echo -n $i; sleep .1; echo -n " - "; sleep .1; done
```
Maintenant, nous avons donné l'ordre au noyau de ne plus allouer de temps de
calcul à notre shell et ses fils :
```
echo FROZEN > /sys/fs/cgroup/freezer/virli/freezer.state
```
À cet instant, vous devriez voir votre compteur s'arrêter. Pour reprendre
l'exécution :
```
echo THAWED > /sys/fs/cgroup/freezer/virli/freezer.state
```
## Script de monitoring
À nous maintenant de concevoir un script qui va enregistrer vers la base de
données créée (*metrics*) dans la partie précédente, des statistiques issues
des *cgroup*s.
### Monitoring instantané vers la console
Dans un premier temps, commençons par afficher dans la console la quantité de
mémoire utilisée par le groupe monitoré.
* Arguments de la ligne de commande :
- premier fils à lancer dans le groupe,
- intervalle de temps entre deux rafraîchissement ;
* *cgroup* `memory`;
* `memory.usage_in_bytes`.
Vous pouvez utiliser un programme comme `memhog` pour remplir rapidement votre
mémoire.
Si vous n'avez pas le *cgroup* memory, il est possible qu'il ne soit
pas activé par défaut par votre système. Si vous êtes dans ce cas, essayez d'ajouter
```
cgroup_enable=memory
```
### Monitoring vers InfluxDB
Maintenant, envoyons nos données vers la base
<https://influxdb.com/docs/v0.9/guides/writing_data.html> :
```
curl -i -XPOST 'http://172.23.42.2:8086/write?db=metrics' --data-binary \
"$my_cgroup_name memory.usage_in_bytes=$(cat .../my_cgroup_name/memory.usage_in_bytes)"
```
Pour vérifier que les données sont bien ajoutées, vous pouvez effectuez la
requête suivante dans l'interface web d'InfluxDB :
```
SELECT * from "$my_cgroup_name";
```
### Monitorer davantage de données
Liste non exhaustive de données à monitorer :
* Nombre d'IOs effectué ;
* nombre d'octets lu/écrit sur les disques ;
* temps de calcul utilisé ;
* trafic réseau généré ;
* ...
<https://www.kernel.org/doc/Documentation/cgroups/>
### Permettre à l'utilisateur de monitorer des process
Maintenant, séparer votre script en deux parties afin qu'un utilisateur normal
(non-root) puisse utiliser la partie monitoring de notre script.
Un premier script doit s'occuper de créer le(s) *cgroup*s et lui attribuer les
bons droits, tandis que le deuxième va utiliser effectuer le monitoring, sans
#### Exemple
```
42sh# ./monitor_init my_cgroup_name
42sh$ ./monitor my_cgroup_name memhog 500
```
## Rendu
### Script de monitoring
Rendez la révision la plus avancée de vos scripts de monitoring de process via
les *cgroup*s.
### Questions
1. Un même processus peut-il être dans plusieurs *cgroup*s de type différents
(freezer et cpuacct par exemple) ?
1. Que sera-t-il possible de limiter via un nouveau *cgroup* dans la prochaine
version du noyau (4.3) ?
1. Actuellement, comment peut-on limiter le nombre de processus lancés par un
utilisateur ou un groupe ?

47
tutorial/1/cleaning.md Normal file
View file

@ -0,0 +1,47 @@
\newpage
Faire le ménage
===============
Au fur et à mesure de vos tests, la taille utilisée par les données de Docker
peut devenir conséquente et son interface peut commencer à déborder
d'informations dépréciées.
Dans la mesure du possible, Docker essaie de ne pas encombrer inutilement votre
disque dur avec les vieilles images qui ne sont plus utilisées. Il ne va
cependant jamais supprimer une image encore lié à un conteneur, ni les
conteneurs qui n'auront pas été démarrés avec `--rm`.
## Conteneurs
Vous pouvez afficher l'ensemble des conteneurs, quelque soit leur état (en
cours d'exécution, arrêté, ...) avec la commande suivante :
```
42sh$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
552d71619723 hello-world "/hello" 4 days ago Exited (0) 4 days ago dreamy_gates
0e8bbff6d500 debian "/bin/bash" 2 weeks ago Exited (0) 2 weeks ago cranky_jones
```
IL y a de fortes chances pour que vous n'ayez plus besoin de ces vieux
conteneurs. Pour les supprimer, utilisez la commande :
```
docker rm 0e8bbff6d500 552d71619723
```
ou encore :
```
docker rm cranky_jones dreamy_gates
```
## Images
Les vieilles images qui n'ont plus de références sur elles (ni tag, ni
conteneur lié) sont automatiques supprimées. Vous n'avez généralement pas à
vous occuper de faire du nettoyage dans les images. Néanmoins, vous pouvez les
lister avec la commande `docker images` et en supprimer grâce à la commande
`docker rmi`.

170
tutorial/1/dockerfile.md Normal file
View file

@ -0,0 +1,170 @@
\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`
Dans un `Dockerfile`, chaque ligne est exécutée indépendamment des
autres et correspondra à une nouvelle couche de notre image.
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
```

145
tutorial/1/first.md Normal file
View file

@ -0,0 +1,145 @@
\newpage
Mon premier conteneur
=====================
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.
## Exécuter un programme dans un conteneur
Maintenant que nous avons à notre disposition l'image d'un conteneur Ubuntu,
lançons-la !
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 :
```
docker run ubuntu /bin/echo "Hello World"
```
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.
## Modifier un conteneur
À 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.
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` !

View file

@ -1,55 +1,107 @@
\newpage
# Installation
Installation
============
## Noyau Linux
## Prérequis
Ce TP requiert un noyau Linux 3.8 au minimum. De plus, il doit être
compilé avec les options suivantes :
Docker repose sur plusieurs techniques implémentés dans les récents noyaux
Linux. Nous consacrerons les prochains cours à comprendre leurs
fonctionnement. Ces techniques ne sont pas limitées à une architecture de
microprocesseur spécifique (comme peuvent l'être les instructions de
virtualisation nécessaire pour rendre les hyperviseurs attractifs) ; cependant
la communauté autour de Docker utilisant principalement l'architecture `amd64`,
c'est sur cette dernière que Docker pourra être exploité à son plein potentiel.
Avant de continuer, assurez-vous que votre machine a bien démarré sur un noyau
64 bits. Le retour de la commande `uname -m` doit vous indiquer :
```
General setup --->
[*] Control Group support --->
[*] Freezer cgroup subsystem
[*] Device controller for cgroups
[*] Cpuset support
[*] Include legacy /proc/<pid>/cpuset file
[*] Simple CPU accounting cgroup subsystem
[*] Group CPU scheduler --->
[*] Group scheduling for SCHED_OTHER
[*] Group scheduling for SCHED_RR/FIFO
<*> Block IO controller
-*- Namespaces support
[*] UTS namespace
[*] IPC namespace
[*] User namespace
[*] PID Namespaces
[*] Network namespace
[*] Networking support --->
Networking options --->
<*> 802.1d Ethernet Bridging
<M> 802.1Q VLAN Support
[*] Network priority cgroup
[*] Network classid cgroup
Device Drivers --->
[*] Network device support --->
<M> MAC-VLAN support
<*> Virtual ethernet pair device
Character devices --->
-*- Unix98 PTY support
[*] Support multiple instances of devpts
x86_64
```
Une fois que vous aurez installé LXC, vous pouvez vérifier la compatibilité de
la configuration de votre noyau en utilisant la commande `lxc-checkconfig`.
Assurez-vous également d'avoir un noyau récent, avec la commande `uname -r` :
```
4.7.2-gentoo
```
Vous ne pourrez pas utiliser Docker avec un noyau antérieur à la version 3.10.
## LXC
## Par le gestionnaire de paquets
Pour installer LXC, utilisez le gestionnaire de paquets de votre
distribution. Toute les bonnes distributions fournissent un paquet
`lxc`. Vérifiez que la version installée est au moins la 1.0.
En général, votre distribution mettra à votre disposition une version de Docker
plus ou moins récente. Sous Debian et ses dérivés (Ubuntu, Mint, ...) le paquet
a été nommé `docker.io`.
Aucune configuration ne devrait vous être demandé durant l'installation. Une
fois installé, exécutez la commande `lxc-checkconfig` pour vérifier que votre
noyau possède bien toutes les options nécessaires.
Si dans un environnement de production, on préférera sans doute utiliser une
version déjà bien éprouvée, pour ce cours, nous allons avoir besoin de la
dernière version disponible :
<https://docs.docker.com/engine/installation/linux/>
Installez également le paquet `docker-compose`.
## 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.12.1
API version: 1.24
Go version: go1.7
Git commit: 23cf638
Built:
OS/Arch: linux/amd64
Server:
Version: 1.12.1
API version: 1.24
Go version: go1.7
Git commit: 23cf638
Built:
OS/Arch: linux/amd64
```
### `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/engine/security/security/#docker-daemon-attack-surface>
## Rendu
### Questions
1. Dans quel langage Docker a-t-il été écrit ?
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.

36
tutorial/1/linking.md Normal file
View file

@ -0,0 +1,36 @@
\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/>.

View file

@ -1,214 +0,0 @@
\newpage
# Utiliser `lxc`
Le but de cette première partie est d'appréhender la virtualisation légère au
travers d'un programme, `lxc`, qui va mettre en place pour nous tout un
environnement distinct.
## Lancer un conteneur
Avec le paquet `lxc` que nous avons précédemment installé, nous avons également
récupéré un certain nombre de *modèles* de système (souvent installés dans le
dossier `/usr/share/lxc/templates/`) : il s'agit d'une suite de commandes
(principalement des `wget`, `chroot` ou `debootstrap`) permettant d'obtenir un
système basic fonctionnel, en suivant les étapes d'installation habituelle de
la distribution.
La méthode la plus simple pour lancer un conteneur `lxc` est d'utiliser l'un de
ces modèles pour obtenir un nouveau système. On utilise pour cela la commande
`lxc-create` :
```
lxc-create --name toto_first --template debian
```
Ce modèle va créer un dossier dans `/var/lib/lxc/` (pouvant varier d'une
distribution à l'autre) portant le nom que nous avons précisé. Ce dossier va
contenir la configuration `lxc` du conteneur (`config`), la table des
partitions (`fstab`) s'il y a besoin de faire des montages particuliers et
enfin le dossier `rootfs` contenant le système en lui-même.
Une fois l'installation terminée, on peut démarrer le conteneur :
```
lxc-start --name toto_first
```
`lxc` va appeler `/sbin/init` et démarrer tous les services que l'on peut
s'attendre à trouver dans n'importe quelle machine virtuelle (et même physique)
plus classique (la seule différence réside donc dans le fait que le noyau est
partagé avec l'hôte).
Généralement on lance `lxc-start` avec l'option `--daemon`, car on n'a pas
vraiment envie d'avoir un conteneur bloquant un terminal. En mode daemon, on va
utiliser la commande `lxc-console` pour nous attacher aux conteneurs. À tout
moment, nous pouvons nous détacher de la console (sans que cela n'affecte
l'état du conteneur) en pressant les touches : `^A q`.
Connectons-nous, lancons quelques commandes puis éteignons la machine en
lançant la commande `poweroff` dans le conteneur. Il est également possible de
lancer la commande `lxc-stop --name toto_first` dans un autre terminal, depuis
la machine hôte.
## Le réseau
Le modèle *Debian*, que nous avons utilisé, préremplit un fichier de
configuration sans définir de paramètre pour le réseau. Il n'y a donc pas
d'interface dans le conteneur pour le connecter :
```
lxc.network.type = empty
```
Un excellent article détaillant les différents types de configuration réseau
est accessible à
<https://blog.flameeyes.eu/2010/09/linux-containers-and-networking>.
N'ayant qu'une seule interface physique sur la machine et n'ayant pas accès à
la configuration des VLAN de la pièce, il ne nous reste que deux méthodes pour
obtenir du réseau dans nos conteneurs : Virtual Ethernet ou MACVLAN.
### MACVLAN
Cette méthode est la plus simple : le noyau va orienter les paquets en fonction
de leur adresse MAC de destination. Le conteneur sera donc comme une machine
supplémentaire sur le réseau.
Modifions notre fichier de configuration afin qu'il ressemble à quelque chose
comme :
```
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.flags = up
lxc.network.link = eth0
```
Après avoir démarré le conteneur, il devrait avoir obtenu une IP du serveur
DHCP de l'école. L'inconvénient dans cette configuration est qu'il faille un
client netsoul dans chaque conteneur, puisque chacun est considéré comme une
machine différente aux yeux du routeur.
### Virtual Ethernet
Virtual Ethernet est la configuration la moins optimale, mais sans doute la
plus flexible.
Voici un extrait de configuration correspondant au paramétrage d'une interface
virtuelle pour un conteneur donné :
```
lxc.network.type = veth
lxc.network.ipv4 = 172.23.42.2/24
lxc.network.flags = up
```
Dans cette situation, au démarrage du conteneur, `lxc` va créer une interface
veth, avec un côté placé dans la machine hôte et l'autre côté placé dans le
conteneur. `lxc` configure l'interface dans le conteneur, il nous appartient
ensuite de configurer la machine hôte.
Commençons par attribuer une IP à cette nouvelle interface, en adaptant à votre
identifiant d'interface :
```
ip addr add 172.23.42.1/24 dev vethYJWD6R
```
À partir de là, nous devrions pouvoir pinger notre conteneur depuis notre
machine hôte : `ping 172.23.42.2`.
Notre conteneur ne peut cependant pas encore accéder à Internet. Pour cela, la
machine hôte doit faire office de routeur et donc router les paquets d'un
réseau à l'autre : en l'occurence, du réseau 172.23.42.1 vers Internet
via 10.0.0.0/8, le réseau de l'école.
Pour que notre machine hôte route les paquets, exécuter la commande :
```
sysctl -w net.ipv4.ip_forward=1
```
Cette variable, que nous retrouvons dans `/proc/sys/net/ipv4/ip_forward`,
indique au noyau qu'il peut faire passer les paquets réseau d'une interface à
l'autre. Sans plus de directives, les paquets vont conserver leur adresse
source (172.23.42.2 pour les paquets en provenance du conteneur). Cette adresse
est une adresse privée, non routable sur Internet, ni même par le bocal. Il
faut donc ajouter une couche de NAT/PAT pour réécrire les adresses sources
avant d'envoyer les paquets sur internet :
```
iptables -t nat -A POSTROUTING ! -o vethYJWD6R -s 172.23.42.0/24 -j MASQUERADE
```
Dernière étape, dans notre conteneur, nous devons indiquer la route à utiliser
pour accéder à internet :
```
ip route add default via 172.23.42.1
```
Nous avons maintenant internet dans notre conteneur !
## Utilisation du conteneur
### Installation de InfluxDB
```
apt-get update
apt-get install wget
wget https://s3.amazonaws.com/influxdb/influxdb_0.9.4.2_amd64.deb
dpkg -i influxdb_0.9.4.2_amd64.deb
```
### Test de l'installation
```
/opt/influxdb/influxd
```
Une fois que le service est démarré, vous devriez pouvoir accéder à l'interface
à : <http://172.23.42.2:8083/>
Créons une nouvelle base de données "metrics", elle nous servira dans la partie suivante.
## Rendu
### Configuration du conteneur
En plus des modifications que vous avez effectuées durant le TP, modifiez la
configuration du conteneur afin qu'il ne puisse pas utiliser plus que 256 MB de
RAM et 512 MB de swap.
Limitez ensuite les `capabilities(7)` de ce conteneur afin qu'il s'exécute avec
le strict minimum de droits, nécessaire au bon fonctionnement des programmes
installés.
**Rendez le fichier `config` de ce premier conteneur.** N'hésitez pas à laisser
des commentaires justifiant vos éventuels choix.
### Questions
1. Quels sont les autres types de virtualisation réseau existants ? Expliquez
en chacun une phrase leurs particularités.
1. Quel fichier de configuration devriez-vous changer pour rendre persistante la
valeur d'`ip_forward` ?
1. Dans quel langage InfluxDB a-t-il été écrit ? Quelle est la particularité
des binaires générés par ce langage ?
1. Quels sont les avantages et les inconvénients associés au linkage statique
et au linkage dynamique ? (pas forcément que dans le cadre de la
virtualisation légère).
1. J'ai utilisé la méthode *Virtual Ethernet* pour relier mes conteneurs à
Internet, via l'interface `br0`. Quelle(s) règle(s) `iptables` devrais-je
écrire sur mon hôte afin de permettre l'accès à InfluxDB depuis une autre
machine ?

View file

@ -1,133 +0,0 @@
\newpage
# Utiliser les *namespaces*
## Comparaison de *namespace*
Écrivez un script ou un programme, `cmpns`, dans le langage courant de votre
choix, permettant de déterminer si deux programmes s'exécutent dans les mêmes
*namespaces*.
### Exemples
```sh
42sh$ cmpns $(pgrep influxdb) $(pgrep init)
- ipc: differ
- mnt: differ
- net: differ
- pid: differ
- user: same
- uts: same
```
```sh
42sh$ cmpns $(pgrep init) self
- ipc: same
- mnt: same
- net: same
- pid: same
- user: same
- uts: same
```
Ici, `self` fait référence au processus actuellement exécuté.
## Rejoindre un *namespace*
Dans le langage courant de votre choix, écrivez un programme : `setns`,
permettant, à la manière de `unshare(1)` et `unshare(2)`, d'utiliser `setns(2)`
via votre interpréteur.
Les options attendues sont :
* rejoindre un *namespace* IPC : `-i`, `--ipc` ;
* rejoindre un *namespace* mount : `-m`, `--mount` ;
* rejoindre un *namespace* net : `-n`, `--net` ;
* rejoindre un *namespace* PID : `-p`, `--pid` ;
* rejoindre un *namespace* UTS : `-u`, `--uts` ;
* rejoindre un *namespace* user : `-U`, `--user`.
### Exemples
```sh
42sh# setns /bin/bash
bash# _
```
#### IPC and PID Namespaces
```sh
42sh# setns --ipc=/proc/42/ns/ipc -p /proc/42/ns/pid /bin/echo toto
toto
```
#### Net Namespace
```sh
42sh# setns --net=/proc/42/ns/net ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00::00
inet 127.0.0.1/8 brd 127.255.255.255 scope lo
valid_lft forever preferred_lft
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
```
#### UTS Namespace
```sh
42sh# hostname --fqdn
koala.zoo.paris
42sh# setns --uts=/proc/42/ns/uts hostname --fqdn
lynx.zoo.paris
```
## My Little Container
En utilisant le langage courant de votre choix, concevez l'exécutable `mlc`,
permettant de lancer une application dans un environnement différent (comme un
`chroot`, mais sans permettre de s'en échapper) et avec des privilèges réduits.
Votre solution doit créer au moins un nouveau *namespace* mount et PID.
Vous aurez sans doute besoin de : `clone(2)`, `capabilities(7)`, `capset(2)`, `pivot_root(2)`,
### Exemples
```sh
42sh# ls newroot
bin etc home usr root proc var
42sh# mlc newroot/ /bin/bash
bash# ls ../../../
bin etc home usr root proc var
bash# escape_chroot ls
bin etc home usr root proc var
bash# ls -ld /proc/[0-9]* | wc -l
2
bash# curl http://www.linuxcontainers.org/ | md5sum
0123456789abcdef
bash# ping 8.8.8.8
Operation not permitted
```
## Rendu
Pour chaque exercice de cette partie, vous pouvez rendre un seul fichier s'il
s'agit d'un script ; sinon, vous devez rendre une tarball contenant un
`Makefile` permettant de générer les éventuels exécutables et/ou un `README`
expliquant comment s'en servir.
Vous devez donc rendre 3 fichiers : `cmpns` ou `cmpns.tar.bz2`, `setns` ou
`setns.tar.bz2` et `mlc` ou `mlc.tar.bz2`.
\vspace{3em}
Bon courage !

114
tutorial/1/rendu.md Normal file
View file

@ -0,0 +1,114 @@
\newpage
Sujet à rendre
==============
## Projet
Écrivez un `Dockerfile` pour conteneriser un client netsoul.
Vous pouvez utiliser le client de votre choix, comme par exemple
[tumsoul.py](https://virli.nemunai.re/misc/tumsoul_0.3.3.py).
Une fois construit, le conteneur doit se lancer comme cela :
```
docker run -e NETSOUL_LOGIN="login_x" -e PASSSOCKS="xnigol42" netsoul
```
Passer la configuration d'un conteneur dans des variables d'environnement est
une méthode couramment utilisée. Ces variables sont récupérées et traitées par
le script d'ENTRYPOINT. Ce qui permet d'utiliser la ligne de commande pour
d'autres choses.
## Modalité de rendu
Un service automatique s'occupe de réceptionner vos rendus, de faire les
vérifications nécessaires et de vous envoyer un accusé de réception (ou de
rejet).
Ce service écoute sur l'adresse <virli@nemunai.re>, c'est donc à cette adresse
et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu
envoyé à une autre adresse et/ou non signé ne sera pas pris en compte.
## Tarball
Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
placer dans une tarball (pas d'archive ZIP, RAR, ...).
Les réponses aux questions sont à regrouper dans un fichier `questions.txt` à
placer à la racine de votre rendu.
Voici une arborescence type:
```
login_x-TP1/questions.txt
login_x-TP1/webserver
login_x-TP1/webserver/Dockerfile
login_x-TP1/webserver/index.html
login_x-TP1/webserver/datavolume-run.sh
login_x-TP1/netsoul
login_x-TP1/netsoul/Dockerfile
login_x-TP1/netsoul/docker-entrypoint.sh
login_x-TP1/netsoul/tumsoul.py
```
## Signature du rendu
Deux méthodes sont utilisables pour signer votre rendu :
* signature du courriel ;
* signature de la tarball.
Dans les deux cas, si vous n'en avez pas déjà une, vous devrez créer une clef
PGP à votre nom et prénom.
Pour valider la signature, il est nécessaire d'avoir reçu la clef publique
séparément. Vous avez le choix de l'uploader sur un serveur de clefs, soit de
me fournir votre clef en main propre, soit l'envoyer dans un courriel distinct.
### Signature du courriel
[Enigmail](https://enigmail.net) est une extension très bien réputée pour
signer ses mails depuis Thunderbird.
Utilisez le service automatique <signcheck@nemunai.re> pour savoir si votre
courriel est correctement signé et que je suis en mesure de vérifier la
signature.
### Astuces
#### No public key
Si vous recevez un rapport avec l'erreur suivante :
```
[FAIL] Bad signature. Here is the gnupg output:
gpg: Signature made Tue Jan 01 16:42:23 2014 CET
gpg: using RSA key 842807A84573CC96
gpg: requesting key E2CCD99DD37BD32E from hkp server pool.sks-keyservers.net
gpg: Can't check signature: No public key
```
C'est que votre clef publique n'est pas dans mon trousseau et que les méthodes
de récupérations automatiques n'ont pas permis de la trouver. Uploadez votre
clef sur un serveur de clefs (et attendez quelques minutes sa propagation) ou
envoyez un courriel avec votre clef publique en pièce-jointe, avant de retenter
votre rendu.
#### Not explicit username
Si vous recevez un rapport avec l'erreur suivante :
```
[FAIL] The username of your key is not explicit, I can't find you.
```
Votre clef ne contient sans doute pas vos noms et prénoms ou l'adresse
électronique associée à la clef n'est pas celle que j'ai dans ma base de
données.

2
tutorial/1/todo.org Normal file
View file

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

View file

@ -1,21 +1,27 @@
% Virtualisation légère -- TP n^o^ 1
% Pierre-Olivier *nemunaire* Mercier
% Jeudi 8 octobre 2015
---
title: Virtualisation légère -- TP n^o^ 1
subtitle: Les bases de Docker
author: Pierre-Olivier *Nemunaire* Mercier
institute: EPITA
date: Jeudi 8 septembre 2016
...
Le but de ce premier TP est d'utiliser les commandes et les appels systèmes vu
durant le cours.
Durant ce premier TP, nous allons apprendre à utiliser Docker !
Tous les éléments de ce TP (exercices et questions) sont à rendre à
<virli@nemunai.re> au plus tard le **mercredi 21 octobre 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 texte joint.
<virli@nemunai.re> au plus tard le jeudi 15 septembre 2016 à 8 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 à
En tant que personnes sensibilisées à la sécurité des échanges
électroniques, vous devrez 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/).
Vous pouvez utiliser l'adresse <signcheck@nemunai.re> pour savoir si
vous vous y prenez correctement.
\hypersetup{linkcolor=black}
\tableofcontents

115
tutorial/1/volumes.md Normal file
View file

@ -0,0 +1,115 @@
\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.

84
tutorial/1/what.md Normal file
View file

@ -0,0 +1,84 @@
\newpage
Composition de Docker
=====================
Docker est une suite d'outils de haut niveau, permettant d'utiliser les
conteneurs.
Docker est composé d'un daemon lancé au démarrage de votre machine, avec lequel
vous interagissez via un client (le programme `docker`). La communication entre
le daemon et le client s'effectuant au travers d'une socket, le client peut ne
pas être forcément sur la machine qui exécutera effectivement les conteneurs.
C'est ce qu'il se passe lorsque vous utilisez Docker4Windows et Docker4Mac :
une machine virtuelle Linux est lancé parallèlement à votre système et chaque
commande `docker` que vous tapez est passé au deamon dans la machine virtuelle.
## `runc` et `containerd`
La notion de conteneurs est maintenant normalisées par
[l'Open Container Initiative](https://opencontainers.org).
Docker lance des conteneurs respectant cette norme grâce au programme `runc`.
Toute la gestion de l'exécution du conteneur est délégué au programme
`containerd`, également issue de l'initiative. C'est lui aussi un daemon (géré
par Docker), dont le but est de monitorer les conteneurs lancés (pour les
relancer en cas de crash par exemple) ou encore de récupérer les logs de chaque
conteneur.
## 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 selon le principe d'UnionFS.
Une image peut, par exemple, être un système Ubuntu complet ou juste contenir
le programme 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 et distinct de
l'environnement principal où vous avez votre bureau. Par exemple, dans cet
environnement, vous ne pouvez pas voir les processus qui n'y sont pas.
## 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) ;
* **Docker-swarm :** désormais intégré à Docker (depuis la version 1.12), cela
permet de gérer un cluster de machine et de faire de l'orchestration ;
* **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).