P23 Application de gestion de conteneurs pour sites Web

De Wiki de Projets IMA

Présentation générale du projet

  • La manière d'hébergé un serveur web à grandement évoluée au fil du temps. Il y a quelques années, un serveur web utilisait un serveur physique à lui seul. Puis la virtualisation est apparue permettant à un serveur physique d'héberger plusieurs services. Cependant les machines virtuelles, bien qu'ayant été très efficaces, commencent à devenir obsolètes à cause des difficultés à les mettre en place. C'est pourquoi on voit apparaître une forte augmentation de l'utilisation des conteneurs depuis quelques temps, existant pourtant depuis longtemps, ce n'est qu'aujourd'hui que leur potentiel est exploité dans la plupart des architectures système et réseau modernes.
  • 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.
    • L'objectif principal est donc de mettre en place l'architecture permettant le déploiement des conteneurs.
    • La seconde partie du projet sera de créer l'interface web permettant de gérer cette architecture.

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.

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.

  • L'application Web devra comporter plusieurs fonction pouvant être utilisées par les utilisateurs, notamment créer un sous-domaine pouvant héberger le contenu de l'utilisateur, lancer ou stopper ce site, mettre à jour le contenu du site. On pourra aussi proposer une page administrateur pouvant vérifier l'état des sous-domaines et conteneurs les hébergeant.

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.

Asr3.jpg

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

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.

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 (Reverse Proxy)

  • 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

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 plille.space
nameserver 10.1.1.254
    • 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

La gestion de la mémoire est une partie importante du projet, cependant le cgroup memory n'est pas disponible sur la machine virtuelle xen que j'utilise. J'ai d'abord chercher un moyen d'ajouter le cgroup memory par le grub.

  • Sur la machine virtuelle, il faut modifier le fichier /boot/config-3.16.0-4-amd64 :
CONFIG_MEMCG=y
CONFIG_MEMCG_DISABLED=n
CONFIG_MEMCG_SWAP=y

Cependant cela ne suffit pas à autoriser la gestion de la mémoire.

  • Il faut modifier le grub de la machine virtuelle en passant par le fichier /etc/xen/ima5-pfe.cfg (sur cordouan) :
#
#  Kernel + memory size
#
kernel      = '/boot/vmlinuz-3.16.0-4-amd64'
extra       = 'elevator=noop cgroup_enable=memory'
ramdisk     = '/boot/initrd.img-3.16.0-4-amd64'

Une fois la VM rebootée, le dossier cgroup/memory est disponible sur la machine virtuelle.

Accès HTTPS

  • L'obtention des certificats pour l'accès en https sur le domaine principal ainsi que les sous-domaines se fait par le biais du programme certbot de letsencrypt.
  • Il faut d'abord télécharger le code de cerbot :
wget https://dl.eff.org/certbot-auto
  • On peut ensuite exécuter la commande suivante pour obtenir un certificat :
 ./certbot-auto --authenticator standalone --installer apache --pre-hook "apachectl -k stop" --post-hook "apachectl -k start" --register-unsafely-without-email

Cette commande est interactive et demande des entrées console : le domaine, redirection d'http vers https. Il faudra changer les options pour l'automatisation. De plus l'option --installer apache fait planter le serveur apache, il faudra donc changer cela pour l'obtention des certificats des sous-domaines.

  • Dans le cas précédent, cerbot modifie la configuration d'apache :
<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
RewriteEngine on
RewriteCond %{SERVER_NAME} =plille.space [OR]
RewriteCond %{SERVER_NAME} =www.plille.space
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>


<IfModule ssl_module>
        <VirtualHost *:443>
                ServerName plille.space
                ServerAlias www.plille.space
                DocumentRoot /var/www/html/
                ErrorLog /var/log/apache2/error.log
                CustomLog /var/log/apache2/access.log combined
                SSLEngine on
                SSLVerifyClient None
                SSLCertificateFile /etc/letsencrypt/live/plille.space/fullchain.pem
                SSLCertificateKeyFile /etc/letsencrypt/live/plille.space/privkey.pem
                Include /etc/letsencrypt/options-ssl-apache.conf
        </VirtualHost>
</IfModule>
  • Pour les sous-domaines, j'utilise la commande suivante dans le script de création d'un conteneur :
./certbot-auto certonly  -n --register-unsafely-without-email --webroot --webroot-path=/root/sous-domaine/var/www/html -d sous-domaine.plille.space

Elle permet d’obtenir uniquement le certificat pour le sous-domaine sans toucher à apache et sans entrées console, évitant alors de faire planter le serveur. Cependant, il faut stopper apache le temps de l'obtention (environ 2 ou 3 secondes). Il faut que le conteneur soit lancé et que le proxy inverse ne fasse pas de redirection http vers https.

  • Il faut ensuite configurer manuellement le virtualhost :
<VirtualHost *:80>
        ServerName sous-domaine.plille.space
        ProxyPass / http://10.1.1.1:80/
        ProxyPassReverse / http://10.1.1.1:80/
        ProxyRequests Off
        RewriteEngine on
        RewriteCond %{SERVER_NAME} =sous-domaine.plille.space
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<IfModule ssl_module>
        <VirtualHost *:443>
                ServerName sous-domaine.plille.space
                SSLEngine on
                SSLVerifyClient None
                SSLProxyEngine on
                ProxyPass / http://10.1.1.1:80/
                ProxyPassReverse / http://10.1.1.1:80/
                ProxyRequests Off
                SSLCertificateFile /etc/letsencrypt/live/sous-domaine.plille.space/fullchain.pem
                SSLCertificateKeyFile /etc/letsencrypt/live/sous-domaine.plille.space/privkey.pem
                Include /etc/letsencrypt/options-ssl-apache.conf
        </VirtualHost>
</IfModule>
  • Pour supprimer un certificat lors de la de la destruction d'un sous-domaine, j'utilise cette commande :
./certbot-auto revoke --cert-path /etc/letsencrypt/live/sous-domaine.plille.space/cert.pem --non-interactive --quiet

--non-interactive permet de contourner les entrées consoles et supprime automatiquement les fichiers associés.

  • Les certificats de Letsencrypt sont valables 3 mois, pour le renouvellement des certificats j'ai ajouté un script dans /etc/cron.daily/ :
./root/certbot-auto renew --non-interactive --quiet

Ceci permet de vérifier de manière journalière si les certificats sont à jour. On pourrait aussi effectuer la vérification de manière hebdomadaire voire mensuelle plutôt que journalière.

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.

Création

  • 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 serveur dans le conteneur sera lui aussi configuré.

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

#verification des arguments
if [ "$#" -ne 2 ]; then
        echo "error: Must have two arguments"
        exit
fi

if [ "$2" -gt 253 ] || [ "$2" -lt 1 ]; then
        echo "error: 2nd argument must be integer between 1 and 253"
        exit
fi

cat list_cont.txt | while read ligne
do
        TEST="$(echo $ligne | cut -d '%' -f2)"
        if [ "$2" = "$TEST" ]; then
                echo "error: IP 10.1.1.$2 is already used"
                exit 2
        fi
        TEST="$(echo $ligne | cut -d '%' -f1)"
        if [ "$1" = "$TEST" ]; then
                echo "error: "$1" already exists"
                exit 2
        fi
done 

if [ "$?" = "2" ]; then
        exit
fi

#debut
echo "$1"%"$2"% >> list_cont.txt                                                                #ajoute le conteneur a la liste
echo "$1        IN      CNAME   plille.space." >> /etc/bind/db.plille.space                     #met à jour le DNS
service bind9 reload                                                                            #prend en compte les changements du DNS
debootstrap --include "$(cat ./packages.txt)" wheezy ./"$1" http://ftp.us.debian.org/debian   #installe le système de fichiers du conteneur
CONF="<VirtualHost *:80> 
        ServerName "$1".plille.space
        ProxyPass / http://10.1.1."$2":80/
        ProxyPassReverse / http://10.1.1."$2":80/
        ProxyRequests Off
</VirtualHost>"                                                                                    #configuration du virtualhost
touch /etc/apache2/sites-available/"$1".plille.space.conf                                       #création du fichier du virtualhost
echo "$CONF" > /etc/apache2/sites-available/"$1".plille.space.conf                              #edition
a2ensite "$1".plille.space                                                                      #prend en compte le nouveau virtualhost
service apache2 reload                                                                          #prend en compte les changements d'apache
cp inside_cont.sh "$1"/                                                                         #copie le script dans le conteneur
echo "127.0.0.1 $1.plille.space" > "$1"/etc/hosts                                               #configure les hosts du conteneur
echo "10.1.1.$2 $1.plille.space" >> "$1"/etc/hosts
echo "$1.plille.space" > "$1"/etc/hostname                                                      #configure l'hostname du conteneur
echo "search "$1".plille.space" > "$1"/etc/resolv.conf                                          #configure la resolution DNS
echo "nameserver 10.1.1.254" >> "$1"/etc/resolv.conf
CONF="
auto eth1
iface eth1 inet static
 address 10.1.1.$2
 netmask 255.255.255.0
 gateway 10.1.1.254

 post-up ip route add 10.1.1.254 dev eth1 scope link
 post-up ip route add default via 10.1.1.254 dev eth1"                                          #configuration reseau
echo "$CONF" >> "$1"/etc/network/interfaces                                                     #edite le fichier de config reseau du conteneur
rm "$1"/etc/apache2/sites-available/*                                                           #supprime le virtualhost par defaut du conteneur
rm "$1"/etc/apache2/sites-enabled/*
CONF="<VirtualHost 10.1.1."$2":80>
        ServerName "$1".plille.space
        DocumentRoot /var/www/html/
</VirtualHost>"                                                                                 #configuration du virtualhost du conteneur
touch "$1"/etc/apache2/sites-available/"$1".plille.space.conf                                   #création du fichier du virtualhost du conteneur
echo "$CONF" > "$1"/etc/apache2/sites-available/"$1".plille.space.conf                          #edition
mkdir -p "$1"/var/www/html                                                                      #création du dossier html et son arborescence
touch "$1"/var/www/html/index.html
echo "$1" > "$1"/var/www/html/index.html
sh launch_cont.sh "$1"
wait 5
./certbot-auto certonly  -n --register-unsafely-without-email --webroot --webroot-path=/root/"$1"/var/www/html -d "$1".plille.space
sh stop_cont.sh "$1"
CONF="<VirtualHost *:80> 
        ServerName "$1".plille.space
        ProxyPass / http://10.1.1."$2":80/
        ProxyPassReverse / http://10.1.1."$2":80/
        ProxyRequests Off
        RewriteEngine on
        RewriteCond %{SERVER_NAME} ="$1".plille.space
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<IfModule ssl_module>
        <VirtualHost *:443>
                ServerName "$1".plille.space
                SSLEngine on
                SSLVerifyClient None
                SSLProxyEngine on
                ProxyPass / http://10.1.1."$2":80/
                ProxyPassReverse / http://10.1.1."$2":80/
                ProxyRequests Off
                SSLCertificateFile /etc/letsencrypt/live/"$1".plille.space/fullchain.pem
                SSLCertificateKeyFile /etc/letsencrypt/live/"$1".plille.space/privkey.pem
                Include /etc/letsencrypt/options-ssl-apache.conf
        </VirtualHost>
</IfModule>"
echo "$CONF" > /etc/apache2/sites-available/"$1".plille.space.conf
service apache2 reload
echo done 

Lancement

  • 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

#verification des arguments
if [ "$#" -ne 1 ]; then
    echo "error : Must have one argument"
    exit
fi

TEST="$(cat list_cont.txt | grep "$1" | cut -d '%' -f1)"
if [ "$1" != "$TEST" ]; then
        echo "error: "$1" doesn't exists"
        exit
fi

TEST="$(cat list_cont.txt | grep "$1" | cut -d '%' -f3)"
if [ "$TEST" != "" ]; then
        echo "error: "$1" is already running"
        exit
fi

#debut
IP="$(cat list_cont.txt | grep "$1" | cut -d '%' -f2)"                                         #recherche l'ip dans la liste
ip link add vif"$IP" type veth peer name eth1@vif"$IP"                                         #creer la paire d'interfaces
ip link set master br0 dev vif"$IP"                                                            #monte une partie du paire sur le pont
ip link set vif"$IP" up                                                                        #up l'interface sur le pont
(nohup unshare -p -f -u -n --mount-proc=/proc chroot "$1" sh inside_cont.sh "$1") &            #lance le conteneur
sleep 0.1                                                                                      #assure que le conteneur est bien lance
PID="$(ps ax | grep unshare | grep "$1" | grep -v grep | sed 's/^[ ]*//g' | cut -d ' ' -f1)"   #recherche le PID du conteneur
sed -i "/$1/s/.*/&$PID/1" list_cont.txt                                                        #ajoute le PID dans la liste
ip link set eth1@vif"$IP" netns /proc/"$PID"/ns/net name eth1                                  #envoie la 2e partie du paire dans le conteneur
sleep 0.1                                                                                      #assure la fin avant de rendre la main
echo done

La commande nohup redirige les sorties vers un fichier texte, cela permet de garder un oeil sur ce qui se passe dans le conteneur.

  • inside_cont.sh est le script qui s'exécute à l’intérieur d'un conteneur afin de lancer le serveur hébergé par ce dernier. Il prend en paramètreson nom qui lui est donné lors du lancement. Dans le cas du site1, il va lancer le serveur écoutant sur 10.1.1.3:80 et affichant site1 sur la page site1.plille.space.
    • inside_cont.sh :
#!/bin/bash

hostname "$1".plille.space                                             #configure le hostname
sleep 1                                                                #attend que eth1 soit pret
ifup eth1                                                              #monte eth1
a2ensite "$1".plille.space.conf                                        #prend en compte le virtualhost
service apache2 restart                                                #redemarre le serveur
while true                                                             #fait tourner le serveur
do
        sleep 10
done

Arrêt

  • stop_cont.sh va simplement kill le process que ce conteneur est en train d'exécuter (stoppant ainsi le unshare), 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

#verification des arguments
if [ "$#" -ne 1 ]; then
    echo "error : Must have one argument"
    exit
fi

TEST="$(cat list_cont.txt | grep "$1" | cut -d '%' -f1)"
if [ "$1" != "$TEST" ]; then
        echo "error: "$1" doesn't exists"
        exit
fi

TEST="$(cat list_cont.txt | grep "$1" | cut -d '%' -f3)"
if [ -z "$TEST" ]; then
        echo "error: "$1" is not running"
        exit
fi

#debut
PID="$(cat list_cont.txt | grep "$1" | cut -d '%' -f3)"        #recherche le PID du conteneur dans la liste 
sed -i "s/$PID//1" list_cont.txt                               #supprime le PID de la liste
                                                               #recherche le PID faisant tourner le serveur du conteneur
PID="$(ps ax | grep inside | grep "$1" | grep -v unshare | grep -v grep | sed 's/^[ ]*//g' | cut -d ' ' -f1)"
kill -9 "$PID"                                                 #kill le processus du serveur, stop aussi le unshare
sleep 0.1                                                      #assure la fin avant de rendre la main
echo done

Suppression

  • destroy_cont.sh supprime le système de fichier d'un conteneur, supprime la partie du reverse proxy concernant ce conteneur et le certificat, 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

#verification des arguements
if [ "$#" -ne 1 ]; then
    echo "error : Must have one argument"
    exit
fi

TEST="$(cat list_cont.txt | grep "$1" | cut -d '%' -f1)"
if [ "$1" != "$TEST" ]; then
        echo "error: "$1" doesn't exists"
        exit
fi

TEST="$(cat list_cont.txt | grep "$1" | cut -d '%' -f3)"
if [ -n "$TEST" ]; then
        echo "error: "$1" is running"
        exit
fi

#debut
sed -i "/$1/d" list_cont.txt                                 #supprime le conteneur de la liste
rm -rf "$1"                                                   #supprime le systeme de fichiers du conteneur
                                                              #supprime le certificat du sous-domaine
./certbot-auto revoke --cert-path /etc/letsencrypt/live/"$1".plille.space/cert.pem --non-interactive --quiet
rm /etc/apache2/sites-available/"$1".plille.space.conf        #suprime le virtualhost associe
rm /etc/apache2/sites-enabled/"$1".plille.space.conf
service apache2 reload                                        #prend en compte les changements d'apache
sed -in "/$1/d" /etc/bind/db.plille.space                     #supprime le sous-domaine du DNS
service bind9 reload                                          #prend en compte les changements du DNS
echo done

Application Web

Création de la BDD

Bdd plille.png

  • La base de donnée utilisée est mysql.
  • mysql possède un shell permettant de gérer les bases de données :
create table bdd;

pour créer une nouvelle base

use bdd;

permet de modifier la base de données

create table user (
login varchar(20) primary key,
pwd varchar(20)
);

créer la table contenant les utilisateurs

create table docker (
owner varchar(20),
domain varchar(30) primary key,
ip int,
state int,
foreign key (owner) references user (login)
);

créer la table contenant les conteneurs

  • Autoriser l'accès à l'application web :
use sql;
grant all privileges on *.* to 'root'@'plille.space' identified by '$pwd' with grant option;
flush privileges;

Présentation de l'interface web

Page de connexion

Plille connexion.png

  • La page index.php (à laquelle on accède par plille.space) constitue la page de connexion.
  • On peut y entrer son login est son mot de passe qui sont envoyés via un formulaire vers connexion.php.
  • La page connexion.php n'affiche rien, elle vérifie seulement dans la base de données si les identifiants sont bons.
  • Si les identifiants sont corrects, l'utilisateur est envoyé vers menu.php.
  • Dans le cas contraire, la page affiche une alerte (grâce à une variable dans l'url) :

Plille connexion fail.png

Menu principal

Plille menu.png

  • Le menu principal permet à l'utilisateur d'observer les domaines qu'il possède, notamment si le serveur tourne ou non. Il peut cliquer sur des boutons associés aux noms de domaines pour lancer/arrêter un serveur, ou changer le code du domaines.
  • Par exemple en cliquant sur stopper, la page exécute un srcipt php qui va à son tour utiliser stop_cont.sh pour stopper le conteneur et mettre à jour la base de données. L'utilisateur reste sur la même page et une alerte est émise :

Plille menu stop.png

  • Des alertes sont émises pour toutes les actions : création, suppression, upload de code, lancement.
  • Les informations concernant l'utilisateur, le conteneur choisi et les alertes transitent par l'url.

Création d'un nouveau site

Plille creation.png

  • En cliquant sur le lien Créer un site (en haut de page), l'utilisateur est renvoyé vers creation.php.
  • Cette page, par l'intermédiaire d'un formulaire récupère le nom de domaine choisi ainsi que l'ip. Ces données sont envoyées au script cree.php, ce dernier vérifie la disponibilité des champs choisis précédemment. Si le nom ou l'ip n'est pas disponible ou non valide, une alerte est émise. Sinon create_cont.sh est éxecuté et la base de données est mise à jour. L'installation prend du temps mais l'utilisateur peut bien sûr changer de page pendant l'installation.

Suppression d'un site

Plille sup.png

  • En cliquant sur le lien Supprimer un site (en haut de page), l'utilisateur est renvoyé vers suppression.php
  • Cette page affiche les noms de domaine que l'utilisateur possède, il peut cliquer sur un nom pour supprimer un conteneur. Il est alors envoyé vers validation.php (encore une fois, le nom du domaine choisi passe par l'url).

Plille sup val.png

  • Cette page propose simplement à l'utilisateur de confirmer sont choix. Si il valide la page est rechargée avec une nouvelle variable dans l'url qui permet le lancement de destroy_cont.php ainsi que la mise à jour de la base de données.
  • Une fois la destruction terminée, l'utilisateur est renvoyé sur menu.php est une alerte de succès est émise.

Téléchargement de code

Plille upload.png

  • Dans le menu principale, l'utilisateur peut cliquer sur "Changer le code" à côté d'un nom de domaine. Il est alors envoyé sur la page code.php qui lui permet de parcourir ses fichier afin d'upload un dossier html.zip.
  • Le dossier est envoyé par formulaire à upload.php. Ce code récupère le fichier téléchargé et vérifie le nom du fichier. Si le dossier est valide, celui-ci est téléchargé sur la machine host puis décompressé dans le dossier /var/www/html du conteneur (l'ancien dossier est écrasé). Dans le cas contraire, la page est code.php est rechargée et une alerte est émise.
  • Une fois le dossier dans le conteneur, l'utilisateur est renvoyé vers le menu et une alerte de succès est émise.

Utilisation de PHP

post et get

  • l'utilisation de formulaire post permet d'envoyer des données à une autre page :
<html>
<form method=post action=connexion.php>
     <input type="text" name="login" id="login" placeholder="Enter login">
     <input type="password" name="pwd" id="pwd" placeholder="Password">
     <input type="submit" class="btn btn-primary" value="Se connecter">
</form>
</html>

Ici la page connexion pourra récupérer le login et le mot de passe entrés par l'utilisateur.

  • pour récupérer les données on peut utiliser php :
<?php
$login=$_POST['login'];
$pwd=$_POST['pwd'];
?>
  • les information peuvent aussi transiter par l'url :

en html :

<html>
href='<?php echo "menu.php?login=";echo $input; ?>'
</html>

en php:

<?php
header("Location: https://plille.space/menu.php?login=$input");
?>
  • de la même manière, on les récupère avec :
<?php
$login=$_GET['login'];
$pwd=$_GET['pwd'];
?>

my sql

  • Il y a plusieurs commandes utiles pour lire une base de données en php.
  • connexion à mysql :
$link=mysql_connect('plille.space','root','root');
  • choix de la base :
mysql_select_db('bdd');
  • commander la base :
$result=mysql_query("select pwd from user where login='$input'");
$field = mysql_fetch_assoc($result);
echo $field['pwd'];

pour récupérer un tableau contenant les valeurs demandées par select

mysql_query("update docker set state=1 where domain='$input'");

pour changer une valeur dans la base

mysql_query("insert into docker (owner, domain, ip ,state) values ('$input','$name','$ip',0)");

pour insérer une nouvelle ligne dans la base

mysql_query("delete from docker where domain='$site'");

pour supprimer une ligne de la base

  • libérer la base :
mysql_free_result($result);

shell_exec

  • Shell_exec permet d'exécuter des commandes côté serveur
  • Il faut autoriser l'exécution des scripts par apache
chown www-data:root script.sh
  • /etc/sudoers :
www-data ALL=(ALL) NOPASSWD:/root/script.sh
  • Maintenant que l'application à l’autorisation d'exécuter les scripts:
shell_exec("sudo /root/create_cont.sh $domain $ip");
  • Ne pas oublier d'ajouter cd /root en tête des scripts, sinon le script est exécuté dans /var/www/html

Tests

  • J'ai testé ces scripts avec 3 conteneurs à la fois (cont0, cont1 et cont2). Les sous-domaines respectifs affichent bien la page web associée à un conteneur et le domaine principale ne change pas. L'ordre de lancement ou de l'arrêt des conteneurs n'influe pas sur leur comportement.
  • Le lancement et l'arrêt des serveurs sur l'application fonctionne bien, cependant lors du lancement, le script php entre dans le conteneur et ne rend pas la main au navigateur, il faut recharger la page manuellement.
  • La création d'un conteneur fonctionne aussi, mais il faut lancer le conteneur pour obtenir le certificat, on se heurt au même problème vu ci-dessus.
  • La destruction d'un conteneur fonctionne parfaitement, la base de données est bien mise à jour.
  • Le téléchargement de code dans le conteneur ne permet pas d'envoyer plusieurs fichiers à la fois. Il faut forcer l'utilisateur à envoyé un dossier compressé.

Annexes