Créer un serveur de log avec Docker, Elastic Search, Kibana et Nginx : Architecture et mise en place
Elastic search et Kibana sont des outils performants pour stocker et requêter les données en mode "full text search". Le moteur Lucêne offre de bonnes performances en lecture. Ce couple d’outils peut être une option intéressante à considérer pour la mise en place d’un serveur de log : Elastic Search expose des APIs pour envoyer des données de logs applicatifs et Kibana offre des outils (grilles et graphiques) de visualisation de données. De plus, certain logger comme Seri Log permettent d’envoyer les logs applicatifs directement vers une instance Elastic Search.
Dans cet article, nous allons découvrir comment créer un serveur de log en utilisant Docker pour "conteneriser" Elastic Search et Kibana. Dans un second temps nous verrons comment configurer Seri Log dans une application ASP net Core pour utiliser notre serveur de log. Dans un second article, nous verrons comment héberger ce serveur de log dans Azure.
Installation classique vs Docker
Actuellement, il est parfaitement possible d’installer Elastic Search et Kibana sur une machine Windows ou Linux, cela fonctionne parfaitement. Cependant le setup d’une machine et l’installation des outils peut s’avérer longue et fastidieuse. L’idée de ce post est de proposer une solution simple et rapide pour mettre en place un serveur de log en quelques minutes. Docker nous permet de définir les différentes briques de notre serveur de log dans un seul fichier et de le rendre fonctionnel avec deux lignes de commande.
Architecture cible
Nous allons comparer deux architectures, un première basique et une seconde plus robuste et sécurisée.
Architecture v1
Au minimum deux conteneurs suffisent pour mettre en place le serveur de log :
- Kibana
- Port interne : 5601
- Port externe : 80
- Image : docker.elastic.co/kibana/kibana:6.1.2
- Configuration: Pas de configuration spécifique, ce conteneur a simplement besoin de communiquer avec l’instance Elastic Search (cette communication est obligatoire, car Kibana utilise les API de l’instance Elastic Search)
- Elastic Search
- Port interne : 9200
- Port externe : 9200
- Base Image : elastic.co/elasticsearch/elasticsearch:6.3.2
- Configuration: Elastic Search utilise un fichier de configuration nommé elasticsearch.yml. Si nous voulons surcharger les valeurs par défaut de ce dernier, nous pouvons en créer un et le copier dans le conteneur lors de la phase de build de l’image Docker.
Ces deux conteneurs doivent être placés sur le même réseau Docker.
Shema
Cette architecture fonctionne en l’état, cependant elle n’est pas sécurisée. Nous exposons publiquement les ports 80 et 9200 (ce qui est une mauvaise pratique !). Par défaut, l’accès à une instance Elastic Search n’est pas sécurisé, l’instance est exposée publiquement sur le port 9200. Pour sécuriser son accès nous avons deux possibilités :
- Installer un plugin payant (ex : X-Pack)
- Configurer un système d’authentification au niveau du host Docker
La seconde option peut être mise en place en utilisant un conteneur Nginx qui fera office de proxy entre le port public exposé depuis l’hôte Docker et les conteneurs Elastic Search et Kibana. Avec Nginx nous pouvons définir des routes et protéger leur accès avec une autorisation « basic ».
Architecture v2 (sécurisée)
Dans la seconde version de l’architecture, nous allons utiliser trois conteneurs :
- Kibana
- Port interne : 5601
- External port : 80
- Base Image : docker.elastic.co/kibana/kibana:6.1.2
- Elastic Search
- Port interne : 9200
- External port : 80
- Image : elastic.co/elasticsearch/elasticsearch:6.3.2
- Nginx
- Internal port : 80
- External port : 8080 *
- Image : nginx
- Configuration : 2 fichiers sont requis, nginx.conf qui contient la configuration du reverse proxy et .htpasswd qui stocke les informations d’authentification “basic” (login / password)
* sur le schéma suivant, c'est le port 80 qui définit en tant que port externe du conteneur nginx.
Schéma
Persistance des données
Avant de rédiger notre Docker file, une problématique se pose, « Comment allons nous persister les données stockées par Elastic Search ? ». Par défaut, Elastic Search stocke ses données dans le répertoire /var/lib/elasticsearch/data pour Ubuntu et Debian (ce dernier peut changer selon la distribution Linux). Ces données sont importantes car elles représenteront à terme nos logs de production, nous ne pouvons pas les perdre lorsque le conteneur s’arrête de fonctionner pour un raison X. Nous allons donc utiliser le système de Volume Docker pour persister ces données sur l’hôte Docker. Dans cet article nous allons utiliser le driver Docker local pour gérer les volumes. Nous pourrions également choisir de stocker les données dans un File Share Azure le driver suivant : https://github.com/Azure/azurefile-dockervolumedriver.
Travail local
Nous allons travailler dans un répertoire parent nommé « log server ». Dans ce répertoire parent, chaque conteneur possède son propre répertoire.
Conteneur Elastic Search
La configuration de ce conteneur est relativement simple, le dossier contient un simple Docker file qui récupère l’image officielle Elastic Search.
Contenu du dossier :
Contenu du Docker file:
FROM docker.elastic.co/elasticsearch/elasticsearch:6.3.2
Conteneur Kibana
Comme le conteneur Elastic Search, sa configuration est simple, le dossier contient également un seul fichier : le Docker file.
Contenu du dossier :
Contenu du Docker file :
FROM docker.elastic.co/kibana/kibana:6.1.2
Conteneur nginx
La configuration du conteneur nginx est la plus intéressante car notre proxy a deux responsabilités :
- Router les requêtes provenant de l’extérieur vers les conteneurs Elastic Search et Kibana
- Appliquer une authentification “Basic” pour restreindre l’accès aux conteneurs Elastic Search et Kibana
Dans le dossier nginx, 3 fichiers sont présents :
- Docker file
- nginx.conf : Configuration des règles de reverse proxy
- .htpasswd : Authentication Basic credentials (login & password)
Contenu du dossier:
Docker file
Le Docker file exécute les actions suivantes :
- Récupération de l’image nginx
- Copie le fichier conf dans le conteneur, dans le dossier /etc/nginx/nginx.conf
- Copie le fichier .htpasswd dans le conteneur, dans le dossier /etc/nginx/.htpasswd
Contenu du Docker file :
FROM nginx COPY nginx.conf /etc/nginx/nginx.conf COPY .htpasswd /etc/nginx/.htpasswd
Nginx
Le fichier nginx.conf est le cœur de notre architecture. C’est lui qui nous permet de router les requêtes vers nos conteneurs de façon sécurisé.
Contenu du fichier nginx.conf :
events { worker_connections 2048; } http { upstream docker-kibana { server kibana:5601; } upstream docker-elasticsearch { server elasticsearch:9200; } server { listen 80; location / { proxy_pass http://docker-kibana; auth_basic "Access limited"; auth_basic_user_file /etc/nginx/.htpasswd; } location /api/es { rewrite ^/api/es(.*) /$1 break; proxy_pass http://docker-elasticsearch; auth_basic "Access limited"; auth_basic_user_file /etc/nginx/.htpasswd; } location = /favicon.ico { log_not_found off; } } }
Un peu de details… ! Dans un premier temps nous avons besoin de définir deux “upstream”, c’est à dire les endpoints vers lesquels le reverse proxy dirigera les requêtes provenant de l’hôte Docker. Ces endpoints sont les conteneurs Elastic Search et Kibana. Ce routing est effectué au sein du réseau interne Docker, en effet c’est le conteneur nginx qui a besoin de communiquer avec les conteneurs Elastic Search et Kibana, c’est pourquoi nous devons spécifier les ports internes des conteneurs.
- kibana:5601
- elasticsearch:9200
Ensuite, il est nécessaire de définir la configuration du proxy nginx : le port qu’il doit écouter par défaut (80) ainsi que les routes qu’il expose (avec le mot clef location). Pour chaque route exposée, il est nécessaire de définir deux éléments :
- Le endpoint (upstream) vers lequel la route redirige (avec le mot clef proxy_pass)
- La configuration de l’authentification “Basic” en utilisant les mots clefs auth_basic_user_file et auth_basic.
- auth_basic_user_file spécifie le chemin du fichier dans lequel le login et le mot de passe de connexion sont stockées
- auth_basic définit le message affiché par la popup de connexion
Dans notre exemple, nous définissons deux routes :
- / (racine) : Redirige vers le conteneur Kibana
- /api/es : Redirige vers le conteneur Elastic Search
.htpasswd
Ce fichier contient les informations de connexion pour l’authentification « Basic ».
Contenu du fichier :
tranise:$apr1$SDzAe541$QOrVvoHMR0BLPGDDxiOSM0
Ce générateur en ligne peut être utilisé pour l’encodage du mot de passe. Dans l'exemple ci-dessus, le mot de passe encodé est test.
Docker compose
Maintenant que nous avons configuré l’ensemble de conteneurs, nous pouvons créer notre fichier Docker Compose. Ce dernier va définir comment les conteneurs seront « instanciés » sur l’hôte Docker.
Il définit les éléments suivants :
- Un réseau nommé “net”, ce dernier est commun à tous les conteneurs
- Un volume nommé “esdata” (géré par le driver local)
- 3 services
- Elasticsearch
- Utilise le réseau “net”
- Monte le volume “esdata” sur son prope système de fichier, dans le dossier /usr/share/elasticsearch/data
- Utilise une variable d’environnement indiquant à Elastic Search de s’exécuter en mode « single node »
- Kibana
- Utilise le réseau “net”
- Utilise deux variables d’environnement pour définir le nom du serveur et l’instance Elastic Search à utiliser
- Proxy
- Utilise le réseau “net”
- Définit un mapping de port : les requêtes provenant de l’hôte de Docker sur le port 8080 seront routées vers le port 80 du conteneur. C’est le point d’entrée du serveur de log.
- Elasticsearch
Contenu du fichier docker-compose.yml:
version: '3' services: elasticsearch: build : ./elasticsearch container_name: elasticsearch environment: - "discovery.type=single-node" networks: - net volumes: - esdata:/usr/share/elasticsearch/data kibana: build : ./kibana container_name: kibana environment: SERVER_NAME : kibana ELASTICSEARCH_URL : http://elasticsearch:9200 networks: - net proxy: build : ./nginx container_name : proxy ports: - "8080:80" networks: - net volumes: esdata: driver: local networks: net:
Déploiement local du serveur de log
Pour lancer et utiliser le serveur de log, il nous suffit d’utiliser le fichier docker-compose.yml. Les deux commandes suivantes doivent être exécutées :
- docker-compose build
- docker-compose up
Si on essaye maintenant d’accéder au conteneur nginx sur le port 8080 (http://localhost:8080) nous somme bloqué par un popup d’authentification :
Une fois les informations d’authentification renseignées, nous accédons au portail Kibana :
Pour accéder directement à l’instance Elastic Search, la route /api/es peut être utilisée :
Utilisation du serveur de log depuis une application ASP net Core
Seri Log permet de configurer un endpoint Elastic Search pour stocker les logs d’une application ASP net core.
Il suffit d’utiliser les deux packages Nuget suivants :
- Extensions.Logging
- Sinks.Elasticsearch
La configuration du logger est faite dans le fichier startup.cs, plus précisément dans la méthode Startup(IHostingEnvironment env). L’idée est de créer une instance du logger configurée pour envoyer les logs vers une instance Elastic Search :
Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.FromLogContext() .WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt")) .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:8080/api/es")) { AutoRegisterTemplate = true, IndexFormat = "testlogs-{0:yyyy.MM.dd}", TypeName = "logs-api", ModifyConnectionSettings = x => x.BasicAuthentication("tranise", "*********") }) .CreateLogger();
Bien évidemment, les informations d’authentification (login / password) doivent être stockées au minimum dans un fichier de configuration, aux mieux dans un Azure Key Vault.
Ensuite, il suffit d’ajouter le logger SeriLog fraîchement configuré dans la collection de logger Asp net via la méthode Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) :
loggerFactory.AddSerilog();
Nous pouvons maintenant utiliser notre serveur de log depuis l'application ASP Net core. Il ne reste plus qu'a configurer le format de l'index Elastic Search à utiliser : testlogs-*.
Dans cet article, nous avons vu comment mettre en place un serveur de log dans un environnement local. L’objectif est bien entendu d’héberger ce dernier sur un serveur distant. C’est ce que nous découvrirons dans le prochain article !
Happy coding :)
Commentaires