Chez Volterra, la mission de l'équipe SRE est d'exploiter une plateforme Edge mondiale basée sur SaaS . Nous devons résoudre divers défis dans la gestion d'un grand nombre de clusters d'applications dans différents états (c'est-à-dire en ligne, hors ligne, administrateur en panne, etc.) et nous le faisons en tirant parti de l'écosystème Kubernetes et des outils avec un modèle déclaratif basé sur l'extraction utilisant GitOps.
Dans ce blog, nous décrirons :
Utiliser GitOps pour gérer et surveiller efficacement une grande flotte d'infrastructures (hôtes physiques ou cloud) et de clusters K8s
Nous approfondirons les leçons apprises à grande échelle (3 000 sites périphériques), qui ont également été abordées lors de ma récente conférence à Cloud Native Rejekts à San Diego.
Le diagramme d'architecture (Figure 1) ci-dessus montre la connectivité logique entre nos RE et CE, où chaque CE établit des connexions redondantes (IPSec ou VPN SSL) avec le RE le plus proche.
Lorsque nous avons commencé à concevoir notre plateforme il y a environ 2 ans, notre équipe produit nous avait demandé de résoudre les défis suivants :
Compte tenu de nos exigences et des défis liés à l'exploitation d'un système hautement distribué, nous avons décidé d'établir plusieurs principes que notre équipe SRE devait suivre afin de réduire les problèmes en aval :
Dans le cadre de la gestion du cycle de vie du site Edge, nous avons dû déterminer comment provisionner le système d'exploitation hôte, effectuer les configurations de base (par exemple, la gestion des utilisateurs, l'autorité de certification, les pages géantes, etc.), lancer K8, déployer les charges de travail et gérer les modifications de configuration en cours.
Nous avons envisagé mais finalement abandonné l’option KubeSpray+Ansible (pour gérer le système d’exploitation et déployer K8s) avec Helm/Spinnaker (pour déployer les charges de travail). Nous avons rejeté cette option car elle nous aurait obligés à gérer 2 à 3 outils open source et à effectuer des modifications majeures pour répondre à des besoins qui évoluaient, notamment l’auto-scalabilité des clusters edge, la prise en charge des modules TPM sécurisés, les upgrades différentielles, etc.
Comme notre objectif était de rester simple et de minimiser le nombre de composants exécutés directement dans le système d'exploitation (en dehors de Kubernetes), nous avons décidé d'écrire un démon Golang léger appelé Volterra Platform Manager (VPM). Il s'agit du seul conteneur Docker systemd du système d'exploitation et il agit comme un couteau suisse qui remplit de nombreuses fonctions :
VPM est responsable de la gestion du cycle de vie du système d'exploitation hôte, y compris l'installation, les mises à niveau, les correctifs, la configuration, etc. De nombreux aspects doivent être configurés (par exemple, l'allocation de pages énormes, /etc/hosts, etc.)
Gestion pour fournir un cycle de vie pour le manifeste Kubernetes. Au lieu d'utiliser Helm, nous avons décidé d'utiliser la bibliothèque client-go K8s, que nous avons intégrée à VPM et avons utilisé plusieurs fonctionnalités de cette bibliothèque :
Optimiste = créez la ressource sans attendre le statut. C’est très proche de la commande kubernetes apply, où vous ne savez pas si les pods démarrent réellement avec succès.
Pessimiste = attendre que la ressource Kubernetes soit dans l’état désiré. Par exemple, le déploiement attend que tous les pods soient opérationnels. Cela correspond à la nouvelle commande kubectl wait.
En plus des configurations liées aux manifestes K8s, nous devons également configurer divers services Volterra via leurs API. Les configurations VPN IPsec/SSL en sont un exemple : VPM reçoit la configuration de notre plan de contrôle global et la programme dans des nœuds individuels.
Cette fonctionnalité nous permet de réinitialiser une box à distance dans son état d'origine et de refaire tout le processus d'installation et d'enregistrement. Il s’agit d’une fonctionnalité très critique pour récupérer un site nécessitant un accès console/physique.
Même si la gestion du cycle de vie de K8 peut sembler être un grand sujet de discussion pour de nombreuses personnes, pour notre équipe, elle ne représente probablement que 40 à 50 % du volume de travail global.
Le provisionnement sans intervention du site Edge dans n'importe quel emplacement (cloud, sur site ou Edge nomade) est une fonctionnalité essentielle, car nous ne pouvons pas nous attendre à avoir accès à des sites individuels et nous ne voulons pas non plus engager autant d'experts Kubernetes pour installer et gérer des sites individuels. Cela ne s'adapte tout simplement pas à des milliers de personnes.
Le diagramme suivant (Figure 2) montre comment VPM est impliqué dans le processus d'enregistrement d'un nouveau site :
Comme vous pouvez le voir, l’ensemble du processus est entièrement automatisé et l’utilisateur n’a pas besoin de connaître quoi que ce soit sur la configuration détaillée ni d’exécuter des étapes manuelles. Il faut environ 5 minutes pour mettre l'ensemble de l'appareil en ligne et être prêt à répondre aux applications et aux demandes des clients.
La mise à niveau est l’une des choses les plus compliquées que nous avons dû résoudre. Définissons ce qui est mis à niveau dans les sites périphériques :
Il existe deux méthodes connues qui pourraient être utilisées pour fournir des mises à jour aux sites périphériques :
Notre objectif lors de la mise à niveau était de maximiser la simplicité et la fiabilité, à l’instar des mises à niveau standard des téléphones portables. De plus, la stratégie de mise à niveau doit tenir compte d'autres considérations : le contexte de mise à niveau peut concerner uniquement l'opérateur du site, ou l'appareil peut être hors ligne ou indisponible pendant un certain temps en raison de problèmes de connectivité, etc. Ces exigences pourraient être plus facilement satisfaites avec la méthode pull et nous avons donc décidé de l'adopter pour répondre à nos besoins.
De plus, nous avons choisi GitOps car il permettait de fournir plus facilement un modèle d'exploitation standard pour la gestion des clusters Kubernetes, des workflows et des modifications d'audit à notre équipe SRE.
Afin de résoudre les problèmes de mise à l'échelle de milliers de sites, nous avons mis au point l'architecture SRE illustrée dans la figure 3 :
Tout d’abord, je tiens à souligner que nous n’utilisons pas Git uniquement pour stocker l’état ou les manifestes. La raison est que notre plateforme doit non seulement gérer les manifestes K8s, mais également les configurations d'API en cours, les versions K8s, etc. Dans notre cas, les manifestes K8 représentent environ 60 % de l’ensemble de la configuration déclarative. Pour cette raison, nous avons dû créer notre propre abstraction DSL par-dessus, qui est stockée dans git. De plus, comme git ne fournit pas d'API ni de fonctionnalités de fusion de paramètres, nous avons dû développer des démons Golang supplémentaires pour SRE : Configuration de l'API, de l'exécuteur et du contrôleur VP.
Passons en revue le flux de travail de publication d’une nouvelle version du logiciel au niveau du client à l’aide de notre plateforme SaaS :
Vous pouvez regarder une démonstration de l'ensemble du flux de travail ici :
Dans les sections précédentes, nous avons décrit comment nos outils sont utilisés pour déployer et gérer le cycle de vie des sites périphériques. Pour valider notre conception, nous avons décidé de créer un environnement à grande échelle avec trois mille sites clients périphériques (comme illustré dans la figure 4).
Nous avons utilisé Terraform pour provisionner 3 000 machines virtuelles sur AWS, Azure, Google et notre propre cloud bare metal sur site afin de simuler l'évolutivité. Toutes ces machines virtuelles étaient des CE (sites périphériques clients) indépendants qui établissaient des tunnels redondants vers nos sites périphériques régionaux (également appelés PoP).
La capture d'écran ci-dessous provient de notre tableau de bord SRE et affiche les numéros de bord dans des emplacements représentés par la taille du cercle. Au moment de la capture d'écran, nous avions environ 2711 sites périphériques sains et 356 sites périphériques non sains.
Dans le cadre de la mise à l'échelle, nous avons découvert quelques problèmes de configuration et d'exploitation qui nous ont obligés à apporter des modifications à nos démons logiciels. De plus, nous avons rencontré de nombreux problèmes avec un fournisseur de cloud qui ont conduit à l'ouverture de plusieurs tickets d'assistance, par exemple, la latence de réponse de l'API, l'impossibilité d'obtenir plus de 500 machines virtuelles dans une seule région, etc.
L’observabilité au sein d’un système distribué a posé un ensemble de défis beaucoup plus importants à mesure que nous avons fait évoluer le système.
Au départ, nous avons utilisé la fédération Prometheus pour les métriques — un Prometheus central dans le contrôle global fédère les Prometheus situés aux périphéries régionales (RE), qui collectent leurs propres métriques de service et fédèrent celles des CE connectés. Le Prometheus de niveau supérieur évaluait les alertes et fournissait une source de métriques pour des analyses plus approfondies. Nous avons rapidement atteint les limites de cette approche (environ 1 000 CE) et avons cherché à minimiser l’impact de la croissance du nombre de CE. Nous avons commencé à générer des séries précalculées pour les histogrammes et autres métriques à forte cardinalité. Cela nous a économisé un jour ou deux avant que nous devions appliquer des listes blanches pour les métriques. Finalement, nous avons réussi à réduire le nombre de séries chronologiques, passant d'environ 60 000 à 2 000 par site CE.
En dépassant les 3000 sites CE et après plusieurs jours en production, nous avons compris que la solution n’était pas viable à grande échelle et qu’il fallait repenser notre infrastructure de monitoring. Nous avons choisi de supprimer Prometheus de niveau supérieur (sous contrôle global) et de diviser Prometheus dans chaque RE en deux instances distinctes. L’une collecte les métriques des services locaux, l’autre fédère les métriques CE. Les deux génèrent des alertes et envoient les données vers un stockage centralisé avec Cortex. Cortex sert pour l’analyse et la visualisation, mais ne fait pas partie du processus principal de monitoring et d’alerte. Après avoir testé plusieurs solutions centralisées comme Thanos et M3db, nous avons constaté que Cortex correspondait le mieux à nos besoins.
La capture d'écran suivante (Figure 7) montre la consommation de mémoire provenant du scraping de prometheus-cef à l'heure de 3000 points de terminaison. Ce qui est intéressant, c'est la consommation de 29,7 Go de RAM, ce qui n'est pas vraiment beaucoup compte tenu de la taille du système. Il peut être encore optimisé en divisant le scraping en plusieurs d'entre eux ou en déplaçant l'écriture à distance sur Cortex directement dans le bord lui-même.
La capture d'écran suivante (Figure 8) montre les ressources mémoire et CPU dont nous avons eu besoin pour les ingesteurs Cortex (jusqu’à 19 Go de RAM) et les distributeurs à cette échelle. L'atout majeur de Cortex réside dans sa montée en charge horizontale, qui vous permet d'ajouter plus de réplicas, contrairement à Prometheus qui nécessite une montée en charge verticale.
Pour l'infrastructure de journalisation dans les CE et les RE, nous utilisons les services Fluentbit par nœud qui collectent les événements du journal de service et du système et les transmettent à Fluentd dans le RE connecté. Fluentd transmet les données à l'ElasticSearch présent dans le RE. Les données d'ElasticSearch sont évaluées par Elastalert et des règles sont définies pour créer des alertes Alertmanager. Nous utilisons notre intégration personnalisée d'Elastalert à Alertmananger pour produire des alertes avec les mêmes étiquettes que celles produites par Prometheus.
Les points clés de notre parcours de surveillance :
- Au départ, nous avions environ 50 000 séries chronologiques par CE avec une moyenne de 15 étiquettes
- Nous l'avons optimisé à 2000 par CE en moyenne
Listes while simples pour les noms de métriques et listes noires pour les noms d'étiquettes
- Le Prometheus centralisé a supprimé tous les Prometheus des RE et des CE
- En 1000 CE, il est devenu insoutenable de gérer la quantité de mesures
- Actuellement, nous avons Prometheus à chaque RE (fédération vers les Promethei des CE connectés) avec RW vers Cortex
- Architecture de journalisation décentralisée
- Fluentbit en tant que collecteur sur chaque nœud transmet les journaux à Fluentd (agrégateur) dans RE
- ElasticSearch est déployé dans chaque RE à l'aide d'une recherche de cluster à distance pour interroger les journaux à partir d'une seule instance Kibana
J’espère que ce blog vous donnera un aperçu de tout ce qui doit être pris en compte pour gérer des milliers de sites et de clusters périphériques déployés à travers le monde. Même si nous avons pu répondre et valider la plupart de nos exigences de conception initiales, de nombreuses améliorations restent encore devant nous…