Notes d'étude Kubernetes - Meilleures pratiques pour le développement d'applications (2) 20230604

3. Assurez-vous que toutes les demandes des clients sont traitées correctement

Comment s'assurer que toutes les connexions sont correctement gérées au démarrage du pod

1. Éviter la déconnexion de la connexion client au démarrage du pod

Lorsqu'un pod démarre, il expose tous les services en tant que points de terminaison de service dont les sélecteurs d'étiquettes correspondent à l'étiquette du pod. Le pod doit envoyer un signal à kubernetes indiquant qu'il est prêt. Une fois que le pod est prêt, il peut devenir un point de terminaison de service, sinon il ne peut accepter aucune demande de connexion client.

Un pod est toujours considéré comme prêt si aucune sonde de préparation n'est spécifiée dans la spécification du pod. Lorsque le premier kube-proxy met à jour les règles iptables sur son nœud et que le premier pod client commence à se connecter au service, le pod, qui est considéré comme prêt par défaut, commencera à accepter les requêtes presque immédiatement. Si l'application n'est pas prête à accepter les connexions à ce moment, le client recevra un message d'erreur tel que "connexion refusée".

Tout ce que vous avez à faire est de faire en sorte que la sonde de préparation réussisse si et seulement si le message est prêt à traiter la demande entrante. En pratique, la première étape consiste à ajouter une sonde de préparation pour les requêtes HTTP GET à l'URL racine de l'application.

2. Lorsque le pod est fermé, la connexion client est déconnectée

Lorsque le pod est supprimé et que le conteneur du pod est terminé, comment le conteneur du pod s'arrête-t-il proprement lorsqu'il reçoit le signal SIGTERM. Comment s'assurer que toutes les demandes des clients sont traitées correctement ?

Comprendre la chaîne d'événements qui se produit lorsqu'un pod est supprimé

1) Lorsque le serveur API reçoit une demande de suppression d'un pod, il modifie d'abord le statut dans etcd et notifie l'observateur de l'événement de suppression. Deux des observateurs sont le kubelet et le contrôleur de point de terminaison.

Lorsque le kubelet reçoit la notification indiquant que l'application de pod est terminée, il exécute le hook de pré-arrêt, envoie le signal SIGTERM, attend pendant un certain temps, puis tue de force le conteneur si le conteneur ne s'auto-termine pas.

Si l'application comprend qu'elle arrête d'accepter les demandes des clients en réponse au signal SIGTERM, toute tentative de connexion à l'application recevra une erreur de connexion refusée. Le temps entre la suppression du pod et le moment où cette demande se produit est relativement court. Parce qu'il s'agit d'une communication directe entre le serveur api et le kubelet.

2) Avant que le pod ne soit supprimé des règles iptables, lorsque le contrôleur de point de terminaison reçoit la notification que le pod est sur le point d'être supprimé, il supprime le point de terminaison de service du pod de tous les services où se trouve le pod. Il modifie l'objet API Endpoit en envoyant une requête REST au serveur API. Ensuite, le serveur API avertira tous les clients de prêter attention à cet objet Endpoint. Certains de ces observateurs sont des services kube-proxy exécutés sur des nœuds de travail. Chaque service kube-proxy met à jour les règles iptables sur son propre nœud pour empêcher le transfert de nouvelles connexions vers ces pods arrêtés.

Les deux séries de temps ci-dessus se produisent en parallèle. Très probablement, le temps nécessaire pour arrêter le processus d'application dans le pod est légèrement plus court que le temps nécessaire pour terminer la mise à jour des règles iptables, et la série d'événements qui conduisent à la la mise à jour des règles iptables est relativement longue. Parce que ces événements doivent d'abord atteindre le contrôleur Endpoint, puis le contrôleur Endpoint envoie une nouvelle requête au serveur api, puis le serveur api doit contrôler le kube-proxy, et enfin le kube-proxy modifie les règles iptables. Il y a une forte possibilité que le signal SIGTERM soit envoyé avant que les règles iptables ne soient mises à jour sur tous les nœuds.

Le résultat final est qu'après avoir envoyé un signal d'arrêt au pod, le pod peut toujours recevoir les demandes des clients. Si l'application comprend qu'il faut fermer le socket du serveur et ne plus recevoir de requêtes, le client recevra une erreur de type "connexion refusée"

Résoudre le problème

L'ajout d'une sonde de préparation au pod résout le problème. Si tout ce qui est requis est que les sondes de préparation commencent à échouer lorsque le pod reçoit un signal SIGTERM, cela entraînera la suppression du pod du point de terminaison du service. Mais cette action de suppression ne se produira qu'après que la sonde de préparation continue d'échouer pendant un certain temps (peut être configurée dans les spécifications de la sonde de préparation), et cette action de suppression doit d'abord atteindre kube-proxy, puis la règle iptables sera supprimé ce module.

La seule chose raisonnable que vous puissiez faire est d'attendre suffisamment longtemps pour que tous les kube-proxy aient terminé leur travail, alors combien de temps est suffisant ? Dans la plupart des scénarios, quelques secondes devraient suffire, mais rien ne garantit que ce sera suffisant à chaque fois. Lorsque le serveur API ou le contrôleur de point de terminaison est surchargé, les notifications mettront plus de temps à atteindre kube-proxy. Il est important de comprendre qu'il n'existe pas de solution parfaite à ce problème, mais l'ajout d'un délai de 5 ou 10 secondes peut également grandement améliorer l'expérience utilisateur. Vous pouvez utiliser un délai plus long, mais pas trop long, car cela entraînera l'échec de l'arrêt normal du conteneur, et cela entraînera la suppression du pod pendant une longue période et sera toujours affiché dans la liste, ce qui entraînera problème pour les utilisateurs qui suppriment des pods.

résumé:

La fermeture d'une application implique les étapes suivantes :

  • Attendez quelques secondes, puis arrêtez d'accepter de nouvelles connexions
  • Fermer toutes les connexions longues qui n'ont pas été demandées
  • Attendez que toutes les demandes soient terminées
  • puis fermez complètement l'application

4. Laissez l'application s'exécuter et gérer facilement dans kubernetes

1. Créer une image de conteneur gérable

Lors de l'empaquetage d'une application dans une image, elle peut inclure le fichier binaire de l'application et ses bibliothèques dépendantes, ou empaqueter un système d'exploitation complet et l'application ensemble. Chaque fichier du système d'exploitation de l'image est-il nécessaire ? Non, la plupart des fichiers ne sont jamais utilisés et ne feront que rendre votre image plus grande que nécessaire. Lorsqu'un pod est planifié sur un nœud pour la première fois, cela prend beaucoup de temps. Les images de construction minimales sont très difficiles à déboguer. Lorsque vous devez exécuter certains outils, tels que ping, dig, curl ou d'autres commandes similaires dans le conteneur, vous réaliserez à quel point il est important que le conteneur contienne au moins l'ensemble minimum de ces outils.

Les outils inclus dans l'image et ceux qui ne le sont pas dépendent de vos propres besoins.

2. Étiquetez raisonnablement l'image et utilisez correctement ImagePullPolicypod

Il est préférable de ne pas utiliser latest dans le manifeste, sinon vous ne pourrez pas revenir à la version spécifiée.

Vous devez utiliser une étiquette qui peut spécifier la version spécifique. Si vous utilisez une étiquette modifiable, vous devez définir la stratégie d'extraction d'image sur toujours dans la spécification de pod. Mais si vous utilisez cette méthode dans un environnement de production, vous devez faire attention à ses instructions supplémentaires. Si la règle d'extraction de l'image est définie sur toujours, l'opération de conteneur contacte le registre d'images lorsqu'elle rencontre un nouveau pod qui doit être déployé. Cela ralentira la vitesse de démarrage du pod, car le nœud doit vérifier si l'image a été modifiée. Pire encore, cette stratégie empêche le démarrage de nouveaux pods lorsque le registre miroir est inaccessible.

3. Utilisez des étiquettes multidimensionnelles au lieu d'étiquettes unidimensionnelles

Les libellés peuvent contenir les éléments suivants :

  • Le nom de l'application (ou du microservice) à laquelle appartient la ressource
  • Niveau applicatif (frontend, backend, etc.)
  • Environnement d'exécution (dev, test, staging, production, etc.)
  • numéro de version
  • type de version
  • Type de version (stable, canari, vert ou bleu en développement bleu-vert, etc.)
  • Locataires (si vous exécutez différents pods dans chaque locataire au lieu d'utiliser des espaces de noms)
  • Sharding (système avec sharding)

La gestion des balises vous permet de gérer les ressources en groupes plutôt que de manière isolée, ce qui facilite la compréhension de l'appartenance des ressources

4. Décrivez chaque ressource à l'aide d'annotations

Les annotations peuvent être utilisées pour ajouter des informations supplémentaires à vos ressources. Une ressource doit au moins inclure une annotation décrivant la ressource et une annotation décrivant le propriétaire de la ressource.

Dans un framework de microservices, un pod doit contenir une annotation décrivant les noms des autres services dont dépend le pod. Cela facilite l'affichage des dépendances entre les pods. D'autres annotations peuvent inclure des informations de construction et de version, ainsi que des méta-informations (noms d'icônes, etc.) utilisées par d'autres outils ou interfaces graphiques.

5. Fournir plus d'informations pour la fin du processus

Pour faciliter le diagnostic, l'affichage de la raison de l'arrêt du conteneur dans l'état du pod permet au processus du conteneur d'écrire un message d'arrêt dans un fichier spécifié du système du conteneur. Le contenu de ce fichier sera lu par kubelet après la fin du conteneur, puis affiché dans le pod de description de kubectl. Le chemin par défaut vers le fichier dans lequel ce processus a besoin d'écrire des messages de terminaison est /dev/termination-log. Ce chemin peut également être personnalisé en définissant le champ terminateMessagePath dans la partie de définition du conteneur de la spécification de pod.

Remarque : Si le conteneur n'écrit aucun message dans les fichiers, le champ terminateMessagePolicy ne peut être défini que sur FallbackToLogsOnError. Dans ce cas, les dernières lignes du journal du conteneur seront traitées comme des messages de terminaison (uniquement si le conteneur ne s'est pas terminé avec succès)

6. Traitement des journaux d'application

L'application écrit le journal sur l'interruption de sortie standard au lieu du fichier, et le journal de l'application peut être facilement visualisé via la commande kubectl log.

Conseil : Si un conteneur tombe en panne et qu'un nouveau conteneur le remplace, vous verrez les journaux du nouveau conteneur. Si vous souhaitez voir les journaux du conteneur précédent, lorsque vous utilisez la commande kubectl logs, ajoutez l'option --provious

Si l'application écrit des journaux dans un fichier au lieu du terminal de sortie standard, il existe un autre moyen d'afficher les journaux :

$kubectl exec <pod> cat <fichier journal>        

Cette commande exécute la commande cat à l'intérieur du conteneur, renvoie le flux de journalisation à kubectl et kubectl les affiche sur votre terminal.

Copier des journaux ou d'autres fichiers vers et depuis le conteneur

Transférez le fichier sur la machine locale :

$kubectl cp foo-pod :/var/log/foo.log foo.log

Pour copier des fichiers de votre ordinateur local dans un pod, vous pouvez spécifier le nom du pod comme deuxième argument :

$kubectl cp localfile foo-pod:/etc/remotefile

Utiliser la journalisation centralisée

kubectl lui-même ne fournit aucune journalisation centralisée et doit prendre en charge le stockage et l'analyse centralisés de tous les journaux de conteneur via d'autres composants, qui s'exécutent généralement comme des pods ordinaires dans le cluster.

Déployer une solution de journalisation centralisée est très simple, il vous suffit de déployer quelques fichiers manifestes YAML/JSON, et c'est tout.

Sur le moteur kubernetes de Google, c'est encore plus simple, il suffit de sélectionner l'option "Activer Strackdriver Logging" lors de la configuration du cluster.

Vous avez peut-être entendu parler de la pile ELK composée d'ElasticSearch, Logstash et Kibanna, une variante légèrement modifiée de la pile EFK, où Logstash est remplacé par FluentD.

Lors de l'utilisation d'EFK en tant que journalisation centralisée, chaque nœud de cluster Kubernetes exécutera un agent FluentD (déployé à l'aide de DaemonSet en tant que pod).Cet agent est chargé de collecter les journaux des conteneurs, de marquer les journaux avec des informations relatives au pod, puis de les envoyer à ElasticSearch et ElasticSearch les stockeront de manière permanente. ElasticSearch est également déployé en tant que pod dans le cluster. Ces journaux peuvent être consultés et analysés dans un navigateur Web via kubana, un outil de visualisation des données ElasticSearch, qui est souvent exécuté en tant que pod et exposé en tant que service. Les trois composants d'EFK sont illustrés dans la figure ci-dessous :

Gérer l'entrée de journal multi-lignes

L'agent FluentD stocke chaque ligne du fichier journal en tant qu'entrée dans le stockage de données ElasticSearch. Lorsque la sortie du journal s'étend sur plusieurs lignes, comme la pile d'exceptions de Java, elle sera stockée dans le système de journalisation centralisé sous différentes entrées.

Pour résoudre ce problème, vous pouvez créer le contenu de sortie du journal d'application au format JSON au lieu de texte brut. De cette manière, une sortie de journal multiligne peut être stockée en tant qu'entrée. Il peut également être affiché sous forme d'entrée dans kibana, mais cette approche rendra la commande kubectl log pour afficher le journal moins conviviale.

La solution est que les journaux de sortie vers le terminal de sortie standard sont toujours des journaux à l'échelle de l'utilisateur, mais les journaux écrits dans le fichier journal que FluentD doit traiter sont au format JSON. Cela nécessite une configuration raisonnable de l'agent FluentD au niveau du nœud ou l'ajout d'un conteneur de journalisation léger à chaque pod.

 

Je suppose que tu aimes

Origine blog.csdn.net/wwxsoft/article/details/131034972
conseillé
Classement