---
title: Howto Elasticsearch
categories: web bdd nosql
...

* Documentation : <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/index.html>
* Forum : <https://discuss.elastic.co/c/elasticsearch>

[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 notamment 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 :

~~~
# apt install apt-transport-https
# echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" >> /etc/apt/sources.list.d/elastic.list
# wget https://artifacts.elastic.co/GPG-KEY-elasticsearch -O /etc/apt/trusted.gpg.d/elastic.asc
# apt update
# apt install elasticsearch
~~~

Infos basiques sur le cluster (nom, version, etc.) :

~~~
$ curl 127.0.0.1:9200
{
  "name" : "my_node",
  "cluster_name" : "my_cluster",
  "cluster_uuid" : "RwTUfzym2fTq-URwSNGaeg",
  "version" : {
    "number" : "5.6.6",
    "build_hash" : "7d99d36",
    "build_date" : "2018-01-09T23:55:47.880Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.1"
  },
  "tagline" : "You Know, for Search"
}
~~~

### Java 1.8

Elasticsearch 5.x nécessite Java 1.8. Pour Jessie, il faut installer la machine virtuelle Java depuis les backports.

~~~
# echo "deb http://mirror.evolix.org/debian jessie-backports main " >> /etc/apt/sources.list.d/backports.list
~~~

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

Package: openjdk-8-jre openjdk-8-jre-headless ca-certificates-java
Pin: release a=jessie-backports
Pin-Priority: 900
~~~

~~~
# apt install openjdk-8-jre-headless
# update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
~~~

### Breaking changes

Si vous faites la mise à jour depuis une version 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>.

### Démarrage

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
~~~

## Mise à jour

Selon la version de départ et la version d'arrivée, la procédure peut être triviale et sans coupure ou bien plus complexe et avec coupure. La grille des versions est disponible ici : [https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-upgrade.html]().

Lors de changements de versions, certaines fonctionnalités peuvent changer ou disparaître. Il faut donc consulter attentivement les [notes de versions](https://www.elastic.co/guide/en/elasticsearch/reference/current/es-release-notes.html), en particulier les sections [breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes.html).

Il est important de noter qu'il n'est pas possible (sauf exceptions) de revenir en arrière après une montée de version.

## Configuration de base

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 :

~~~{.yaml}
cluster.name: foo
node.name: bar
~~~

## 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"]
~~~

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>.

### mode "production"

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`).

## 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

### Occupation disque

Elasticsearch prend en considération l'espace disque disponible avant d'allouer des shards sur un nœud (pour des nouveaux index ou des déplacements). Par défaut il stoppe les nouvelles allocations à 85% d'occupation ("low watermark"), tente de déplacer des shards vers d'autres nœuds à 90% ("high watermark") et enfin passe les index en lecture seule à 95% ("flood watermark"). Plus d'info sur https://www.elastic.co/guide/en/elasticsearch/reference/current/disk-allocator.html

Si le cluster (qu'il y ait un ou plusieurs nœuds) se trouve dans l'incapacité d'allouer des shards pour des données entrantes, il passera alors en état "RED" et les données de ces index ne seront pas écrites.

Les seuils peuvent être adaptés (en pourcentage ou en valeur absolue).
Il est conseillé de régler les niveaux d'alerte de l'occupation disque à des seuils cohérents avec les choix faits pour Elastisearch (par exemple 80% par défaut) afin d'avoir une alerte de monitoring avant d'avoir un cluster dégradé.
Il est également possible de désactiver complètement cette fonctionnalité, mais c'est à réserver à des situations très maîtrisées.

### Changer le thread_pool

<https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-threadpool.html>

À la manière d'une base de données tel que MySQL ou PostgreSQL, Elasticsearch dispose de plusieurs pools de connexions selon le type de requêtes. Par exemple, le pool pour les requêtes de type « search ». Par défaut il y a une auto-configuration qui est basé sur le nombre de CPU de la machine.

L'auto-configuration n'est pas toujours pertinente, car on peut saturer les pools alors qu'on ne sature pas les ressources de la machine.

Exemple de message indiquant une saturation (49 threads sur 49) :

~~~
[Running, pool size = 49, active threads = 49, queued tasks = 2279, completed tasks = 122546879]]
~~~

On peut définir nous-même les paramètres à la hausse ou à la baisse, exemple avec le pool « search » :

~~~
thread_pool:
    search:
        size: 128
        queue_size: 4096
~~~

## 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
~~~

### Munin

Des plugins pour Munin sont disponible sur ce dépot Github : https://github.com/y-ken/munin-plugin-elasticsearch/

Procedure d'installation :

~~~
# apt install libjson-perl
# git clone https://github.com/y-ken/munin-plugin-elasticsearch.git
# cp -a munin-plugin-elasticsearch /usr/local/src/
# chmod go+rx /usr/local/src/munin-plugin-elasticsearch
# chmod 755 /usr/local/src/munin-plugin-elasticsearch/elasticsearch_*
# ln -s /usr/local/src/munin-plugin-elasticsearch/elasticsearch_* /etc/munin/plugins/
# systemctl restart munin-node.service
~~~


Puis tester quelques sondes :

~~~
# munin-run elasticsearch_cache 
field_size.value 1410084
query_size.value 659968

# munin-run elasticsearch_jvm_memory
heap_max.value 2130051072
heap_used.value 1368633616
non_heap_committed.value 187817984
non_heap_used.value 175388648
heap_committed.value 2130051072
~~~

## Snapshots 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 :

~~~
# install -d -o elasticsearch -g elasticsearch -m 750 /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.

Après avoir créé le répertoire des snapshots et chown(8) sur l’usager / groupe elasticsearch.

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 -H'Content-Type: application/json' -XPUT 'http://localhost:9200/_snapshot/snaprepo' -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

Il faut donc avoir une directive `path.repo: ["/home/backup-elasticsearch"]` prise en compte par Elasticsearch avec un répertoire existant.
Puis on crée 

Créons un snapshot repository nommé `snaprepo` :

~~~
$ curl -XPUT 'http://localhost:9200/_snapshot/snaprepo' -d '{
"type": "fs",
    "settings": {
        "location": "/home/backup-elasticsearch",
        "compress": true
    }
}'
{"acknowledged":true}
~~~

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/snaprepo/h${date}" | grep -v -Fx '{"acknowledged":true}'
$ curl -s -X PUT "127.0.0.1:9200/_snapshot/snaprepo/h${date}?wait_for_completion=true" -o /tmp/es_snapshot_h${date}.log
~~~

Plus classiquement pour avoir un snapshot par jour :

~~~
$ date=$(date +%F)
$ curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot_${date}" | grep -v -Fx '{"acknowledged":true}'
$ curl -s -XPUT "localhost:9200/_snapshot/snaprepo/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/snaprepo
$ for i in $(ls -1d snapshot-* | head -n -10 | sed s'/snapshot-snapshot_//g'); do curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snaps
hot_${i}"; done
~~~

Dans le cas de snapshot pour un ElasticSearsch clusterisé :

~~~
# if ss | grep ':nfs' | grep -q 'ip\.add\.res\.s1' && ss | grep ':nfs' | grep -q 'ip\.add\.res\.s2'
# then
#     curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/snapshot.daily" -o /tmp/es_delete_snapshot.daily.log
#     curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot.daily?wait_for_completion=true" -o /tmp/es_snapshot.daily.log
# else
#     echo 'Cannot make a snapshot of elasticsearch, at least one node is not mounting the repository.'
# fi
# for snapshot in $(curl -s -XGET "localhost:9200/_snapshot/snaprepo/_all?pretty=true" | grep -Eo 'snapshot_[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -n -10); do
#     curl -s -XDELETE "localhost:9200/_snapshot/snaprepo/${snapshot}" | grep -v -Fx '{"acknowledged":true}'
# done
# date=$(date +%F)
# curl -s -XPUT "localhost:9200/_snapshot/snaprepo/snapshot_${date}?wait_for_completion=true" -o /tmp/es_snapshot_${date}.log
~~~

### Restauration d'un snapshot

Le snapshot doit être listé dans le snapshot repository.

Pour restaurer le snapshot `snapshot.daily` :

~~~
$ curl -XPOST "localhost:9200/_snapshot/snaprepo/snapshot.daily/_restore"
{"accepted":true}
~~~

> *Note* : si vous avez un message d'erreur du type `{"error":"SnapshotRestoreException[[snaprepo:snapshot.daily] cannot restore index [foo] because it's open]","status":500}` vous pouvez fermer l'index en faisant `curl -XPOST "localhost:9200/foo/_close`


## Rotation des logs

Elasticsearch fait de lui-même une rotation des logs en datant le fichier du jour et en créant un nouveau fichier. Par contre, aucune compression ni nettoyage n'est fait. Il est possible de déclencher une tâche tous les jours pour faire cela :

~~~{.bash}
#!/bin/sh

LOG_DIR=/var/log/elasticsearch
USER=elasticsearch
MAX_AGE=365

find ${LOG_DIR} -type f -user ${USER} \( -name "*.log.????-??-??" -o -name "*-????-??-??.log" \) -exec gzip --best {} \;
find ${LOG_DIR} -type f -user ${USER} \( -name "*.log.????-??-??.gz" -o -name "*-????-??-??.log.gz" \) -ctime +${MAX_AGE} -delete
~~~

En l'indiquant dans une crontab elle sera exécutée quand vous le souhaitez, exemple :

~~~
# crontab -l
10 1 * * * /path/to/rotate_elasticsearch_logs.sh
~~~

Pour logstash :

~~~
LOG_DIR=/var/log/logstash/
USER=logstash
MAX_AGE=365
find ${LOG_DIR} -type f -user ${USER} -name "logstash-*-????-??-??.log" -exec gzip --best {} \;
~~~

## cluster

<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.0.2.133}{192.0.2.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.0.2.133}{192.0.2.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]] ...]).
~~~

## Plugins

Elasticsearch dispose d'un système de plugins, certains officiels et d'autres communautaires.
Un **redémarrage du service** est nécessaire pour prendre en comptes ces changements.

### Installation, suppression

Les plugins officiels peuvent être installés en indiquant simplement leur nom :

~~~
# /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-phonetic
~~~

Pour installer une nouvelle version d'un plugin, il faut d'abord le supprimer :

~~~
# /usr/share/elasticsearch/bin/elasticsearch-plugin remove analysis-phonetic
# /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-phonetic
~~~

### Liste

~~~
# /usr/share/elasticsearch/bin/elasticsearch-plugin list
analysis-phonetic
~~~

### Blocage suite à mise à jour

**Attention** : certains plugins (ex. : _analysis-icu_ et _analysis-phonetic_) sont étroitement liés à une version d'Elasticsearch et peuvent bloquer son démarrage en cas d'incohérence. On aura alors une erreur de ce type dans les logs du cluster :

~~~
[2017-10-30T09:51:46,918][ERROR][o.e.b.Bootstrap          ] Exception
java.lang.IllegalArgumentException: plugin [analysis-phonetic] is incompatible with version [5.6.3]; was designed for version [5.6.2]
~~~

On peut utiliser ce script pour automatiser la mise à jour de tous les plugins et le redémarrage d'Elasticsearch :

~~~{.bash}
#!/bin/bash

set -e
set -u

PLUGIN_BIN=/usr/share/elasticsearch/bin/elasticsearch-plugin
NEED_RESTART=""

for plugin in $(${PLUGIN_BIN} list); do
    "${PLUGIN_BIN}" remove "${plugin}"
    "${PLUGIN_BIN}" install "${plugin}"
    NEED_RESTART="1"
done

if [ -n "${NEED_RESTART}" ]; then
    systemctl restart elasticsearch
fi

exit 0
~~~

## Principe de fonctionnement d'Elasticsearch

_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.

### 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, <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
~~~

### En créant une base de données

#### Opérations CRUD

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}}
~~~

#### 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 ?).

## Curator

Curator est un outil indépendant d'Elasticsearch qui permet de réaliser des opérations diverses sur un cluster, le plus souvent déclenchées par des taches cron, un peu à la manière de logrotate.

Documentation : https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.html

### Installation

~~~
# echo "deb https://packages.elastic.co/curator/5/debian9 stable main" >> /etc/apt/sources.list.d/elastic.list
# apt update
# apt install curator
~~~

### Configuration

Curator s'appuie sur un fichier de configuration qui contient toutes les informations pour se connecter au cluster Elasticsearch (adresse, authentification, chiffrement…). 

Dans le de l'exécution via cron, il est conseillé d'envoyer les logs dans un fichier plutôt que dans la sortie standard.

~~~
[…]
logging:
  loglevel: INFO
  logfile: /var/log/curator/curator.log
[…]
~~~

Note : ne pas oublier le logrotate :
~~~
# cat /etc/logrotate.d/curator
/var/log/curator/*.log {
        monthly
        rotate 12
        compress
        delaycompress
        missingok
        notifempty
}
~~~

### Actions

Curator utilise également un fichier d'action (potentiellement différent à chaque appel). Il doit contenir les filtres permettant de déterminer quels index sont concernés (motif sur le nom, âge, taille ou nombre de documents…), puis une série d'actions (compression, déplacement, optimisation.)

## API "cat"

Elasticsearch expose une API depremier niveau appelée "cat". Elle permet d'obtenir des informations sur un cluster avec un formattage texte facile à manipuler en ligne de commande là où les sorties JSON le sont moins.

Toute "la documentation est ici":https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html

### État général du cluster

~~~
$ curl -s 127.0.0.1:9200/_cat/health
1545659505 14:51:45 cluster-name green 4 4 418 207 0 0 0 0 - 100.0%
~~~

Avec l'option `v` on peut avoir les en-têtes de colonnes :

~~~
$ curl -s 127.0.0.1:9200/_cat/health?v
epoch      timestamp cluster  status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1545659712 14:55:12  cluster-name green           4         4    418 207    0    0        0             0                  -                100.0%
~~~

### Liste des nœuds

~~~
$ curl -s 127.0.0.1:9200/_cat/nodes?v
ip        heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.0.0.13           52          83   3    0.01    0.14     0.16 mdi       *      node3
10.0.0.11           32          92   0    0.09    0.07     0.09 mdi       -      node1
10.0.0.10           51          87   0    0.36    0.20     0.12 mdi       -      node0
10.0.0.12           29          87   9    0.31    0.38     0.31 mdi       -      node2
~~~

Dans la dernière colonne, l'étoire indique le nœud master. Cette info est aussi disponible par `_cat/master` :

~~~
$ curl -s 127.0.0.1:9200/_cat/master
C6blxAg5SrmkCx8C80qiig 10.0.0.13 10.0.0.13 node3
~~~

### Liste des index

On peut récupérer la liste de tous les index, classés par nom :

~~~
$ curl -s 127.0.0.1:9200/_cat/indices | sort -k 3
green open index1 vewTWN8WRB-2V6xIgE7oQQ  1 1  2190434      0      2gb      1gb
green open index2 k0a2yjwSS_CKKSydoVzcoQ  1 1  2164950      0    1.9gb 1020.5mb
green yellow index3 rMtU36wXSNS9q9w6BDp0sA  1 1  3156725      0    2.9gb    1.4gb
[…]
~~~

On peut limiter la liste selon la valeur de certaines colones (exemple :`health`) :

~~~
$ curl -s 127.0.0.1:9200/_cat/indices?health=yellow
green yellow index3 rMtU36wXSNS9q9w6BDp0sA  1 1  3156725      0    2.9gb    1.4gb
~~~

### Consulter l'état des shards

On peut voir si les shards sont correctement alloués (`STARTED`) ou pas.

~~~
$ curl -s 127.0.0.1:9200/_cat/shards
index3     1 p STARTED        0    191b 10.0.0.13  node3
index3     1 r STARTED        0    191b 10.0.0.12 node2
index3     1 r UNASSIGNED
~~~

Ici on voit que `index3` a un shard non alloué. Pour savoir pourquoi on eput interroger l'état de l'index à propos de ses chards :

~~~
$ curl 127.0.0.1:9200/index3/_shard_stores?pretty
{
  "indices" : {
    "index3" : {
      "shards" : {
        "1" : {
          "stores" : [
            {
              "wPZvU6MUQkmhSb4YrszYeA" : {
                "name" : "node3",
                "ephemeral_id" : "6EK7veXhQrKAi42UB_97Zw",
                "transport_address" : "10.0.0.13:9300",
                "attributes" : { }
              },
              "allocation_id" : "RkL8f2wNS5CVL-MJYVHDaA",
              "allocation" : "primary"
            },
            {
              "yuF0yDqHTeyj7Z3rDt9HXw" : {
                "name" : "node2",
                "ephemeral_id" : "sIkpMVcGTyCkXx4RDd_mgA",
                "transport_address" : "10.0.0.12:9300",
                "attributes" : { }
              },
              "allocation_id" : "-piepczfSEiyTV9vN-l5MQ",
              "allocation" : "replica"
            }
          ]
        }
      }
    }
  }
}
~~~

On voir qu'il y a bien un _primary_ mais un seul _replica_.
On peut alors interroger le cluster sur l'état des allocations :

~~~
$ curl '127.0.0.1:9200/_cluster/allocation/explain?pretty'
~~~

La sortie est en JSON et peut être assez verbeuse.
Elle contiendra des explications a priori assez claires sur d'éventuels soucis d'allocations de shards.
Dans cet exemple (tronqué) un shard _replica_ ne peut pas être alloué sur un nœud dont la version d'Elasticsearch n'est pas cohérente.

~~~.json
{
  "index" : "index3",
  "shard" : 1,
  "primary" : false,
  "current_state" : "unassigned",
  "unassigned_info" : {
    "reason" : "REPLICA_ADDED",
    "at" : "2019-07-31T07:04:15.845Z",
    "last_allocation_status" : "no_attempt"
  },
  "can_allocate" : "no",
  "allocate_explanation" : "cannot allocate because allocation is not permitted to any of the nodes",
  "node_allocation_decisions" : [
    {
      "node_id" : "--e8PpGsSKifZ5lFlDkW3A",
      "node_name" : "node1",
      "transport_address" : "10.0.0.11:9300",
      "node_decision" : "no",
      "deciders" : [
        {
          "decider" : "node_version",
          "decision" : "NO",
          "explanation" : "target node version [5.6.6] is older than the source node version [5.6.15]"
        }
      ]
    }
  ]
}
~~~

## 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é.

### Comment supprimer proprement des vieux index

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](https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.html), 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.

~~~{.bash}
#!/bin/bash

#garder les 10 plus récent
indices=$(curl http://127.0.0.1:9200/_cat/indices/logstash-*?h=i | sort | head -n -10)

for index in ${indices}; do
    # echo Delete ${index}
    curl --silent --fail --show-error -XDELETE http://127.0.0.1:9200/${index} > /dev/null
done
~~~


### Lister les index

Cette commande est pratique pour voir la taille que prennent les index

~~~
$ curl -XGET "http://localhost:9200/_cat/shards?v"
~~~

### Lister le statut des index

~~~
$ curl -XGET 'http://127.0.0.1:9200/_cluster/health?level=indices&pretty'
~~~

### Relancer l'allocation des shards

Dans certains cas le cluster ne parvient pas à allouer des shards (primaires ou réplicas).
Il est possible de forcer le reroutage avec cette commande :

~~~
$ curl -XPOST 'http://127.0.0.1:9200/_cluster/reroute?retry_failed'
~~~

### Modifier en masse le nombre de replica :

Par exemple sur un cluster avec un seul nœud, on ne veut pas de replica. On prend alors tous les index en état "yellow" et on passe le nombre de replica à "0".

~~~
# for index in $(curl 127.0.0.1:9200/_cat/indices/?h=index&health=yellow); do curl -X PUT 127.0.0.1:9200/$index/_settings -H 'Content-Type: application/json' -d '{ "index": {"number_of_replicas": 0} }'; done
~~~

### Nombre de replicas par défaut

On utilise un [template](https://www.elastic.co/guide/en/elasticsearch/reference/7.8/indices-templates.html) qui définira la valeur par défaut du nombre de replicas de tous les nouveaux index :

~~~
# curl -X PUT "localhost:9200/_template/template_1?pretty" -H 'Content-Type: application/json' -d'
{
  "index_patterns": ["*"],
  "settings": {
    "number_of_replicas": 0
  }
}'
~~~

### Consulter l'état du recovery

Cette requête donnera une vision sur l'état des synchro et d'éntuelles opérations de recovery :

~~~
# curl -s 127.0.0.1:9200/_cat/recovery?v
~~~

### Licence X-Pack

Le module propriétaire X-Pack nécessite une licence pour une utilisation avancée, voici la procédure :

~~~
$ curl -XPUT 'http://127.0.0.1:9200/_xpack/license' -H "Content-Type: application/json" -d @license.json
{"acknowledged":true,"license_status":"valid"}

$ curl -XGET 'http://127.0.0.1:9200/_xpack/license'
~~~

> *Note* : si l'authentification est activée, on ajoutera l'option pour préciser un utilisateur `-u jdoe`
