--- title: Howto Elasticsearch categories: web bdd nosql ... * Documentation : * Forum : [Elasticsearch](https://www.elastic.co/fr/products/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](HowtoELK). ## Installation Vu le développement actif d'Elasticsearch, nous préconisons l'installation des paquets Debian distribués par Elasticsearch : ~~~ # echo "deb http://packages.elastic.co/elasticsearch/2.x/debian stable main" >> /etc/apt/sources.list.d/elasticsearch.list # wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | apt-key add - # apt install elasticsearch openjdk-7-jre ~~~ On vérifie qu'on a bien Java >= 1.7 activé : ~~~ $ java -version java version "1.7.0_111" OpenJDK Runtime Environment (IcedTea 2.6.7) (7u111-2.6.7-1~deb8u1) OpenJDK 64-Bit Server VM (build 24.111-b01, mixed mode) ~~~ 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 ~~~ ## Configuration de base Les paramètres système (répertoires utilisés, configuration JVM) se trouvent dans le fichier `/etc/default/elasticsearch`, 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). On peut aussi définir un *tmpdir* spécifique (utile quand `/tmp` est en _noexec_) dans `/etc/default/elasticsearch` : ~~~ RESTART_ON_UPGRADE=true 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 : ~~~{.yaml} cluster.name: foo node.name: bar ~~~ ## Journaux On conseille de ne pas activer le logging stdout vers la console, mais de conserver seulement les logs dans `/var/log/elasticsearch/`. On peut faire cela via le fichier `/etc/elasticsearch/logging.yml` : ~~~{.diff} -rootLogger: ${es.logger.level}, console, file +rootLogger: ${es.logger.level}, file ~~~ Les journaux intéressants sont dans `/var/log/elasticsearch/_cluster_name_.log` ## Configuration réseau 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 : ~~~{.yaml} network.host: ["192.0.2.42", "198.51.100.42", "127.0.0.1"] ~~~ ## Plugins Les plugins permettent différentes améliorations, notamment en terme de visualisation simple et conviviale d'une base de données. On verra ici les plus utiles. ### Kopf - web admin interface for elasticsearch ~~~ # /usr/share/elasticsearch/bin/plugin install lmenezes/elasticsearch-kopf # chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/plugins/ ~~~ *Note :* cela nécessite un accès réseau vers https://github.com/ L'interface **Kopf** est ainsi disponible sur http://127.0.0.1:9200/_plugin/kopf/ ### Head - web front end for an elastic search cluster ~~~ # /usr/share/elasticsearch/bin/plugin install mobz/elasticsearch-head # chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/plugins/ ~~~ L'interface **Head** est ainsi disponible sur http://127.0.0.1:9200/_plugin/head/ ## Configuration avancée * Si on veut lancer Elasticsearch avec une JVM différente que celle par défaut sur le système, on peut définir JAVA_HOME dans `/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 silencieux ## Monitoring ### Nagios On 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 ~~~ ## Snapshost et sauvegardes ### Configuration des snapshots * 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} ~~~ ### Gestion des snapshots 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" ~~~ ### Sauvegarde via snapshots 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") : ~~~{.bash} $ 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 ~~~ ## cluster 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 suffitd'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]] ...]). ~~~ ## Principe de fonctionnement d'Elasticsearch _Basé sur le livre _ 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. ### Avec une base de données d'exemple Nous allons utiliser une base de données d'exemple pour faire des tests. Télécharger, , décompresser l'archive et exécuter le programme Java qui va injecter la BDD "movie_db" dans votre cluster ES. ~~~{.bash} $ 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 : ~~~{.bash} $ curl http://localhost:9200/movie_db/_search?pretty=true ~~~ ### En créant une base de données #### Opérations CRUD Créer un index (équivalent d'une base de données) nommé *planet* : ~~~{.bash} $ 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 : ~~~{.bash} $ 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 : ~~~{.bash} $ 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 : ~~~{.bash} $ 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 : ~~~{.bash} $ 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}} ~~~ #### Recherche basique 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 ?). ## FAQ ### Erreur "failed to map segment from shared object: Operation not permitted" 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]() ### Lancer plusieurs instances sur un même système pour du test Il faut définir 3 paramètres minimum pour lancer une instance Elasticsearch : * default.path.conf (répertoire de configuration) * default.path.data (répertoire pour les données) * default.path.logs (répertoire pour les logs) ~~~ # 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 [...] ~~~ ### Erreur "missing authentication token for REST request" 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](https://www.elastic.co/guide/en/shield/current/installing-shield.html) est activé.