T206 ‐ Administration système et réseaux II (Théorie) - Nini1551/EPHEC_Syntheses GitHub Wiki

1. Virtualisation

Motivations

  • Gestion efficace des ressources hardware : Auparavant, il était considéré comme une bonne pratique de dédier des serveurs à des tâches spécifiques. Cela permettait de bien séparer les tâches, d’éviter les conflits entre les services, et d’adapter le hardware à un besoin unique spécifique. Avec l’évolution des performances du matériel et avec les pratiques d’over-provisionning (prévoir de la marge au niveau des performances du matériel pour les besoins futurs), les services n’utilisaient finalement en pratique qu’une petite partie du matériel.
  • Isolation : déployer les services sur des machines séparées permet de les isoler les uns des autres et d’éviter qu’une faiblesse de l’un impacte l’autre, de part le partage des ressources.
  • Migration : Migrer un service ou une application sur un hardware différent peut être hasardeux, voire impossible lorsque des technologies propriétaires sont en jeu.
  • Partage de machines entre différents utilisateurs
  • Accès facilité à des systèmes d’exploitation multiples
  • Environnements multiples : Disposer d’un environnement isolé permettant de tester des applications ou des manipulations hasardeuses et potentiellement risquées, sans mettre en danger le système principal.

Virtualisation ?

Virtualisation via un hyperviseur

Virtualiser consiste à donner l’illusion à un OS qu’il est seul à avoir le contrôle de la machine sur laquelle il tourne, alors qu’en pratique, plusieurs OS sont exécutés sur cette même machine.
Hyperviseur (Virtual Machine Monitor) : une plate-forme de virtualisation qui permet à plusieurs systèmes d’exploitation de travailler sur une même machine physique en même temps.

On peut faire une analogie entre les systèmes d’exploitation et les hyperviseurs :

  • Un système d’exploitation permet à des applications/processus de tourner sur une machine en étant isolés les uns des autres (partage de CPU, système de mémoire virtuelle, …)
  • Un hyperviseur permet à des systèmes d’exploitation de tourner sur une machine en étant isolés les uns des autres (partage de CPU, de la mémoire, des périphériques)

Les challenges de l’hyperviseur sont, entre autres, liés au fait qu’un OS s’attend à pouvoir exécuter les instructions CPU en mode privilégié alors qu’en pratique, il ne dispose pas de ces privilèges. L’hyperviseur doit donc intercepter ces instructions (“Trap and Emulate”) et faire en sorte qu’elles s’exécutent de manière adéquate.

Différents types de virtualisation

Hyperviseur

Diagramme hyperviseur

Un hyperviseur de type I, ou hyperviseur natif, ou encore bare metal, est un logiciel qui tourne directement sur le hardware et permet de faire tourner différents systèmes d’exploitation sur le matériel cible. Ce type d’hyperviseur va typiquement être utilisé sur des serveurs qui serviront à héberger des infrastructures virtualisées. ESXi et Proxmox sont deux exemples d’hyperviseurs de type I.

Un hyperviseur de type II tourne, quant à lui, au dessus d’un système d’exploitation “hôte”, et permet à l’utilisateur d’utiliser des machines virtuelles en plus de son système principal. C’est ce type de virtualisation qui permet aux particuliers de faire tourner des OS virtualisés sur leurs machines personnelles. VMWare Workstation, Player, Fusion, ou bien VirtualBox sont des hyperviseurs de type II.

Virtualisation complète, assistée par le matériel et paravirtualisation

La virtualisation complète est la technique qui permet de virtualiser des systèmes d’exploitations “classiques”, sans les informer ou devoir les modifier du fait qu’ils sont virtualisés. Cette technique est plus facile à mettre en oeuvre (pas de modification de l’OS invité), mais aura des performances moindres car l’hyperviseur ne peut pas compter sur la “collaboration” de l’OS invité.

La virtualisation assistée par le matériel est une technique rendue possible par les évolutions récentes des plateformes matérielles, qui intègrent des mécanismes facilitant la virtualisation. Ces mécanismes sont par exemple Intel-VT et AMD-V. Ils consistent par exemple à fournir un niveau de privilèges supplémentaires permettant l’exécution de l’hyperviseur “en dessous” de l’OS invité tout en laissant ce dernier au niveau privilégié classique, ou à fournir des instructions CPU liées à la gestion de la virtualisation, ou encore à proposer un niveau supplémentaire à la table des pages pour la gestion de la mémoire des systèmes virtualisés.

La paravirtualisation consiste quant à elle à obtenir la collaboration de l’OS virtualisé en le modifiant de telle sorte qu’il coopère avec l’hyperviseur. L’OS invité doit donc tourner dans une version adaptée à cet usage.

Emulation

Lorsqu’on souhaite exécuter un système sur un hardware avec lequel il n’est pas compatible, il faut alors traduire chaque instruction vers une instruction correspondante sur le CPU hôte. Ce mécanisme s’appelle l’émulation. Bien qu’il puisse répondre à des besoins fonctionnels spécifiques, il nécessite des ressources de calcul importantes pour assurer la traduction de l’ensemble des instructions. QEmu, Bochs, ou encore CrossOver sont des exemples d’émulateurs.

Conteneurisation

Diagramme conteneur

La conteneurisation ou virtualisation au niveau de l’OS (OS-level virtualization) est une alternative permettant l’isolation des applications sans devoir ajouter une couche de système d’exploitation supplémentaire, permettant un gain de performances important. L’idée est de permettre aux applications conteneurisées d’utiliser le noyau du système d’exploitation hôte, en étant entièrement isolés du système hôte et des autres conteneurs. Docker et LXC (Linux Container) sont deux exemples de techniques de conteneurisation.

2. Containers et Docker

Container

Diagramme conteneur

Un container est un environnement d’exécution isolé pour une application, contenant tout ce qui est nécessaire à l’exécution de cette dernière (exécutables, bibliothèques, dépendances, fichiers de configuration). Il tourne en mode utilisateur. Il est définit par des concepts fondateurs :

  • Isolation : Permet la limitation des accès et des ressources dédiées à l’application, ce qui apporte une protection entre l’hôte et les containers, et entre les différents containers.
  • Portabilité : Le container peut tourner facilement sur des environnements différents tant que ces derniers sont pourvu du noyau adéquat.
  • Légèreté : les containers permettent un déploiement et une adaptation à la charge rapide, puisqu’ils peuvent être démarrés et arrêtés rapidement.

Images

Les images sont un élément essentiel dans l’écosystème des containers, puisqu’il s’agit de fichiers légers, portables et interopérables contenant tous les éléments nécessaires au démarrage d’un ou plusieurs conteneurs dans un cadre applicatif spécifique. Une image de container est donc un “package” contenant les libraires, fichiers et éléments de configuration systèmes. Une image peut être utilisée sur différents systèmes ou différentes machines. Depuis une image, un ou plusieurs containers peuvent être démarrés. Ils auront alors tous un environnement d’exécution de départ identique.

Explication système de couches superposées

La plupart des formats d’image de conteneur fonctionnent sur base d’un système de fichiers en couches (Layered File System ou Union File System) : l’image est une superposition de couches par dessus une couche de base. L’intérêt de cette technique est que les couches peuvent être partagées entre images. Toutes les couches d’une image sont accessibles uniquement en lecture, à l’exception de la couche supérieure, qui, une fois un container démarré sur base de cette image, est également accessible en écriture afin de permettre l’exécution du container.

Explication du passage d'un dockerfile à un container en passant par une image

Les images peuvent être construites sur base de fichiers descriptifs (Dockerfile, ou containerfile), qui indiquent l’image de base à utiliser, et les opérations à effectuer par dessus cette image de base. Chaque opération génère une nouvelle couche, “personnalisant” ainsi l’image de départ en fonction de l’application souhaitée.

3. Docker en pratique

T206 ‐ CheatSheet Docker
Documentation Docker

Ce qui suit consistera en des illustrations pratiques.

Création d'un container & Publication de port

Nous souhaitons que notre serveur Web puisse être accessible de l’extérieur de l’hôte. Imaginons que l’hôte héberge déjà un service sur le port 80. Nous allons donc utiliser le port 3000 de l’hôte pour notre serveur web, et faire en sorte que le trafic reçu sur ce port 3000 soit redirigé vers le port 80 du container :

Gestion de port Docker

docker run -d -p 3000:80 --name web nginx

A présent, notre service sera accessible sur l’adresse IP de l’hôte, via le port 3000 (http://:3000/). Dans l’exemple ci-dessous, le client lynx utilise l’adresse locale de l’hôte et son port 3000, qui est donc redirigé vers l’adresse IP interne du container, sur le port 80.

Container Build

Gestion des images

schéma création d'un container via un Dockerfile

Deux techniques sont possibles pour créer des images :

  • Partir d’un container en cours d’exécution, et en effectuer un snapshot : Enregistrer son état actuel (couche supérieure inscriptible) par dessus les couches de l’image d’origine du conteneur pour en faire une nouvelle image. C’est l’opération docker commit. Cette technique permet de configurer un container à la main, puis, une fois l’état visé obtenu, de sauvegarder cette configuration dans une image. L’inconvénient de cette méthode est que l’historique des opérations appliquées pour obtenir l’image n’est plus accessible.

  • Créer une image depuis une image de base (généralement une distribution Linux), en listant les opérations nécessaires pour adapter cette image de base à l’usage applicatif prévu. Ces opérations consistent soit à exécuter des commandes, soit à copier des fichiers dans le système de fichiers cible, ou encore à configurer les paramètres d’exécution des futurs containers. Chaque étape de construction de l’image représente une nouvelle couche par dessus l’image de base. Il s’agit en fait de rédiger le plan de construction de l’image. Cela se fait dans un fichier spécifique appelé Dockerfile. Contrairement à la première méthode, l’utilisation d’un Dockerfile permet de documenter explicitement les opérations effectuées pour obtenir l’image.

Dans l’exemple du screencast ci-dessous, une image représentant un simple poste client avec quelques outils de débugging est construite. Le Dockerfile est très simple, et comporte trois lignes :

  • L’image de base (ubuntu)
  • Un RUN qui sert à installer les outils nécessaires (après un apt-update)
  • le CMD, qui se contente de lancer un shell. Ces trois lignes définissent trois couches sur l’image de base.

Docker Build

On assiste lors du premier build à l’installation des packages demandés. Notez cependant qu’au second build, comme les couches intermédiaires sont restées en cache, l’image n’est pas réellement reconstruite. On peut clairement observer les trois étapes de la construction de l’image qui génèrent chacune une couche supplémentaire, identifiée par un hash.

⚠️ **GitHub.com Fallback** ⚠️