Login Logout

Howto Elasticsearch

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.

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/7.x/apt stable main" >> /etc/apt/sources.list.d/elastic.list
# wget -O /etc/apt/trusted.gpg.d/elastic.asc https://artifacts.elastic.co/GPG-KEY-elasticsearch
# dos2unix /etc/apt/trusted.gpg.d/elastic.asc
# chmod 644 /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" : "7.17.0",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "bee86328705acaa9a6daede7140defd4d9ec56bd",
    "build_date" : "2022-01-28T08:36:04.875279988Z",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Démarrage

Pour activer le démarrage automatique :

# systemctl enable elasticsearch.service 
Synchronizing state of elasticsearch.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable elasticsearch
Created symlink /etc/systemd/system/multi-user.target.wants/elasticsearch.service → /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, en particulier les sections breaking changes.

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.

Mise à jour depuis une version inférieure à 5.0

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.

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 /etc/elasticsearch/jvm.options.d/*.options (Note: on privilégiera le deuxième - Attention, l’extention .options est importante pour que les réglages soient pris en compte) 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

Via le fichier /etc/elasticsearch/elasticsearch.yml vous devez au minimum configurer :

cluster.name: foo
node.name: bar

Changement du dossier temporaire

On peut aussi définir un tmpdir spécifique (important quand /tmp est en noexec). 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

Version >= 6.x

Le tmpdir se change dans /etc/default/elasticsearch :

ES_TMPDIR=/var/lib/elasticsearch/tmp

Cette variable est ensuite utilisée par /etc/elasticsearch/jvm.options qui contient normalement la directive suivante :

-Djava.io.tmpdir=${ES_TMPDIR}

Version < 6.x

On force directement dans les options de la JVM via le fichier /etc/elasticsearch/jvm.options.d/evolinux.options :

ES_JAVA_OPTS="-Djava.io.tmpdir=/var/lib/elasticsearch/tmp"

Mémoire allouée

La mémoire allouée au tas de la JVM pour ElasticSearch sont les lignes -Xms et -Xmx dans /etc/elasticsearch/jvm.options.d/evolinux.options.

Les deux valeurs doivent être égales.

Pour plus d’informations sur la mémoire allouée au tas de la JVM : HowtoJava#gestion-de-la-mémoire.

Configuration réseau

Par défaut, Elasticsearch écoute sur 127.0.0.1 sur TCP/9200 pour son interface client 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", "10.0.42.21", "127.0.0.1"]

Il est possible d’utiliser des adresses virtuelles, telles que _local_ (127.0.0.1 et ::1), _site_ (IPs spécifiques à un réseau privé), _global_

Tous les détails sont sur https://www.elastic.co/guide/en/elasticsearch/reference/7.17/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).

On peut aussi forcer l’exécution de ces contrôles en activant es.enforce.bootstrap.checks à true dans les options de la JVM

Rotation et compression 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 :

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

On désactive aussi les logs du ramasse-miettes de la JVM, qui peut être très verbeux :

/etc/elasticsearch/jvm.options
### JDK 8 GC logging
#8:-XX:+PrintGCDetails
#8:-XX:+PrintGCDateStamps
#8:-XX:+PrintTenuringDistribution
#8:-XX:+PrintGCApplicationStoppedTime
#8:-Xloggc:/var/log/elasticsearch/gc.log
#8:-XX:+UseGCLogFileRotation
#8:-XX:NumberOfGCLogFiles=32
#8:-XX:GCLogFileSize=64m

## JDK 9+ GC logging
#9-:-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m

Puis redémarrer ElasticSearch (l’unité systemd ne propose pas de reload) : systemctl restart elasticsearch.

On peut ensuite faire le ménage et compresser les anciens /var/log/elasticsearch/gc.log.??, par exemple avec :

for f in $(ls -1 /var/log/elasticsearch/gc*[!.gz]); do gzip -f $f; done

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 {} \;

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

Attention : Il faut compléter cette commande avec les options suivantes si les options de sécurisation d’Elasticsearch son activées ! (ie: Authentification HTTP & Chiffrement avec TLS) :

  • --authorization 'remote_monitoring_user:xxxx' - Pour l’authentification HTTP
  • -S - Utilisation de TLS

On obtient ainsi :

/usr/lib/nagios/plugins/check_http -I 127.0.0.1 -S -u /_cat/health?h=st -p 9200 -r 'red' --invert-regex  --authorization 'remote_monitoring_user:xxxx'

Munin

Versions 5.x à 7.x

Un plugin Munin est compatible jusqu’à la version 7 d’Elasticsearch :

# apt install ruby-json
# mkdir -p /usr/local/share/munin/plugins
# chmod 755 /usr/local/share/munin /usr/local/share/munin/plugins
# wget https://raw.githubusercontent.com/claygregory/munin-elasticsearch/master/elasticsearch_ -O /usr/local/share/munin/plugins/elasticsearch_
# chmod 755 /usr/local/share/munin/plugins/elasticsearch_
# for p in jvm gc gc_time cache docs ops store; do ln -s /usr/local/share/munin/plugins/elasticsearch_ /etc/munin/plugins/elasticsearch_${p}; done
# systemctl restart munin-node

On peut éventuellement configurer le plugin dans /etc/munin/plugin-conf.d/elasticsearch

[elasticsearch_*]
env.host 10.1.2.14
env.port 9200
env.node pinky rat
env.user johnsmith
env.user passw0rd

Version 1.x à 5.x

Un plugin pour Munin (compatibles jusqu’à la version 5 d’Elasticsearch) est 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

Sécurisation

Elastic définit 3 niveaux de sécurisation :

  • Minimal : Authentification HTTP
  • Basique (Environnement de production) : Authentification HTTP + Chiffrement inter-nœuds
  • Basique avec chiffrement des connexion clients : Authentification HTTP + Chiffrement inter-nœuds + HTTPS sur l’interface REST

Pour aller plus loin, c’est expliqué en détail dans la documentation d’Elasticsearch https://www.elastic.co/guide/en/elasticsearch/reference/7.17/secure-cluster.html

Authentification HTTP

Il faut d’abord activer les fonctions de sécurité dans /etc/elasticsearch/elasticsearch.yml sur tous les membres du cluster :

xpack.security.enabled: true

Après un redémarrage d’Elasticsearch pour la prise en compte de l’activation de xpack, on utilisera la commande /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto pour créer un mot de passe pour tous les comptes par défaut.

# /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto
Initiating the setup of passwords for reserved users elastic,apm_system,kibana_system,logstash_system,beats_system,remote_monitoring_user.
The passwords will be randomly generated and printed to the console.
Please confirm that you would like to continue [y/N]y


Changed password for user apm_system
PASSWORD apm_system = PASSWORD

Changed password for user kibana_system
PASSWORD kibana_system = PASSWORD

Changed password for user logstash_system
PASSWORD logstash_system = PASSWORD

Changed password for user beats_system
PASSWORD beats_system = PASSWORD

Changed password for user remote_monitoring_user
PASSWORD remote_monitoring_user = PASSWORD

Changed password for user elastic
PASSWORD elastic = PASSWORD

On conservera ces mots de passe avec précaution. Les comptes ont tous des rôles (et donc des permissions) différents.

Chacun son usage :

  • elastic : Super-utilisateur, administration du cluster
  • kibana_system : Compte système pour le fonctionnement de Kibana
  • remote_monitoring_user : Compte pour le monitoring du cluster
  • etc

Par la suite, il faut s’assurer que tous les outils de la suite elastic (Kibana, logstash…), mais aussi le monitoring utilisent bien un compte pour s’authentifier, si non ils ne vont plus fonctionner !

Par la suite la gestion des comptes (ajout/suppression/…) se fait au travers de Kibana (après connexion) : Stack management > Users On pourra ainsi créer des comptes applicatifs, avec

Ajustement configuration monitoring

On ajoutera l’argument suivant à la commande de check_http pour s’authentifier --authorization 'remote_monitoring_user:PASSWORD'

Ajustement configuration Kibana

Dans /etc/kibana/kibana.yml, on spécifie le compte a utiliser :

--- a/etc/kibana/kibana.yml
+++ b/etc/kibana/kibana.yml
@@ -44,7 +44,7 @@ elasticsearch.hosts: ["https://localhost:9200"]
 # the username and password that the Kibana server uses to perform maintenance on the Kibana
 # index at startup. Your Kibana users still need to authenticate with Elasticsearch, which
 # is proxied through the Kibana server.
+elasticsearch.username: "kibana_system"
-#elasticsearch.username: "kibana_system"
 #elasticsearch.password: "pass"
 
 # Kibana can also authenticate to Elasticsearch via "service account tokens".

Pour le mot de passe, pour ne pas l’enregistrer en clair dans la configuration, on utilisera le keystore de Kibana

# /usr/share/kibana/bin/kibana-keystore add elasticsearch.password
Enter value for elasticsearch.password: PASSWORD

Puis redémarrer Kibana systemctl restart kibana

Passage en HTTPS

Il y a plusieurs options pour. Le plus simple est de générer une PKI spéciale au cluster Elasticsearch avec les outils proposés.

Amorce de la PKI

# /usr/share/elasticsearch/bin/elasticsearch-certutil ca  --days 7300
This tool assists you in the generation of X.509 certificates and certificate
signing requests for use with SSL/TLS in the Elastic stack.

The 'ca' mode generates a new 'certificate authority'
This will create a new X.509 certificate and private key that can be used
to sign certificate when running in 'cert' mode.

Use the 'ca-dn' option if you wish to configure the 'distinguished name'
of the certificate authority

By default the 'ca' mode produces a single PKCS#12 output file which holds:
    * The CA certificate
    * The CA's private key

If you elect to generate PEM format certificates (the -pem option), then the output will
be a zip file containing individual files for the CA certificate and private key

Please enter the desired output file [elastic-stack-ca.p12]: 
Enter password for elastic-stack-ca.p12 : 

Le certificat est enregistré par défaut dans /usr/share/elasticsearch/elastic-stack-ca.p12. Comme ce n’est pas un dossier conventionnel, on peut choisir de le déplacer dans /etc/elasticsearch/

# mv /usr/share/elasticsearch/elastic-stack-ca.p12 /etc/elasticsearch/elastic-stack-ca.p12

Communication inter-nœuds

Pour chaque nœud Elasticsearch, il faut générer un certificat avec l’outil /usr/share/elasticsearch/bin/elasticsearch-certutil

On peut essayer, pour garder les choses au clair, d’avoir la nomenclature suivante pour les noms de certificats $(hostname).$(service).p12

# /usr/share/elasticsearch/bin/elasticsearch-certutil cert --ca /etc/elasticsearch/elastic-stack-ca.p12  --days 7300
This tool assists you in the generation of X.509 certificates and certificate
signing requests for use with SSL/TLS in the Elastic stack.

The 'cert' mode generates X.509 certificate and private keys.
    * By default, this generates a single certificate and key for use
       on a single instance.
    * The '-multiple' option will prompt you to enter details for multiple
       instances and will generate a certificate and key for each one
    * The '-in' option allows for the certificate generation to be automated by describing
       the details of each instance in a YAML file

    * An instance is any piece of the Elastic Stack that requires an SSL certificate.
      Depending on your configuration, Elasticsearch, Logstash, Kibana, and Beats
      may all require a certificate and private key.
    * The minimum required value for each instance is a name. This can simply be the
      hostname, which will be used as the Common Name of the certificate. A full
      distinguished name may also be used.
    * A filename value may be required for each instance. This is necessary when the
      name would result in an invalid file or directory name. The name provided here
      is used as the directory name (within the zip) and the prefix for the key and
      certificate files. The filename is required if you are prompted and the name
      is not displayed in the prompt.
    * IP addresses and DNS names are optional. Multiple values can be specified as a
      comma separated string. If no IP addresses or DNS names are provided, you may
      disable hostname verification in your SSL configuration.

    * All certificates generated by this tool will be signed by a certificate authority (CA)
      unless the --self-signed command line option is specified.
      The tool can automatically generate a new CA for you, or you can provide your own with
      the --ca or --ca-cert command line options.

By default the 'cert' mode produces a single PKCS#12 output file which holds:
    * The instance certificate
    * The private key for the instance certificate
    * The CA certificate

If you specify any of the following options:
    * -pem (PEM formatted output)
    * -keep-ca-key (retain generated CA key)
    * -multiple (generate multiple certificates)
    * -in (generate certificates from an input file)
then the output will be be a zip file containing individual certificate/key files

Enter password for CA (elastic-stack-ca.p12) : 
Please enter the desired output file [elastic-certificates.p12]: HOSTNAME.elasticsearch.p12
Enter password for HOSTNAME.elasticsearch.p12 : 

Certificates written to /usr/share/elasticsearch/HOSTNAME.elasticsearch.p12

This file should be properly secured as it contains the private key for 
your instance.

This file is a self contained file and can be copied and used 'as is'
For each Elastic product that you wish to configure, you should copy
this '.p12' file to the relevant configuration directory
and then follow the SSL configuration instructions in the product guide.

For client applications, you may only need to copy the CA certificate and
configure the client to trust this certificate.

Par la suite on va mv le certificat généré vers /etc/elasticsearch (ou le scp vers les nœud distants)

# mv /usr/share/elasticsearch/HOSTNAME.elasticsearch.p12 /etc/elasticsearch/
# chgrp elasticsearch /etc/elasticsearch/HOSTNAME.elasticsearch.p12
# chmod g+r /etc/elasticsearch/HOSTNAME.elasticsearch.p12

Puis on ajout la configuration de Elasticsearch :

xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: HOSTNAME.elasticsearch.p12
xpack.security.transport.ssl.truststore.path: HOSTNAME.elasticsearch.p12

Après, redémarrage avec systemctl restart elasticsearch

Communication client-serveur

Pour chaque nœud elastic, il faut générer un certificat avec l’outil /usr/share/elasticsearch/bin/elasticsearch-certutil

On peut essayer, pour garder les choses au clair, d’avoir la nomenclature suivante pour les noms de certificats $(hostname).http.p12 Dans le cas d’un déploiement de nombreux noeuds, on peut avoir recours a un certificat wildcard qu’on déploiera partout.

Ici exemple pour créer un seul certificat pour notre instance :

# /usr/share/elasticsearch/bin/elasticsearch-certutil http

## Elasticsearch HTTP Certificate Utility

The 'http' command guides you through the process of generating certificates
for use on the HTTP (Rest) interface for Elasticsearch.

This tool will ask you a number of questions in order to generate the right
set of files for your needs.

## Do you wish to generate a Certificate Signing Request (CSR)?

A CSR is used when you want your certificate to be created by an existing
Certificate Authority (CA) that you do not control (that is, you don't have
access to the keys for that CA). 

If you are in a corporate environment with a central security team, then you
may have an existing Corporate CA that can generate your certificate for you.
Infrastructure within your organisation may already be configured to trust this
CA, so it may be easier for clients to connect to Elasticsearch if you use a
CSR and send that request to the team that controls your CA.

If you choose not to generate a CSR, this tool will generate a new certificate
for you. That certificate will be signed by a CA under your control. This is a
quick and easy way to secure your cluster with TLS, but you will need to
configure all your clients to trust that custom CA.

Generate a CSR? [y/N]n

## Do you have an existing Certificate Authority (CA) key-pair that you wish to use to sign your certificate?

If you have an existing CA certificate and key, then you can use that CA to
sign your new http certificate. This allows you to use the same CA across
multiple Elasticsearch clusters which can make it easier to configure clients,
and may be easier for you to manage.

If you do not have an existing CA, one will be generated for you.

Use an existing CA? [y/N]y

## What is the path to your CA?

Please enter the full pathname to the Certificate Authority that you wish to
use for signing your new http certificate. This can be in PKCS#12 (.p12), JKS
(.jks) or PEM (.crt, .key, .pem) format.
CA Path: /etc/elasticsearch/elastic-stack-ca.p12
Reading a PKCS12 keystore requires a password.
It is possible for the keystore's password to be blank,
in which case you can simply press <ENTER> at the prompt
Password for elastic-stack-ca.p12:

## How long should your certificates be valid?

Every certificate has an expiry date. When the expiry date is reached clients
will stop trusting your certificate and TLS connections will fail.

Best practice suggests that you should either:
(a) set this to a short duration (90 - 120 days) and have automatic processes
to generate a new certificate before the old one expires, or
(b) set it to a longer duration (3 - 5 years) and then perform a manual update
a few months before it expires.

You may enter the validity period in years (e.g. 3Y), months (e.g. 18M), or days (e.g. 90D)

For how long should your certificate be valid? [5y] 

## Do you wish to generate one certificate per node?

If you have multiple nodes in your cluster, then you may choose to generate a
separate certificate for each of these nodes. Each certificate will have its
own private key, and will be issued for a specific hostname or IP address.

Alternatively, you may wish to generate a single certificate that is valid
across all the hostnames or addresses in your cluster.

If all of your nodes will be accessed through a single domain
(e.g. node01.es.example.com, node02.es.example.com, etc) then you may find it
simpler to generate one certificate with a wildcard hostname (*.es.example.com)
and use that across all of your nodes.

However, if you do not have a common domain name, and you expect to add
additional nodes to your cluster in the future, then you should generate a
certificate per node so that you can more easily generate new certificates when
you provision new nodes.

Generate a certificate per node? [y/N]y

## What is the name of node #1?

This name will be used as part of the certificate file name, and as a
descriptive name within the certificate.

You can use any descriptive name that you like, but we recommend using the name
of the Elasticsearch node.

node #1 name: HOSTNAME.example.net

## Which hostnames will be used to connect to HOSTNAME.example.net?

These hostnames will be added as "DNS" names in the "Subject Alternative Name"
(SAN) field in your certificate.

You should list every hostname and variant that people will use to connect to
your cluster over http.
Do not list IP addresses here, you will be asked to enter them later.

If you wish to use a wildcard certificate (for example *.es.example.com) you
can enter that here.

Enter all the hostnames that you need, one per line.
When you are done, press <ENTER> once more to move on to the next step.

HOSTNAME.example.net
localhost

You entered the following hostnames.

 - HOSTNAME.example.net
 - localhost

Is this correct [Y/n]

## Which IP addresses will be used to connect to HOSTNAME.example.net?

If your clients will ever connect to your nodes by numeric IP address, then you
can list these as valid IP "Subject Alternative Name" (SAN) fields in your
certificate.

If you do not have fixed IP addresses, or not wish to support direct IP access
to your cluster then you can just press <ENTER> to skip this step.

Enter all the IP addresses that you need, one per line.
When you are done, press <ENTER> once more to move on to the next step.

192.0.2.10
2001:DB8::10
127.0.0.1
::1

You entered the following IP addresses.

 - 192.0.2.10
 - 2001:DB8::10
 - 127.0.0.1
 - ::1

Is this correct [Y/n]y

## Other certificate options

The generated certificate will have the following additional configuration
values. These values have been selected based on a combination of the
information you have provided above and secure defaults. You should not need to
change these values unless you have specific requirements.

Key Name: HOSTNAME.example.net
Subject DN: CN=HOSTNAME, DC=example, DC=net
Key Size: 2048

Do you wish to change any of these options? [y/N]
Generate additional certificates? [Y/n]n

## What password do you want for your private key(s)?

Your private key(s) will be stored in a PKCS#12 keystore file named "http.p12".
This type of keystore is always password protected, but it is possible to use a
blank password.

If you wish to use a blank password, simply press <enter> at the prompt below.
Provide a password for the "http.p12" file:  [<ENTER> for none]

## Where should we save the generated files?

A number of files will be generated including your private key(s),
public certificate(s), and sample configuration options for Elastic Stack products.

These files will be included in a single zip archive.

What filename should be used for the output zip file? [/usr/share/elasticsearch/elasticsearch-ssl-http.zip] 

Zip file written to /usr/share/elasticsearch/elasticsearch-ssl-http.zip

On récupère ainsi une archive zip contenant tous les fichiers nécessaires :

~/tmp# unzip elasticsearch-ssl-http.zip 
~/tmp# tree .
.
├── elasticsearch
│   ├── http.p12
│   ├── README.txt
│   └── sample-elasticsearch.yml
├── elasticsearch-ssl-http.zip
└── kibana
    ├── elasticsearch-ca.pem
    ├── README.txt
    └── sample-kibana.yml

2 directories, 7 files

Le dossier elasticsearch contient le fichier certificat + clé au format p12 pour Elasticsearch ainsi qu’un extrait de configuration d’exemple pour configurer le HTTPS client. Le dossier kibana contient le certificat de la CA (pour pouvoir valider le certificat donné par le serveur Elasticsearch) ainsi qu’un extrait de configuration d’exemple pour activer le tls client.

Note On pourra ré-utiliser le certificat CA kibana/elasticsearch-ca.pem avec tous les services/logiciels clients du cluster Elasticsearch pour pouvoir vérifier la validité du certificat donné par le serveur.

On déplace les fichiers aux bons endroits ~ ~/tmp# mv elasticsearch/http.p12 /etc/elasticsearch/HOSTNAME.http.p12 ~/tmp# chgrp elasticsearch /etc/elasticsearch/HOSTNAME.http.p12 ~/tmp# chmod g+r /etc/elasticsearch/HOSTNAME.http.p12 ~/tmp#
~/tmp# mv kibana/elasticsearch-ca.pem /etc/kibana/elasticsearch-ca.pem ~/tmp# chgrp kibana /etc/kibana/elasticsearch-ca.pem ~/tmp# chmod g+r /etc/kibana/elasticsearch-ca.pem ~

Puis on ajoute l’extrait de configuration suivant pour elasticsearch :

xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: HOSTNAME.http.p12

Après, redémarrage avec systemctl restart elasticsearch

Ajustement configuration monitoring

On ajoutera l’argument suivant à la commande de check_http pour passer en HTTPS -S

Ajustement configuration Kibana

Dans /etc/kibana/kibana.yml, on doit faire les changelents suivants :

--- a/kibana/kibana.yml
+++ b/kibana/kibana.yml
@@ -31,7 +31,7 @@ server.basePath: "/kibana"
 #server.name: "your-hostname"
 
 # The URLs of the Elasticsearch instances to use for all your queries.
-#elasticsearch.hosts: ["http://localhost:9200"]
+elasticsearch.hosts: ["https://localhost:9200"]
 
 # Kibana uses an index in Elasticsearch to store saved searches, visualizations and
 # dashboards. Kibana creates a new index if the index doesn't already exist.
@@ -65,7 +65,7 @@ server.basePath: "/kibana"
 
 # Optional setting that enables you to specify a path to the PEM file for the certificate
 # authority for your Elasticsearch instance.
-#elasticsearch.ssl.certificateAuthorities: [ "/path/to/your/CA.pem" ]
+elasticsearch.ssl.certificateAuthorities: [ "/etc/kibana/elasticsearch-ca.pem" ]
 
 # To disregard the validity of SSL certificates, change this setting's value to 'none'.
 #elasticsearch.ssl.verificationMode: full

Puis redémarrer Kibana systemctl restart kibana

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 où enregistrer les snapshots :

# install -d -o elasticsearch -g elasticsearch -m 750 /home/backup-elasticsearch

Attention : 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 à snapshots pour l’utilisateur et le groupe elasticsearch, on précise le répertoire dans la fichier /etc/elasticsearch/elasticsearch.yml :

path.repo: /home/backup-elasticsearch

Il faut redémarrer Elasticsearch et créer un dépôt de snapshots :

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

Pour vérifier :

$ curl -XGET 'http://localhost:9200/_snapshot/?pretty'
{
  "snaprepo" : {
    "type" : "fs",
    "settings" : {
      "compress" : "true",
      "location" : "/home/backup-elasticsearch"
    }
  }
}

Si l’on veut supprimer un dépôt de snapshots :

$ curl -s -XDELETE "localhost:9200/_snapshot/snaprepo?pretty"
{"acknowledged":true}

Gestion des snapshots

Pour créer un snapshot dans le dépôt snaprepo :

$ curl -s -XPUT "localhost:9200/_snapshot/snaprepo/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/snaprepo/_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/snaprepo/snapshot_test"

Sauvegarde via snapshots

On suppose qu’on a déjà configuré un dépôt de snapshots.

On peut ainsi 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 Elasticsearch 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

Si on veut tester la présence d’un snapshot du jour avant de le faire (attention, il faut avoir jq installé) :

ES_HOST="http://localhost:9200"
ES_CURL_COMMON_OPTIONS="--insecure -u USER:PASSWORD --silent"
ES_SNAPSHOT_REPO="snaprepo"
ES_SNAPSHOT_NAME="snapshot.daily"
ES_CURL_BASE_URL="${ES_HOST}/_snapshot/${ES_SNAPSHOT_REPO}/${ES_SNAPSHOT_NAME}"

last_daily_raw=$(curl ${ES_CURL_COMMON_OPTIONS} --fail "${ES_CURL_BASE_URL}/" | jq ".snapshots[] | select(.snapshot | contains(\"${ES_SNAPSHOT_NAME}\")) | .end_time" | tr -d '"')
last_daily=$(date --iso-8601=date --date="${last_daily_raw:-"@0"}")
today=$(date --iso-8601=date)

if [ "${last_daily}" != "${today}" ]; then
    # Don't display errors for the delete command
    curl ${ES_CURL_COMMON_OPTIONS} -XDELETE "${ES_CURL_BASE_URL}" > /dev/null
    curl ${ES_CURL_COMMON_OPTIONS} --fail --show-error -XPUT "${ES_CURL_BASE_URL}?wait_for_completion=true" > /dev/null
fi

Restauration d’un snapshot

Le snapshot doit être listé dans le dépôt de snapshot.

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 [snaprepo] because it's open]","status":500} vous pouvez fermer l’index en faisant curl -XPOST "localhost:9200/snaprepo/_close

cluster

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/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 :

#!/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 de premier niveau appelée « cat ». Elle permet d’obtenir des informations sur un cluster avec un formatage texte facile à manipuler en ligne de commande là où les sorties JSON le sont moins.

La documentation amont est disponible.

É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%

L’option v permet d’afficher 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 l’avant-dernière colonne, l’étoile indique le nœud master. Cette information 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.

{
  "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 est activé.

Supprimer proprement des vieux index

Si on utilise Elasticsearch pour des logs, voici une solution pour supprimer d’anciennes données avec Curator, mais lorsque ça n’est pas possible (à cause de compatibilité avec le système) on peut recourir à une approche manuelle:

Voici un exemple pour conserver que les 20 derniers index nommés logstash-*:

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

Pas de réponse au curl ou erreur “Empty reply from server”

Si lorsque on tente un curl pour voir l’état du cluster ES, et qu’on a pas de réponse ou alors si en mode verbose on a l’erreur suivante “Empty reply from server” :

# curl -vs 127.0.0.1:9200/_cat/indices
* Expire in 0 ms for 6 (transfer 0x55efb2682530)
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x55efb2682530)
* Connected to 127.0.0.1 (127.0.0.1) port 9200 (#0)
> GET /_cat/indices HTTP/1.1
> Host: 127.0.0.1:9200
> User-Agent: curl/7.64.0
> Accept: */*
> 
* Empty reply from server
* Connection #0 to host 127.0.0.1 left intact

C’est probablement que les requêtes au serveur ES doivent se faire en https, avec authentification, on peut se connecter comme ceci avec la commande suivante :

curl -s -k https://127.0.0.1:9200/_cat/indices --user user:password

Réinitialiser le mot de passe de elastic (sans accès à un utilisateur superuser)

Pour réinitialiser le mot de passe de elastic (ou de tout autre utilisateur) lorsqu’on n’a pas déjà accès à un utilisateur ayant le rôle superuser, il faut créer un utilisateur ayant le rôle superuser dans les fichiers /etc/elasticsearch/users de chaque node (il doit être créé sur chaque node) :

# sudo -g elasticsearch /usr/share/elasticsearch/bin/elasticsearch-users useradd <admin> -r superuser

Il est ensuite possible d’utiliser cet utilisateur pour réinitialiser le mot de passe de n’importe quel utilisateur (par le biais de kibana ou de l’API). Dans le cas de kibana, la gestion des utilisateur est dans “[Hamburger Menu]”>Management>“Stack Management”>Security>Users. Pour l’API il s’agit d’une requête POST vers /_security/user/<username>/_password avec un json contenant soit une entrée nommée “password” (mot de passe en clair) soit une entrée nommée password_hash (le hash du mot de passe).

Il est fortement conseillé de supprimer l’utilisateur créé après cette action :

# sudo -g elasticsearch /usr/share/elasticsearch/bin/elasticsearch-users userdel <admin>

OutOfMemoryError: Java heap space

Solution : augmenter la mémoire allouée à la pile d’appel dans les arguments Xms et Xmx de la JVM dans /etc/elasticsearch/jvm.options :

# Xms represents the initial size of total heap space
-Xms3g
# Xmx represents the maximum size of total heap space
-Xmx3g

Les deux valeurs doivent être égales.

Erreur no_valid_shard_copy

On peut rencontrer ce problème sur un cluster.

L’explication donnée par Elastic est : « cannot allocate because a previous copy of the primary shard existed but can no longer be found on the nodes in the cluster » (sortie de curl '127.0.0.1:9200/_cluster/allocation/explain?pretty').

Il est possible que le shard se trouvait sur un noeud du cluster inaccessible ou mal enlevé du cluster, ou qu’il y ait eu un problème matériel là ou se trouvait le shard.

Erreur Failed to authenticate user 'elastic' avec elasticsearch-setup-passwords

Si en exécutant elasticsearch-setup-passwords vous rencontrez cette erreur :

# /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto 

Failed to authenticate user 'elastic' against https://172.20.3.233:9200/_security/_authenticate?pretty
Possible causes include:
 * The password for the 'elastic' user has already been changed on this cluster
 * Your elasticsearch node is running against a different keystore
   This tool used the keystore at /etc/elasticsearch/elasticsearch.keystore

You can use the `elasticsearch-reset-password` CLI tool to reset the password of the 'elastic' user

ERROR: Failed to verify bootstrap password, with exit code 78

D’abord, if faut savoir que l’utilisation de elasticsearch-setup-passwords est dépréciée à partir d’Elasticsearch 8 en faveur de elasticsearch-reset-passwords.

Cet utilitaire utilise le mot de passe « boostrap » créé à l’installation d’Elasticseach. S’il a été changé ou que vous ne l’avez pas (installation via Ansible par exemple), alors vous ne pouvez pas utiliser cet utilitaire.

Alternativement, vous pouvez changer les mots de passe via via elasticsearch-setup-passwords, Kibana ou l’API de changement des mots de passe.

Par exemple, avec elasticsearch-setup-passwords :

# /usr/share/elasticsearch/bin/elasticsearch-reset-password --auto --user <USER>
This tool will reset the password of the [<USER>] user to an autogenerated value.
The password will be printed in the console.
Please confirm that you would like to continue [y/N] y
Password for the [<USER>] user successfully reset.
New value: <PASSWORD>