Logs Kubernetes et Fluentd

Photo by Trang Doan from Pexels

TL;DR: La gestion des logs est spécifique à votre configuration Kubernetes. Elle dépend des systèmes, des versions ou encore des intégrations. Cet article détaille l'utilisation de Fluentd dans ce contexte et quelques spécificités que vous pourrez recontrer.

Quand il s'agit de gérer les logs Kubernetes, les choix possibles et leurs combinaisons donnent vite le vertige. Heureusement, la plupart des services et des distributions viennent avec des solutions pré-intégrées.

Si vous lisez la documentation, vous verrez que le projet Kubernetes propose 2 exemples d'implémentations, dont un qui intègre Elasticsearch appelé fluentd-elasticsearch. Ses images sont stockées sur [quay.io/fluentd_elasticsearch] (https://quay.io/repository/fluentd_elasticsearch/fluentd?tab=tags).

Si vous lisez la section Kubernetes de la documentation de Fluentd, qui fait partie du CNCF, vous apercevez un autre projet appelé pour sa part, fluent/fluentd-kubernetes-daemonset. Ses images sont stockées sur le Docker Hub. Ce projet permet aussi d'envoyer les logs dans Elasticsearch.

Si vous continuez, vous apercevrez que Fluent Bit, qui fait également partie du CNCF propose une implémentation d'un driver pour Kubernetes qui fonctionne également avec Elasticsearch.

Et si vous élargissez l'horizon, vous découvrirez vite:

  • Qu'elastic.co propose avec sa propre solution open source avec FileBeat
  • Qu'il existe des solutions de plus haut-niveaux avec un Chart Helm basé sur la version Fluentd de Kubernetes ou un opérateur banzaicloud/logging-operator basé sur Fluentd et Fluentd Bit.
  • Que vous pouvez intégrer les plugins Fluentd Kubernetes et Fluentd Elasticsearch pour créer votre propre solution et l'adapter à vos besoins, y compris en filtrant par namespace ou en utilisant un sidecar sur certains pods choisis.

Et ce n'est que si Elasticsearch est votre cible pour stocker les logs... Les sections qui suivent se basent sur la solution issue de Kubernetes. Elle est plus à jour que celle de Fluentd ; elle est mieux référencée au moment où j'écris cet article et elle traite plus de cas comme notamment l'utilisation de CRI Containerd. Quoiqu'il en soit, les principes, à l'exception de FileBeat, sont sensiblement les mêmes.

Configurer ElasticSearch et Kibana

Pour des besoins de démonstration, nous utiliserons un cluster Kubernetes basé sur Kind. Au préalable, il faut créer une infrastructure Elasticsearch/Kibana. La façon de créer une telle infrastructure va au-delà de cet articles.

Pour une solution simple, utiliser le fichier docker-compose.yml situé dans le répertoire blog/efk-kubernetes du projet easyteam-fr/side-effects

Le cluster Kind s'appuyant sur le bridge par défaut de docker, la configuration est également publiée sur ce réseau comme l'indique les propriétés network_mode: bridge pour chacuns des services. En contre partie, l'adressage n'est pas géré par docker-compose; vous exécuterez donc la commande suivante qui ajoute l'adresse IP de elasticsearch dans le fichier /etc/hosts de Kibana

docker exec --user root kibana bash -c \
  "echo \"$(docker inspect -f {{.NetworkSettings.IPAddress}} elasticsearch)\" elasticsearch | tee -a /etc/hosts"
docker exec kibana cat /etc/hosts

Une fois la configuration réalisée, vous pourrez vous connecter sur Kibana en utilisant l'URL localhost:5601.

Démarrer le cluster Kind

Vous pouvez simplement démarrer le cluster Kind à l'aide de la commande ci-dessous:

kind create cluster

Note: Pour plus d'informations sur l'utilisation de Kind, lisez Kind et build Kubernetes

Démarrer Fluentd

Il est possible d'ajouter un service headless qui pointe vers l'adresse IP d'Elasticsearch en appliquant le fichier elasticsearch-svc.yaml après avoir récupéré l'adresse dans la variable IP_ADDRESS comme ci-dessous:

export IP_ADDRESS="$(docker inspect -f {{.NetworkSettings.IPAddress}} elasticsearch)"
envsubst < elasticsearch-svc.yaml | kubectl apply -f-

Note: envsubst est un utilitaire qui substitue des variables dans un fichier et fait partie de gettext

Pour tester le fonctionnement, on se connectera au pod, on installera curl et on validera que le service est disponible:

kubectl exec -it debian -n kube-system -- bash
apt -y update
apt -y install curl
curl http://elasticsearch-logging:9200/
exit
kubectl delete pod debian -n kube-system

Elasticsearch doit répondre à la requête précédente curl indiquant que la connexion à elasticsearch fonctionne correctement.

Configurer Fluentd

La configuration Fluentd est constituée de 2 sections:

  • le fichier fluentd-es-configmap.yaml contient la configuration de fluentd. C'est ce fichier qu'on modifie le cas échéant
  • le fichier fluentd-es-ds.yaml contient les roles ainsi que le daemonset qui assurent que sur chaque noeud du cluster un container fluentd s'exécute et accède aux bonnes ressources.

Pour créer les resources associées, vous exécuterez les commandes ci-dessous:

export K8S_URL=https://raw.githubusercontent.com/kubernetes/kubernetes
export FLUENTD_URL=$K8S_URL/master/cluster/addons/fluentd-elasticsearch
kubectl apply -f $FLUENTD_URL/fluentd-es-configmap.yaml
kubectl apply -f $FLUENTD_URL/fluentd-es-ds.yaml

Afficher les logs via Kibana

Fluentd étant installé, vous pouvez vous connecter à Kibana, aller dans le menu Management et ajouter un index pattern logstash-*. Vous sélectionnerez @timestamp comme Time Filter. Vous pourrez ensuite sélectionner le menu Discover et créer un pod qui génère des logs de manière régulière:

cat <<EOF | kubectl apply -f-
apiVersion: v1
kind: Pod
metadata:
  name: debian
spec:
  containers:
  - name: debian
    image: debian:latest
    command:
      - bash
      - -c
      - "while true; do hostname; sleep 1; done"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
EOF

Pour supprimer le pod, on lancera la commande ci-dessous:

kubectl delete pod debian

Un exemple de visualisation des logs est disponible ci-dessous:

log Kibana

Sections intéressantes

Les fichiers de configurations de Fluentd sont particulièrement intéressants surtout si vous venez de configuration plus anciennes. Quelques éléments en vrac:

Logs des containers

La section ci-dessous supporte le fait que les logs des containers sont générés par docker (1ere partie) ou via CRI Containerd ou CRI-O (2nd partie):

  <parse>
    @type multi_format
    <pattern>
      format json
      time_key time
      time_format %Y-%m-%dT%H:%M:%S.%NZ
    </pattern>
    <pattern>
      format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
      time_format %Y-%m-%dT%H:%M:%S.%N%:z
    </pattern>
  </parse>

Enrichissement des logs

La section ci-dessous s'appuie sur la plugin fabric8io/fluent-plugin-kubernetesmetadatafilter pour enrichir les logs des containers avec les différents éléments capturés à travers Kubernetes comme les noms des containers ou des pods. C'est la raison pour laquelle le pod doit avoir accès à l'API kubernetes:

<filter kubernetes.**>
  @id filter_kubernetes_metadata
  @type kubernetes_metadata
</filter>

Kubelet systemd

La section ci-dessous collecte les logs de la Kubelet, ci celle-ci est gérée par systemd. Elle s'appuie sur la plugin fluent-plugin-systemd/fluent-plugin-systemd :

<source>
  @id journald-kubelet
  @type systemd
  matches [{ "_SYSTEMD_UNIT": "kubelet.service" }]
  <storage>
    @type local
    persistent true
    path /var/log/journald-kubelet.pos
  </storage>
  read_from_head true
  tag kubelet
</source>

Sections inutilisées

Selon votre configuration de nombreuses sections sont par ailleurs inutilisées. Cela est du au fait que la plupart des cas sont prévus et, selon les intégrations, des configurations sont très variées. Par exemple, il est de plus en plus propable que la Kubelet soit gérée par systemd et ses logs par journald. Si c'est le cas, la section suivante n'est plus utilisée:

<source>
  @id kubelet.log
  @type tail
  format multiline
  multiline_flush_interval 5s
  format_firstline /^\w\d{4}/
  format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
  time_format %m%d %H:%M:%S.%N
  path /var/log/kubelet.log
  pos_file /var/log/es-kubelet.log.pos
  tag kubelet
</source>

Pour aller plus loin...

Elasticsearch est un outil assez exceptionnel pour gérer les logs et si ça mise en oeuvre initiale avec Kubernetes est relativement rapide, on en arrive vite à avancer dans les configurations. Par exemple:

  • Si vous utilisez le service managé par AWS, vous voudrez utiliser un sidecar basé sur abutaha/aws-es-proxy
  • Si vous utilisez un Ingress Controller comme celui basé sur Nginx, vous voudrez personnaliser le format des logs
  • Si vos applications mettent en place des formats de logs standardisés s'appuyant par exemple sur sirupsen/logrus en Go ou winstonjs/winston en Typescript ou Javascript, vous voudrez également personnaliser les formats des index
  • Si les éléments collectés sont utiles, vous voudrez vite créer des rapports métiers dans Kibana ou connecter Grafana

Gérer les logs d'une infrastructure distribuée est à la fois un challenge et une opportunité de comprendre l'utilisation de l'application et de générer du retour pour les équipes techniques, produit et métier. Autant dire que si vous gérez correctement les logs Kubernetes, il est probable que ce n'est qu'un début pour tout ce que vous pourrez apporter à vos projets.

Easyteam DevOps

Easyteam DevOps