Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité

Nous sommes en 2019 et nous n'avons toujours pas de solution standard pour l'agrégation de journaux dans Kubernetes. Dans cet article, nous souhaitons, à partir d'exemples issus de la pratique réelle, partager nos recherches, les problèmes rencontrés et leurs solutions.

Cependant, je ferai d’abord une réserve sur le fait que différents clients comprennent des choses très différentes en collectant des journaux :

  • quelqu'un veut voir les journaux de sécurité et d'audit ;
  • quelqu'un - journalisation centralisée de l'ensemble de l'infrastructure ;
  • et pour certains, il suffit de collecter uniquement les logs des applications, excluant par exemple les équilibreurs.

Vous trouverez ci-dessous un aperçu de la manière dont nous avons mis en œuvre diverses « listes de souhaits » et des difficultés que nous avons rencontrées.

Théorie : à propos des outils de journalisation

Contexte des composants d'un système de journalisation

L'exploitation forestière a parcouru un long chemin, grâce auquel des méthodologies de collecte et d'analyse des journaux ont été développées, que nous utilisons aujourd'hui. Dans les années 1950, Fortran a introduit un analogue des flux d'entrée/sortie standard, ce qui a aidé le programmeur à déboguer son programme. Ce furent les premiers journaux informatiques qui facilitèrent la vie des programmeurs de cette époque. Aujourd'hui, nous y voyons le premier composant du système de journalisation - source ou « producteur » de journaux.

L'informatique ne s'est pas arrêtée : des réseaux informatiques sont apparus, les premiers clusters... Des systèmes complexes composés de plusieurs ordinateurs ont commencé à fonctionner. Désormais, les administrateurs système étaient obligés de collecter les journaux de plusieurs machines et, dans des cas particuliers, ils pouvaient ajouter des messages au noyau du système d'exploitation au cas où ils auraient besoin d'enquêter sur une panne du système. Pour décrire les systèmes centralisés de collecte de journaux, il a été publié au début des années 2000 RFC 3164, qui a standardisé remote_syslog. C'est ainsi qu'est apparu un autre élément important : collecteur de journaux et leur stockage.

Avec l'augmentation du volume des journaux et l'introduction généralisée des technologies Web, la question s'est posée de savoir quels journaux doivent être facilement présentés aux utilisateurs. Les outils de console simples (awk/sed/grep) ont été remplacés par des outils plus avancés visionneuses de journaux - troisième volet.

En raison de l'augmentation du volume des journaux, quelque chose d'autre est devenu clair : les journaux sont nécessaires, mais pas tous. Et différents journaux nécessitent différents niveaux de conservation : certains peuvent être perdus en une journée, tandis que d'autres doivent être stockés pendant 5 ans. Ainsi, un composant de filtrage et de routage des flux de données a été ajouté au système de journalisation - appelons-le filtre.

Le stockage a également fait un grand pas en avant : des fichiers classiques aux bases de données relationnelles, puis au stockage orienté document (par exemple, Elasticsearch). Le stockage a donc été séparé du collecteur.

En fin de compte, le concept même de journal s'est étendu à une sorte de flux abstrait d'événements que nous souhaitons préserver pour l'histoire. Ou plutôt, au cas où vous auriez besoin de mener une enquête ou de rédiger un rapport d'analyse...

En conséquence, dans un laps de temps relativement court, la collecte de journaux est devenue un sous-système important, que l'on peut à juste titre appeler l'une des sous-sections du Big Data.

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité
Si autrefois des impressions ordinaires pouvaient suffire à un « système de journalisation », la situation a maintenant beaucoup changé.

Kubernetes et journaux

Lorsque Kubernetes est arrivé à l'infrastructure, le problème déjà existant de la collecte des journaux ne l'a pas non plus contourné. À certains égards, cela est devenu encore plus pénible : la gestion de la plate-forme d'infrastructure était non seulement simplifiée, mais aussi compliquée en même temps. De nombreux anciens services ont commencé à migrer vers des microservices. Dans le contexte des journaux, cela se reflète dans le nombre croissant de sources de journaux, leur cycle de vie particulier et la nécessité de suivre les relations de tous les composants du système via des journaux...

Pour l’avenir, je peux affirmer qu’à l’heure actuelle, malheureusement, il n’existe pas d’option de journalisation standardisée pour Kubernetes qui se comparerait favorablement à toutes les autres. Les programmes les plus populaires dans la communauté sont les suivants :

  • quelqu'un déroule la pile SFAO (Elasticsearch, Fluentd, Kibana) ;
  • quelqu'un essaie le logiciel récemment sorti Loki ou utilise Opérateur forestier;
  • nous (et peut-être pas seulement nous ?..) Je suis largement satisfait de mon propre développement - chalet...

En règle générale, nous utilisons les bundles suivants dans les clusters K8s (pour les solutions auto-hébergées) :

Cependant, je ne m'attarderai pas sur les instructions pour leur installation et leur configuration. Au lieu de cela, je me concentrerai sur leurs lacunes et sur des conclusions plus globales sur la situation des journaux en général.

Entraînez-vous avec les journaux dans les K8

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité

« Carnets du quotidien », combien êtes-vous ?..

La collecte centralisée de journaux provenant d'une infrastructure assez importante nécessite des ressources considérables, qui seront consacrées à la collecte, au stockage et au traitement des journaux. Au cours de l'exploitation de divers projets, nous avons été confrontés à diverses exigences et problèmes opérationnels qui en découlaient.

Essayons ClickHouse

Regardons un stockage centralisé sur un projet avec une application qui génère des logs de manière assez active : plus de 5000 lignes par seconde. Commençons à travailler avec ses journaux, en les ajoutant à ClickHouse.

Dès qu'un temps réel maximum est requis, le serveur à 4 cœurs avec ClickHouse sera déjà surchargé sur le sous-système de disque :

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité

Ce type de chargement est dû au fait que nous essayons d'écrire dans ClickHouse le plus rapidement possible. Et la base de données réagit à cela avec une charge de disque accrue, ce qui peut provoquer les erreurs suivantes :

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts

Le fait est que Tables MergeTree dans ClickHouse (ils contiennent des données de journal) ont leurs propres difficultés lors des opérations d'écriture. Les données qui y sont insérées génèrent une partition temporaire, qui est ensuite fusionnée avec la table principale. De ce fait, l'enregistrement s'avère très exigeant sur le disque, et il est également soumis à la limitation dont nous avons été informés ci-dessus : pas plus de 1 sous-partitions peuvent être fusionnées en 300 seconde (en fait, il s'agit de 300 insertions par seconde).

Pour éviter ce comportement, devrait écrire à ClickHouse en morceaux aussi gros que possible et pas plus d'une fois toutes les 1 secondes. Cependant, écrire en grandes rafales suggère que nous devrions écrire moins fréquemment dans ClickHouse. Ceci, à son tour, peut entraîner un débordement de tampon et une perte de journaux. La solution consiste à augmenter le tampon Fluentd, mais la consommation de mémoire augmentera également.

Noter: Un autre aspect problématique de notre solution avec ClickHouse était lié au fait que le partitionnement dans notre cas (loghouse) est implémenté via des tables externes connectées Fusionner le tableau. Cela conduit au fait que lors de l'échantillonnage de grands intervalles de temps, une RAM excessive est nécessaire, car la métatable parcourt toutes les partitions - même celles qui ne contiennent évidemment pas les données nécessaires. Cependant, cette approche peut désormais être déclarée obsolète en toute sécurité pour les versions actuelles de ClickHouse (c 18.16).

En conséquence, il devient clair que tous les projets ne disposent pas de suffisamment de ressources pour collecter des journaux en temps réel dans ClickHouse (plus précisément, leur distribution ne sera pas appropriée). De plus, vous devrez utiliser accumulateur, sur lequel nous reviendrons plus tard. Le cas décrit ci-dessus est réel. Et à cette époque, nous n'étions pas en mesure de proposer une solution fiable et stable qui conviendrait au client et nous permettrait de collecter les journaux dans un délai minimal...

Qu’en est-il d’Elasticsearch ?

Elasticsearch est connu pour gérer de lourdes charges de travail. Essayons-le dans le même projet. Maintenant, la charge ressemble à ceci :

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité

Elasticsearch a pu digérer le flux de données, cependant, l'écriture de tels volumes utilise considérablement le processeur. Cela se décide en organisant un cluster. Techniquement, ce n'est pas un problème, mais il s'avère que juste pour faire fonctionner le système de collecte de journaux, nous utilisons déjà environ 8 cœurs et disposons d'un composant supplémentaire très chargé dans le système...

En fin de compte : cette option peut être justifiée, mais seulement si le projet est de grande envergure et que sa direction est prête à consacrer des ressources importantes à un système de journalisation centralisé.

Une question naturelle se pose alors :

Quels journaux sont vraiment nécessaires ?

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité Essayons de changer l'approche elle-même : les journaux doivent à la fois être informatifs et ne pas couvrir chacun événement dans le système.

Disons que nous avons une boutique en ligne prospère. Quels journaux sont importants ? Collecter autant d’informations que possible, par exemple à partir d’une passerelle de paiement, est une excellente idée. Mais tous les logs du service de découpage d'images du catalogue de produits ne sont pas critiques pour nous : seules les erreurs et une surveillance avancée suffisent (par exemple, le pourcentage de 500 erreurs que génère ce composant).

Nous sommes donc arrivés à la conclusion que la journalisation centralisée n'est pas toujours justifiée. Très souvent, le client souhaite collecter tous les journaux en un seul endroit, même si en fait, à partir de l'ensemble du journal, seuls 5 % conditionnels des messages critiques pour l'entreprise sont requis :

  • Parfois, il suffit de configurer, par exemple, uniquement la taille du journal du conteneur et du collecteur d'erreurs (par exemple, Sentry).
  • Une notification d'erreur et un journal local volumineux peuvent souvent suffire à enquêter sur les incidents.
  • Nous avions des projets qui se contentaient de tests exclusivement fonctionnels et de systèmes de collecte d'erreurs. Le développeur n'avait pas besoin de journaux en tant que tels - il voyait tout depuis les traces d'erreurs.

Illustration tirée de la vie

Une autre histoire peut servir de bon exemple. Nous avons reçu une demande de l'équipe de sécurité d'un de nos clients qui utilisait déjà une solution commerciale développée bien avant l'introduction de Kubernetes.

Il était nécessaire de « se lier d'amitié » avec le système centralisé de collecte de journaux avec le capteur de détection de problèmes d'entreprise - QRadar. Ce système peut recevoir des journaux via le protocole syslog et les récupérer depuis FTP. Cependant, il n'a pas été immédiatement possible de l'intégrer au plugin remote_syslog pour fluentd. (comme il est apparu, Nous ne sommes pas seuls). Les problèmes liés à la configuration de QRadar se sont avérés être du côté de l'équipe de sécurité du client.

En conséquence, une partie des journaux critiques pour l'entreprise a été téléchargée sur FTP QRadar et l'autre partie a été redirigée via syslog distant directement depuis les nœuds. Pour cela nous avons même écrit graphique simple - peut-être que cela aidera quelqu'un à résoudre un problème similaire... Grâce au schéma résultant, le client lui-même a reçu et analysé les journaux critiques (à l'aide de ses outils préférés), et nous avons pu réduire le coût du système de journalisation, en économisant uniquement le le mois dernier.

Un autre exemple est assez révélateur de ce qu’il ne faut pas faire. Un de nos clients pour le traitement chaque événements provenant de l'utilisateur, rendus multilignes sortie non structurée informations dans le journal. Comme vous pouvez le deviner, ces journaux étaient extrêmement peu pratiques à lire et à stocker.

Critères pour les journaux

De tels exemples conduisent à la conclusion qu'en plus de choisir un système de collecte de journaux, vous devez concevoir également les journaux eux-mêmes! Quelles sont les exigences ici ?

  • Les journaux doivent être dans un format lisible par machine (par exemple, JSON).
  • Les journaux doivent être compacts et permettre de modifier le degré de journalisation afin de déboguer d'éventuels problèmes. Dans le même temps, dans les environnements de production, vous devez exécuter des systèmes avec un niveau de journalisation tel que ou Erreur.
  • Les journaux doivent être normalisés, c'est-à-dire que dans un objet journal, toutes les lignes doivent avoir le même type de champ.

Les journaux non structurés peuvent entraîner des problèmes de chargement des journaux dans le stockage et un arrêt complet de leur traitement. A titre d'illustration, voici un exemple avec l'erreur 400, que beaucoup ont certainement rencontrée dans les logs fluentd :

2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"

L'erreur signifie que vous envoyez un champ dont le type est instable à l'index avec un mappage prêt à l'emploi. L'exemple le plus simple est un champ dans le journal nginx avec une variable $upstream_status. Il peut contenir soit un nombre, soit une chaîne. Par exemple:

{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staffs/265.png", "protocol": "HTTP/1.1", "status": "200", "body_size": "906", "referrer": "https://example.com/staff", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staff", "protocol": "HTTP/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}

Les journaux montrent que le serveur 10.100.0.10 a répondu avec une erreur 404 et que la demande a été envoyée vers un autre stockage de contenu. En conséquence, la valeur dans les journaux est devenue comme ceci :

"upstream_response_time": "0.001, 0.007"

Cette situation est si courante qu'elle mérite même une discussion à part références dans la documentation.

Qu'en est-il de la fiabilité ?

Il y a des moments où tous les journaux sans exception sont vitaux. Et avec cela, les schémas typiques de collecte de journaux pour les K8 proposés/discutés ci-dessus posent des problèmes.

Par exemple, fluentd ne peut pas collecter les journaux des conteneurs de courte durée. Dans l'un de nos projets, le conteneur de migration de base de données a duré moins de 4 secondes puis a été supprimé - selon l'annotation correspondante :

"helm.sh/hook-delete-policy": hook-succeeded

Pour cette raison, le journal d’exécution de la migration n’a pas été inclus dans le stockage. La politique peut aider dans ce cas. before-hook-creation.

Un autre exemple est la rotation des journaux Docker. Disons qu'il existe une application qui écrit activement dans les journaux. Dans des conditions normales, nous parvenons à traiter tous les journaux, mais dès qu'un problème apparaît - par exemple, comme décrit ci-dessus avec un format incorrect - le traitement s'arrête et Docker effectue une rotation du fichier. Le résultat est que les journaux critiques pour l'entreprise peuvent être perdus.

C'est pourquoi il est important de séparer les flux de journaux, intégrant l'envoi des plus précieux directement dans l'application pour assurer leur sécurité. En outre, il ne serait pas superflu de créer des « accumulateur » de journaux, qui peut survivre à une brève indisponibilité du stockage tout en enregistrant les messages critiques.

Enfin, il ne faut pas oublier que Il est important de surveiller correctement tout sous-système. Sinon, il est facile de se retrouver dans une situation dans laquelle fluentd est dans un état CrashLoopBackOff et n'envoie rien, ce qui promet la perte d'informations importantes.

résultats

Dans cet article, nous ne nous penchons pas sur les solutions SaaS comme Datadog. De nombreux problèmes décrits ici ont déjà été résolus d'une manière ou d'une autre par des sociétés commerciales spécialisées dans la collecte de journaux, mais tout le monde ne peut pas utiliser le SaaS pour diverses raisons. (les principaux sont le coût et le respect du 152-FZ).

La collecte centralisée des journaux semble à première vue une tâche simple, mais ce n’est pas du tout le cas. Il est important de rappeler que :

  • Seuls les composants critiques doivent être enregistrés en détail, tandis que la surveillance et la collecte des erreurs peuvent être configurées pour d'autres systèmes.
  • Les journaux en production doivent rester minimes afin de ne pas ajouter de charge inutile.
  • Les journaux doivent être lisibles par machine, normalisés et avoir un format strict.
  • Les journaux vraiment critiques doivent être envoyés dans un flux séparé, qui doit être séparé des principaux.
  • Il vaut la peine d'envisager un accumulateur de bûches, qui peut vous éviter des rafales de charge élevée et rendre la charge sur le stockage plus uniforme.

Logs dans Kubernetes (et pas seulement) aujourd'hui : attentes et réalité
Ces règles simples, si elles étaient appliquées partout, permettraient aux circuits décrits ci-dessus de fonctionner, même s'il leur manque des composants importants (la batterie). Si vous ne respectez pas ces principes, la tâche vous mènera facilement, vous et l'infrastructure, vers un autre composant très chargé (et en même temps inefficace) du système.

PS

A lire aussi sur notre blog :

Source: habr.com

Ajouter un commentaire