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.
L’une des options que nous avons envisagées mais finalement rejetées était d’utiliser KubeSpray+Ansible (pour gérer le système d’exploitation et déployer K8s) et Helm/Spinnaker (pour déployer les charges de travail). La raison pour laquelle nous avons rejeté cette option est que cela nous aurait obligé à gérer 2 à 3 outils open source, puis à effectuer des modifications importantes pour répondre à nos exigences, qui ont continué à croître à mesure que nous avons ajouté davantage de fonctionnalités telles que la mise à l'échelle automatique des clusters de périphérie, la prise en charge des modules TPM sécurisés, les mises à niveau 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éer des ressources et ne pas attendre le statut. C'est très similaire à la commande kubernetes apply où vous ne savez pas si les pods réels démarrent avec succès.
Pessimiste = attendre l’état de la ressource Kubernetes. Par exemple, le déploiement attend que tous les pods soient prêts. Ceci est similaire à 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, pour les métriques, nous avons commencé avec la fédération Prometheus — Prometheus central dans le contrôle global fédérant Promethei dans les périphéries régionales (RE), qui récupère ses métriques de service et fédère les métriques de leurs CE connectés. Le Prometheus de niveau supérieur a évalué les alertes et a servi de source de métrique pour une analyse plus approfondie. Nous avons rapidement atteint les limites de cette approche (environ 1000 CE) et avons essayé de minimiser l’impact du nombre croissant de CE. Nous avons commencé à générer des séries pré-calculées pour les histogrammes et d’autres mesures à cardinalité élevée. Cela nous a fait gagner un jour ou deux, puis nous avons dû utiliser des listes blanches pour les mesures. Au final, nous avons pu réduire le nombre de mesures de séries chronologiques d’environ 60 000 à 2 000 pour chaque site CE.
Finalement, après avoir continué à évoluer au-delà de 3 000 sites CE et à fonctionner pendant plusieurs jours en production, il était clair que ce n'était pas évolutif et nous avons dû repenser notre infrastructure de surveillance. Nous avons décidé d'abandonner le Prometheus de niveau supérieur (sous contrôle global) et de diviser le Prometheus de chaque RE en deux instances distinctes. L'un est responsable de la récupération des métriques de service locales et le second de la fédération des métriques CE. Les deux génèrent des alertes et envoient des métriques vers le stockage central dans Cortex. Cortex est utilisé comme source d'analyse et de visualisation et ne fait pas partie du flux d'alerte de surveillance de base. Nous avons testé plusieurs solutions de métriques centralisées, à savoir Thanos et M3db, et avons trouvé que Cortex était celui qui répondait 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 la quantité de mémoire et de ressources CPU dont nous avions besoin pour les ingesteurs Cortex (19 Go de RAM maximum) et les distributeurs à cette échelle. Le plus grand avantage de Cortex est la mise à l’échelle horizontale, qui nous permet d’ajouter plus de répliques par rapport à Prometheus où la mise à l’échelle doit se faire verticalement.
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…