Comment nous avons modernisé l'infrastructure de la Xoxoday

1. Introduction

En jetant un coup d'œil à l'infrastructure lors de mes premiers jours de travail à Xoxoday, j'ai rapidement réalisé que l'infrastructure était relativement naissante. Les nombreuses lacunes que j'ai remarquées étaient des indicateurs potentiels de la dette technique, strictement parlant en termes de configuration de l'infrastructure. Ces lacunes concernaient la conception, l'architecture, l'outillage, la surveillance et la journalisation, ainsi que divers processus et pratiques.

Compte tenu de la nature de l'activité, Xoxoday est un service web orienté vers l'utilisateur qui pourrait typiquement être classé dans la catégorie des logiciels en tant que service (SaaS). Cela exerce une forte pression sur les exigences techniques spécifiques telles que la disponibilité, le temps de fonctionnement, la fiabilité, la robustesse et l'évolutivité.

Un site web de commerce électronique typique tel que Xoxoday avec un trafic mondial élevé nécessite que nous nous adaptions aux modèles de conception, à l'architecture, aux outils et aux pratiques les plus récents et les plus uniques. Cela nous permet de garantir la qualité de service attendue par la base d'utilisateurs et, idéalement, de la dépasser.

Enfin, la plupart des clients se soucient de la localisation des données, ce qui constitue une autre exigence essentielle liée au respect de la vie privée et de la sécurité. Ils s'attendent généralement à ce que les données résident et ne quittent jamais le pays ou la région qu'ils ont demandé.

Il est donc devenu nécessaire d'envisager le déploiement de plusieurs environnements de production en parallèle, des déploiements atomiques indépendants les uns des autres. Nous appelons cela un polycloud, bien que le terme puisse être discuté. Cette exigence nous a obligés à mettre en place une automatisation de pointe, car nous devions désormais répliquer la gestion et la maintenance de notre infrastructure dans plusieurs régions.

Par conséquent, après une réflexion approfondie, nous avons rapidement réalisé que nous devions disposer des meilleurs processus et pratiques d'automatisation de l'infrastructure, ainsi que d'un portefeuille de technologies modernes basées sur le cloud, afin de passer au cloud-natif et de fournir le meilleur service et la meilleure expérience à nos utilisateurs.

Le point positif pour nous était que les développeurs avaient déjà décomposé leurs services en microservices. Cela nous a permis d'adopter rapidement les dernières tendances et pratiques en matière d'infrastructure.

2. Besoin d'automatisation

L'automatisation des services dans le nuage a été un sujet brûlant ces dernières années, conduisant à l'essor de nombreux outils et technologies excellents. Nos besoins étaient typiquement dictés par la nature de l'entreprise et ses exigences. Pour nous, les facteurs suivants étaient cruciaux :

  • Haute disponibilité
  • Mise à l'échelle automatique
  • Auto-guérison

En outre, l'automatisation offre la possibilité de réaliser l'infrastructure en tant que code, ce qui nous permet d'interagir avec l'infrastructure tout en tirant parti des avantages fantastiques des systèmes de contrôle de version.

Il s'agissait d'un changement de paradigme important, car il nous permettait d'examiner l'infrastructure de manière proactive et de réduire les interactions réactives (impératives). Elle a facilité les retours en arrière et nous a aidés à suivre efficacement les changements dans l'infrastructure. En outre, l'automatisation a permis à nos équipes DevOps de se reposer en paix à des heures indues.

Un autre avantage de l'automatisation est qu'elle ouvre diverses voies pour la modernisation et l'amélioration de la productivité des développeurs. Nous approfondirons ce sujet dans la section suivante sur la livraison continue.

3. Intégration, livraison et déploiement continus

Les mises en production peuvent être un cauchemar douloureux si les pipelines, les outils et les directives appropriés ne sont pas en place. Cela signifie que l'épine dorsale de DevOps était les pipelines CI/CD qui nous aidaient à tester, intégrer, construire et déployer notre code en production.

Nous voulions être en mesure d'effectuer plusieurs déploiements de production en une journée, et ce de manière transparente. En cas de panne, nous voulions pouvoir annuler les mises à jour de manière transparente et rapide.

Comme nous l'avons mentionné précédemment, nos développeurs ont fait un travail fantastique en décomposant les services monolithiques en microservices, ce qui leur a ouvert les portes du monde du cloud natif. Cependant, cela s'est accompagné de son lot de défis. Le plus grand défi auquel nous avons été confrontés a été celui des tests d'intégration.

En outre, notre architecture et d'excellents outils cloud-native nous ont permis d'aborder le développement logiciel de manière innovante. Nous avons bouleversé les conventions traditionnelles pour nous adapter au monde nouveau et passionnant des microservices.

Commencer par la pratique bien établie d'avoir plusieurs environnements nous a permis de tester et d'examiner minutieusement notre portefeuille de fonctionnalités avant de le déployer en production. Une fois qu'ils ont fini de travailler sur leurs fonctionnalités, bugs ou améliorations, nos développeurs ouvrent une demande de téléchargement sur la branche "dev" de git.

Une fois le PR approuvé, les commits ont été fusionnés sur la branche dev. Nous avions prévu un mécanisme de déclenchement manuel pour déployer la branche dev auprès des développeurs. ( que nous explorerons plus tard)

La base de code de cette branche a été déployée dans l'environnement de développement, un environnement dédié aux développeurs pour tester leurs fonctionnalités de pointe. Il y avait deux environnements de développement, l'un pour que les développeurs testent leurs fonctionnalités et leurs microservices et l'autre pour les tests d'intégration.

Une fois la base de code testée et approuvée par l'équipe QA, les commits ont été fusionnés dans la branche "staging or uat". À partir de là, la base de code a été déployée (manuellement) dans l'environnement Staging, que nous nous sommes efforcés de maintenir aussi proche que possible de la production.

Il y avait une autre paire d'yeux sur Staging avant de considérer quelles fonctionnalités et microservices seront livrés à la production. Nous avions un autre environnement, l'environnement de démonstration, réservé uniquement à la démonstration de nos services aux clients potentiels intéressés par nos services.

Nous avons étudié et analysé les meilleurs outils possibles pour CI/CD, et nous avons finalement opté pour Github Actions. En bref, Github Actions nous a fourni un environnement intégré qui fonctionne bien avec le flux de travail de GitHub et dont l'intégration est minimale pour les équipes de développeurs.

En plus de l'intégration étroite, il nous a également fourni des mécanismes CI/CD de pointe. Il est super rapide, abordable, facile à configurer et à personnaliser. De plus, il disposait d'une syntaxe YAML très simple pour définir les tâches, ce qui nous permettait d'intégrer la définition CI/CD à la base de code et de la suivre grâce à la puissance de git.

a. Intégration continue

Nous avons exploré quelques moyens de déclencher des builds automatiquement, mais nous avons rapidement réalisé qu'il valait mieux laisser les développeurs contrôler le moment de la construction. Il peut y avoir de nombreuses raisons en faveur ou en défaveur du déploiement automatique et du déploiement manuel.

Il est préférable d'aligner les attentes des équipes et de prendre cette décision par commodité, car une trop grande automatisation comporte des pièges potentiels.

Pour nos environnements de non-production, nous avions un déclencheur manuel pour construire et déployer les dernières versions dans les environnements respectifs. Dans nos environnements de production, les builds étaient déclenchés automatiquement sur la base d'une balise git. Les étiquettes Git nous ont permis de respecter un schéma de versionnement et de faciliter le retour en arrière des versions en production si nécessaire.

Laissant de côté les tests unitaires et les tests fonctionnels que les développeurs ont exécutés sur leurs commits, concentrons-nous sur l'aspect infrastructurel de notre CI/CD. La tâche de construction, une fois déclenchée, s'authentifie auprès d'ECR, gère le cache d'image et exécute la commande docker build pour créer une image de conteneur.

Cette image de conteneur est poussée vers le dépôt ECR correspondant. Il s'agit d'une configuration simple mais extrêmement puissante et flexible, car elle nous permet de confier le déclenchement aux développeurs tout en automatisant l'ensemble du processus.

b. Déploiements continus

Nous avions un déclencheur manuel fourni aux développeurs pour déployer les dernières versions dans les environnements respectifs. Nous avons utilisé les étiquettes d'images Docker pour distinguer les images de conteneurs pour la branche git individuelle et l'environnement.

Nos microservices sont exécutés sur Kubernetes. Un job de déploiement typique fait ce qui suit :

  1. Mise à jour des fichiers de configuration et des variables : Nous supprimons les cartes de configuration existantes et les recréons car Kubernetes ne met pas à jour une carte de configuration existante. Nos fichiers de configuration sont enregistrés dans un dépôt git privé, et les informations sensibles sont stockées dans AWS Secrets Manager. Ainsi, git nous permet de suivre les fichiers de configuration, ce qui facilite la suppression et la recréation des cartes de configuration dans Kubernetes.
  2. Mettre à jour le déploiement et le service sur Kubernetes (en cas de modification des fichiers Yaml).
  3. Enfin, effectuez un déploiement kubernetes rollout car Kubernetes ne met pas automatiquement à jour les déploiements/pods uniquement pour les changements apportés aux fichiers de configuration (configmaps).

Nous avons utilisé les ressources suivantes de la communauté d'actions GitHub pour nous aider à nous connecter, à nous authentifier et à communiquer avec EKS/ECR et Docker.

4. L'architecture

Nous avons utilisé AWS pour nos besoins en infrastructure. AWS nous fournit un riche écosystème de services qui exécute et gère efficacement notre flotte de microservices et les backend et middleware associés.

Ces microservices sont exécutés sur Kubernetes géré par AWS EKS. L'offre AWS EKS nous permet d'économiser beaucoup de temps et d'efforts que Xoxoday consacrerait autrement à la gestion du cycle de vie du cluster Kubernetes lui-même. Cela nous permet de profiter de l'écosystème cloud native tout en nous concentrant sur nos services et en travaillant dessus.

‍Kubernetes apporte une dizaine d'années d'expérience dans l'exploitation de l'infrastructure globale de Google. Il nous offre diverses fonctionnalités. Pour n'en citer que quelques-unes : L'autorégénération, l'autoscaling, la haute disponibilité tout en consolidant et en augmentant l'efficacité avec laquelle nous utilisons notre infrastructure sous-jacente. Nous utilisons l'ingress pour exposer nos services au monde extérieur via un mélange d'équilibreurs de charge classiques et d'applications et l'automatisation de l'entrée DNS de route53.

Ces services se connectent à notre backend, qui est hébergé dans un mélange de services gérés AWS comme Kafka, RDS et des instances EC2 auto-hébergées. Nous nous appuyons fortement sur Terraform pour l'installation automatisée d'EKS, un mélange unique de Terraform et de modèles de lancement AWS pour créer les instances EC2 et les gérer automatiquement.

En outre, SaltStack est utilisé pour les manœuvres complexes de gestion de la configuration automatisée de la flotte d'instances EC2. Il nous permet de gérer, de mettre à jour et de maintenir automatiquement le système d'exploitation et les services qui tournent dessus. SaltStack dispose d'un ensemble de fonctionnalités robustes et d'une architecture brillamment conçue, flexible et enfichable. Nous provisionnons ensuite automatiquement les services et prenons les paramètres backend nouvellement configurés (adresses IP, etc.) et remplissons les fichiers de configuration kubernetes, mettons à jour les cartes de configuration et les déployons dans l'environnement.

Cela nous permet de configurer et de reconfigurer les services sans état qui s'exécutent dans l'environnement Kubernetes afin qu'ils soient automatiquement provisionnés et configurés en fonction des changements dynamiques apportés à la configuration du backend. Nos images de conteneurs sont stockées sur AWS ECR, qui nous fournit un autre service fantastique qui s'intègre parfaitement à notre ensemble de technologies et à notre architecture.

5. Journalisation, surveillance, alerte et APM

Nous serions aveugles si nous ne disposions pas des mécanismes de retour d'information appropriés pour comprendre ce qui se passe dans notre infrastructure, surtout s'il s'agit d'un scénario Polycloud.

a. Elasticsearch/Fluentd/Kibana

Nous utilisons la pile EFK unique pour nos préoccupations liées à la journalisation. Cela nous permet de créer et de maintenir un ensemble dynamique de microservices fonctionnant dans des conteneurs et des machines virtuelles, tout en conservant les journaux dans un pipeline centralisé.

Cela nous permet d'accéder aux journaux des instances/conteneurs qui sont détruits pour diverses raisons. Il est ainsi plus facile pour nous d'accéder à de multiples préoccupations et de les résoudre en production. Voici d'autres alternatives à elasticsearch que vous pouvez choisir.

b. Grafana/Prometheus

Le tableau de bord Grafana, associé à la base de données de séries temporelles Prometheus, nous permet de conserver divers détails sur notre infrastructure, comme le CPU/RAM/Storage, etc. et nous tient au courant de l'état actuel de l'infrastructure en temps quasi réel.

Cela nous permet de mettre en œuvre des mécanismes d'alerte, ce qui facilite la gestion des incidents dans notre infrastructure par notre équipe DevOps. Malgré tous les préparatifs et la meilleure des architectures et des conceptions, les choses peuvent toujours se briser. Les systèmes Prometheus de Grafana, ainsi que Alertmanager, permettent d'atténuer les incidents en production.

En outre, cette pile bénéficie de la gestion de la programmation des applications, qui nous fournit un riche ensemble de mesures nous permettant d'avoir une vision critique des produits, de leur utilisation, etc.

6. Perturber les environnements des développeurs

Comme nous étions sur la voie des microservices, nous avons dû gérer plus de 40 microservices qui, une fois orchestrés ensemble, ont formé notre service web www.xoxoday.com. Cela implique beaucoup de communication entre ces microservices, et leur nombre même rend impossible et peu pratique de les exécuter localement sur la machine du développeur.

Nous avons donc dû faire preuve de créativité et retourner dans les eaux profondes du monde du cloud natif pour trouver une solution possible. Nous en avons vu beaucoup, pour n'en citer que quelques-unes :

- Téléprésence

- Skaffold

- Kubefwd

Pour l'instant, notre préféré et celui qui répond le mieux à nos besoins est Kubefwd, mais nous pourrons explorer les autres outils à l'avenir. Kubefwd nous permet de rendre les services Kubernetes accessibles sur le poste de travail local en utilisant les mêmes DNS que si la machine du développeur local était située à l'intérieur du cluster Kubernetes !

Cela signifie une augmentation considérable de la productivité qui rend les développeurs plus efficaces tout en améliorant l'expérience globale et en réduisant le temps nécessaire à la mise en place d'une fonctionnalité dans le monde.

7. Conclusion

Après des mois d'examen minutieux de nos besoins en infrastructure et d'alignement sur les exigences de l'entreprise, la croissance future et les tendances actuelles dans le monde de l'infrastructure et du cloud-native, nous sommes enfin arrivés à un point où nous pouvons envisager en toute confiance des jours extraordinaires à venir.

Non seulement notre infrastructure a fait un bond en avant d'une décennie, mais elle a également réussi à réduire la pression sur les équipes DevOps tout en augmentant radicalement la productivité des développeurs. Enfin, notre installation est moins chère, elle fait plus, et elle fait mieux !

Cette configuration nous permet de répliquer facilement l'ensemble de nos services, frontend et backend, et de les mettre en place automatiquement en quelques minutes. Cela nous permettra de créer de multiples environnements de production atomiques et indépendants, de les adapter à nos pipelines CI/CD et de gérer et maintenir automatiquement la configuration complexe avec facilité.

En outre, l'avantage le plus crucial de tout cela est que nos équipes DevOps peuvent rester petites et évoluer linéairement tout en fournissant une échelle globale exponentielle pour nos services. Et la cerise sur le gâteau, c'est que l'équipe DevOps peut passer des soirées paisibles et des week-ends formidables sans avoir à lutter contre les problèmes de production de manière réactive, car notre conception et notre architecture nous permettent de relever ces défis de manière proactive.

L'un des aspects fantastiques de notre configuration est que la nature automatisée nous permet d'utiliser des instances ponctuelles pour nos systèmes non productifs et non critiques. Cela nous aide à optimiser et à consolider les coûts tout en disposant d'une grande puissance de calcul. Cela signifie des configurations très rentables, et si AWS décide d'arrêter nos instances spot, pas de problème, en quelques minutes, nous allons autoscaler nos instances dédiées et tirer le meilleur des deux mondes !

Les tendances actuelles dans le monde de l'infrastructure, en particulier si l'on considère les développements Cloud Native, font de cette décennie une décennie passionnante pour DevOps ; il semble que nous soyons sur le point d'atteindre le nirvana. Le nombre d'outils, de solutions, de services et d'écosystèmes qui apparaissent peut parfois être écrasant. Pourtant, rétrospectivement, nous nous dirigeons vers une ère passionnante de services web solides comme le roc, robustes et toujours disponibles, tout en offrant une expérience fantastique aux utilisateurs.