Liste de contrôle: ce qui devait être fait avant de démarrer les microservices dans prod

Cet article contient un bref extrait de ma propre expérience et de l'expérience de mes collègues avec qui j'ai passé des jours et des nuits à ratisser des incidents. Et de nombreux incidents n'auraient jamais eu lieu si tout le monde aimait leurs microservices écrits au moins un peu plus précisément.


Malheureusement, certains programmeurs de bas niveau croient sérieusement qu'un Dockerfile avec une sorte de commande à l'intérieur est en soi un microservice et il peut être déployé même maintenant. Les dockers tournent, le banc est boueux. Cette approche est lourde de problèmes allant d'une baisse des performances, de l'incapacité à déboguer et du déni de service à un cauchemar appelé incohérence des données.


Si vous pensez que le moment est venu de lancer une autre application dans Kubernetes / ECS / quoi que ce soit, alors j'ai quelque chose à objecter.


La version anglaise est également disponible .


Je me suis formé un certain ensemble de critères pour évaluer l'état de préparation des demandes de lancement en production. Certains points de cette liste de contrôle ne peuvent pas être appliqués à toutes les applications, mais uniquement à des applications spéciales. D'autres s'appliquent généralement à tout. Je suis sûr que vous pouvez ajouter vos options dans les commentaires ou contester certains de ces points.


Si votre micro-service ne répond pas à au moins un des critères, je ne permettrai pas qu'il soit dans mon cluster idéal, construit dans un bunker à 2000 mètres sous terre avec chauffage au sol et un système d'alimentation Internet autonome fermé.


Allons-y ...


Remarque: l'ordre des articles n'a pas d'importance. Quoi qu'il en soit, pour moi.


Readme Brève description


Il contient une brève description de lui-même au tout début de Readme.md dans son référentiel.

Dieu, cela semble si simple. Mais combien de fois j'ai rencontré que le référentiel ne contient pas la moindre explication pourquoi il est nécessaire, quelles tâches il résout, etc. Il n'est pas nécessaire de parler de quelque chose de plus compliqué, comme les options de configuration.


Intégration avec un système de surveillance


Envoie des métriques à DataDog, NewRelic, Prometheus, etc.

Analyse de la consommation des ressources, des fuites de mémoire, des traces de pile, de l'interdépendance des services, du taux d'erreur - sans comprendre tout cela (et pas seulement), il est extrêmement difficile de contrôler ce qui se passe dans une grande application distribuée.


Alertes configurées


Le service comprend des alertes qui couvrent toutes les situations standard ainsi que les situations uniques connues.

Les métriques sont bonnes, mais personne ne les suivra. Par conséquent, nous recevons automatiquement les appels / push / sms si:


  • La consommation de CPU / mémoire a considérablement augmenté.
  • Le trafic a augmenté / diminué fortement.
  • Le nombre de transactions traitées par seconde a radicalement changé dans toutes les directions.
  • La taille de l'artefact après assemblage a considérablement changé (exe, app, pot, ...).
  • Le pourcentage d'erreurs ou leur fréquence a dépassé le seuil autorisé.
  • Le service a cessé d'envoyer des métriques (situation souvent négligée).
  • La régularité de certains événements attendus est violée (le travail cron ne fonctionne pas, tous les événements ne sont pas traités, etc.)
  • ...

Runbooks créés


Un document a été créé pour le service qui décrit les éventualités connues ou attendues.

  • comment s'assurer que l'erreur est interne et ne dépend pas d'un tiers;
  • si cela dépend où, à qui et quoi écrire;
  • comment le redémarrer en toute sécurité;
  • comment restaurer à partir d'une sauvegarde et où se trouvent les sauvegardes;
  • Quels tableaux de bord / requêtes spéciaux sont créés pour surveiller ce service;
  • Le service a-t-il son propre panneau d'administration et comment y accéder?
  • existe-t-il une API / CLI et comment l'utiliser pour résoudre les problèmes connus;
  • et ainsi de suite.

La liste peut varier considérablement entre les organisations, mais au moins les choses de base devraient être là.


Tous les journaux sont écrits en STDOUT / STDERR


Le service ne crée aucun fichier journal en mode production, ne les envoie à aucun service externe, ne contient aucune abstraction redondante pour la rotation des journaux, etc.

Lorsqu'une application crée des fichiers journaux, ces journaux sont inutiles. Vous n'entrerez pas dans 5 conteneurs fonctionnant en parallèle, en espérant rattraper l'erreur dont vous avez besoin (et vous voilà, pleurer ...). Le redémarrage du conteneur entraînera une perte complète de ces journaux.


Si une application écrit ses propres journaux sur un système tiers, par exemple, dans Logstash, cela crée une redondance inutile. Le service voisin ne sait pas comment procéder, car a-t-il un cadre différent? Vous obtenez un zoo.


L'application écrit une partie des journaux dans des fichiers et une partie sur stdout car il est pratique pour le développeur de voir INFO dans la console et DEBUG dans les fichiers? C'est généralement la pire option. Personne n'a besoin de complexité et de code et de configurations complètement redondants que vous devez connaître et maintenir.


Les journaux sont Json


Chaque ligne de journal est écrite au format Json et contient un ensemble cohérent de champs

Jusqu'à présent, presque tout le monde écrit des journaux en texte brut. C'est un vrai désastre. Je serais heureux de ne jamais connaître Grok Patterns . Je rêve parfois d'eux et je me fige, essayant de ne pas bouger, pour ne pas attirer leur attention. Essayez d'analyser les exceptions Java dans les journaux une seule fois.


Json est bon, c'est le feu qui vient du ciel. Ajoutez-y simplement:


  • horodatage en millisecondes selon la RFC 3339 ;
  • niveau: info, avertissement, erreur, débogage
  • user_id;
  • nom_app
  • et d'autres domaines.

Téléchargez sur n'importe quel système approprié (ElasticSearch correctement configuré, par exemple) et profitez-en. Connectez les journaux de nombreux microservices et ressentez à nouveau les bonnes applications monolithiques.


(Et vous pouvez ajouter Request-Id et obtenir le traçage ...)


Journaux avec niveaux de verbosité


L'application doit prendre en charge une variable d'environnement, par exemple LOG_LEVEL, avec au moins deux modes de fonctionnement: ERREURS et DEBUG.

Il est souhaitable que tous les services d'un même écosystème prennent en charge la même variable d'environnement. Pas une option de configuration, pas une option sur la ligne de commande (bien que cela soit réversible, bien sûr), mais immédiatement par défaut depuis l'environnement. Vous devriez pouvoir obtenir autant de journaux que possible en cas de problème et le moins de journaux possible, si tout va bien.


Versions de dépendances fixes


Les dépendances des gestionnaires de packages sont fixes, y compris les versions mineures (par exemple, cool_framework = 2.5.3).

Cela a déjà été beaucoup discuté, bien sûr. Certaines corrections de dépendances sur les versions majeures, en espérant que seules les corrections de bugs mineurs et les correctifs de sécurité seront dans les versions mineures. C'est faux.
Chaque modification de chaque dépendance doit être reflétée dans une validation distincte . Pour qu'il puisse être annulé en cas de problème. Est-ce difficile à contrôler avec vos mains? Il existe des robots utiles, comme celui-ci , qui garderont une trace des mises à jour et créeront des demandes d'extraction pour chacun d'entre vous.


Dockerized


Le référentiel contient Dockerfile et docker-compose.yml prêts pour la production

Docker est depuis longtemps la norme pour de nombreuses entreprises. Il y a des exceptions, mais même si vous n'avez pas Docker en production, tout ingénieur devrait être en mesure de composer Docker et de ne penser à rien d'autre pour obtenir un assemblage de dev pour la vérification locale. Et l'administrateur système doit avoir l'assembly déjà vérifié par les développeurs avec les versions nécessaires des bibliothèques, des utilitaires, etc., dans lesquels l'application fonctionne au moins en quelque sorte pour l'adapter à la production.


Configuration de l'environnement


Toutes les options de configuration importantes sont lues à partir de l'environnement et l'environnement a une priorité supérieure aux fichiers de configuration (mais inférieure aux arguments de ligne de commande au démarrage).

Personne ne voudra jamais lire vos fichiers de configuration et étudier leur format. Acceptez-le.


Plus de détails ici: https://12factor.net/config


Sondes de préparation et de vivacité


Contient des points de terminaison ou des commandes cli appropriés pour tester la préparation à répondre aux demandes au démarrage et à la disponibilité tout au long de la vie.

Si l'application sert des requêtes HTTP, elle devrait avoir deux interfaces par défaut:


  1. Pour vérifier que l'application est active et non bloquée, un test de vivacité est utilisé. Si l'application ne répond pas, elle peut être automatiquement arrêtée par des orchestrateurs comme Kubernetes, " mais ce n'est pas exact ". En fait, tuer une application gelée peut provoquer un effet domino et mettre définitivement votre service. Mais ce n'est pas un problème de développeur, faites simplement ce point final.


  2. Pour vérifier que l'application n'a pas seulement démarré, mais est prête à accepter les demandes, un test de préparation est effectué. Si l'application a établi une connexion à la base de données, au système de mise en file d'attente, etc., elle doit répondre avec un statut de 200 à 400 (pour Kubernetes).



Limites des ressources


Contient des limites sur la consommation de mémoire, CPU, espace disque et toutes autres ressources disponibles dans un format cohérent.

L'implémentation spécifique de cet élément sera très différente selon les organisations et les orchestrateurs. Cependant, ces limites doivent être définies dans un format unique pour tous les services, être différentes pour différents environnements (prod, dev, test, ...) et être en dehors du référentiel avec le code d'application .


L'assemblage et la livraison sont automatisés


Le système CI / CD utilisé dans votre organisation ou projet est configuré et peut fournir l'application à l'environnement souhaité selon le flux de travail accepté.

Rien n'est jamais livré à la production manuellement.


Peu importe la difficulté d'automatiser l'assemblage et la livraison de votre projet, cela doit être fait avant que ce projet ne soit mis en production. Cet élément comprend la création et l'exécution d'Ansible / Chef cookbooks / Salt / ..., la création d'applications pour les appareils mobiles, la construction d'une fourchette du système d'exploitation, la création d'images de machines virtuelles, peu importe.
Vous ne pouvez pas automatiser? Vous ne pouvez donc pas lancer cela dans le monde. Après vous, personne ne le récupérera.


Arrêt progressif - arrêt correct


L'application peut traiter SIGTERM et d'autres signaux et interrompre systématiquement son travail après la fin du traitement de la tâche en cours.

C'est un point extrêmement important. Les processus Docker deviennent orphelins et fonctionnent pendant des mois en arrière-plan où personne ne les voit. Les opérations non transactionnelles s'interrompent au milieu de l'exécution, créant une incohérence des données entre les services et les bases de données. Cela conduit à des erreurs imprévisibles et très, très coûteuses.


Si vous ne contrôlez aucune dépendance et ne pouvez pas garantir que votre code traitera correctement SIGTERM, utilisez quelque chose comme dumb-init .


Plus d'infos ici:



Connexion à la base de données vérifiée régulièrement


L'application envoie constamment un ping à la base de données et répond automatiquement à l'exception de «perte de connexion» pour toutes les demandes, essayant de la restaurer par elle-même ou de terminer son travail correctement

J'ai vu de nombreux cas (ce n'est pas seulement une tournure de discours) lorsque des services créés pour traiter des files d'attente ou des événements ont perdu leur connexion par timeout et ont commencé à verser sans cesse des erreurs dans les journaux, à renvoyer des messages dans les files d'attente, à les envoyer à Dead Letter Queue ou tout simplement à ne pas faire leur travail.


Mise à l'échelle horizontalement


Avec une charge croissante, il suffit d'exécuter davantage d'instances d'application pour garantir que toutes les demandes ou tâches sont traitées.

Toutes les applications ne peuvent pas évoluer horizontalement. Un exemple frappant est le Kafka Consumers . Ce n'est pas nécessairement mauvais, mais si une application particulière ne peut pas être lancée deux fois, toutes les parties intéressées doivent en être informées à l'avance. Ces informations devraient être une horreur, se bloquer dans le fichier Lisez-moi et dans la mesure du possible. Certaines applications en général ne peuvent en aucun cas être lancées en parallèle, ce qui crée de sérieuses difficultés dans son support.


Il est préférable que l'application elle-même contrôle ces situations ou qu'un wrapper soit écrit pour elle qui surveille efficacement les «concurrents» et ne permette tout simplement pas au processus de démarrer ou de démarrer le travail jusqu'à ce qu'un autre processus termine son travail ou jusqu'à ce qu'une configuration externe permette à N processus de fonctionner simultanément.


Files d'attente de lettres mortes et mauvaise résilience des messages


Si le service écoute les files d'attente ou répond aux événements, la modification du format ou du contenu des messages n'entraîne pas sa chute. Les tentatives infructueuses de traitement de la tâche sont répétées N fois, après quoi le message est envoyé à Dead Letter Queue.

Plusieurs fois, j'ai vu redémarrer sans cesse des consommateurs et des lignes qui ont gonflé à une taille telle que leur traitement ultérieur a pris de nombreux jours. Tout écouteur de file d'attente doit être prêt à modifier le format, à des erreurs aléatoires dans le message lui-même (en tapant des données dans json, par exemple), ou lorsqu'il est traité par du code enfant. J'ai même rencontré une situation où la bibliothèque RabbitMQ standard pour un framework extrêmement populaire ne prenait pas en charge les tentatives, les compteurs de tentatives, etc.


Pire encore, lorsqu'un message est simplement détruit en cas d'échec.


Limitation du nombre de messages et de tâches traités par processus


Il prend en charge une variable d'environnement, qui peut être forcée de limiter le nombre maximal de tâches traitées, après quoi le service s'arrêtera correctement.

Tout coule, tout change, surtout la mémoire. Le graphique en constante augmentation de la consommation de mémoire et OOM tués à la fin est la norme dans les esprits kubernétiques modernes. La mise en œuvre d'un test primitif qui vous ferait simplement économiser, même le besoin même d'examiner toutes ces fuites de mémoire, vous faciliterait la vie. J'ai souvent vu des gens consacrer beaucoup de temps et d'efforts (et d'argent) pour arrêter ce roulement, mais rien ne garantit que le prochain engagement de votre collègue ne fera pas empirer les choses. Si l'application peut survivre une semaine - c'est un excellent indicateur. Laissez-le ensuite se terminer et redémarrez. C'est mieux que SIGKILL (sur SIGTERM voir ci-dessus) ou l'exception "out of memory". Pendant quelques décennies, cette fiche vous suffit.


N'utilise pas d'intégration tierce avec filtrage par adresses IP


Si l'application envoie des demandes à un service tiers qui autorise l'accès à partir d'adresses IP limitées, le service effectue ces appels indirectement via un proxy inverse.

C'est un cas rare, mais extrêmement désagréable. Il est très gênant lorsqu'un petit service bloque la possibilité de changer le cluster ou de déplacer l'ensemble de l'infrastructure dans une autre région. Si vous devez communiquer avec une personne qui ne sait pas utiliser oAuth ou VPN, configurez à l'avance le proxy inverse . N'implémentez pas dans votre programme l'ajout / suppression dynamique de telles intégrations externes, car ce faisant, vous vous clouez dans le seul runtime disponible. Il est préférable d'automatiser immédiatement ces processus pour gérer les configurations Nginx, et dans votre application, contactez-le.


Agent utilisateur HTTP évident


Le service remplace l'en-tête User-agent par un en-tête personnalisé pour toutes les demandes à n'importe quelle API, et cet en-tête contient suffisamment d'informations sur le service lui-même et sa version.

Lorsque vous avez 100 applications différentes qui se parlent, vous pouvez devenir fou en voyant dans les journaux quelque chose comme "Go-http-client / 1.1" et l'adresse IP dynamique du conteneur Kubernetes. Identifiez toujours votre application et sa version de manière explicite.


Ne viole pas la licence


Il ne contient pas de dépendances qui limitent excessivement l'application, ce n'est pas une copie du code de quelqu'un d'autre, etc.

C'est un cas qui va de soi, mais il s'est avéré que même l'avocat qui a écrit la NDA a maintenant le hoquet.


N'utilise pas de dépendances non prises en charge


Lorsque vous démarrez le service pour la première fois, il n'inclut pas les dépendances déjà obsolètes.

Si la bibliothèque que vous avez utilisée dans le projet n'est plus prise en charge par quiconque, recherchez un autre moyen d'atteindre l'objectif ou développez la bibliothèque elle-même.


Conclusion


Il y a des vérifications très spécifiques sur ma liste pour des technologies ou des situations spécifiques, mais j'ai juste oublié d'ajouter quelque chose. Je suis sûr que vous trouverez également quelque chose à retenir.

Source: https://habr.com/ru/post/fr438064/


All Articles