TP – Découverte Big Data ELK

Ce TP sert d’introduction au Big Data. Pour appréhender ce concept, l’application M2M sera prise pour exemple.

Durant le TP, l’application simulera des flux de données. L’objectif est de mettre en place une stack ELK qui permettra d’observer les tendances et caractéristiques de l’application. Cette stack sera démarrée sur Kubernetes à côté de l’application M2M.

Avant de commencer le TP, il faut bien se mettre dans le namespace M2M:

$ kubectl config set-context --current --namespace=m2m

Structure ELK

ELK architecture

Voici l’architecture d’une stack ELK. Elle est composée de:

Cette stack sera lancé comme pour le TP précédent sur Kubernetes. Elle sera configurée comme pourrait l’être une application en production.

La stack ELK étant déportée de l’application, elle sera déployée à partir d’un dossier différent. L’application ayant un dossier de déploiement k8s-app, on choisira un dossier k8s-elk pour ELK.

Elasticsearch

Avant toute chose, il faut démarrer un cluster Elasticsearch qui est le cœur de cette stack. Elasticsearch peut être démarré en standalone. Mais vu que nous utiliserons Kubernetes, nous allons démarrer un cluster.

Pour ce faire, Elasticsearch a besoin d’un ou plusieurs nœuds maître, et de connaitre leurs IP. Ce mode de construction de cluster ressemble à celui utilisé par Hazelcast vu dans le TP précédent.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: elastic
  name: elastic
  namespace: m2m
spec:
  serviceName: "elastic"
  replicas: 1
  selector:
    matchLabels:
      app: elastic
  template:
    metadata:
      labels:
        app: elastic
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      containers:
      - name: elasticsearch
        securityContext:
          privileged: true
          capabilities:
            add:
              - IPC_LOCK
              - SYS_RESOURCE
        image: elasticsearch:7.9.2
        resources:
          requests:
            memory: 2Gi
            cpu: 1
          limits:
            memory: 3Gi
            cpu: 1
        env:
        - name: node.name
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: cluster.name
          value: m2m
        - name: network.host
          value: "0.0.0.0"
        - name: discovery.seed_hosts
          value: "elastic-0.elastic"
#          value: "elastic-0.elastic,elastic-1.elastic"
        - name: cluster.initial_master_nodes
          value: "elastic-0"
#          value: "elastic-0,elastic-1"
        - name: bootstrap.memory_lock
          value: "false"
        - name: ES_JAVA_OPTS
          value: -Xms2g -Xmx2g
        ports:
        - containerPort: 9200
        - containerPort: 9300

Le cluster Elasticsearch sera accessible via elastic-0.elastic, elastic-1.elastic… suivant le nombre d’instances qui sont démarrées.

Créer un fichier de déploiement elasticsearch.yml dans le dossier k8s-elk. Ce déploiement devra contenir le cluster StatefulSet, ainsi que le Service rest (port 9200) et cluster (port 9300).

Pour connaitre l’état du cluster:

$ kubectl exec -ti elastic-0 -- curl -XGET 'http://localhost:9200/_cluster/health?pretty=true'

De manière générale, Elasticsearch est prévu pour fonctionner en mode rest. Tous les outils qui s’y connectent exécute des commandes, soit pour pousser des données, soit pour en récupérer.

Cette API est documentée. Par exemple la commande utilisée pour obtenir le status du cluster.

Logstash application M2M

La configuration du service Kubernetes Logstash est déjà présent dans le dossier k8s-elk: logstash.yml. Dans cette configuration, 2 Logstash ou plus peuvent être lancés. Ils sont indépendants les uns des autres, à l’inverse d’Elasticsearch. On peut donc en lancer autant que nécessaire suivant la charge. Pour le besoin du TP, 1 sera largement suffisant.

Néanmoins, Logstash va avoir besoin d’un fichier de configuration pour savoir comment il devra ajouter les données à Elasticsearch.

Comme présenté sur le schéma de la structure ELK, c’est le front et le backend qui enverront leurs métriques au format JSON aux Logstash. Ces métriques sont envoyées grâce à log4j.

Le front n’enverra que des données techniques. C’est-à-dire le détail des sessions client, le nombre d’octets échangé… Dans le fichier de configuration log4j.xml de ce composant, vous devez voir 2 sections pour les sorties vers Logstash:

Pour le Backend, il enverra des données techniques en plus de données applicatives. Il enverra donc comme le front le détail sur les sessions, message avec celui-ci. Dans le fichier de configuration log4j.xml de ce composant, vous devez voir 3 sections pour les sorties vers Logstash:

Vérifier que ces sections sont bien présentes et non commenté. Aussi veiller à ce que log4j envois les métriques vers logstash:9500.

Logstash devra donc être à l’écoute d’un port en entrée et accepter du JSON en entrée

input {
  tcp {
    port => 9500
    codec => json
  }
}
filter {
  date {
    match => [ "timeMillis", "UNIX_MS" ]
  }
}
filter {
  json {
    source => "message"
  }
}

Pour l’output de logstash, il faut conditionner la sortie pour séparer les différentes métriques. Pour cela, il faut utiliser une condition sur le loggerName. Les cinq sorties auront comme cela 5 noms d’index différents:

output {
  stdout {
    codec => rubydebug
  }
  if [loggerName]=="FRONT-REQ" {
    elasticsearch {
      hosts => [ "elastic:9200" ]
      index => "req-front-%{+YYYY.MM.dd}"
      document_type => "log4j_type"
    }
  }
  else if [loggerName]=="FRONT-SESS" {
    elasticsearch {
      hosts => [ "elastic:9200" ]
      index => "sess-front-%{+YYYY.MM.dd}"
      document_type => "log4j_type"
    }
  }
  else if [loggerName]=="BACK-REQ" {
    elasticsearch {
      hosts => [ "elastic:9200" ]
      index => "req-back-%{+YYYY.MM.dd}"
      document_type => "log4j_type"
    }
  }
  else if [loggerName]=="BACK-SESS" {
    elasticsearch {
      hosts => [ "elastic:9200" ]
      index => "sess-back-%{+YYYY.MM.dd}"
      document_type => "log4j_type"
    }
  } else if [loggerName]=="APP" {
    elasticsearch {
      hosts => [ "elastic:9200" ]
      index => "app-%{+YYYY.MM.dd}"
      document_type => "log4j_type"
    }
  }
}

Lorsque vous avez créé le fichier de configuration logs.conf, il faut le prendre en compte. Dans docker-compose, il aurait fallu mapper le fichier de configuration comme volume: -v logs.conf:/usr/share/logstash/config/logstash.yml

Avec Kubernetes, il faut le charger en tant que configmap dans Kubernetes.

$ kubectl create configmap logstash-config --namespace=m2m --from-file=logstash/logs.conf

La configuration logstash-config se retrouve dans le fichier Kubernetes de logstash k8s-elk/logstash.yml

Lancer l’application et la stack ELK. Vérifier que tout démarre correctement. Les services ne doivent pas planter ni redémarrer. Si c’est le cas, vérifier les logs.

Pour lancer la stack ELK:

$ kubectl create -f k8s-elk

Pour vérifier que Logstash a poussé des données dans Elasticsearch, les index doivent être créés:

$ kubectl exec -ti elastic-0 -- curl -XGET 'http://localhost:9200/_all/_mapping'

Kibana

Le service Kibana est déjà présent dans k8s-elk, il doit donc déjà être lancé. Pour obtenir son interface:

Il met du temps à démarrer. Il faut être patient. La plupart du temps, il indique Kibana server is not ready yet depuis son interface. Si le message persiste, le problème vient peut-être du cluster Elasticsearch.

Lorsque la stack ELK est démarrée, vous pouvez démarrer l’application:

$ kubectl create -f k8s-app

Paramétrage des index

La première chose à faire lorsque Kibana est lancé sur un nouveau cluster, c’est de créer les index. Les index permettront à Kibana de savoir comment indexer les données avec les différents champs qu’elles contiennent.

Lors de la configuration de Logstash, 5 index ont été configurés. Parmi ces 5 index, certains ont le même format:

Pour ces index, nous allons les considérer comme communs, et nous les différencierons grâce à l’origine (front ou backend). Donc nous allons créer 3 index:

Pour les ajouter, il faut aller dans le menu Index patterns dans Kibana. Ce menu est disponible dans Management > Stack Management > Index Patterns dans le menu déroulant de gauche.

Si les index patterns ne sont pas visibles, c’est qu’ils ne sont pas contenus par Elasticsearch. Dans ce cas, il faut vérifier que l’application envoie bien des métriques.

Lorsque les patterns sont ajoutés, il faut spécifier le Time field. C’est sur ce champ que Kibana se basera pour l’indice de temps. Le champ qui indique le timestamp dans les messages que l’application envoie est @timestamp

Discover

Quand tout est mis en place, les métriques devraient être visibles dans Discover. Ce mode permet d’observer toutes les métriques dans une forme brute.

En sélectionnent l’index req-* ajouter un filtre pour n’afficher que les requestType: /m2m/happybeer/kegload

Discover est pratique, mais lorsque beaucoup de grosses données sont présentes, il est impossible de trouver quelque chose sans utiliser des filtres très spécifiques.

Dashboard

La vraie force d’une stack ELK est de pouvoir créer des graphiques sur toutes les données qui sont présentes. L’objectif du TP est de créer 2 dashboards:

  1. Les données techniques pour l’exploitation de la plateforme, convenant aux personnes du run.
  2. Les données applicatives pour le business de la plateforme, convenant aux clients ou à l’équipe commerciale.

Données techniques

La première métrique à ajouter est le nombre de messages traiter par l’application.

Pour ce faire, il faut utiliser le pattern req-*. Dans le menu Kibana > Visualize. Créer une nouvelle visualisation.

Beaucoup de types de visualisations sont disponibles. Pour le moment, la plus simple Metric fera l’affaire. Par défaut, lorsque la métrique est créée, elle nous donne le nombre de requête qui a été reçu par le front et le backend.

Ce que l’on veut c’est le nombre de requête reçu par le front. Donc il faut ajouter un filtre.

Ajouter un filtre sur le loggerName = FRONT_REQ.

Pour connaitre les valeurs à mettre pour le filtre, le plus simple est d’aller dans l’onglet Discover et de regarder ce que l’on veut.

Maintenant, il faudrait afficher le nombre de message reçu par le front et le backend pour comparer la valeur. Pour ce faire, il faut faire un split sur la donnée.

Lors de la création de la visualisation, il y a un menu Buckets.

Ajouter un Split Group, avec une agrégation Terms sur le champ loggerName.

Lorsque le split est fait, 2 métriques s’affiche. Une pour le front, et une pour le backend. Ce n’est pas très visuel. Donc il faudrait plutôt utiliser une autre représentation. Par exemple un Pie, Horizontal Bar, ou Vertical Bar.

Refaite une visualisation, mais avec un autre type de métriques plus adapté

Une fois que vous avez fait vos visualisations, vous pouvez créer un dashboard les regroupant. Les dashboards peuvent être créés à partir du menu Kibana > Dashboard.

Il y a beaucoup de métriques, de paramètres disponibles… Le plus simple est d’essayer de faire vous-mêmes vos métriques. La plupart du temps, une première ébauche est faite, et c’est grâce aux retours utilisateurs que les dashboard s’enrichisse.

Ajouter des métriques techniques (req-* et sess-*). Les métriques techniques intéressantes seraient le volume de données échangé, les durées de sessions, temps de réponses…

Données applicatives

Ajouter des métriques applicatives (app-*). Les métriques applicatives pourraient être la consommation moyenne par tireuses, la répartition des bières consommées…

Autres services

Dans sa version payante, la stack ELK propose beaucoup de services intéressants. Pour aller plus loin dans l’analyse de données, cela peut être une alternative viable à d’autres solutions.

Par Jérémy HERGAULT, le .