P23 Application de gestion de conteneurs pour sites Web

De Wiki de Projets IMA
Révision datée du 17 décembre 2017 à 16:46 par Amachere (discussion | contributions) (Automatisation)

Présentation générale du projet

L'objectif du projet est de créer une application web de gestion de conteneurs pour l’hébergement de sites web de manière simple. Cette application pourra notamment permettre à l'utilisateur de choisir un nom de domaine, préciser certaines caractéristiques du conteneur hébergeant le site web, modifier le code source du site, ainsi que lancer ou détruire le conteneur.

Cahier des charges

L'application web sera hébergée sur une machine virtuelle linux qui regroupera tous les conteneurs. Cette machine virtuelle consommera une adresse IPv4 routée qui devra être partagée entre les conteneurs. Les sites web doivent être accessibles en IPv4 et IPv6 et communiquer en HTTP et HTTPS. Aussi les ressources processeur de la machine physique devront être gérées avec le mécanisme des cgroups. Il faut donc prévoir l'architecture réseau permettant la connexion des conteneurs, un ou plusieurs modèle(s) de conteneur capable d'héberger un site web, ainsi que l'architecture le l’application web. Les conteneurs seront donc sur un réseau privé (10.1.1.0/24) et connectés à la machine hôte grâce à un pont et des interfaces ethernet virtuelles. Le serveur de la machine hôte sera configuré en reverse proxy afin de rediriger les requêtes vers les conteneurs adéquats.

Tableau de bord

Architecture Système Réseau

  • L'architecture système réseau doit pouvoir permettre de gérer plusieurs conteneurs à la fois tout en ne consommant qu'une adresse ip publique, il faut donc mettre en place une machine virtuelle avec une interface ethernet statique pouvant accueillir les conteneurs. Pour cela un pont sera utilisé sur lequel les conteneurs seront eux-mêmes connecté grâce à un paire ethernet virtuelle. La communication avec l’extérieur sera assurée par la machine hôte via un reverse proxy. Un système de fichiers cgroup sera créé et monté sur la machine hôte afin de gérer les ressources utilisés par les conteneurs.

Asr2.jpeg

Préparation de l'hôte

  • Pour commencer, la machine virtuelle servant d'hôte sera installée sur le serveur cordouan.

Cette dernière est installée avec xen grâce à la commande :

xen-create-image --hostname=ima5-pfe --dir=/usr/local/xen --ip=193.48.57.186 --netmask=255.255.255.240 --gateway=193.48.57.177

Il faut ensuite modifier le fichier /etc/xen/ima5-pfe.cfg pour mettre la machine en réseau :

vif         = [ 'mac=00:16:3E:95:9E:ED ,bridge=IMA5sc ]

On peut lancer la machine avec :

xl create /etc/xen/ima5-pfe.cfg

Puis y accéder par console avec :

xl console ima5-pfe
  • Les paquetages suivants ont été installés :
apt-get install apache2 nginx bind9 debootstrap unshare ssh

Malheureusement, nginx ne s'installe pas correctement et apache2 sera utilisé pour mettre en place le reverse proxy

  • La configuration réseau est rendue automatique au boot dans /etc/network/interfaces :
# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 static
 address 193.48.57.186
 netmask 255.255.255.240
 gateway 193.48.57.177

# Bridge for container
auto br0
iface br0 inet static
 address 10.1.1.254
 netmask 255.255.255.0
 bridge_ports none
 bridge_stp off
 bridge_fd 0

# iptables rules to connect br0 and eth0
post-up iptables -I FORWARD -i br0 -o eth0 -j ACCEPT
post-up iptables -I FORWARD -i eth0 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT

Le pont servira de gateway aux conteneurs qui seront reliés à ce dernier avec des paires ethernet virtuelles. Aucun conteneur n'étant connecté au boot, bridge_ports none indique qu'aucune interface n'est montée sur le pont au lancement. Les règles d'iptables servent à faire le lien entre le pont et l'interface ethernet de l'hôte.

Mise en place du serveur de nom (DNS)

  • J'ai commencé par reserver un nom de domaine sur gandi.net : plille.space
  • Il m'a ensuite fallu configuré le serveur DNS avec bind9
    • J'ai créé la zone dans /etc/bind/named.conf.local :
zone "plille.space" {
       type master;
       file "/etc/bind/db.plille.space";
       allow-transfert {217.70.177.40; };
};

217.70.177.40 est l'adresse d'un DNS de gandi qui servira de DNS secondaire

    • Il faut aussi editer /etc/bind/named.conf.options :
options {
        directory "/var/cache/bind";
        dnssec-validation auto;
        auth-xdomain no;
        listen-on-v6 { any; };
};
    • Enfin le fichier de zone /etc/bind/db.plille.space et créé avec touch et édité :
;
; BIND data file for local loopback interface
;
$TTL    604800
@       IN      SOA     ns1.plille.space. root.plille.space. (
                     2017110901         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800         ; Negative Cache TTL
;
plille.space.   IN      NS      ns1.plille.space.
plille.space.   IN      NS      ns6.gandi.net.
NS1     IN      A       193.48.57.186
NS6     IN      A       217.70.177.40
@       IN      A       193.48.57.186
www     IN      A       193.48.57.186
    • Puis bind9 est redémaré :
service bind9 restart
  • Pour finir la configuration, il faut modifier les onglets "Gérer les glues records" et "Modifier les serveurs DNS" sur gandi.net, respectivement :
Nom du serveur : ns1.plille.space
IP : 193.48.57.186

et

DNS1 : ns1.plille.space
DNS2 : ns6.gandi.net
  • Pour vérifier la configuration :
:~# named-checkconf -z
zone plille.space/IN: loaded serial 2017110901
zone localhost/IN: loaded serial 2
zone 127.in-addr.arpa/IN: loaded serial 1
zone 0.in-addr.arpa/IN: loaded serial 1
zone 255.in-addr.arpa/IN: loaded serial 1

Mise en place du serveur Apache2

  • Pour commencer j'ai édité les hosts pour plus de propreté
    • hostname :
hostname plille.space

Il faut aussi changer la configuration /etc/xen/ima5-pfe.cfg pour qu'elle soit prise en compte au boot. La machine se lancera maintenant par : xl console plille.space

    • /etc/hosts :
127.0.0.1       localhost
193.48.57.186   plille.space
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcasterprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
    • /etc/resolv.conf :
search plille.space
nameserver 193.48.57.186
  • Ensuite j'ai configuré apache2
a2enmod proxy_http

Autorise les modules proxy et proxy_http pour utiliser le reverse proxy

    • J'ai créer un fichier de configuration basique pour le domaine principal /etc/apache2/sites-available/plille.space.conf :
<VirtualHost *:80>
        ServerName plille.space
        ServerAlias www.plille.space
        DocumentRoot /var/www/html/
        ErrorLog /var/log/apache2/error.log
        CustomLog /var/log/apache2/access.log combined
</VirtualHost>

Les logs serviront à vérifier que la connexion se passe bien et que le reverse proxy fonctionne.

    • Pour que le site soit pris en compte :
a2ensite plille.space.conf
service apache2 reload
    • La configuration des ports n'a pas été changée, il faut juste ecouter sur le port 80 (http) /etc/apache2/ports.conf :
Listen 80

Dans la suite du projet, il faudra prévoir un accès en https (port 443).

    • Modification du fichier html /var/www/html/index.html :
proxy

Ceci affichera juste un titre pour vérifier que le domaine principal se trouve bien sur l'hôte.

    • Le reverse proxy est mis en place grâce à la création d'un virtual host pour un conteneur. /etc/apache2/sites-available/"$1".plille.space.conf :
<VirtualHost *:80>
        ServerName "$1".plille.space
        ProxyPass / http://10.1.1.x:80/
        ProxyPassReverse / http://10.1.1.x:80/
        ProxyRequests Off
</VirtualHost>

Où "$1" est le nom du sous-domaine et x l'indice de l'adresse IP du conteneur.

Conteneur à la main et tests

J'ai commencé par lancer des conteneur à la main pour voir les comportements et les configuration nécessaires.

  • Création du système de fichiers :
debootstrap --include apache2 wheezy ./conteneurtest http://ftp.us.debian.org/debian

Où l'option --include permet d'installer apache2, wheezy est le type de distribution debian, conteneurtest est le nom du système de fichiers, et l'url est le miroir depuis lequel l'archive est télechargée.

  • Lancement du conteneur avec un namespace :
unshare -p -u -f -n --mount-proc=/proc conteneurtest /bin/bash

Qui me permet d'acceder au bash du conteneur.

  • J'ai commencer par configurer l'interface réseau du conteneur :
    • Depuis le host en ssh :
ip link add vif1 type veth peer name eth1@vif1

Créer la paire d'interfaces ethernet virtuelles

ip link set master br0 dev vif1
ip link set vif1 up
ip link set br0 up

Monte une partie du pair sur le pont puis monte cette interface et le pont. On peut vérifier :

:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.000000000000       no              vif1

L'interface vif1 est bien montée mais la console a retourné :

device vif1 entered promiscuous mode
vif1: link is not ready

En effet vif1 n'est reliée a rien. Il faut configuré le pair dans le conteneur.

    • Depuis l'hôte en ssh:
ip link set eth1@vif1 netns /proc/"$PID"/ns/net name eth1

Envoie le pair dans le conteneur, "$PID" étant le pid de unshare obtenu grâce à ps aux ou ps ax.

    • Depuis le conteneur :
ip address add dev eth1 10.1.1.1/24
ip link set eth1 up

L'interface est maintenant configurée et montée, on peut pinger le pont br0 (ping 10.1.1.254) mais on ne peut pas pinger l'hôte (ping 193.48.57.186). Il faut encore configurer les routes dans le conteneur :

ip route add 10.1.1.254 dev eth1 scope link
ip route add default via 10.1.1.254 dev eth1

Maintenant le conteneur est en réseau, il ne peut pas pinger le monde exterieur mais ce n'est pas utile puisque c'est le reverse proxy de l'hôte qui va servir d'intermédiaire.

  • Je dois maintenant créer un sous-domaine depuis l'hôte, dans /etc/bind/db.plille.space il suffit d'ajouter la ligne suivante à la fin du fichier :
conteneurtest      IN      CNAME      plille.space.

Une fois le DNS propagé, on peut aller à conteneurtest.plille.space, mais on est redirigé sur la page "proxy".

  • Mise à jour du reverse proxy sur l'hôte, /etc/apache2/sites-available/conteneurtest.plille.space.conf :
<VirtualHost *:80>
        ServerName conteneurtest.plille.space
        ProxyPass / http://10.1.1.1:80/
        ProxyPassReverse / http://10.1.1.1:80/
        Proxyrequets Off
</VirtualHost>

Puis

a2ensite
service apache2 reload

Cette fois le reverse proxy redirige la requête vers le conteneur, mais celui n'hébergeant pas de serveur, la page renvoie l'erreur 503 : Service Unavailable.

  • Configuration du serveur apache2 dans le conteneur :
    • hostname :
hostname conteneurtest.plille.space

Sans cette modification, le serveur ne se lancera pas.

    • /etc/hosts :
10.1.1.1 conteneurtest.plille.space
    • /etc/resolv.conf :
search conteneurtest.plille.space
nameserver 10.1.1.1
    • Configuration du virtual host, /etc/apache2/sites-available/conteneurtest.plille.space.conf :
<VirtualHost 10.1.1.1:80>
        ServerName conteneurtest.plille.space
        ErrorLog /var/log/apache2/error.log
        CustomLog /var/log/apache2/access.log combined
        DocumentRoot /var/www/html
</VirtualHost>

Puis

a2ensite conteneurtest.plille.space
service apache2 reload
    • Création du fichier html :
mkdir -p /var/www/html
touch /var/www/html/index.html
echo conteneurtest > /var/www/html/index.html

On peut enfin visiter la page conteneurtest.plille.space qui affiche bien "conteneurtest". Et on peut vérifier la connexion dans /var/log/apache2/access.log.

Gestion des cgroups

Automatisation

La partie importante du projet est de pouvoir automatiser cette architecture pour pouvoir lancer un serveur hébergé par un conteneur depuis l’extérieur. J'ai donc écrit 5 scripts shell pour réaliser de façon automatique les étapes vues précédemment.

  • D'abord deux fichiers textes sont utilisés par ces script :
    • packages.txt qui regroupe simplement les paquetages à installer avec deboostrap et se présente sous le format suivant :
apache2,sqlite3,...,...
    • list_cont.txt qui liste les conteneurs crées sous le format :
"$NOM"%"$IP"%"$PID"

Les % séparant les champs utiles permettent une séparation facile avec cut ou sed. Le champ "$PID" et vide si le conteneur est stoppé, les champs "$NOM" et "$IP" sont crées à l'installation du système de fichiers.

  • create_cont.sh installe le système fichiers en prenant en compte les paquetages de packages.txt, met à jour list_cont.txt, crée le sous-domaine avec bind, met à jour le reverse proxy, et place le script inside_cont.sh dans le répertoire du conteneur pour qu'il puisse être lu par le conteneur lors de son lancement.

Le script prend 2 paramètres qui sont le nom du sous-domaine (le conteneur aura le même nom) et le dernier numéro de l'adresse IP du conteneur. Par exemple :

sh create_cont.sh site1 3

Va créer un répertoire nommé site1 contenant le système de fichier (avec les paquetages) pour le conteneurs du même nom qui aura comme ip 10.1.1.3, et va mettre à jour le reverse proxy pour qu'il redirige les requêtes adressées à site1.plille.space vers 10.1.1.3:80. Et list_cont.txt sera mis à jour et contiendra :

site1%3%
    • create_cont.sh :
#!/bin/bash

debootstrap --include "$(cat ./packages.txt)" wheezy ./"$1" http://ftp.us.debian.org/debian   #installe le système de fichiers dans le répertoire "$1"
echo "$1"%"$2"% >> list_cont.txt                                                              #met à jour list_cont.txt
echo "$1" IN CNAME plille.space. >> /etc/bind/db.plille.space                                 #met à jour le DNS pour créer le sous-domaine "$1"
CONF="<VirtualHost *:80>
        ServerName "$1".plille.space
        ProxyPass / http://10.1.1."$2":80/
        ProxyPassReverse / http://10.1.1."$2":80/
        ProxyRequests Off
</VirtualHost>"                                                                               #met à jour le reverse proxy :
touch /etc/apache2/sites-available/"$1".plille.space.conf                                     #créer le fichier de configuration du nouveau reverse proxy
echo "$CONF" > /etc/apache2/sites-available/"$1".plille.space.conf                            #edite ce fichier du reverse proxy, redirection vers 10.1.1."$2"
a2ensite "$1".plille.space.conf                                                               #autorise le nouveau site (reverse proxy)
service apache2 reload                                                                        #prend en compte les changements d'apache2 (reverse proxy)
service bind9 restart                                                                         #prend en compte les changements de bind9 (DNS)
cp inside_cont.sh "$1"/                                                                       #place un script utilise au lancement du conteneur "$1" dans son répertoire

  • launch_cont.sh lance un conteneur qui va exécuter un script, configure l'interface réseau de l'hôte et du conteneur et met à jour list_cont.txt en ajoutant le PID. Il prend en paramètre le nom du conteneur à lancer.

Par exemple :

sh launch_cont.sh site1

Va lancer le conteneur site1, créer le pair vif3, configurer son interface réseau sur 10.1.1.3/24, et mettre à jour list_cont.txt qui deviendra :

site1%3%489

Et la page web site1.plille.space sera opérationnelle.

    • launch_cont.sh :
#!/bin/bash

IP="$(cat list_cont.txt | grep "$1" | cut -d '%' -f2)"                                                 #recherche l'ip du conteneur dans list_cont.txt et la place dans "$IP"
ip link add vif"$IP" type veth peer name eth1@vif"$IP"                                                 #créer le pair ethernet virtuelle
ip link set master br0 dev vif"$IP"                                                                    #monte une partie du pair sur le bridge
ip link set vif"$IP" up                                                                                #monte le pair coté host
ip link set br0 up                                                                                     #monte le bridge
(nohup unshare -m -p -f -u -n --mount-proc=/proc chroot "$1" sh inside_cont.sh "$1" "$IP") &           #créer un namespace pour le conteneur "$1" qui execute un script
sleep 5                                                                                                #attend que le conteneur soit pret
PID="$(ps ax | grep unshare | grep "$1" | grep -v grep | sed q | sed 's/^[ ]*//g' | cut -d ' ' -f1)"   #recherche le PID du unshare et le place dans "$PID"
sed -i "/$1/s/.*/&$PID/1" list_cont.txt                                                                #ajoute le PID a la fin de la ligne correspondante dans list_cont.txt
ip link set eth1@vif"$IP" netns /proc/"$PID"/ns/net name eth1                                          #deplace l'autre partie du pair dans le conteneur
nsenter -t "$PID" -n ip address add dev eth1 10.1.1."$IP"/24                                           #configure eth1 dans le conteneur
nsenter -t "$PID" -n ip link set eth1 up                                                               #monte eth1 dans le conteneur
nsenter -t "$PID" -n ip route add 10.1.1.254 dev eth1 scope link                                       #configure les routes dans le conteneur
nsenter -t "$PID" -n ip route add default via 10.1.1.254 dev eth1                                      #      "      "      "      "
  • inside_cont.sh est le script qui s'exécute à l’intérieur d'un conteneur afin de créer le serveur hébergé par ce dernier. Il prend deux paramètres qui sont son ip et son nom et lui sont donnés lors du lancement. Dans le cas du site1, il va créer un serveur écoutant sur 10.1.1.3:80 et affichant site1 sur la page site1.plille.space.
    • inside_cont.sh :
#!/bin/bash 

echo 10.1.1."$2" "$1".plille.space > /etc/hosts                           #met à jour les hosts
echo "$1".plille.space > /etc/hostname                                    #      "      "
echo search "$1".plille.space > /etc/resolv.conf                          #      "      "
echo nameserver 10.1.1."$2" >> /etc/resolv.conf                           #      "      "
hostname "$1".plille.space                                                #      "      "
rm /etc/apache2/sites-available/*                                         #supprime le site par default
rm /etc/apache2/sites-enabled/*                                           #      "      "
touch /etc/apache2/sites-available/"$1".plille.space.conf                 #créer le fichier de configuration du nouveau site
CONF="<VirtualHost 10.1.1."$2":80>
        ServerName "$1".plille.space
        ErrorLog /var/log/apache2/error.log
        CustomLog /var/log/apache2/access.log combined
        DocumentRoot /var/www/html
</VirtualHost>"
echo "$CONF" > /etc/apache2/sites-available/"$1".plille.space.conf        #edite le fichier de configuration du nouveau site
a2ensite "$1".plille.space.conf                                           #autorise le nouveau site
mkdir -p /var/www/html                                                    #créer le dossier html et son arborescence
touch /var/www/html/index.html                                            #créer le fichier 
echo "$1" > /var/www/html/index.html                                      #le nouveau site affichera "$1"
service apache2 restart                                                   #prend en compte les changement d'apache2 et demarre le serveur
while true
do
        sleep 10                                                          #attend le signal kill
done
  • stop_cont.sh va simplement kill le unshare correspondant à un conteneur ainsi que le process que ce conteneur est en train d'exécuter, et met à jour list_cont.txt. Le pair associé au conteneur supprimé sera détruit et retiré du pont. Il prend en paramètre le nom du conteneur à stoppé.

Par exemple :

sh stop_cont.sh site1

Va stopper le processus de PID 489 (le unshare) ainsi que le processus sh inside_cont.sh site1 3, vif3 sera détruit et plus sur br0. Et list_cont.txt sera :

site1%3%
    • stop_cont.sh :
#!/bin/bash 

PID="$(cat list_cont.txt | grep "$1" | cut -d '%' -f3)"                                  #recherche le PID du unshare dans list_cont.txt
kill -9 "$PID"                                                                           #kill le unshare
sed -i "s/$PID//1" list_cont.txt                                                         #supprime le PID de list_cont.txt
PID="$(ps ax | grep "$1" | grep -v grep | sed q | sed 's/^[ ]*//g' | cut -d ' ' -f1)"    #recherche le PID du processus execute dans le conteneur
kill -9 "$PID"                                                                           #kill ce processus

  • destroy_cont.sh supprime le système de fichier d'un conteneur, supprime la partie du reverse proxy concernant ce conteneur, supprime le sous domaine, et met à jour list_cont.txt.

Il prend en paramètre le nom du conteneur à supprimer. Par exemple :

sh destroy_cont site1

Va détruire le répertoire site1, supprimer le le reverse proxy qui redirigeait site1.plille.space vers 10.1.1.3, supprimer le sous domaine site1 et supprimer la ligne correspondant à site1 dans list_cont.txt.

    • destroy_cont.sh :
#!/bin/bash

sed -in "/$1/d" list_cont.txt                              #supprime la ligne du conteneur "$1" dans list_cont.txt
rm -rf "$1"                                                #supprime le repertoire "$1"
rm /etc/apache2/sites-available/"$1".plille.space.conf     #supprime le reverse proxy associé à "$1"
rm /etc/apache2/sites-enabled/"$1".plille.space.conf       #      "      "      "      "
service apache2 reload                                     #prend en compte les changement d'apache2
sed -in "/$1/d" /etc/bind/db.plille.space                  #supprime la ligne correspondant au sous-domaine "$1" du DNS
service bind9 restart                                      #prend en compte les changements de bind9

A faire

  • Les conteneurs ont une adresse IPv4 non routé, le système hôte dispose d'une adresse IPv4 routée ;
  • Au niveau du système principal installer un reverse proxy (apache2 ou nginx) pour accèder aux sites des conteneurs ;
  • Réaliser des scripts (shell) pour créer un conteneur, pour lancer un conteneur, pour arrêter un conteneur, pour détruire un conteneur et pour sauvegarder un conteneur ;
  • Créer un conteneur c'est : créer le système de fichier racine (debbootstrap), ajouter les paquetages nécessaires, modifier l'annuaire des conteneurs (adresse IPv4, port SFTP, nom de domaine, ...?), mettre à jour le reverse proxy, mettre à jour les redirections de port (sftp sur le conteneur) ;
  • Lancer un conteneur c'est : lancer un processus avec unshare, créer la connexion réseau, configurer le réseau, lancer la BdD, lancer le serveur Web ;
  • Arrêter un contenur c'est : arrêter proprement les processus (la BdD), arrêter le unshare, peut être supprimer la paire Ethernet virtuelle, supprimer la redirection de port ;
  • Détruire un conteneur c'est modifier l'annuaire des conteneurs (supprimer une "ligne"), supprimer le système de fichier racine ;
  •  Gérer les cgroups dans tous les scripts, partir sur UN modèle, a terme proposer plusieurs modèles ;
  • Test technique : utiliser un fichier du système principale comme partition pour un conteneur plutôt que d'utiliser un sous-répertoire, donc plutôt que répertoire + chroot, essayer -m (namespace montage SF) et monter le fichier comme système de fichier racine ;
  • DNS dynamique pour une gestion facile des noms de domaine ;
  • Application : pas de gestion des utilisateurs passer par le LDAP de l'école (ou un LDAP de test) ;
  • Application : utiliser la bibliothèque bootstrap.

Conclusion

Annexes