title | categories |
---|---|
Howto Elasticsearch | web bdd nosql |
Elasticsearch est un serveur de base de données écrit en Java disposant d’une interface REST HTTP. Elasticsearch est notammment utilisé dans la stack Elastic avec Logstash et Kibana.
Vu le développement actif d'Elasticsearch, nous préconisons l'installation des paquets Debian distribués par Elasticsearch :
# echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" >> /etc/apt/sources.list.d/elastic.list
# wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
# apt install elasticsearch
Elasticsearch 5.x nécessite Java 1.8. Pour Jessie, il faut installer la machine virtuelle Java depuis les backports.
# cat /etc/apt/sources.list.d/backports.list
deb http://mirror.evolix.org/debian jessie-backports main contrib non-free
Il vaut mieux aussi s'assurer que les backports aient une préférence basse, dans /etc/apt/preferences.d/backports
Package: *
Pin: release a=jessie-backports
Pin-Priority: 50
# apt install openjdk-8-jre
# update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
Si vous faites la mise à jour depuis une verison inférieure à 5.0, il faut penser à supprimer tous les plugins de type "site" comme head ou kopf qu'il faudra réinstaller différemment. :
# rm -rf /usr/share/elasticsearch/plugins/{kopf,head}
La liste complète des changements est diponible sur https://www.elastic.co/guide/en/elasticsearch/reference/5.0/breaking-changes.html.
Pour activer le démarrage automatique :
# systemctl enable elasticsearch
Synchronizing state for elasticsearch.service with sysvinit using update-rc.d...
Executing /usr/sbin/update-rc.d elasticsearch defaults
Executing /usr/sbin/update-rc.d elasticsearch enable
Created symlink from /etc/systemd/system/multi-user.target.wants/elasticsearch.service to /usr/lib/systemd/system/elasticsearch.service.
# systemctl start elasticsearch
Les paramètres système se trouvent dans le fichier /etc/default/elasticsearch
, les paramètres liés à la JVM sont dans /etc/elasticsearch/jvm.options
et les options applicatives (nom du cluster, nom du nœud, mémoire, réseau) se trouvent dans le fichier /etc/elasticsearch/elasticsearch.yml
.
Il faut activer le redémarrage automatique en cas de mise à jour (classique sous Debian) dans /etc/default/elasticsearch
:
RESTART_ON_UPGRADE=true
On peut aussi définir un tmpdir spécifique (utile quand /tmp
est en noexec
) dans /etc/default/elasticsearch
:
ES_JAVA_OPTS="-Djava.io.tmpdir=/var/lib/elasticsearch/tmp"
Dans ce cas, assurez-vous de créer le répertoire avec les bons droits :
# mkdir /var/lib/elasticsearch/tmp
# chown elasticsearch: /var/lib/elasticsearch/tmp
# chmod 750 /var/lib/elasticsearch/tmp
Via le fichier /etc/elasticsearch/elasticsearch.yml
vous devez au minimum configurer :
cluster.name: foo
node.name: bar
Par défaut, Elasticsearch écoute sur 127.0.0.1 sur TCP/9200 pour son interface REST HTTP.
La directive de configuration suivante peut être positionnée pour qu'il écoute sur plusieurs interfaces réseau :
network.host: ["192.0.2.42", "198.51.100.42", "127.0.0.1"]
Il est possible d'utiliser des adresses virtuelles, telles que _site_
, _local_
…
Tous les détails sont su https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-network.html#network-interface-values.
Lorsqu'Elasticsearch est configuré pour écouter sur une IP non locale, il passe en mode "production".
Il active alors un certain nombre de "bootstrap checks" qui bloquent le démarrage s'ils ne sont pas tous respectés. Les éventuels échecs sont lisibles dans le fichier de log (généralement dans /var/log/elasticsearch/_cluster_name_.log
).
/etc/default/elasticsearch
:JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre/
Le wrapper qui lance Elasticsearch est /usr/share/elasticsearch/bin/elasticsearch
voici les options possibles :
-Edefault.path.XXX=/foo/
: répertoire à utiliser (XXX peut être conf, data, logs etc.)-d
: lancer en mode daemon-p /var/run/elasticsearch/elasticsearch.pid
: chemin du fichier PID--quiet
: mode silencieuxOn check sur la page /_cat/health
si le status n'est pas en red.
/usr/lib/nagios/plugins/check_http -I 127.0.0.1 -u /_cat/health?h=st -p 9200 -r 'red' --invert-regex
Documentation : http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-snapshots.html
Il faut définir un répertoire pour stocker les snapshots :
# mkdir /home/backup-elasticsearch
# chmod 750 /home/backup-elasticsearch
# chown elasticsearch: /home/backup-elasticsearch
Note : en cas de cluster multi-nœuds, le répertoire de snapshots doit impérativement être partagé entre chaque nœud, classiquement via NFS, car chaque nœud ne gère que ses propres données.
On précise le répertoire des snapshots dans la configuration /etc/elasticsearch/elasticsearch.yml
:
path.repo: ["/home/backup-elasticsearch"]
Il faut redémarrer Elasticsearch et créer un ensemble de snapshots (snapshots repository) ainsi :
$ curl -XPUT 'http://localhost:9200/_snapshot/foo' -d '{
"type": "fs",
"settings": {
"location": "/home/backup-elasticsearch",
"compress": true
}
}'
{"acknowledged":true}
On peut ainsi lister les infos :
$ curl -XGET 'http://localhost:9200/_snapshot/?pretty'
{
"foo" : {
"type" : "fs",
"settings" : {
"compress" : "true",
"location" : "/home/backup-elasticsearch/foo"
}
}
}
Si l'on veut supprimer un ensemble de snapshots :
$ curl -s -XDELETE "localhost:9200/_snapshot/foo?pretty"
{"acknowledged":true}
Pour créer un snapshot dans l'ensemble foo :
$ curl -s -XPUT "localhost:9200/_snapshot/foo/snapshot_test?wait_for_completion=true"
$ ls -l /home/backup-elasticsearch
-rw-r--r-- 1 elasticsearch elasticsearch 34 Apr 11 01:35 index
drwxr-xr-x 22 elasticsearch elasticsearch 4096 Apr 11 01:35 indices
-rw-r--r-- 1 elasticsearch elasticsearch 3006 Apr 11 01:35 metadata-snapshot_test
-rw-r--r-- 1 elasticsearch elasticsearch 419 Apr 11 01:35 snapshot-snapshot_test
Si l'on tente de créer un snapshot déjà existant, on obtiendra :
{"error":"InvalidsnapshotNameException[[backup:snapshot_test] Invalid snapshot name [snapshot_test], snapshot with such name already exists]","status":400}
Lister les snapshots :
$ curl -XGET "localhost:9200/_snapshot/foo/_all?pretty=true"
[...]
"snapshots" : [ {
"snapshot" : "snapshot_201403190415",
"indices" : [...],
"state" : "SUCCESS",
"start_time" : "2014-03-19T03:15:03.380Z",
"start_time_in_millis" : 1395198903380,
"end_time" : "2014-03-19T03:16:33.381Z",
"end_time_in_millis" : 1395198993381,
"duration_in_millis" : 90001,
[...]
"snapshot" : "snapshot_201403201222",
"indices" : [...],
"state" : "SUCCESS",
"start_time" : "2014-03-20T11:22:07.441Z",
"start_time_in_millis" : 1395314527441,
"end_time" : "2014-03-20T11:22:56.176Z",
"end_time_in_millis" : 1395314576176,
"duration_in_millis" : 48735,
"failures" : [ ],
"shards" : {
"total" : 86,
"failed" : 0,
"successful" : 86
Pour supprimer un snapshot :
$ curl -s -XDELETE "localhost:9200/_snapshot/foo/snapshot_test"
On peut ainsi créer régulièrement des snapshots pour les sauvegardes.
Pour créer un snapshot toutes les heures, et en conserver 24 en permanence (notion de snapshots "roulants") :
$ date=$(date +%H)
$ curl -s -X DELETE "127.0.0.1:9200/_snapshot/foo/h${date}" | grep -v acknowledged..true
$ curl -s -X PUT "127.0.0.1:9200/_snapshot/foo/h${date}?wait_for_completion=true" -o /tmp/es_snapshot_h${date}.log
Plus classiquement pour avoir un snapshot par jour :
$ date=$(date +%Y-%m-%d)
$ curl -s -XDELETE "localhost:9200/_snapshot/foo/snapshot_${date}" | grep -v acknowledged..true
$ curl -s -XPUT "localhost:9200/_snapshot/foo/snapshot_${date}?wait_for_completion=true" -o /tmp/es_snapshot_${date}.log
On peut ensuite purger les snapshots vieux de plus de 10 jours ainsi :
$ cd /home/backup-elasticsearch/foo
$ for i in $(ls -1d snapshot-* | head -n -10 | sed s'/snapshot-snapshot_//g'); do curl -s -XDELETE "localhost:9200/_snapshot/foo/snaps
hot_${i}"; done
https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-cluster.html
Si l'on compare à d'autres services (MySQL, PostgreSQL, MongoDB…) la gestion d'un cluster Elasticsearch est vraiment simple. Il faut lancer plusieurs instances Elasticsearch sur un réseau avec le même cluster.name et un node.name différent, et il suffit d'indiquer une (ou plusieurs) adresse(s) IP qui va permettre à l'instance de communiquer avec un (ou plusieurs) autre(s) nœud(s) :
cluster.name: foo
node.name: bar0
node.master: true
node.data: true
discovery.zen.ping.unicast.hosts: ["192.0.2.42"]
En démarrant un 2ème nœud bar1 on verra ainsi dans les logs de l'instance master bar0 que le cluster passe de YELLOW à GREEN :
[INFO ][o.e.c.s.ClusterService ] [bar0] added {{bar1}{_jwXmQsAQEyseSOc4pG2IA}{PTpsbMBAQEKTs_OFgW_RYw}{192.0.2.42}{192.0.2.42:9301},}, reason: zen-disco-node-join[{bar1}{_jwXmQsAQEyseSOc4pG2IA}{PTpsbMBAQEKTs_OFgW_RYw}{192.0.2.42}{192.0.2.42:9301}]
[WARN ][o.e.d.z.ElectMasterService] [bar0] value for setting "discovery.zen.minimum_master_nodes" is too low. This can result in data loss! Please set it to at least a quorum of master-eligible nodes (current value: [-1], total number of master-eligible nodes used for publishing in this round: [2])
[INFO ][o.e.c.r.a.AllocationService] [bar0] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-data-2][0]] ...]).
On peut consulter le statut du cluster via la requête :
$ curl 'http://localhost:9200/_nodes?pretty=true'
{
"_nodes" : {
"total" : 3,
"successful" : 3,
"failed" : 0
},
"cluster_name" : "foo",
"nodes" : {
"4Tt8FlV4TG2Hf_1T4EayQg" : {
"name" : "bar0",
[...]
On voit ainsi qu'il y a 3 nœuds dans le cluster.
Si l'on coupe le master, un autre est élu :
[INFO ][o.e.d.z.ZenDiscovery ] [bar2] master_left [{bar0}{4Tt8FlV4TG2Hf_1T4EayQg}{5nbXw3F5RWCWjUSiRzv9DA}{192.0.2.42}{192.0.2.42:9300}], reason [shut_down]
[WARN ][o.e.d.z.ZenDiscovery ] [bar2] master left (reason = shut_down), current nodes: {{bar2}{5wUhAI79SsyY-DKv4va26Q}{_VQTiZXxTCi2KIsijyQBpg}{192.0.2.42}{192.0.2.42:9302},{bar1}{_jwXmQsAQEyseSOc4pG2IA}{_pQMtkFLSTe3p-eDHMkalw}{192.0.2.42}{192.0.2.42:9301},}
[INFO ][o.e.c.s.ClusterService ] [bar2] removed {{bar0}{4Tt8FlV4TG2Hf_1T4EayQg}{5nbXw3F5RWCWjUSiRzv9DA}{192.168.0.133}{192.168.0.133:9300},}, reason: master_failed ({bar0}{4Tt8FlV4TG2Hf_1T4EayQg}{5nbXw3F5RWCWjUSiRzv9DA}{192.0.2.42}{192.0.2.42:9300})
[INFO ][o.e.c.r.a.AllocationService] [bar2] Cluster health status changed from [GREEN] to [YELLOW] (reason: [removed dead nodes on election]).
[INFO ][o.e.c.s.ClusterService ] [bar2] new_master {bar2}{5wUhAI79SsyY-DKv4va26Q}{_VQTiZXxTCi2KIsijyQBpg}{192.168.0.133}{192.168.0.133:9302}, reason: zen-disco-elected-as-master ([0] nodes joined)
[INFO ][o.e.c.r.DelayedAllocationService] [bar2] scheduling reroute for delayed shards in [59.8s] (2 delayed shards)
[INFO ][o.e.c.r.a.AllocationService] [bar2] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-es-2-2016.11.06][0]] ...]).
Basé sur le livre http://exploringelasticsearch.com/book
On utilisera l'outil cURL pour faire les requêtes.
En plaçant à la fin d'une URI ?pretty=true
on pourra obtenir un JSON formaté, plus lisible pour les humains.
Nous allons utiliser une base de données d'exemple pour faire des tests.
Télécharger, https://github.com/andrewvc/ee-datasets/archive/master.zip, décompresser l'archive et exécuter le programme Java qui va injecter la BDD "movie_db" dans votre cluster ES.
$ java -jar elastic-loader.jar http://localhost:9200 datasets/movie_db.eloader
La BDD movie_db contient quelques noms de films, avec leurs informations associés (genre, date, acteurs, …).
Pour consulter tout son contenu :
$ curl http://localhost:9200/movie_db/_search?pretty=true
Créer un index (équivalent d'une base de données) nommé planet :
$ curl -X PUT localhost:9200/planet
{"acknowledged":true,"shards_acknowledged":true}
Créer un type de donnée nommé « hacker » :
$ curl -X PUT localhost:9200/planet/hacker/_mapping -d '
{
"hacker": {
"properties": {
"handle": {
"type": "string"
},
"age": {
"type": "long"
}
}
}
}
'
Créer un document de type hacker avec l'ID 1 :
$ curl -X PUT localhost:9200/planet/hacker/1 -d '{"handle": "jean-michel", "age": 18}'
{"_index":"planet","_type":"hacker","_id":"1","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"created":true
Voir son contenu :
$ curl localhost:9200/planet/hacker/1?pretty=true
{
"_index" : "planet",
"_type" : "hacker",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"handle" : "jean-michel",
"age" : 18
}
}
Mise à jour du champ âge :
$ curl -X POST localhost:9200/planet/hacker/1/_update -d '{"doc": {"age": 19}}'
{"_index":"planet","_type":"hacker","_id":"1","_version":2,"result":"updated","_shards":{"total":2,"successful":1,"failed":0}}
Suppression du document :
$ curl -X DELETE localhost:9200/planet/hacker/1
{"found":true,"_index":"planet","_type":"hacker","_id":"1","_version":3,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0}}
Recréons un index de test :
$ curl -X DELETE localhost:9200/planet
{"acknowledged":true}
$ curl -X PUT localhost:9200/planet -d '
{
"mappings": {
"hacker": {
"properties": {
"handle": {
"type": "string"
},
"hobbies": {
"type": "string",
"analyzer": "snowball"
}
}
}
}
}
'
Ajoutons quelques documents :
$ curl -X PUT localhost:9200/planet/hacker/1 -d '
{
"handle": "mark",
"hobbies": ["rollerblading", "hacking", "coding"]
}
'
$ curl -X PUT localhost:9200/planet/hacker/2 -d '
{
"handle": "gondry",
"hobbies": ["writing", "skateboarding"]
}
'
$ curl -X PUT localhost:9200/planet/hacker/3 -d '
{
"handle": "jean-michel",
"hobbies": ["coding", "rollerblades"]
}
'
Recherchons ceux qui ont pour hobby rollerblading :
$ curl -X POST localhost:9200/planet/hacker/_search?pretty=true -d '
{
"query": {
"match": {
"hobbies": "rollerblading"
}
}
}
'
On obtiens 2 résultats, jean-michel et mark. Pourtant le hobby de jean-michel n'est pas rollerblading mais rollerblades, alors comment Elastic Search l'a trouvé ? C'est parce qu’il comprend que rollerblading et rollerblades sont très similaires ! Cela grâce à l'analyseur de type « snowball » que nous avons indiqué lors de la création du type hobbies. Cela indique à ES qu'il s'agit non pas d'une chaîne de caractère banale mais du texte Anglais (Gestion des autres langues ?).
Si vous obtenez une erreur du type :
[2016-06-15 14:53:05,714][WARN ][bootstrap ] unable to load JNA native support library, native methods will be disabled.
java.lang.UnsatisfiedLinkError: /tmp/jna--1985354563/jna3461912487982682933.tmp: /tmp/jna--1985354563/jna3461912487982682933.tmp: failed
to map segment from shared object: Operation not permitted
C'est peut-être que vous avez votre partition /tmp
en noexec, il faut alors changer le chemin comme indiqué sur #configuration-de-base
Il faut définir 3 paramètres minimum pour lancer une instance Elasticsearch :
# cp -pr /etc/elasticsearch /usr/local/etc/elasticsearch0
# mkdir -p /srv/es-data/bar0 /srv/es-log/bar0
# chown elasticsearch: /srv/es-data/bar0 /srv/es-log/bar0
Configuration via /usr/local/etc/elasticsearch0/elasticsearch.yml
:
cluster.name: foo
node.name: bar0
node.master: true
node.data: true
On peut ensuite lancer cette nouvelle instance en ligne de commande :
# su -s /bin/sh elasticsearch
$ /usr/share/elasticsearch/bin/elasticsearch -Edefault.path.conf=/usr/local/etc/elasticsearch0 \
-Edefault.path.data=/srv/es-data/bar0 -Edefault.path.logs=/srv/es-log/bar0
Note : si Elasticsearch nécessite une version de Java différent (Java 8 pour Elasticsearch 5.0), il suffit d'ajouter la variable JAVA_HOME en début de ligne de commande :
$ JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre/ /usr/share/elasticsearch/bin/elasticsearch [...]
Si vous obtenez une erreur HTTP 401 Unauthorized avec le détail "missing authentication token for REST request...", c'est probablement que le plugin shield est activé.
Lorsqu'on utilise (par exemple) Elasticsearch pour des logs, il peut être utile de supprimer les données anciennes.
La solution la plus propre est d'utiliser Curator, mais lorsque ça n'est pas possible (à cause de compatibilité avec le système) on peut recourir à une approche manuelle moins souple mais efficace :
Voici un exemple qui pour les index nommés logstash-*
, ne va conserver que les 20 derniers.
#!/bin/bash
indices=$(curl http://127.0.0.1:9200/_cat/indices/logstash-*?h=i | sort | head -n -20)
for index in ${indices}; do
# echo Delete ${index}
curl -XDELETE http://127.0.0.1:9200/${index}
done