Les environnements de recette à L’Équipe

julien Rousseau
lequipe-tech
Published in
5 min readDec 13, 2022

C’est la base de tout service informatique avant de livrer du code sur la production il est nécessaire de tester le code pour savoir si cela correspond bien au comportement attendu et si cela ne va pas introduire des régressions.

Le développeur, lorsqu’il travaille en local peut décider que son code est prêt mais uniquement dans son environnement de test avec les jeux de données qu’il a choisi. Il voudra sans doute rapidement déployer son code en preprod avec des données et un environnement infra proche de la production.

Le problème qui se pose rapidement dans le cadre d’une grosse équipe et de plusieurs projets qui évoluent en parallèle c’est que la preprod va être en permanence mobilisée pour recetter les nouvelles fonctionnalités. Et de plus en plus souvent, les chefs de produit et/ou testeurs voudront que le nouveau développement reste plusieurs jours en preprod si la nouveauté demande du temps pour tout vérifier.

Les UAT

A l’équipe depuis plusieurs années, nous avons résolu ce problème en mettant en place des UAT (user acceptance test). Les UAT sont des environnements complets que l’on instancie en fonction des projets en cours. Si une équipe travaille sur la refonte des lives, elle pourra déployer au fur et à mesure son code sur l’UAT dédié à la refonte des lives. Ainsi la preprod ne sera pas bloquée et servira uniquement à la recette finale avant la mise en production. Également, les chefs de produit peuvent suivre au jour le jour l’avancement du projet sans forcément passer voir le développeur pour qu’il lui montre sur sa machine.

Première version en 2018

Avec la généralisation de l’usage de Docker à l’équipe, nous avons mis en place une stack complète, configurable pour reproduire un environnement de production. L’idée était de générer un docker compose et de le faire démarrer sur une machine interne visible uniquement sur le réseau local pour des raisons de confidentialité. Sur le fond, cette idée est simple et plutôt efficace. Avec l’utilisation de Traefik il est possible de router le trafic sur des noms de domaines dédiés aux différents environnements et aux différents composants de l’architecture.

Là où cela se complique, c’est pour faire vivre ces environnements en les mettant à jour dès qu’un développeur pousse un nouveau code. A cette époque-là le service travaillait avec un Jenkins 2.0 qui permettait d’écouter les modifications sur les branches et ainsi de déployer le code sur l’UAT correspondant. Avec Groovy, le script de programmation de Jenkins, nous avions mis en place toute une mécanique pour récupérer les modifications sur le point de montage du Docker et de relancer les services impactés par la mise à jour.

Rapidement nous avons atteint les limites de cette mise en place, les teams demandaient de plus en plus d’UAT pour leurs projets, la construction d’un UAT devenait de plus en plus lente, il fallait parfois récupérer jusqu’à 15 projets, les configurer et lancer les container Docker correspondants. La machine qui hébergeait la solution commençait à saturer au niveau des ressources, mémoire principalement, mais aussi nous atteignions le nombre maximum de container que Docker peut gérer sur une instance. Il a donc été pris comme décision de basculer ces UAT sur GKE (Google Kubernetes Engine).

GKE

Dans un premier temps, nous sommes partis vers un mode lift and shift, c’est-à-dire réutiliser un maximum de choses qui marchait déjà bien avant. Les images Docker en faisaient partie, nous les avons juste basculées sur GCR (Google Cloud Registry) pour qu’elle puisse être facilement accessible depuis le cluster GKE.

Avec Docker et docker-compose, il est assez simple de monter des volumes. Volumes qui contiennent le code source. Il n’était pas envisageable de reconstruire les images Docker à chaque modification en embarquant le code comme c’est plutôt la bonne pratique. Avec GKE, les volumes partagés ne peuvent pas être montés sur plusieurs nœuds à la fois et notre cluster qui sert à héberger les nouveaux UAT est régional. C’est à dire que pour une meilleure tolérance aux pannes, les nœuds sont répartis dans plusieurs zones de Google Cloud. Et c’est là que les difficultés surviennent.

NFS is devil

Sur GKE il existe 3 modes de montages d’un volume persistant.

  • ReadWriteOnce : le volume peut être monté en lecture-écriture par un seul nœud
  • ReadOnlyMany : le volume peut être monté en lecture seule par plusieurs nœuds
  • ReadWriteMany : le volume peut être monté en lecture-écriture par de nombreux nœuds

Les volumes persistant de type GCEPersistentDisk ne supportent que le mode ReadWriteOnce. Et la seule façon de faire du ReadWriteMany et d’utiliser un storageClass de type NFS.

Après quelques recherches nous décidons d’utiliser le chart Helm NFS Ganesha Server. Le concept est séduisant, le chart va créer un disque GCE et le rendre disponible de manière transparente au pod au travers d’un serveur NFS. La configuration au niveau du pod s’écrit aussi naturellement que l’on utiliserait un volume empty dir par exemple.

--- 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nfs-pvc
namespace: devops-tester
labels:
uat: devops-tester
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs
resources:
requests:
storage: 20Gi
containers:
volumeMounts:
- name: nfs-volume
mountPath: /data/www
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-pvc

Ganesha Server gère automatiquement la storageClass NFS ainsi que la taille du disque, ici 20 Gi.

Mais dans les faits ?

Sauf que dans les faits cela ne marche pas très bien, en effet à l’usage nous nous sommes rendu que très souvent des pods ne parvenaient pas à démarrer avec un message d’erreur de ce type :

Unable to attach or mount volumes: unmounted volumes=[nfs-volume], unattached volumes=[nfs-volume kube-api-access-f9gjw]: timed out waiting for the network is not ready: container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

Nous avons bien essayé de nous passer de Ganesha en montant nous-même un serveur NFS à partir de l’image gcr.io/google_containers/volume-nfs mais sans plus de succès.

Pour contourner ce problème nous avons décidé de revoir notre cluster GKE et notamment les nodes pool. L’idée étant de créer un node pool mono zone sur lequel des disques ReadWriteOnce pourraient être montés pour les pods qui ont besoin du montage et d’un autre node pool multi zone pour les autres pods ne nécessitant pas de montage. La seule limitation de ce système c’est que sur le node pool mono zone le nombre de pods doit être inférieur à 120 ce qui pour nos besoins est largement suffisant.

Pour bien scheduler un pod qui a besoin d’un volume sur le noeud single (le nœud qui fait partie du node pool mono zone) nous avons utilisé les tolerations :

volumes: 
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-devops-tester
tolerations:
- key: "single"
operator: "Exists"
effect: "NoSchedule"
nodeSelector:
node-pool: single

Et depuis, plus de problèmes, nous avons des UAT bien isolés avec des namespaces, les mises à jour du code sur les volumes se font facilement et surtout on a gagné en rapidité par rapport à la première version.

La suite

Les chantiers futurs sont d’ouvrir avec une configuration certains UATs à l’extérieur. En effet, parfois sur des projets nous avons des intervenants extérieurs qui doivent pouvoir tester sur la plateforme et cela serait pratique de juste avoir à leur donner un login mot de passe plutôt qu’une ouverture temporaire sur le VPN.

--

--