Nous avons commencé à créer notre premier cluster Kubernetes en 2017 (à partir de la version K8s 1.9.4). Nous avions deux groupes. L'un a travaillé sur du bare metal, sur des machines virtuelles RHEL, l'autre sur le cloud AWS EC2.
Aujourd'hui, notre infrastructure compte plus de 400 machines virtuelles réparties dans plusieurs centres de données. La plate-forme sert de base à des applications et des systèmes critiques hautement disponibles qui gèrent un vaste réseau de près de 4 millions d'appareils actifs.
En fin de compte, Kubernetes nous a rendu la vie plus facile, mais le chemin pour y parvenir était épineux et nécessitait un changement de paradigme complet. Il y a eu une transformation totale non seulement de l'ensemble des compétences et des outils, mais aussi de l'approche du design et de la réflexion. Nous avons dû maîtriser de nombreuses nouvelles technologies et investir massivement dans le développement des infrastructures et le développement des équipes.
Voici les principales leçons que nous avons tirées de l'utilisation de Kubernetes en production sur trois ans.
1. Une histoire divertissante avec des applications Java
En ce qui concerne les microservices et la conteneurisation, les ingénieurs ont tendance à éviter Java, principalement en raison de sa gestion de la mémoire notoirement imparfaite. Cependant, aujourd'hui, la situation est différente et la compatibilité de Java avec les conteneurs s'est améliorée ces dernières années. Après tout, même les systèmes populaires comme Apache Kafka et Elasticsearch fonctionnent en Java.
En 2017-2018, certaines de nos applications fonctionnaient en Java version 8. Ils refusaient souvent de fonctionner dans des environnements conteneurisés comme Docker et se bloquaient en raison de problèmes de mémoire de tas et de récupérateurs de place inadéquats. Il s'est avéré que ces problèmes étaient causés par l'incapacité de JVM à exécuter les mécanismes de conteneurisation Linux (
cgroups
et namespaces
).
Depuis lors, Oracle a fait des efforts importants pour améliorer la compatibilité de Java avec le monde des conteneurs. Dès la version 8 de Java, des indicateurs JVM expérimentaux semblaient résoudre ces problèmes:
XX:+UnlockExperimentalVMOptions
et XX:+UseCGroupMemoryLimitForHeap.
malgré toutes les améliorations, personne ne dirait que Java a toujours la mauvaise réputation d'être trop gourmand en mémoire et lent à démarrer par rapport à Python ou aller. Cela est principalement dû aux spécificités de la gestion de la mémoire dans la JVM et le ClassLoader.
Aujourd'hui, si nous avons à travailler avec Java, nous au moins essayer la version d'utilisation 11 ou plus. Et nos limites de mémoire dans Kubernetes sont 1 Go plus élevées que la limite de mémoire maximale du tas dans la JVM (
-Xmx
) (Au cas où). Autrement dit, si la machine virtuelle Java utilise 8 Go pour la mémoire de segment de mémoire, la limite de mémoire Kubernetes pour l'application sera définie sur 9 Go. Grâce à ces mesures et améliorations, la vie est devenue un peu plus facile.
2. Mises à jour liées au cycle de vie de Kubernetes
La gestion du cycle de vie de Kubernetes (mises à jour, ajouts) est une chose lourde et difficile, surtout si le cluster est basé sur du bare metal ou des machines virtuelles . Il s'est avéré que pour mettre à niveau vers une nouvelle version, il est beaucoup plus facile de créer un nouveau cluster, puis de lui transférer les charges de travail. La mise à niveau des sites existants n'est tout simplement pas faisable car elle implique des efforts importants et une planification minutieuse.
En effet, Kubernetes a trop de pièces «mobiles» à prendre en compte lors de la mise à niveau. Pour que le cluster fonctionne, vous devez rassembler tous ces composants - du Docker aux plugins CNI comme Calico ou Flannel. Des projets comme Kubespray, KubeOne, kops et kube-aws simplifient quelque peu le processus, mais ils ne sont pas sans inconvénients.
Nous avons déployé nos clusters dans des machines virtuelles RHEL à l'aide de Kubespray. Il s'est montré excellent. Kubespray avait des scripts pour créer, ajouter ou supprimer des nœuds, mettre à jour une version et à peu près tout ce dont vous avez besoin pour travailler avec Kubernetes en production. Cela dit, le script de mise à niveau était accompagné d'une mise en garde selon laquelle même les versions mineures ne devraient pas être ignorées. En d'autres termes, pour accéder à la version souhaitée, l'utilisateur devait installer toutes les versions intermédiaires.
La principale chose à retenir ici est que si vous prévoyez d'utiliser, ou utilisez déjà Kubernetes, tenez compte de vos étapes liées au cycle de vie de K8 et de son intégration dans votre solution. Il est souvent plus facile de créer et d'exécuter un cluster que de le maintenir à jour.
3. Construire et déployer
Soyez prêt au fait que vous devrez réviser les pipelines de génération et de déploiement. Avec le passage à Kubernetes, nous avons subi une transformation radicale de ces processus. Nous avons non seulement restructuré les pipelines Jenkins, mais avec l'aide d'outils comme Helm, nous avons développé de nouvelles stratégies pour créer et travailler avec Git, baliser les images Docker et gérer les versions des graphiques Helm.
Vous aurez besoin d'une stratégie unique pour gérer votre code, les fichiers de déploiement Kubernetes, les fichiers Docker, les images Docker, les graphiques Helm et un moyen de tout lier.
Après plusieurs itérations, nous nous sommes installés sur le schéma suivant:
- Le code de l'application et ses graphiques Helm sont situés dans différents référentiels. Cela nous permet de les versionner indépendamment les uns des autres ( versioning sémantique ).
- , , . , ,
app-1.2.0
charts-1.1.0
. (values
) Helm, patch- (,1.1.0
1.1.1
). (RELEASE.txt
) . - , Apache Kafka Redis ( ), . , Docker- Helm-. Docker- , .
(. .: Open Source- Kubernetes — werf — , .)
4. Liveness Readiness ( )
Les contrôles de disponibilité et de disponibilité de Kubernetes sont parfaits pour traiter de manière autonome les problèmes du système. Ils peuvent redémarrer les conteneurs en cas de défaillance et rediriger le trafic à partir d'instances «défectueuses». Mais dans certaines circonstances, ces vérifications peuvent se transformer en une arme à double tranchant et affecter le démarrage et la récupération des applications (cela est particulièrement vrai pour les applications avec état telles que les plates-formes de messagerie ou les bases de données).
Notre Kafka est devenu leur victime. Nous avons eu un ensemble avec état de 3
Broker
et 3 Zookeeper
avec replicationFactor
= 3 etminInSyncReplica
= 2. Le problème est survenu lors du redémarrage de Kafka après des plantages aléatoires ou des plantages. Au démarrage, Kafka a exécuté des scripts supplémentaires pour corriger les index corrompus, ce qui a pris 10 à 30 minutes, selon la gravité du problème. Ce retard provoquait l'échec continu des tests de vivacité, provoquant le «kill» de Kubernetes et le redémarrage de Kafka. En conséquence, Kafka pouvait non seulement réparer les index, mais même démarrer.
La seule solution à ce moment-là était d'ajuster le paramètre
initialDelaySeconds
dans les paramètres du test de vivacité afin que les contrôles ne soient effectués qu'après le lancement du conteneur. Le plus grand défi, bien sûr, est de décider du délai à fixer. Le démarrage individuel après un échec peut prendre jusqu'à une heure, et cela doit être pris en compte. D'autre part, plusinitialDelaySeconds
, le Kubernetes plus lent répondra aux échecs lors du démarrage du conteneur.
Dans ce cas, le juste milieu est la valeur
initialDelaySeconds
qui correspond le mieux à vos exigences de résilience tout en laissant à l'application suffisamment de temps pour se lancer avec succès dans toutes les situations de panne (pannes de disque, problèmes de réseau, plantages du système, etc.)
Mise à jour : dans les versions récentes de Kubernetes, un troisième type de test est apparu appelé la sonde de démarrage. Il est disponible en version alpha depuis la version 1.16 et en version bêta depuis la 1.18.
La sonde de démarrage résout le problème ci-dessus en désactivant les contrôles de disponibilité et de vivacité jusqu'à ce que le conteneur démarre, permettant ainsi à l'application de démarrer normalement.
5. Travailler avec une adresse IP externe
Il s'avère que l'utilisation d'adresses IP externes statiques pour accéder aux services exerce une pression considérable sur le mécanisme de suivi des connexions du noyau. Si vous n'y réfléchissez pas attentivement, cela peut "casser".
Dans notre cluster, nous utilisons à la
Calico
fois CNI et BGP
comme protocole de routage, ainsi que pour interagir avec les routeurs frontaliers. Le mode Kube-proxy est activé iptables
. Nous ouvrons l'accès à notre service très chargé dans Kubernetes (il traite des millions de connexions chaque jour) via une adresse IP externe. En raison du SNAT et du masquage qui proviennent du réseau défini par logiciel, Kubernetes a besoin d'un mécanisme pour suivre tous ces flux logiques. Pour cela, K8 utilise ces outils de base comme onntrack
etnetfilter
... Avec leur aide, il gère les connexions externes à une IP statique, qui est ensuite convertie en IP interne du service et enfin en adresse IP du pod. Et tout cela se fait à l'aide d'une table conntrack
et d'iptables.
Cependant, les possibilités de la table ne sont pas
conntrack
illimitées. Lorsque la limite est atteinte, le cluster Kubernetes (plus précisément, le noyau du système d'exploitation en son cœur) ne pourra plus accepter de nouvelles connexions. Dans RHEL, cette limite peut être vérifiée comme suit:
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144
Une façon de contourner cette limitation consiste à combiner plusieurs nœuds avec des routeurs de périphérie afin que les connexions entrantes vers une adresse IP statique soient réparties sur l'ensemble du cluster. Si vous avez un grand parc de machines dans votre cluster, cette approche peut augmenter considérablement la taille de la table
conntrack
pour gérer un très grand nombre de connexions entrantes.
Cela nous a complètement déroutés lorsque nous avons commencé en 2017. Cependant, relativement récemment (en Avril 2019), le projet Calico a publié une étude détaillée sous le titre apt « Pourquoi ne conntrack est plus votre ami » (il y a la traduction d' un tel de celui - ci en russe -. Environ Trad.) .
Avez-vous vraiment besoin de Kubernetes?
Trois ans se sont écoulés, mais nous continuons toujours à découvrir / apprendre quelque chose de nouveau chaque jour. Kubernetes est une plate-forme complexe avec ses propres défis, en particulier dans le domaine du démarrage de l'environnement et du maintien de son fonctionnement. Cela changera votre pensée, votre architecture, votre attitude envers le design. Vous devrez gérer la mise à l'échelle et la mise à niveau des équipes.
D'autre part, travailler dans le cloud et pouvoir utiliser Kubernetes en tant que service vous évitera la plupart des soucis associés à la maintenance de la plate-forme (comme l'extension du CIDR du réseau interne et la mise à jour de Kubernetes).
Aujourd'hui, nous en sommes venus à comprendre que la principale question à se poser est vraimentavez-vous besoin de Kubernetes? Cela vous aidera à évaluer à quel point le problème est mondial et si Kubernetes vous aidera à y faire face.
Le fait est que passer à Kubernetes coûte cher. Ainsi, les avantages de votre cas d'utilisation (et dans quelle mesure et comment il tire parti de la plate-forme) devraient justifier le prix que vous payez. Si tel est le cas, Kubernetes peut considérablement améliorer votre productivité.
N'oubliez pas que la technologie pour le bien de la technologie n'a pas de sens.
PS du traducteur
Lisez aussi sur notre blog: