Tuto 1: wip

This commit is contained in:
nemunaire 2015-10-07 03:45:39 +02:00
parent 7752f11472
commit 50913a7493
8 changed files with 419 additions and 602 deletions

19
tutorial/1/Makefile Normal file
View File

@ -0,0 +1,19 @@
SOURCES = tutorial.md installation.md lxc.md cgroups.md namespaces.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

50
tutorial/1/cgroups.md Normal file
View File

@ -0,0 +1,50 @@
\newpage
# Utiliser les *cgroups*
## Premiers tests
### Création d'un nouveau groupe
### Rattachement de processus
### Consultatation de l'état
### Changement d'état
## Script de monitoring
### Monitoring instantanné vers la console
#### Option pour choisir l'intervalle de polling
### Monitoring vers InfluxDB
### Permettre à l'utilisateur de monitorer des process
#### Séparer en deux scripts
#### Ajouter les bonnes capabilities sur le premier script
#### Rendre ok avec les exécutions concurrentes du second script
### Monitorer chaque fils indépendamment
## Rendu
### Script de monitoring
Rendez la révision la plus avancées de vos scripts de monitoring de process via
les *cgroups*.
### Questions
1. Un même processus peut-il être dans plusieurs cgroups 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. Comment peut-on limiter le nombre de processus lancés par un utilisateur ou
un groupe ?

View File

@ -0,0 +1,52 @@
\newpage
# Installation
## Noyau Linux
Ce TP requiert d'avoir un noyau Linux en version 3.8 au minimum. De plus, il
doit être compilé avec les options suivantes :
```
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
Device Drivers --->
[*] Network device support --->
<M> MAC-VLAN support
<*> Virtual ethernet pair device
Character devices --->
-*- Unix98 PTY support
[*] Support multiple instances of devpts
```
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`.
## LXC
Pour installer LXC, utilisez le gestionnaire de paquets de votre
distribution. Toute les bonnes distributions fournissent un paquet `lxc`.
Aucune configuration ne devrait vous être demandé durant l'installation. Une
fois l'installation terminée, exécutez la commande `lxc-checkconfig` pour
vérifier que votre noyau possède bien toutes les options nécessaires.

148
tutorial/1/lxc.md Normal file
View File

@ -0,0 +1,148 @@
\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 un
environnement distinct.
## Lancer un conteneur
Avec le paquet LXC que vous avez installé, vous avez également récupéré un
certain nombre de modèles de système (souvent installés dans le dossier
`/usr/share/lxc/templates/`).
La méthode la plus simple pour lancer un conteneur LXC est d'utiliser l'un de
ces modèles qui va installer tout un environnement pour vous. On utilise pour
cela la commande `lxc-create` :
```
lxc-create --name toto_first --template ubuntu
```
Ce modèle va créer un dossier dans `/var/lib/lxc/` portant le nom que vous avez
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 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 ne s'attend
pas à ce que la machine s'arrête lorsque l'on va quitter notre terminal. Dans
ce cas, utiliser la commande `lxc-console` pour vous attacher à votre
conteneur. À tout moment, vous pouvez vous détacher de la console (sans que
cela n'affecte l'état du conteneur) en pressant les touches : `^A q`.
Connectez-vous, lancez quelques commandes puis éteignez la machine avec `sudo
poweroff` dans le conteneur. Vous pouvez aussi lancer la commande `lxc-stop
--name toto_first` dans un autre terminal, depuis la machine hôte.
## Le réseau
Le modèle ubuntu que vous avez utilisé initialise un fichier de configuration
sans paramètres pour le réseau. Vous n'avez donc pas d'interface dans le
conteneur pour le connecter au réseau.
Un excellent article détaillant les différents types de 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. Malheureusement, ces deux méthodes nécessitent de mettre en place un
pont Ethernet sur votre machine :
### Installation du pont réseau
#### Bridge interface
#### Port forwarding
### Virtual Ethernet
Virtual Ethernet est la configuration la plus simple. On met en place un pont
sur la machine hôte, puis on crée une interface `veth` par conteneur que l'on
veut lancer. On n'oubliera pas d'ajouter ces interfaces au pont.
Voici un extrait de configuration correspondant au paramétrage d'une interface
`eth0` pour un conteneur donné :
```
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
```
Cette technique a pour inconvénient de laisser au noyau le soin de router les
paquets selon leur adresse IP, ce qui peut être lent et coûteux étant donné que
la carte est placé en mode de promiscuité.
### MACVLAN
Ici, le noyau va orienter les paquets en fonction de leur adresse MAC de
destination.
```
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.flags = up
lxc.network.link = br0
```
## Utilisation du conteneur
### Redémarrage du conteneur
### Installation de InFluxDB et Graphana
### Test de l'installation
## 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. Quel type de virtualisation réseau avez-vous utilisé ? pourquoi ?
1. Quels sont les autres types de virtualisation réseau existants ? Expliquez
en chacun une phrase leurs particularités.
1. Dans quel langage InfluxDB a-t-il était é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 un PAT (`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 ?

129
tutorial/1/namespaces.md Normal file
View File

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

21
tutorial/1/tutorial.md Normal file
View File

@ -0,0 +1,21 @@
% Virtualisation légère -- TP n^o^ 1
% Pierre-Olivier *nemunaire* Mercier
% Jeudi 8 octobre 2015
Le but de ce premier TP est d'utiliser les commandes et les appels systèmes vu
durant le cours.
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 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

View File

@ -1,9 +0,0 @@
all: tutorial.pdf
.md.pdf:
pandoc --latex-engine=xelatex --toc --normalize -M lang=frenchb --standalone -N --template=../template.tex -M fontsize=12pt -M papersize=a4paper -o $@ $<
.md.tex:
pandoc --latex-engine=xelatex --toc --normalize -M lang=frenchb --standalone -N --template=../template.tex -M fontsize=12pt -M papersize=a4paper -o $@ $<
.SUFFIXES: .md .tex .pdf

View File

@ -1,593 +0,0 @@
% Virtualisation légère
% Pierre-Olivier *Nemunaire* Mercier
% Samedi 29 novembre 2014
# Installation
## Noyau Linux
Ce TP requiert d'avoir un noyau Linux en version 3.8 au minimum. De plus, il
doit être compilé avec les options suivantes :
```
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 --->
<M> 802.1d Ethernet Bridging
<M> 802.1Q VLAN Support
Device Drivers --->
[*] Network device support --->
<M> MAC-VLAN support
<*> Virtual ethernet pair device
Character devices --->
-*- Unix98 PTY support
[*] Support multiple instances of devpts
```
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`.
## Docker
### Par le gestionnaire de paquets
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.
### 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.2
Client API version: 1.15
Go version (client): go1.3.3
Git commit (client): 39fa2fa
OS/Arch (client): linux/amd64
Server version: 1.3.2
Server API version: 1.15
Go version (server): go1.3.3
Git commit (server): 39fa2fa
```
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
```
## LXC
Votre distribution fournit sans doute un paquet `lxc`.
# Docker
Docker est un outil haut niveau permettant de faire fonctionner facilement les
conteneurs.
## Composition de Docker
Docker est un daemon lancé au démarrage de votre machine, avec lequel vous
interagissez via un client qui se connecte au daemon au moyen d'une socket (le
client peut donc être sur une machine distincte du daemon où sont exécutés les
conteneurs).
## Mon premier conteneur
Afin de tester la bonne marche de votre installation, exécutez la commande :
```
docker run hello-world
```
Cette commande va automatiquement exécuter une série de commandes pour vous,
comme indiqué dans le message affiché en retour :
D'abord, le démon 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 à votre 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.
Vous pouvez directement utiliser le client pour rechercher une image sur le
hub, en utilisant la commande `search` :
```
docker search mariadb
```
Vous pouvez mettre à jour vos 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 vous disposez localement (soit
parce que vous les avez téléchargées, soit parce que vous les avez
créées vous-même), utilisez la commande `images` :
```
docker images
```
Vous devez 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, vous avez 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 vous 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é (et non pas le programme `/bin/echo` de la machine hôte qui est
transféré dans le conteneur).
## Modifier un conteneur
À chaque fois que vous lancez un `run`, un nouveau conteneur est créé à partir
de l'image que vous précisez (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 vous exécutez une commande modifiant le contenu d'un conteneur, cela ne
modifie pas l'image de base, mais crée une nouvelle image. Que vous pouvez
ensuite utiliser comme image de base.
Commençons par entrer dans un nouveau conteneur pour modifier l'image :
```
docker run -it ubuntu /bin/bash
```
Vous voilà maintenant dans le conteneur ! Il est assez épuré, il n'y a rien de
superflu : vous n'avez pas d'éditeur de texte : ni vim, ni emacs, même pas vi !
La première chose à faire est de mettre à jour la liste des paquets :
```
apt-get update
```
Il peut arriver que des paquets présents dans l'image officielle 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, 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, jetez 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` !
## 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. 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. 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 lancer le processus.
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
```
Consultez <https://docs.docker.com/reference/builder/> pour la liste complète
des instructions reconnues.
## 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`.
## 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).
### Data Volume Container
Dans de nombreuses situation, 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.
## 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
[ici](https://docs.docker.com/userguide/dockerlinks/#environment-variables).
On utiliser 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
(ici)[https://docs.docker.com/articles/ambassador_pattern_linking/].
# LXC
Contrairement à Docker, l'utilisation de LXC est beaucoup plus proche de
l'administration système classique, car l'approche est beaucoup plus bas
niveau.
## Lancer un conteneur
Avec le paquet LXC que vous avez installé, vous avez également récupéré un
certain nombre de modèles de système (souvent installés dans le dossier
`/usr/share/lxc/templates/`).
La méthode la plus simple pour lancer un conteneur LXC est d'utiliser l'un de
ces modèles qui va installer tout un environnement pour vous. On utilise pour
cela la commande `lxc-create` :
```
lxc-create --name toto_first --template ubuntu
```
Ce modèle va créer un dossier dans `/var/lib/lxc/` portant le nom que vous avez
précisé. Ce dossier va contenir la configuration LXC du conteneur, la table des
partitions s'il y a besoin de faire des montages particuliers et enfin le
dossier `rootfs` contenant le système en lui-même.
On peut maintenant démarrer le conteneur :
```
lxc-start --name toto_first
```
À la différence de Docker qui va ne lancer que l'application (ou les
applications listées dans la ligne de commande) dans son environnement, LXC va
appeler `/sbin/init` et démarrer tous les services que l'on peut s'attendre à
trouver dans n'importe quelle machine virtuelle 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`, puis on utilise
`lxc-console` qui permet de se détacher de la console via le binding `^A+q`.
Connectez-vous, lancez quelques commandes puis éteignez la machine avec `sudo
poweroff` ou dans un autre terminal via `lxc-stop --name toto_first`.
## Persistance des données
Contrairement à Docker, lorsque vous arrêtez un conteneur, les modifications
apportées sont conservées. Si vous appelez à nouveau `lxc-start --name
toto_first`, vous constaterez que votre historique contient les dernières
commandes que vous avez tapé et si vous avez apporté d'autres modifications sur
le système, celles-ci sont toujours visibles.
## Le réseau
Le modèle ubuntu que vous avez utilisé initialise un fichier de configuration
sans paramètres pour le réseau. Vous n'avez donc pas d'interface dans le
conteneur pour le connecter au réseau.
Un excellent article détaillant les différents types de réseau est accessible
(ici)[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.
### Virtual Ethernet
Virtual Ethernet est la configuration la plus simple. On met en place un pont
sur la machine hôte, puis on crée une interface `veth` par conteneur que l'on
veut lancer. On n'oubliera pas d'ajouter ces interfaces au pont.
Voici un extrait de configuration correspondant au paramétrage d'une interface
`eth0` pour un conteneur donné :
```
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
```
Cette technique a pour inconvénient de laisser au noyau le soin de router les
paquets selon leur adresse IP, ce qui peut être lent et coûteux étant donné que
la carte est placé en mode de promiscuité.
### MACVLAN
Ici, le noyau va orienter les paquets en fonction de leur adresse MAC de
destination.
```
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.flags = up
lxc.network.link = br0
```
## Stockage
Par défaut, le stockage se fait dans l'arborescence du système hôte, mais il
est possible d'utiliser d'autres backends tels que Btrfs, LVM, overlayfs, AUFS
ou ZFS.
Pour utiliser un type de stockage particulier, préciser lors de la création de
l'environnement du conteneur `-B [btrfs|zfs|lvm|...]`.