Le stockage dans Kubernetes expliqué

Le but de cet article est de présenter de façon vulgarisée quelques notions et problématiques de stockage liées à Kubernetes, système que nous privilégions dans la continuité de la mise en place d’un cloud privé Openstack.

Le but de cet article est de présenter de façon vulgarisée quelques notions et problématiques de stockage liées à Kubernetes, système que nous privilégions dans la continuité de la mise en place d’un cloud privé Openstack. Si vous avez lu notre premier article concernant la mise en place de Kubernetes, vous devez savoir qu’au sein du centre de services de SQLI à Bordeaux, nous sommes désormais capables :

  • D’interagir avec notre cluster Kubernetes on-premise depuis notre poste de travail (via le client kubectl) ;
  • De déployer un serveur Nginx (ou tout type de conteneur Docker) sur un node (worker) choisi par Kubernetes ;
  • De nous connecter au serveur Nginx depuis notre poste de travail via une URL fixe, sans avoir à se soucier de l’IP du node ou des ports utilisés, grâce au déploiement et à l’utilisation de Traefik.

Les applications stateless (sans état)

Ce fonctionnement est suffisant pour toute application dont les données ne sont pas amenées à être modifiées. Prenons l’exemple d’une application web classique : généralement, nous incorporons les sources d’une application dans une image Docker comprenant déjà un serveur Nginx (ou n’importe quel serveur web équivalent). Cette image peut ensuite être livrée sur un cluster Kubernetes. Une fois que le conteneur Docker est déployé, les données que celui-ci contient (typiquement les sources de l’application) ne seront pas altérées car elles sont immutables. Dans le cycle de vie de cette application, aucune action d’écriture sur disque n’est effectuée (à part éventuellement les sessions en cours des utilisateurs et des logs applicatifs). Cela permet, si besoin, de pouvoir stopper le conteneur et de le relancer sur autre machine, sans perte de données, exceptées les sessions et les logs. Ce type d’application est ce qu’on appelle de type stateless (sans état). Mais que se passe-t-il si notre application dépend de données qui sont amenées à évoluer, et qui sont nécessaires et inhérentes à son fonctionnement ?

Les applications stateful (avec état)

Prenons toujours l’exemple d’une application web, mais qui a besoin cette fois-ci d’avoir à sa disposition une base de données. A contrario du type stateless, la base de données sera donc considérée de type stateful (avec état). Dans ce cas, il n’est pas envisageable et acceptable que la base de données soit réinitialisée à chacun de ses redémarrages (ce qui est le cas par défaut avec Docker). Qui plus est, Kubernetes est capable à tout moment de stopper un conteneur (nous parlerons plutôt de pod) et de le relancer sur un autre node pour des raisons de répartition de charge, ou de crash de pod. Il est donc primordial de pouvoir persister les données de la base de données. Docker permet nativement ce mécanisme via ce que l’on appelle des volumes, ce qui permet de monter un dossier de l’hôte dans le conteneur. Ce dossier ne sera ni supprimé ni réinitialisé, et pourra être réutilisé plus tard par n’importe quel conteneur. MAIS dans un cluster Kubernetes, nous ne savons pas toujours sur quel node un conteneur va être exécuté. Par conséquent, si nous utilisons des volumes standards, le conteneur et son montage de données fonctionneront lors de la première exécution, mais si le conteneur est amené à être déplacé sur un autre node, les données resteront quant à elles sur le premier node utilisé. Un volume vierge sera alors mis à disposition du nouveau conteneur sur son nouveau node. Alors comment faire dans un environnement clusterisé comme Kubernetes ?

Les persistent volumes (volumes persistants)

La différence fondamentale entre les Persistent Volumes (volumes persistants) et  les volumes natifs de Docker, est que ces volumes ne sont pas physiquement stockés sur les nodes, mais la plupart du temps sur un service de stockage physiquement externe au cluster Kubernetes, accessible par le réseau. Cela permet donc à un conteneur de pouvoir retrouver son volume persisté même après avoir été déplacé sur un autre node (suite à un redéploiement, crash, maintenance, etc.). Une des bonnes pratiques de Docker étant de n’exécuter qu’un seul processus par conteneur, la base de données doit donc être exécutée sur un conteneur distinct que celui de notre serveur Nginx. Dans notre exemple, nous aurions donc un conteneur Nginx de type stateless sans volume persistant, et un conteneur de base de données avec un volume persistant monté dans le répertoire adéquat. Grâce à Kubernetes, le conteneur Nginx peut communiquer avec le conteneur de base de données même s’ils ont été déployés sur deux nodes distincts.

Les storage class (backends de stockage)

Maintenant que nous avons abordé le principe des Persistent Volumes, nous pouvons nous intéresser à leur fonctionnement à un plus bas niveau. Sur une plateforme de cloud public (comme par exemple AWS, Google Cloud Platform ou Microsoft Azure), il est possible de créer des Persistent Volumes directement dans les services de stockage proposés par le provider. Dans notre cas, nous sommes sur un cloud privé OpenStack, nous devons donc nous-mêmes fournir ce service de stockage afin de pouvoir y créer des Persistent Volumes.

Cinder

Cela tombe bien, car le composant de stockage natif à OpenStack, nommé Cinder est un backend de stockage (appelé Storage Class dans Kubernetes) tout à fait compatible avec Kubernetes. La liste exhaustive de toutes les Storage Classes supportées est disponible sur la documentation officielle de Kubernetes. Cinder est un composant d’OpenStack qui permet de façon générale d’attacher des disques persistants à des VMs. Kubernetes va alors être capable de créer des volumes Cinder directement via les API d’OpenStack, et de les attacher aux nodes (dans notre cas des VMs OpenStack) du cluster Kubernetes, sur lesquels sont exécutés les conteneurs Docker qui nécessitent des Persistent Volumes (ex : le Persistent Volume de notre précédent exemple sera uniquement attaché au node sur lequel le conteneur de la base de données sera exécuté). Dans notre implémentation et plus concrètement, lorsqu’il est demandé à Cinder de créer un volume, ce dernier crée un fichier de la taille demandée sur une baie de stockage iSCSI, mais cela pourrait être tout aussi bien sur un cluster Ceph, GlusterFS ou toute autre solution de stockage.

GlusterFS / Heketi

GlusterFS est un système de fichiers réseau et scalable open source. Il est maintenu par Red Hat depuis le rachat de la société Gluster en 2011.

 

 

 

 

Il repose sur une architecture de type client serveur et est particulièrement bien adapté aux problématiques cloud tout en proposant un bon niveau de fiabilité ainsi qu’une facilité d’installation et de configuration. GlusterFS permet de mettre en cluster plusieurs nodes de stockage et a besoin de bricks pour créer des volumes. Ces bricks s’appuient sur des systèmes de fichiers classiques (EXT4, XFS) au-dessus d’un périphérique en mode bloc (partition, LVM…​). Les volumes créés peuvent être distribués, répliqués, dispersés, etc. en fonction des différents cas d’usage. Pour plus d’informations sur les possibilités, la liste exhaustive est disponible dans la documentation de Gluster. Il est possible d’utiliser GlusterFS directement avec Kubernetes, mais cela nécessiterait de créer et mapper à la main les volumes GlusterFS aux Persistent Volumes de Kubernetes. Autant dire que ce n’est pas vraiment pratique, et c’est là qu’entre en jeu Heketi. C’est un composant qui a pour objectif de faciliter la gestion des volumes et ainsi automatiser les interactions avec GlusterFS. Pour cela, il met à disposition une API de management qui permet de gérer le cycle de vie des volumes GlusterFS. Ainsi, au travers de l’API, Kubernetes est en mesure de piloter la gestion des volumes (création, suppression…​). Plus techniquement, ça passe par la création d’une Storage Class côté Kubernetes qui sait interagir avec l’API d’Heketi, et donc au final avec GlusterFS.

 

 

 

 

CephFS

Créé initialement par Sage Weil dans le cadre de sa thèse de doctorat en 2007 puis racheté par Red Hat en 2014, le système de fichier Ceph ou CephFS est conforme aux normes POSIX et est construit au-dessus d’un stockage object distribué de type RADOS (Reliable Autonomic Distributed Object Store). Les principaux objectifs et avantages de ce système de fichier sont les suivants :

  • Proposer un système de fichier distribué
  • Pas de point unique de défaillance (SPOF)
  • Extensible jusqu’à l’exaoctet
  • Réparation automatique et réduction des coûts d’exploitation
  • Facilement scalable
  • Tourne sur du matériel non spécialisé

CephFS repose sur plusieurs composants :

  • Le serveur d’administration, admin qui permet d’effectuer les taches d’administration sur le cluster
  • Le serveur de métadonnées, mds (Meda Data Server) qui gère les données descriptives des objets stockés dans le cluster et s’occupe de la répartition de la charge
  • Le server moniteur, mon qui permet de suivre l’activité sur le cluster
  • Les serveurs de données OSD qui stockent les objets

Avec autant de composants, Ceph nécessite donc du matériel dédié et il est difficile de faire de l’hyper-convergence en mutualisant l’infrastructure. Cependant, son architecture assure des performances et une scalabilité sans égal. Le couplage avec OpenStack et Kubernetes peut se faire de multiples façons différentes, mais la plus native est de l’utiliser en tant que backend Cinder (cf. paragraphe à propos de Cinder ci-dessus). De cette façon, les interactions s’avèrent « naturelles » entre Kubernetes et l’infrastructure en-dessous.

Conclusion

Le stockage est sans doute la problématique la plus importante ou du moins la plus épineuse dans un cluster Kubernetes. C’est pour cette raison que la plupart des personnes en charge du déploiement d’un cluster Kubernetes n’utilisent pas du tout de Persistent Volume, pour différentes raisons :

  • Soit parce qu’ils n’ont que des applications stateless à déployer et n’ont donc besoin d’aucun Persistent Volume (ce qui est à privilégier de façon générale avec Kubernetes)
  • Soit parce qu’ils sont conscients des contraintes que cela engendre et préfèrent par conséquent utiliser des services de stockage externes mis à disposition par les providers qui sont dédiés à ça (ex : database as a service).

Au centre de services SQLI à Bordeaux (ISC), nous avons à la fois des applications stateless et stateful. Nous avons tout de même fait le choix d’utiliser les Persistent Volumes pour nos applications stateful car les interactions avec OpenStack sont puissantes et efficaces malgré certains risques évoqués dans cet article. Par mesure de précaution, nous avons tout de même choisi dans un premier temps de migrer uniquement les applications stateful non critiques et qui n’ont pas de volume de données trop conséquent (50Go maximum), ce qui les laisse assez faciles à manipuler (copie de données, migration, restauration en cas de problème, etc.). En termes de backend de stockage, nous avons choisi Cinder (backend NFS), mais nous souhaitons migrer à terme vers CephFS comme backend Cinder. Nous sommes actuellement en montée de compétences sur le sujet, et en fonction des résultats nous envisagerons ou pas la migration vers cette solution. Pour les applications critiques et qui ont des volumes de données plus conséquents, nous les avons pour l’instant laissées sur un hébergement plus simple (CoreOS + Docker, mais pas dans un cluster Kubernetes). Après un retour d’expérience plus important, nous prévoyons une migration davantage conséquente. Article écrit par :

  • Romain Ballan, Ingénieur Systèmes & Réseaux