Temps de lecture estimé: 11 minutes
Ça fait maintenant quelques années que j’en parle, mais je ne m’étais pas encore penché dessus. Je commence à avoir plusieurs systèmes qui tournent à la maison avec plusieurs VM et plusieurs containers Docker (même si ça a un peu évolué depuis, tu trouveras un rappel sur mon infra et l’organisation de mes containers Docker). Ça fait pas mal d’accès à gérer et surtout pas mal de fichiers de log.
Aujourd’hui, nous allons voir comment installer une stack ELK avec Docker pour centraliser les logs de mes machines et pouvoir les parcourir et les analyser plus facilement.
Cet article est en majorité basé sur la documentation d’installation officielle d’ElasticSearch.
ElasticSearch
Set vm.max_map_count
to at least 262144
Ok, c’est un homelab, mais ça reste un environnement de production que j’héberge chez moi, donc je suis quelques-unes des préconisations indiquées sur la doc pour utiliser l’image Docker d’ElasticSearch en production. Je ne les suivrai pas toutes, donc je te laisse choisir les tiennes. D’autres recommandations seront aussi faites dans les autres étapes.
Donc, sur mon hôte Ubuntu, je vérifie le setting vm.max_map_count
avec :
grep vm.max_map_count /etc/sysctl.conf
J’ai la valeur par défaut:
vm.max_map_count = 65530
Donc, j’applique directement les changements au système:
sudo sysctl -w vm.max_map_count=262144
Et j’en profite pour éditer le fichier de configuration sysctl
pour garder ces paramètres même après un redémarrage.
sudo nano /etc/sysctl.conf
Pour y rajouter donc, le paramètre de manière persistante:
vm.max_map_count=262144
Préparer les volumes / dossiers sur mon hote
Je vais gérer les données d’ElasticSearch et la config en dehors du container Docker pour les persister (ça m’évitera de perdre toutes les données à chaque redémarrage du container Docker).
Donc, dans le dossier qui m’arrange, je crée 2 nouveaux dossiers : un pour la config, un pour les données.
mkdir ES_data config
Ensuite, comme recommandé
chmod g+rwx ES_data/ config/
sudo chgrp 0 ES_data/ config/
Lancer le container Docker ElasticSearch
Dans mon cas, je ne pars pas d’une installation fraîche de Docker. Mes networks sont déjà définis et je l’ai détaillé dans une série d’articles sur ma migration d’un serveur monolithique à des services containerisés.
Toujours dans mon cas, je me sers d’un script deploy.sh
dans lequel je mets ma commande docker run
.
#!/bin/bash
docker container rm -f elasticsearch.node01
docker run -dit \
-m 2GB \
-e "bootstrap.memory_lock=true" --ulimit memlock=-1:-1 \
-p 9200:9200 -p 9300:9300 \
-v /home/docker-infra/docker-elasticsearch/ES_data/:/usr/share/elasticsearch/data \
--network=infra \
-h elasticsearch.node01.anthony-jacob.com \
--restart=always \
--name elasticsearch.node01 \
docker.elastic.co/elasticsearch/elasticsearch:8.17.0
(Une fois que j’aurai récupéré la configuration de base, j’y ajouterai le volume du fichier de configuration comme indiqué ici : -v /home/docker-infra/docker-elasticsearch/config/custom_elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
)
Dans les logs du container, tu devrais voir apparaître le mot de passe généré pour l’utilisateur elastic et les tokens d’enrollment pour Kibana ou un autre nœud Elastic.
Les tokens ne sont valides que 30 minutes. Une fois ce délai passé, tu devras ensuite en régénérer un.
Réinitialiser le mot de passe
Si jamais tu ne te souviens plus du mot de passe, tu pourras toujours le réinitialiser avec:
docker exec -it elasticsearch.node01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic
Créer un nouveau Token d’enrollment pour Kibana
De même, si tu as perdu le token ou qu’il a dépassé ses 30 minutes de validité, tu peux en régénérer un avec :
docker exec -it elasticsearch.node01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
Créer un nouveau Token d’enrollment pour un nouveau nœud Elastic
Et pour finir, pareil avec le token d’enrollment pour un nouveau nœud Elastic. Si tu l’as perdu ou qu’il a dépassé ses 30 minutes de validité, tu peux en régénérer un avec:
docker exec -it elasticsearch.node01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node
Vérifier l’installation et le bon fonctionnement
À ce stade, le container devrait être up and running ! Pour le vérifier, on va télécharger le certificat d’Elastic et faire un appel simple à l’API.
Pour récupérer le certificat :
docker cp elasticsearch.node01:/usr/share/elasticsearch/config/certs/http_ca.crt .
Puis, on passe l’appel à l’API avec le mot de passe généré et récupéré plus haut.
curl -k --cacert http_ca.crt -u elastic:ELASTIC_USER_PASSWORD https://localhost:9200
Si tout va bien, tu devrais avoir une réponse de ce genre:
{
"name" : "elasticsearch.node01.anthony-jacob.com",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "vXj7rIs3SoWwutlYvgg4Uw",
"version" : {
"number" : "8.17.0",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "2b6a7fed44faa321997703718f07ee0420804b41",
"build_date" : "2024-12-11T12:08:05.663969764Z",
"build_snapshot" : false,
"lucene_version" : "9.12.0",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}
Récupérer les fichiers de configuration
Comme je l’ai dit plus haut, je souhaite récupérer les fichiers de configuration en local sur mon hôte pour ensuite pouvoir les binder et les redéployer.
Pour les récupérer, c’est simple:
docker cp elasticsearch.node01:/usr/share/elasticsearch/config/ /home/docker-infra/docker-elasticsearch/
je peux ensuite mettre à jour mon script de déploiement avec -v /home/docker-infra/docker-elasticsearch/config/:/usr/share/elasticsearch/config/ \
#!/bin/bash
docker container rm -f elasticsearch.node01
docker run -dit \
-m 2GB \
-e "bootstrap.memory_lock=true" --ulimit memlock=-1:-1 \
-p 9200:9200 -p 9300:9300 \
-v /home/docker-infra/docker-elasticsearch/ES_data/:/usr/share/elasticsearch/data \
-v /home/docker-infra/docker-elasticsearch/config/:/usr/share/elasticsearch/config/ \
--network=infra \
-h elasticsearch.node01.anthony-jacob.com \
--restart=always \
--name elasticsearch.node01 \
docker.elastic.co/elasticsearch/elasticsearch:8.17.0
Kibana
Maintenant qu’Elastic est lancé, on s’occupe maintenant de l’interface Web Kibana. Ça va être sensiblement la même chose qu’Elastic.
Préparer les volumes / dossiers sur mon hote
Je vais gérer les données de Kibana et la config en dehors du container Docker pour les persister (ça m’évitera de perdre toutes les données à chaque redémarrage du container Docker).
Donc, dans le dossier qui m’arrange, je crée 2 nouveaux dossiers : un pour la config, un pour les données.
mkdir kibana_data config
Lancer le container Docker Kibana
De la même manière que pour chaque container Docker, j’ai un script deploy.sh
#!/bin/bash
docker container rm -f kibana
docker run -dit \
-p 5601:5601 \
-v /home/docker-infra/docker-kibana/kibana_data/:/usr/share/kibana/data \
--network=infra \
-h kibana.anthony-jacob.com \
--restart=always \
--name kibana \
docker.elastic.co/kibana/kibana:8.17.0
Au premier lancement, tout se passe bien et on est invité à configurer Kibana:
Je lance donc mon navigateur avec l’ip de mon serveur hôte sur le port d’écoute 5601 de Kibana : http://192.168.10.110:5601/ et je configure Kibana avec le token d’enrollment généré avec la commande plus haut
Et voilà, je n’ai plus qu’à me loguer une première fois avec l’utilisateur elastic:
Récupérer les fichiers de configuration
Et de la même manière que pour Elastic, je récupère les fichier de configuration en local sur mon hôte pour ensuite pouvoir le binder et le redéployer.
Pour les récupérer c’est simple:
docker cp kibana:/usr/share/kibana/config /home/docker-infra/docker-kibana/
Je peux ensuite mettre à jour mon script de déploiement avec -v /home/docker-infra/docker-kibana/
config/:/usr/share/kibana/config/ \
#!/bin/bash
docker container rm -f kibana
docker run -dit \
-p 5601:5601 \
-v /home/docker-infra/docker-kibana/kibana_data/:/usr/share/kibana/data \
-v /home/docker-infra/docker-kibana/config/:/usr/share/kibana/config/ \
--network=infra \
-h kibana.anthony-jacob.com \
--restart=always \
--name kibana \
docker.elastic.co/kibana/kibana:8.17.0
J’ai fait une modification dans le fichier kibana.yml
. Par défaut, il s’est généré avec l’IP du container Docker du nœud ElasticSearch. Le problème, c’est que Docker peut changer l’IP du container, et ensuite, forcement, Kibana perd sa connexion (ce qui m’est arrivé). J’ai donc changé le paramètre elasticsearch.hosts
:
elasticsearch.hosts: ['https://elasticsearch.node01.anthony-jacob.com:9200']
Configurer le ssl (httpS)
Pour activer le SSL, il faut ajouter la configuration dans le fichier kibana.yml
et spécifier ses clés et certificats:
server.ssl.enabled: true
server.ssl.certificate: /etc/certs/mydomain.com/certificate.pem
server.ssl.key: /etc/certs/mydomain.com/key.pem
server.ssl.certificateAuthorities: /etc/certs/mydomain.com/CA.pem
server.publicBaseUrl: 'https://kibana.anthony-jacob.com'
De mon côté, j’utilise un reverse proxy apache. Je ne garde que server.publicBaseUrl
dans mon fichier kibana.yml
et je définis mon fichier de configuration Apache pour mon container reverse proxy Apache.
Intégrer les logs avec Elastic Agent
J’ai un moteur Elastic prêt à intégrer des logs, j’ai l’interface Kibana, il ne me reste maintenant plus qu’à récupérer les logs et les faire manger à Elastic.
Avant de commencer, je comptais utiliser Logstash ou Filebeat comme je l’avais déjà fait dans des expériences professionnelles précédentes. En creusant la documentation et en regardant l’interface Kibana, je m’aperçois qu’il existe Fleet et Elastic Agent.
Je tâtonne sur cette partie et je ne vais sûrement pas le mettre en place dans les règles de l’art… mais j’arriverai à une solution technique fonctionnelle (pour moi, en tout cas !)
Ajouter un serveur Fleet
Je commence par déclarer un serveur Fleet:
Ce qui me permet de récupérer un token d’enrollment.
Lancer le container Docker Elastic Agent
Ensuite, tu connais la chanson, je vais lancer une première fois le container Docker d’Elastic Agent. La documentation est là. Cet Elastic Agent fera aussi office de serveur Fleet.
#!/bin/bash
docker container rm -f elastic-agent-ubuntu-host
docker run -dit \
-p 8220:8220 \
--network=infra \
-h elastic-agent-ubuntu-host.anthony-jacob.com \
--restart=always \
--name elastic-agent-ubuntu-host \
-v /var/run/docker.sock:/var/run/docker.sock \
-e FLEET_SERVER_ENABLE=1 \
-e FLEET_SERVER_ELASTICSEARCH_HOST=https://elasticsearch.node01:9200 \
-e FLEET_SERVER_SERVICE_TOKEN=AAEAAWVsYXN0aWMvZmxlZXQtc2VydmVyL3Rva2VuLTE3MzcyOTg1MDg4OTI6UFZDODJoaldUaXFoc2U0cDVaenljUQ \
-e FLEET_SERVER_POLICY_ID=fleet-server-policy \
-e FLEET_SERVER_ELASTICSEARCH_CA_TRUSTED_FINGERPRINT=0eabef19154ebcbe037cc11f2851d1ced1d684eb7f467d3e1fcb5e43cb88f6e9 \
docker.elastic.co/elastic-agent/elastic-agent:8.17.0
Préparer les volumes / dossiers sur mon hote
Je vais gérer les données d’Elastic Agent et la config en dehors du container Docker pour les persister (ça m’évitera de perdre toutes les données à chaque redémarrage du container Docker).
Et je récupère les données et les fichiers de configuration seulement après le premier run.
Donc, dans le dossier qui m’arrange, je crée 2 nouveaux dossiers:
mkdir state config
Récupérer les fichiers de configuration
Et de la même manière que pour Elastic, je récupère les fichiers de configuration en local sur mon hôte pour ensuite pouvoir les binder et les redéployer.
Pour les récupérer, c’est simple:
docker cp elastic-agent-ubuntu-host:/usr/share/elastic-agent/state/ /home/docker-infra/docker-elasticagent/state/
Et:
docker cp elastic-agent-ubuntu-host:/usr/share/elastic-agent/elastic-agent.yml /home/docker-infra/docker-elasticagent/config/
Je peux ensuite mettre à jour mon script de déploiement avec -v /home/docker-infra/docker-elasticagent/config/state/:/usr/share/elastic-agent/state/ \
et -v /home/docker-infra/docker-elasticagent/config/elastic-agent.yml:/usr/share/elastic-agent/elastic-agent.yml \
#!/bin/bash
docker container rm -f elastic-agent-ubuntu-host
docker run -dit \
-p 8220:8220 \
--network=infra \
-h elastic-agent-ubuntu-host.anthony-jacob.com \
--restart=always \
--name elastic-agent-ubuntu-host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/docker-infra/docker-elasticagent/config/state/:/usr/share/elastic-agent/state/ \
-v /home/docker-infra/docker-elasticagent/config/elastic-agent.yml:/usr/share/elastic-agent/elastic-agent.yml \
-e FLEET_SERVER_ENABLE=1 \
-e FLEET_SERVER_ELASTICSEARCH_HOST=https://elasticsearch.node01:9200 \
-e FLEET_SERVER_SERVICE_TOKEN=AAEAAWVsYXN0aWMvZmxlZXQtc2VydmVyL3Rva2VuLTE3MzcyOTg1MDg4OTI6UFZDODJoaldUaXFoc2U0cDVaenljUQ \
-e FLEET_SERVER_POLICY_ID=fleet-server-policy \
-e FLEET_SERVER_ELASTICSEARCH_CA_TRUSTED_FINGERPRINT=0eabef19154ebcbe037cc11f2851d1ced1d684eb7f467d3e1fcb5e43cb88f6e9 \
docker.elastic.co/elastic-agent/elastic-agent:8.17.0
Et pareil que pour Kibana, j’ai fait une modification dans le fichier elastic-agent.yml. Par défaut, il s’est généré avec l’IP du container Docker du nœud Elasticsearch. Le problème, c’est que Docker peut changer l’IP du container, et ensuite Kibana perd sa connexion forcément (ce qui m’est arrivé). J’ai donc changé le paramètre hosts
.
(La valeur définie est le fallback de ${ELASTICSEARCH_HOSTS}
, qui lui-même est écrasé si l’on fournit FLEET_SERVER_ELASTICSEARCH_HOST
. Donc normalement, dans mon cas, cela ne sera pas utilisé, c’est juste pour la forme.)
######################################
# Fleet configuration
######################################
outputs:
default:
type: elasticsearch
hosts: '${ELASTICSEARCH_HOSTS:http://elasticsearch.node01.anthony-jacob.com:9200}'
Utiliser les integrations d’Elastic pour se faciliter la tâche
Et voilà, maintenant que l’agent tourne sur mon hôte, je peux facilement configurer l’intégration des logs sans me prendre la tête avec Grok et Logstash (au moins pour 90 % de mes besoins).
Exemple pour récupérer les logs et metrics de Docker
Je fais quelques modifications pour le déploiement d’Elastic Agent. Pour que mon agent ait accès aux données du Docker engine dans un container, je rajoute un volume de la même manière que pour Portainer.
-v /var/run/docker.sock:/var/run/docker.sock \
Je fournis également les logs avec:
-v /var/lib/docker/containers:/var/lib/docker/containers:ro \
Et ensuite, depuis Kibana, j’ai juste à rechercher l’intégration souhaitée, comme Docker dans mon exemple ici:
Tu peux éventuellement configurer quelques paramètres:
Et une fois l’intégration ajoutée, j’ai juste à naviguer et filtrer sur les logs qui m’intéressent ou à consulter les dashboards déjà fournis:
Exemple pour récupérer les logs de mon reverse proxy apache
Je fais quelques modifications pour le déploiement d’Elastic Agent. Pour que mon agent ait accès aux logs Apache de mon reverse proxy, et étant donné que mon reverse proxy est un container Docker, j’ai choisi d’avoir un dossier de logs monté en tant que volume sur mon reverse proxy Docker.
Une fois que c’est en place côté reverse proxy (c’est-à-dire une fois que les logs Apache de mon reverse proxy Docker sont accessibles depuis mon hôte), je rajoute un volume pour les fournir à mon agent Elastic.
-v /home/docker-infra/docker-reverse-proxy/log/:/var/log/reverse-proxy/:ro \
Ce qui donne schématiquement :
Fichiers logs Container -> Fichiers logs hôte -> Fichiers logs Elastic Agent
Et ensuite, depuis Kibana, j’ai juste à rechercher l’intégration souhaitée, comme Apache HTTP Server dans mon exemple ici:
Tu peux éventuellement configurer quelques paramètres. Dans mon cas, je change l’emplacement de mes logs.
Et une fois l’integration ajoutée, il ne me reste plus qu’à naviguer et filtrer sur les logs qui m’intéressent ou à consulter les dashboards déjà fournis:
Exemple pour récupérer les logs d’un système hote
Je fais quelques modifications pour le déploiement d’Elastic Agent afin qu’il ait accès aux logs de mon système hôte.
-v /var/log/:/host/var/log/:ro \
Et ensuite, depuis Kibana, j’ai juste à rechercher l’intégration souhaitée, comme System dans mon exemple ici.
Tu peux éventuellement configurer quelques paramètres. Dans mon cas, je change l’emplacement de mes logs:
Et une fois l’intégration ajoutée, j’ai juste à naviguer et filtrer sur les logs qui m’intéressent ou à consulter les dashboards déjà fournis:
Pour aller plus loin
Pour ma part, j’en ai fini avec le cœur du réacteur. Je vais maintenant installer de la même manière des agents sur d’autres hôtes, soit avec la méthode Docker (comme ici), soit avec l’archive Tar directement. Je le documenterai peut-être s’il y a des particularités.
Encore une fois, le sujet est large et je n’ai sûrement pas tout fait dans les règles de l’art.
Au niveau de Docker, je dois me pencher sur la transcription de mes scripts bash en fichier docker-compose.yml
et généraliser mon utilisation des secrets
Docker. Au niveau d’Elastic, je dois encore explorer quelques réglages (notamment sur la sécurité et le keystore).
En tout cas, ça faisait un moment que je devais mettre en place cette stack pour monitorer plus facilement mes serveurs. Ça me permet, par exemple, de plus facilement repérer ce genre de chose :
D’ailleurs, à ce propos, j’avais fais une série d’articles:
- Sécuriser ssh sur un serveur Ubuntu 22.04 après son installation
- Installation et configuration de fail2ban sur Ubuntu 22.04
- Sécuriser un serveur Ubuntu 20.04 et bannir les IP avec iptables
Voilà ! J’espère que ce contenu pourra t’être utile. N’hésite pas à me faire un retour. En attendant, je te dis à bientôt pour de nouvelles aventures !!