BLOG

Gérer des milliers de clusters Edge Kubernetes avec GitOps

Miniature F5
F5
Publié le 18 décembre 2019

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

  1. L'outillage que nous avons construit pour résoudre les problèmes liés au CI/CD
  2. Orchestration d'objets et gestion de configuration
  3. Observabilité sur l'ensemble de la flotte de clusters Infra et 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.

TL;DR (Résumé)

  1. Nous n'avons pas pu trouver de solution simple et prête à l'emploi qui pourrait être utilisée pour déployer et exploiter des milliers (et potentiellement des millions) de clusters d'applications et d'infrastructures dans le cloud public, sur site ou dans des emplacements nomades.
     
  2. Cette solution devait fournir la gestion du cycle de vie des hôtes (physiques ou cloud), du plan de contrôle Kubernetes, des charges de travail des applications et la configuration continue de divers services. De plus, la solution devait répondre à nos exigences de conception SRE : définition déclarative, cycle de vie immuable, gitops et aucun accès direct aux clusters.
     
  3. Après avoir évalué divers projets open source, par exemple Kubespray+Ansible (pour le déploiement de Kubernetes) ou Helm/Spinnaker (pour la gestion de la charge de travail), nous sommes arrivés à la conclusion qu'aucune de ces solutions ne pouvait répondre à nos exigences ci-dessus sans ajouter une surcharge logicielle importante sur chaque site périphérique. En conséquence, nous avons décidé de créer notre propre démon logiciel basé sur Golang qui effectuait la gestion du cycle de vie de l'hôte (physique ou cloud), du plan de contrôle Kubernetes et des charges de travail des applications.
     
  4. À mesure que nous avons étendu le système à 3 000 clusters et au-delà (au sein d’un seul locataire), toutes nos hypothèses sur nos fournisseurs de cloud public, l’évolutivité de nos démons logiciels, nos outils d’exploitation et notre infrastructure d’observabilité ont été systématiquement détruites. Chacun d’entre eux a nécessité une réarchitecture de certains de nos composants logiciels pour surmonter ces défis.

Définition du bord

  • Customer Edge (CE) : il s'agit d'emplacements clients dans le cloud (comme AWS, Azure, GCP ou un cloud privé), d'emplacements sur site (comme une usine, une installation pétrolière/gazière, etc.) ou d'emplacements nomades (comme l'automobile, la robotique, etc.). CE est géré par l'équipe SRE de Volterra mais peut également être déployé par les clients à la demande dans les emplacements de leur choix.
  • Bordures régionales (RE) — Il s'agit de points de présence (PoP) de Volterra dans des installations de colocation sur les principaux marchés métropolitains qui sont interconnectés avec notre propre dorsale privée hautement maillée. Ces sites périphériques régionaux sont également utilisés pour interconnecter en toute sécurité les emplacements périphériques des clients (CE) et/ou exposer les services d'application à l'Internet public. Les sites RE sont entièrement gérés et détenus par l'équipe d'exploitation des infrastructures de Volterra (Infra SRE).
gérer01
Figure 1 : Présentation du système

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.

Exigences relatives à la gestion des périphériques

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 :

  1. Évolutivité du système : nos clients avaient besoin que nous prenions en charge des milliers (voire des millions) de sites périphériques clients, ce qui est très différent de l’exécution d’une poignée de clusters Kubernetes dans des régions cloud. Par exemple, l’un de nos clients possède environ 17 000 magasins de proximité et un autre exploite plus de 20 000 bornes de recharge. Cette échelle signifiait que nous devions construire nos outils de manière très différente de la gestion de quelques clusters.
  2. Déploiement sans intervention — puisque tout le monde devrait pouvoir déployer un nouveau site sans avoir beaucoup de connaissances sur le matériel, les logiciels ou Kubernetes. Le site périphérique devait se comporter comme une boîte noire qui s’allume, appelle la maison et se connecte.
  3. Gestion de flotte — gestion simplifiée de milliers de sites et de charges de travail sans avoir à les traiter individuellement. Tout site peut être hors ligne ou devenir indisponible au moment d'une modification ou d'une mise à niveau demandée. Par conséquent, les sites doivent récupérer les mises à jour dès qu’elles sont mises en ligne.
  4. Tolérance aux pannes : les sites périphériques doivent être opérationnels même après la défaillance d’un composant. Tout doit être géré à distance et fournir des fonctionnalités telles que la réinitialisation d'usine ou la reconstruction du site en cas de panne. Nous avons dû supposer qu’il n’y avait aucun accès physique au site.

Principes de conception (pas de Kubectl !) Pas d'Ansible ! (Pas de colis !)

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 :

  1. Définition déclarative — l’ensemble du système doit être décrit de manière déclarative, car cela nous permet de créer un modèle d’abstraction simple et d’effectuer une validation par rapport au modèle.
     
  2. Gestion du cycle de vie immuable — dans le passé, nous travaillions sur de grandes installations de cloud privé utilisant des outils LCM mutables tels qu'Ansible, Salt ou Puppet. Cette fois, nous voulions garder le système d'exploitation de base très simple et essayer de tout expédier sous forme de conteneur sans gestion de paquets ni besoin d'outils de gestion de configuration.
     
  3. GitOps — fournit un modèle d’exploitation standard pour la gestion des clusters Kubernetes. Cela nous aide également à obtenir des approbations, des audits et des flux de travail de modifications prêts à l'emploi sans créer un système de gestion de flux de travail supplémentaire. Nous avons donc décidé que tout devait passer par git.
     
  4. Pas de kubectl — C’était l’un des principes les plus importants car personne n’est autorisé à accéder directement aux clusters d’applications. Par conséquent, nous avons supprimé la possibilité d’exécuter kubectl à l’intérieur de clusters de périphérie individuels ou d’utiliser des scripts exécutés à partir d’emplacements centraux, y compris des systèmes CD centralisés. Les systèmes CD centralisés avec une méthode push sont adaptés à des dizaines de clusters, mais certainement pas à des milliers sans garantie de disponibilité du réseau à 100 %.
     
  5. Aucune technologie (ni aucun outil) à la mode — notre expérience passée a montré que de nombreux outils open source populaires ne sont pas à la hauteur de leur battage médiatique. Alors que nous avons évalué plusieurs projets communautaires pour la fourniture d'infrastructures, de K8 et de charges de travail d'application (tels que Helm, Spinnaker et Terraform), nous avons fini par utiliser uniquement Terraform pour la partie infrastructure virtuelle et développé un code personnalisé que nous décrirons dans les parties suivantes de ce blog.

Gestion du cycle de vie du site

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 :

Cycle de vie de l'hôte

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.)

  1. Gestion des mises à niveau du système d’exploitation — malheureusement, Edge ne concerne pas uniquement Kubernetes et nous devons gérer la version du noyau et du système d’exploitation en général. Notre edge est basé sur CoreOS (ou CentOS selon les besoins du client) avec une partition active et passive. Les mises à jour sont toujours téléchargées sur la partition passive lorsqu'une mise à niveau est planifiée. Un redémarrage est la dernière étape de la mise à jour, où les partitions actives et passives sont échangées. La seule partie sensible est la stratégie de redémarrage (pour un cluster multi-nœuds) car tous les nœuds ne peuvent pas être redémarrés en même temps. Nous avons implémenté notre propre verrouillage de redémarrage etcd (dans VPM) où les nœuds d'un cluster sont redémarrés un par un.
  2. Gestion de l'accès des utilisateurs au système d'exploitation : notre besoin est de restreindre les utilisateurs et leur accès à ssh et à la console à distance. VPM effectue toutes ces opérations telles que les rotations SSH CA.
  3. Étant donné que nous avons développé notre propre chemin de données L3-L7 , cela nous oblige à configurer des pages géantes de 2 M ou 1 G dans le système d'exploitation hôte en fonction du type de matériel (ou de machine virtuelle cloud).

Cycle de vie de Kubernetes

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 :

  1. Déploiement optimiste ou pessimiste : cette fonctionnalité nous permet de catégoriser les applications dans lesquelles nous devons attendre qu'elles soient saines. Il surveille simplement l'annotation dans le manifeste K8 ves.io/deploy: optimiste .

    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 .

  2. Actions préalables à la mise à jour telles que la pré-extraction : il n'est parfois pas possible de compter sur les mises à jour continues de K8, en particulier lorsque le plan de données réseau est expédié. La raison est que l'ancienne nacelle est détruite et qu'ensuite la nouvelle nacelle est retirée. Cependant, dans le cas du plan de données, vous perdez la connectivité réseau. Par conséquent, la nouvelle image du conteneur ne peut pas être extraite et le pod ne démarrera jamais. L'annotation de métadonnées ves.io/prepull avec une liste d'images déclenche l'action d'extraction avant l'application du manifeste K8.
  3. Nouvelles tentatives et restaurations en cas d'échecs appliqués. Il s’agit d’une situation très courante lorsqu’un serveur API K8s connaît des pannes intermittentes.

Configuration en cours

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.

Réinitialisation d'usine

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.

Provisionnement sans intervention

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 :

gérer02
Figure 2 — Flux de provisionnement sans intervention
  1. Une fois sous tension, le VPM exécuté sur le CE (représenté par la case verte) présentera un jeton d'enregistrement à notre plan de contrôle global (GC) pour créer un nouvel enregistrement. Le jeton d'enregistrement fourni dans le cadre de cloud-init de la machine virtuelle cloud peut être une clé saisie par un humain pendant le processus de démarrage ou programmée dans le TPM pour le matériel de périphérie.
  2. GC reçoit la demande avec un jeton, ce qui lui permet de créer un nouvel enregistrement sous un locataire (codé dans le jeton). L'opérateur client peut immédiatement voir le nouveau site périphérique sur la carte et l'approuver avec la possibilité de saisir un nom et d'autres paramètres de configuration.
  3. Le contrôleur VP au sein de GC génère la configuration (par exemple, décider qui est le maître, le serviteur, etc. de K8s) et les certificats pour etcd, K8s et VPM.
  4. VPM démarre l'amorçage du site, y compris le téléchargement d'images Docker, la configuration de hugepages, l'installation du cluster K8 et le démarrage des services du plan de contrôle Volterra.
  5. VPM configure des tunnels redondants (IPSec/SSL VPN) vers les deux sites périphériques régionaux les plus proches qui seront utilisés pour le trafic de données et l'interconnectivité entre les sites et le réseau public.

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.

Mises à niveau des logiciels d'infrastructure

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 : 

  • Mises à niveau du système d’exploitation : cela couvre le noyau et tous les packages système. Quiconque a utilisé une distribution de système d'exploitation Linux standard est conscient de la difficulté de mettre à niveau des versions mineures (par exemple, passer d'Ubuntu 16.04.x à 16.04.y) et de la difficulté beaucoup plus grande de mettre à niveau des versions majeures (par exemple, passer d'Ubuntu 16.04 à 18.04). Dans le cas de milliers de sites, les mises à niveau doivent être déterministes et ne peuvent pas se comporter différemment d'un site à l'autre. Nous avons donc choisi CoreOS et CentOS Atomic avec la possibilité de suivre les mises à niveau A/B avec 2 partitions et un système de fichiers en lecture seule sur les chemins. Cela nous donne la possibilité de revenir immédiatement en arrière en changeant les partitions d'ordre de démarrage et de maintenir la cohérence du système d'exploitation sans maintenir les packages du système d'exploitation. Cependant, nous ne pouvons plus mettre à niveau les composants individuels du système, par exemple le serveur openssh, en installant simplement un nouveau package. Les modifications apportées aux composants individuels doivent être publiées sous la forme d'une nouvelle version immuable du système d'exploitation.
     
  • Mises à niveau logicielles : cela couvre les services de contrôle VPM, etcd, Kubernetes et Volterra exécutés en tant que charges de travail K8s. Comme je l'ai déjà mentionné, notre objectif est que tout ce qui se trouve à l'intérieur de K8s fonctionne en tant que conteneur systemd. Heureusement, nous avons pu tout convertir en charges de travail K8, à l'exception de 3 services : VPM, etcd et kubelet.

Il existe deux méthodes connues qui pourraient être utilisées pour fournir des mises à jour aux sites périphériques : 

  1. Basé sur Push — une méthode Push est généralement effectuée par un outil CD (livraison continue) centralisé tel que Spinnaker, Jenkins ou CM basé sur Ansible. Dans ce cas, un outil central doit avoir accès au site ou au cluster cible et doit être disponible pour effectuer l’action.
  2. Basé sur l'extraction : une méthode basée sur l'extraction récupère les informations de mise à niveau de manière indépendante sans aucun mécanisme de livraison centralisé. Il s'adapte mieux et élimine également la nécessité de stocker les informations d'identification de tous les sites en un seul endroit.

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.

GitOps

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 :

gérer03
Figure 3 — Flux GitOps

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 : 

  1. L'opérateur décide de publier une nouvelle version et ouvre une demande de fusion (MR) contre le modèle git
     
  2. Une fois ce MR approuvé et fusionné, CI déclenche l'action pour charger une configuration de modèle git dans notre démon SRE Config-API. Ce démon dispose de plusieurs API pour la fusion de paramètres, la configuration DNS interne, etc.
     
  3. Config-API est surveillé par le démon Executor ; immédiatement après le chargement des modifications git, il commence à restituer les manifestes K8s finaux avec la version dans l'annotation. Ces manifestes sont ensuite téléchargés vers le stockage d'artefacts (comme S3) sous le chemin ce01-site// .yml
     
  4. Une fois la nouvelle version rendue et téléchargée dans le stockage d'artefacts, Executor produit un nouveau statut avec une version disponible pour l'API client ; cela est très similaire à la nouvelle version disponible sur les téléphones portables
     
  5. Le client (ou l'opérateur) peut programmer une mise à jour de son site vers la dernière version et cette information est transmise au VP-Controller. VP-Controller est le démon responsable de la gestion du site, y compris l'approvisionnement, la mise hors service ou la migration vers un autre emplacement. Cela a déjà été partiellement expliqué dans le provisionnement sans contact et est responsable de la mise à jour des sites périphériques via l'API mTLS
     
  6. La dernière étape du diagramme se produit sur le site périphérique : une fois la connexion VPN IPSec/SSL établie, le VP-Controller informe le VPM du périphérique de télécharger les mises à jour avec la nouvelle version ; toutefois, si la connectivité est interrompue ou rencontre des problèmes intermittents, le VPM interroge une mise à jour toutes les 5 minutes
     
  7. Les nouveaux manifestes et configurations K8s sont récupérés et déployés dans K8s. En utilisant la fonctionnalité de déploiement pessimiste décrite dans la section précédente, VPM attend que tous les pods soient prêts
     
  8. En guise de dernière étape, VPM renvoie l’état de la mise à niveau au contrôleur VP et celui-ci est transmis en tant que statut à l’API client.

Vous pouvez regarder une démonstration de l'ensemble du flux de travail ici :

Leçons tirées des tests sur 3 000 sites périphériques

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).

gérer04
Figure 4 — 3 000 sites clients périphériques

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.

gérer05
Figure 5–Déploiement du site Edge Client 3000

Principales conclusions : Opérations

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. 

  1. Optimiser VP-Controller — au départ, nous traitions l’enregistrement en série et chaque opération prenait environ deux minutes car nous devions créer divers certificats pour etcd, kubernetes et VPM. Nous avons optimisé ce temps en utilisant des clés pré-générées utilisant une entropie plus élevée et une parallélisation avec un plus grand nombre de travailleurs. Cela nous a permis de traiter l’inscription de 100 sites en moins de 20 secondes. Nous avons pu servir les 3 000 sites périphériques en consommant seulement environ un vCPU et 2 Go de RAM sur notre VP-Controller.
     
  2. Optimiser la livraison des images Docker — lorsque nous avons commencé à évoluer, nous avons réalisé que la quantité de données transmises pour les sites périphériques est énorme. Chaque bord a téléchargé environ 600 Mo (multiplié par 3000), ce qui représente 1,8 To de données totales transmises. De plus, nous avons reconstruit des sites périphériques plusieurs fois au cours de nos tests, de sorte que ce nombre serait en réalité beaucoup plus important. En conséquence, nous avons dû optimiser la taille de nos images Docker et créer des images cloud et iso avec des images Docker pré-extraites pour réduire le téléchargement. Bien que nous utilisions toujours un service de registre de conteneurs de cloud public, nous travaillons activement sur une conception permettant de distribuer notre registre de conteneurs via des RE (PoP) et d'effectuer des mises à niveau incrémentielles (binaires-diff).
     
  3. Optimisez les opérations de base de données de contrôle global — tous nos services de contrôle Volterra sont basés sur le framework de service Golang qui utilise ETCD comme base de données. Chaque site est représenté comme un objet de configuration. Chaque objet de configuration de site possède quelques StatusObjects tels que software-upgrade, hardware-info ou ipsec-status. Ces StatusObjects sont produits par différents composants de la plateforme et ils sont tous référencés dans une API de configuration globale. Lorsque nous avons atteint 3000 sites, nous avons dû effectuer certaines optimisations dans notre schéma d'objets. Par exemple, limitez le nombre de types StatusObjects acceptés par l'API de configuration globale ou nous avons décidé de les déplacer vers une instance ETCD dédiée pour réduire le risque de surcharge de la base de données d'objets de configuration. Cela nous permet d'offrir une meilleure disponibilité et un meilleur temps de réponse pour la base de données de configuration et nous permet également de reconstruire la base de données d'état en cas de panne. Un autre exemple d’optimisation consistait à arrêter d’effectuer des opérations inutiles sur la liste des objets du site sur tous les locataires ou à introduire des index secondaires pour réduire la charge sur la base de données.

Principales conclusions : Observabilité

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.

gérer06
Figure 6 — Architecture de la collecte de mesures

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.

gérer07
Figure 7 — Utilisation des ressources sur le site RE

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.

gérer08
Figure 8 — Utilisation des ressources au niveau du contrôle global

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 :

  • Utilisation des nouveaux filtres de fédération Prometheus pour supprimer les métriques et les étiquettes inutilisées

    - 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

  • Passer de la fédération mondiale Prometheus au cluster Cortex

    - 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

  • Clusters et journaux Elasticsearch

    - 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

Résumé

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…