menu icon

Java pour Elasticsearch, épisode 1. Requêter le cluster

Découvrir comment intégrer Elasticsearch dans votre code est une aventure passionnante et, en réalité, plus simple qu'il n'y paraît. Dans ce premier article de notre série, nous allons explorer ensemble la mise en place d'un cluster de trois nœuds et la manière de s'y connecter en utilisant des certificats auto-générés, démontrant ainsi l'accessibilité et l'efficacité de ce processus.

Java pour Elasticsearch, épisode 1.  Requêter le cluster

Elasticsearch est un moteur de recherche très puissant et la mise en place d’une stack de développement n’est pas toujours simple.

Dans cette série d’articles, nous allons progresser pas à pas pour en percevoir un large éventail de possibilités.

Nous aborderons des sujets comme le requêtage, l’administration d’un cluster ou la recherche vectorielle.

Le code présenté est disponible sur Gitlab. La branche correspondante à cet article est la branche 01-dev-elastic.

Etape 1 : Déployer un cluster Elasticsearch local

Une des principales caractéristiques d’Elasticsearch est de pouvoir être déployé dans un cluster composé de plusieurs noeuds (nodes) ce qui permet plusieurs choses :

  • Assurer une scalabilité horizontale

  • Spécialiser certains nodes en fonction des besoins

  • Assurer une continuité de services

  • etc.

La communication avec un cluster se fait en http sur le port 9200.

Afin de nous approcher des conditions de prod, nous allons donc commencer par mettre en place un cluster comportant 3 nodes, non spécialisés.

Nous mettrons également en place un Kibana qui permet, entre autres choses, d’administrer le cluster, de le requêter ou de visualiser les données.

Afin de sécuriser les échanges avec le cluster, nous utiliserons TLS et des certificats auto-générés.

En effet, ne pas sécuriser les échanges serait inconcevable dans une environnement de production.

Afin de simplifier ce déploiement, nous utiliserons Docker.

Elastic fourni un fichier docker-compose sur son site.

Celui qui est proposé ici en est très fortement inspiré, seules quelques légères modifications ont été apportées.

Il faut noter que le dossier référencé dans le fichier .env dans la variable DATA_PATH doit être accessible à l’utilisateur qui lance Docker sur votre système, surtout si ce système est basé sur Linux.

Une fois le fichier .env modifié pour répondre à vos besoins, il est possible de lancer un

docker compose up -d

Une fois lancé, deux vérifications sont possibles.

D’abord dans un terminal, avec une commande curl

curl -k -X GET "https://localhost:9200/" --user "elastic:elasticpwd"

Ce qui doit renvoyer la réponse suivante :

{
"name" : "es01",  
  "cluster_name" : "adlean-cluster",
  "cluster_uuid" : "0ku-2DJrRKObuCUMT4nTfA",
  "version" : {
    "number" : "8.11.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "64cf052f3b56b1fd4449f5454cb88aca7e739d9a",
    "build_date" : "2023-12-08T11:33:53.634979452Z",
    "build_snapshot" : false,
    "lucene_version" : "9.8.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

Ensuite, se connecter à Kibana à l’adresse http://localhost:5601 qui va alors afficher la page de connexion.

Une fois connecté avec l’utilisateur “elastic”, la page d’accueil propose d’essayer des données d’exemple (Try sample data)

alt text
Page d'accueil de Kibana avec le choix 'Try sample data'

Sur la page de choix des données, il y a un menu “Other sample data sets” dans lequel nous allons choisir d’importer “Sample eCommerce orders” en cliquant sur le bouton “Add data”.

alt text
Choix des données d'exemple à importer

Etape 2 : Créer le projet java

Nous allons partir sur un projet Maven en Java 21, avec Spring Boot 3.2.

Pour ne pas se déconcentrer sur la création d’un interface utilisateur, nous utiliserons les tests unitaires afin de lancer nos différentes requêtes.

Le POM va donc ressembler à ça

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.adelean</groupId>
    <artifactId>dev-elastic-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>01-dev-elastic</name>
    <description>01-dev-elastic</description>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>8.11.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.7.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Etape 3 : Créer la connexion à Elasticsearch

Pour créer la connexion, il faut utiliser le client java développé par Elastic.

Cependant, comme nous sommes sur un cluster utilisant SSL, il va falloir travailler avec les certificats.

Dans le répertoire défini dans la variable PATH_DATA du fichier .env, de nouveaux répertoires ont été créés.

Le certificat qui nous intéresse se trouve dans $PATH_DATA/certs/ca/ca.crt

Nous avons besoin de son empreinte.

Elle peut être obtenue de deux manières.

Soit en regardant dans les logs d’un node lors de son premier démarrage.

Soit en passant la commande suivante :

openssl s_client -connect localhost:9200 -servername localhost -showcerts </dev/null 2>/dev/null \
  | openssl x509 -fingerprint -sha256 -noout -in /dev/stdin

Dans src/main/resources, nous allons alimenter le fichier application.properties avec les éléments suivants :

elastic.host=localhost  
elastic.port=9200  
elastic.ca.fingerprint=<empreinte obtenue avec la commande précédente>

Une fois fait, on peut créer une classe de service qui va pouvoir instancier le client et l’utiliser.

package com.adelean.develastic01.services.supervision;

/* imports*/

@Service("IndicesService")
public class Indices {

    @Value("${elastic.host}")
    private String elasticHost;

    @Value("${elastic.port}")
    private int elasticPort;

    @Value("${elastic.ca.fingerprint}")
    private String fingerPrint;

    @Value("${elastic.user}")
    private String elasticUser;

    @Value("${elastic.password}")
    private String elasticPassword;

    private ElasticsearchClient getClient() {

        SSLContext sslContext = TransportUtils.sslContextFromCaFingerprint(fingerPrint);
        BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
        credsProv.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(elasticUser, elasticPassword));

        RestClient restClient = RestClient
                .builder(new HttpHost(elasticHost, elasticPort, "https"))
                .setHttpClientConfigCallback( ccb -> ccb.setSSLContext(sslContext).setDefaultCredentialsProvider(credsProv))
                .build();

        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

        return new ElasticsearchClient(transport);

    }

}

La classe TransportUtils est un utilitaire qui permet d’obtenir simplement un contexte SSL depuis l’empreinte d’un certificat ou depuis le fichier du certificat lui-même.

La première requête que nous allons coder va permettre de récupérer la liste des indexes présent sur le cluster.

public Set<String> listIndices() {

    GetIndexRequest request = new GetIndexRequest.Builder().index("*").build();

    try {
        GetIndexResponse response = getClient().indices().get(request);
        Map<String, IndexState> indices = response.result();

        return indices.keySet();

    } catch (IOException e) {
        throw new RuntimeException(e);
    }

}

Une fois fait, il ne reste plus qu'à créer la classe de test pour vérifier ce que nous renvoie cette première requête.

package com.adelean.develastic01.services.supervision;

/* imports */

@SpringBootTest
class IndicesTest {

    @Autowired
    private Indices indicesService;

    @Test
    void listAllIndices() {

        var result = indicesService.listIndices();

        assertTrue(result.size() > 1);

    }

}

La liste des index va ressembler à ça :

  • .internal.alerts-ml.anomaly-detection.alerts-default-000001
  • .internal.alerts-observability.slo.alerts-default-000001
  • kibana_sample_data_ecommerce
  • .internal.alerts-observability.metrics.alerts-default-000001
  • .kibana-observability-ai-assistant-kb-000001
  • .internal.alerts-observability.logs.alerts-default-000001
  • .internal.alerts-observability.uptime.alerts-default-000001
  • .internal.alerts-observability.apm.alerts-default-000001
  • .internal.alerts-stack.alerts-default-000001
  • .internal.alerts-security.alerts-default-000001
  • .internal.alerts-observability.threshold.alerts-default-000001
  • .kibana-observability-ai-assistant-conversations-000001

Au milieu de cette liste, on retrouve le kibana_sample_data_ecommerce.

Dans la console Kibana, accessible via le menu “Management” -> “Dev Tools”, la requête équivalente est

GET _cat/indices

Dans le prochain article, nous verrons comment améliorer ce code et faire d’autres requêtes.