À la Russie avec amour : Déployer Kubernetes à l’étranger
Il y a quelques années, CloudOps a commencé à travailler avec un gros client européen qui voulait migrer ses charges de travail dans GCP (Google Cloud Platform). Il cherchait à moderniser son application avec des conteneurs et à utiliser la distribution Kubernetes la plus mature du moment : Google Kubernetes Engine (GKE). Il voulait aussi que sa plateforme applicative utilise des bibliothèques de recettes d’automatisation basées sur Terraform et Ansible. CloudOps l’a accompagné à travers le processus de migration et l’architecture fut complétée en mars 2018.
Déployer Kubernetes dans des environnements exotiques
L’architecture en place, comme le montre l’image de gauche, avait un équilibreur de charge matériel, celui-ci a servi de point d’entrée et a pu desservir le contenu tant de notre RDC que de l’application exploitée dans Kubernetes. L’architecture était composée de nombreux éléments. Redis fut utilisé pour la mise en mémoire cache de la session. Fluentd était un conteneur d’enregistrement qui s’intégrait directement avec StackDriver de GCP. Nous avons créé un conteneur Fluentd sur mesure qui s’enregistrait dans une pile ELK sur mesure elle aussi. Tous ces éléments devaient être migré vers un fournisseur sur place, basé en Russie.
L’architecture répondait aux exigences techniques et commerciales essentielles de notre client. Par contre, lui-même desservait un grand nombre de clients provenant de la fédération russe où les exigences en matière de souveraineté des données doivent être conformes avec celles de la Russie pour tous les individus se trouvant en Russie (en vertu de la loi 152-FZ). Notre client devait donc augmenter son infrastructure conteneurisée dans GCP avec une solution hébergée en Russie. Cela fut effectué en plusieurs étapes.
Trouver un fournisseur infonuagique
Jusqu’à aujourd’hui, il n’existe pas, en Russie, de fournisseur infonuagique à grande échelle, pas même GCP ; notre seule option était une MV traditionnelle hébergée sur place.
La première étape fut de trouver un fournisseur russe sur place. Nous en avons évalué environ cinq qui variaient énormément en matière de qualité. Nous avons éventuellement choisi un fournisseur VMware CloudDirector aux environnements entièrement conformes à la loi 152-FZ et qui pouvait renforcer quelques modules d’extension Terraform.
Automatiser et/ou approvisionner les MV
Le fournisseur infonuagique avait un module d’extension de fournisseur Terraform, mais la distance géographique entre leurs serveurs APIs et nos équipes le rendait difficile à utiliser. On ne pouvait pas se fier aux états et plusieurs pannes ont été causées. De plus, les modules d’extension utilisaient des versions de produits VMware obsolètes et incompatibles.
Nous ne pouvions donc pas utiliser la majorité de nos recettes d’automatisation. Et comme nous ne pouvions pas non plus utiliser ce fournisseur Terraform pour automatiser la majorité du travail de façon fiable, nous avons dû configurer manuellement de nouvelles recettes pour ce cas spécifique.
Notre hôte devait travailler sur deux différentes distributions d’images Debian 9, ayant respectivement des disques racine de 40 et de 100 gigaoctets. Nous avons créé une base d’images pour exécuter les automatisations afin de sécuriser les MV qui pouvaient être utilisées pour bâtir l’infrastructure. Ce fut un processus laborieux et lent, mais qui a finalement permis à nos ressources informatiques d’atteindre la Russie. Au fil du temps, l’IU est devenue plus conviviale.
Elle fut elle-même une histoire cocasse. C’était une IU administrative Flash, ancienne version de CloudDirector. On nous recommandait d’utiliser Internet Explorer afin d’assurer le bon fonctionnement de toutes ses fonctionnalités. Les signaux d’alarme se sont mis à sonner dans notre tête, comme dans la vôtre probablement. Avant de faire confiance à l’application, nous avons fait preuve de prudence et pendant quelques mois, avons exécuté l’IU dans une MV qui lui était consacrée. Heureusement, l’IU fonctionne parfaitement depuis plus d’un an maintenant. Nous avons réussi à approvisionner toutes les MV et avons pu passer à l’étape suivante.
Choisir une distribution Kubernetes
Comme nous ne pouvions utiliser ni prolonger de service Kubernetes géré sur un nuage public, notamment GKE, EKS ou AKS, nous devions trouver une distribution Kubernetes indépendante et facile à exploiter .
Nous ne voulions pas utiliser Kubespray ou Kubeadm, car ils sont tous les deux trop longs à installer et trop complexes à configurer et à exploiter. Ils ont aussi tous les deux des problèmes fréquents à créer des réglages multimaîtres et sont reconnus pour leurs difficultés à exploiter des grappes à long terme.
Nous avons décidé d’utiliser Rancher Kubernetes Engine (RKE), qui selon moi, est le meilleur installateur Kubernetes sur mesure jusqu’à présent. Tout ce dont RKE a besoin pour installer une grappe Kubernetes est une boite de MV qui fonctionne sur Docker et qui est préférablement compatible avec une version Kubernetes. Il est tout de même possible d’exécuter des versions non compatibles, mais cela comporte plus de risques. Il faut aussi une identification SSH, mais c’est à peu près tout !
Il y a de nombreuses raisons pour lesquelles RKE est de plus en plus reconnue comme la meilleure distribution Kubernetes de logiciel libre. L’entreprise Chick-fil-A a écrit un article intéressant qui explique comment elle est, elle aussi, arrivée à cette conclusion.
Exploiter RKE
Pour exécuter RKE, nous avons téléchargé l’unique binaire rke et crée un fichier YAML unique qui définissait notre grappe Kubernetes.
RKE a un ensemble de commande pour exploiter, installer ou démanteler des grappes. La commande de base « rke up » connecte les tunnels SSH, détecte l’état de votre grappe et enfin initialise une installation Kubernetes.
Tout cela vous permet d’exécuter Kubernetes entièrement dans des images Docker qui proviennent de Rancher. Cela nous a pris 5 minutes pour initialiser une grappe Kubernetes avec trois nœuds maîtres et cinq nœuds ouvriers. Le multimaître a fonctionné dès son installation avec etcd complètement distribué. Une fois la grappe approvisionnée, le binaire RKE sortait et exportait un certificat de client KUBECONFIG qui pouvait être utilisé pour interagir avec la grappe. Personnaliser les configurations fut simple avec RKE
spec: imagePullSecrets: - name: my-gcr-secret nodes: address: masterworker port: “22” role: controlplane etcd worker services: Kube-api: Service_cluster_ip_range: 10.43.0.0/21 network: plugin: canal authentication: strategy: x509 ssh_key_path: “/path/to/key” authorization: mode: rbac ignore_docker_version: true docker cluster_name: “my-cluster”
L’image de gauche montre un fichier de configuration RKE minimum, démontrant ce qui doit être réglé. À tout le moins, vous avez besoin d’un nœud unique que vous définissez en tant que plan de contrôle, etcd, nœud ouvrier ou toute combinaison de ces éléments. Définissez les adresses IP pour vos pods et vos services. Dans notre cas, nous utilisions le canal CNI. Cela signifiait que nous n’avions pas à choisir des adresses pouvant être routées en dehors de la grappe, car elles étaient en paquet encapsulé. Choisissez un éventail raisonnable, personnellement, je trouve que 10.43 soit un bon éventail. Définissez le serveur de grappe DNS (la 11e adresse IP dans la plage d’adresses de service est la norme, alors 10). Finalement, activez le CABR en lui indiquant d’ignorer les versions Docker.
RKE est facile d’installation et est reconnu pour sa simplicité de gestion du cycle de vie.
Pour ajouter de nouveaux nœuds, il faut simplement les ajouter à la section appropriée dans «cluster.yaml» et exécuter «rke up» encore une fois.
Pour démanteler des nœuds, supprimez-les de «cluster.yaml» et exécutez «rke up».
You can also fix specific components of Kubernetes without upgrading the entire cluster by changing the Docker image to the desired version.
Vous pouvez aussi réparer des composantes Kubernetes spécifiques sans avoir à mettre à niveau toute la grappe en changeant l’image Docker vers la version désirée.
Pour mettre à niveau Kubernetes en entier, ajustez «cluster.yaml» ajoutez un nouveau binaire et exécutez «rke up». RKE se chargera du roulement de redémarrages et des mises à niveau. Quoi que vous fassiez, lors des mises à niveau, ne sautez jamais de version.
Résolution de problèmes des fonctionnalités infonuagiques clés.
RKE est un très bon outil, mais dans ce cas-ci, ce n’est pas une solution complète en-soi. Il lui manque quelques fonctionnalités importantes :
- La capacité de communiquer avec les registres de Conteneur de Google (Google Container Registry) pour en tirer des images
- Un équivalent pour CloudSQL
- Une Interface de stockage de conteneur (Container Storage Interface – CSI Driver) qui facilite les volumes persistants et les demandes de volume persistant
- Un équilibreur de charge
- Un équivalent pour le stockage d’objet de Google (Google Object Storage)
- Un équivalent pour CloudCDN
Notre cas nécessitait chacune de ces fonctionnalités. Regardons, en ordre, comment nous avons réussi à résoudre ces problèmes.
Registre de conteneurs Google
Pour tirer des images de Google Container Registry, nous avons utilisé un secret Kubernetes à distance provenant de GCR, mais en dehors de GCP. Nous avons pu le faire puisqu’aucune de ses images ne contenait de données de clients. Grâce à un bon secret Kubernetes, il est facile de tirer des images à distance de GCR en dehors de GCP :
apiVersion: v1 data: .dockerconfigjson: BASE64_ENCODED_SERVICE_ACCOUNT_JSON_KEY kind: Secret metadata: name: my-gcr-secret namespace: default type: kubernetes.io/dockerconfigjson
Ensuite, utilisez ce secret pour le manifeste du déploiement :
spec: imagePullSecrets: - name: my-gcr-secret
Un équivalent pour CloudSQL
Pour résoudre le problème d’exécution de CloudSQL sur place, nous devions simplement installer MySQL.
Nous avons utilisé la même image Debian 9 que nous avions utilisée dans les grappes Kubernetes. Nous avons pu utiliser certains de nos playbooks Ansible pour configurer MySQL 5.7.
Nous avons effectué un déploiement standard d’un seul maître avec deux esclaves. Il y en avait un en temps réel pour faire les rapports et le basculement. L’autre était différé dans le temps et envoyait les enregistrements binaires (binlogs) instantanément, mais ne les appliquait pas avant 30 jours. Cela nous permettait de les restaurer à un instant donné même s’ils contenaient une corruption répliquée.
CloudSQL et Google Container Registry furent tous deux des problèmes assez simples à régler.
Solution de stockage – RDC
Le stockage d’objet exigeait une solution plus créative.
À l’époque, Rook/Ceph n’était pas prêt pour la production sur place, nous avons donc utilisé GlusterFS, une technologie standard de RedHat, pour fournir une fonction comparable au stockage d’objet.
Nous avons configuré deux nœuds NGINX entrants pour servir le contenu de GlusterFS, pour agir comme réseau de diffusion de contenu (RDC) et de mandataire inversé pour les NodePorts et les ports d’entrée de service de grappes Kubernetes.
Chaque nœud entrant reçoit dans GlusterFS, des données répliquées sur un dispositif réservé et sert les données de GlusterFS sur un chemin spécifique pour toutes les données RDC.
Le volume GlusterFS est alors monté sur un nœud ouvrier Kubernetes pour la lecture et l’écriture et ajouté au déploiement comme volume HostPath sous /cdn-data. Cela permet à chaque nœud d’écrire du contenu pour le RDC.
Ce fut une solution adéquate pour le stockage d’objet et RDC
Solution de stockage – Redis
Selon la saveur de Redis utilisée, vous aurez peut-être à utiliser des demandes de volume persistant pour stocker ses données dans des mémoires caches de session. Nous utilisions la version Helm de Redis comme mémoire cache de session, ce qui demande des CVP. Comme nous n’avions pas de CVP de disponible, nous avons temporairement configuré l’application afin qu’elle utilise la base de données SQL comme mémoire cache de session, à la place de Redis.
Solution de stockage définitive – Rook/Ceph
En décembre 2018, au Kubecon de Seattle, on annonça Rook/Ceph comme projet de la CNCF en phase « d’incubation », le pilote Ceph fut marqué comme prêt pour la production. Il était donc temps pour nous de commencer à utiliser cette technologie, même de façon limitée : pour fournir du stockage persistant à la mémoire cache de session Redis et possiblement pour la composante ElasticSearch qui fait partie de ELK. De cette façon, si nous avions des difficultés opérationnelles avec Rook/Ceph, nous allions, au pire, perdre des données d’enregistrement (ce qui n’est pas la fin du monde) et les sessions seraient invalidées (ce qui n’est pas si grave).
Il n’y a qu’un changement mineur de configuration qui doit être fait pour que le fichier cluster.yaml de RKE puisse ramasser le pilote CSI (CSI Driver) qui sera installé par Rook une fois qu’il est lancé :
kubelet: extra_args: volume-plugin-dir: /usr/libexec/kubernetes/kubelet- plugins/volume/exec extra_binds: - /usr/libexec/kubernetes/kubelet-plugins/volume/exec:/usr/libexec/kubernetes/kubelet-plugins/volume/exec
Exécuter « rke up » sur une grappe existante va le maintenir en place.
Finalement, nous pouvions ajouter le dépôt Rook Helm Chart à notre installation Helm locale et installer l’opérateur Rook (v0.9.3 au moment de l’écriture de ce texte) :
helm repo add rook-stable https://charts.rook.io/stablehelm install --namespace rook-ceph-system rook-stable/rook-ceph
Puis, nous avons utilisé les exemples qui figurent dans l’arborescence des sources de Rook, situé sous : /cluster/examples/kubernetes/ceph pour créer une « grappe Ceph » (cluster.yaml) et une classe de stockage (storageclass.yaml) pour attribuer un bassin de répliques Ceph (Ceph Replica Pool) et lier le tout avec une classe de stockage Kubernetes (appelée rook-ceph-block).
IMPORTANT : les valeurs par défaut citées ici ne sont pas prêtes pour la production. Spécifiquement, l’élément de configuration replicated.size dans storageclass.yaml est 1 par défaut. Cela signifie que chaque bit de données stockées dans la grappe Rook/Ceph n’existe qu’une seule fois. Selon le nombre de nœuds dans votre grappe, vous allez peut-être vouloir l’augmenter. Dans notre cas, en production, nous avons utilisé replicated.size : 3.
Nous pouvons maintenant utiliser Redis, lui disant d’utiliser cette classe de stockage en utilisant Helm.
helm install --name=redis stable/redis --set master.persistence.storageClass=rook-ceph-block --set slave.persistence.storageClass=rook-ceph-block
Trouver une solution d’enregistrement
Fluentd est l’agent d’enregistrement standard de fait pour Kubernetes, il est aussi utilisé par GKE. Il est mis en œuvre dans Ruby et possède un agent en développement non dynamique et basé sur le langage appelé fluent-bit.
Il a de nombreux modules d’extension entrée/sortie et se trouve en développement actif depuis quelques années. Nous souhaitions avoir une solution d’enregistrement qui soit une configuration de surveillance complète pour Kubernetes et que dès son installation, elle ait un bon parsage de fichier journal d’application et se lie à ElasticSearch (le E dans ELK).
Pour ce faire, nous avons dû utiliser une configuration sur mesure. La version 1.2 a une dépendance sur une bibliothèque Debian non normalisée qui existe sur le nœud ouvrier et si elle est absente, cela cause une très haute charge. Le JSON dans le parsage JSON n’a pas fonctionné dès son installation. Nous avons trouvé la meilleure image FluentD pour notre cas :
fluent/fluentd-kubernetes-daemonset:v1.3.1-debian-elasticsearch-1.3
Nous avons annexé la configuration suivante à l’image FluentD kubernetes.conf pour permettre un parsage JSON adéquat de nos fichiers journaux d’application et pour mouler des types d’éléments d’enregistrement afin de faciliter la recherche dans ElasticSearch :
@type parser@type json json_parser json types elapsed_time:float,status_code:integer,bytes_sent:integer replace_invalid_sequence true emit_invalid_record_to_error false key_name log reserve_data true
Nous avons placé tout le contenu dans une configmap appelée « fluentd-config » avec une seule clé « kubernetes.conf » qui contenait toute la configuration fluentd. Puis nous avons monté cette configuration dans FluentD, en outrepassant kubernetes.conf de l’intérieur de l’image :
volumes: - name: fluentd-config configMap: name: fluentd-configvolumeMounts: - name: fluentd-config subPath: kubernetes.conf mountPath: /fluentd/etc/kubernetes.conf
Trouver plus de ressources informatiques
Au départ, l’installation russe avait une grappe ELK réservée et distincte (3 MV pour la haute disponibilité). Elle était sous-utilisée et surapprovisionnée.
Nous voulions utiliser les ressources informatiques dans la grappe Kubernetes et y exécuter ELK à l’intérieur. Pour ce faire, nous avons pu utiliser le Helm chart officiel Elastic-Stack.
Nous avons nettoyé les MV de la grappe ELK avec une nouvelle image Debian 9 puis ajouté les anciens nœuds ELK au fichier RKE « cluster.yaml » en tant que « nœuds ouvriers » et puis exécuté « rke up ». Quelques minutes plus tard, les capacités informatiques de la grappe ELK furent transférées à la grappe Kubernetes.
Finalement, nous avons installé ELK en utilisant Helm :
helm install –name elk stable/elastic-stack –set elasticsearch.data.persistence.size=50Gi –set elasticsearch.data.persistence.storageClass=rook-ceph-block –set elasticsearch.master.persistence.storageClass=rook-ceph-block –set kibana.env.ELASTICSEARCH_URL=http://elk-elasticsearch-client:9200
IMPORTANT : Les paramètres par défaut de ELK supposent un certain profil de performance. La configuration de mémoire par défaut JVM Heap pour ElasticSearch est assez faible comparée à une grappe autonome (à l’extérieur de Kubernetes), soyez donc à l’affût de la quantité de données que vous y envoyez quotidiennement. Vous devez absolument utiliser un curateur pour fermer les indices et pour éventuellement les nettoyer. Dans notre cas, nous avons décidé de garder les indices ouverts en mémoire pendant 3 jours et de réduire les données plus anciennes que 90 jours. Vous pouvez choisir de garder les indices en mémoire plus longtemps et ainsi, augmenter la taille de JVM Heap pour le serveur ElasticSearch, celui-ci peut être réglé sur le Helm Chart avec une valeur d’outrepassage.
Équilibrage de charge
Le fournisseur infonuagique russe n’avait pas la capacité d’assigner des équilibreurs de charge matériels pour couvrir l’infrastructure. Nous avons décidé d’utiliser une solution qui avait deux nœuds NGINX entrants et des résolutions DNS round-robin pour chaque requête. C’était une solution adéquate qui a pu détourner le trafic sur 80 et 443 tout en servant le trafic RDC sur un autre chemin. Notre solution n’a pas utilisé de terminaisons SSL, qui auraient pu réinitialiser certaines sessions ssl. À la place, nous avons utilisé le contrôleur d’entrées NGINX dans Kubernetes.
État final
En cours de route, nous avons rencontré quelques problèmes imprévus, mais nous avons réussi à bâtir, sur place en Russie, une augmentation de l’infrastructure GCP de notre client. Nous avons dû adapter nos procédés habituels et le déploiement russe ne ressemblait aucunement à l’infrastructure principale, mais tout a fini par bien fonctionner. L’infrastructure finale peut être visualisée dans l’image ci-dessous. Le processus de déploiement de Kubernetes sur place a pris pas mal plus de temps, car nous ne pouvions pas nous fier à recettes et procédés éprouvés. Toutefois, nous étions enchantés de voir l’infrastructure en état de marche.
Apprenez davantage sur la modernisation de votre application avec des conteneurs et inscrivez-vous à l’un de nos ateliers pratiques sur le DevOps. Nous offrons, en ligne et dans plusieurs villes, des formations sur Docker et Kubernetes ainsi que sur la surveillance DevOps. Pour de plus amples informations, consultez notre calendrier des ateliers.
Notre évaluation de la plateforme et des pratiques DevOps vous donnera une vision d’ensemble de l’architecture de votre application et vous fournira des solutions pour augmenter son efficacité et sa rapidité. Contactez-nous pour en apprendre davantage.