Mettre automatiquement à jour Elastic Stack avec Ansible Playbooks

Publié: 2022-03-11

L'analyse des journaux pour un réseau composé de milliers d'appareils était une tâche complexe, chronophage et ennuyeuse avant que je décide d'utiliser la Suite Elastic comme solution de journalisation centralisée. Cela s'est avéré être une très sage décision. Non seulement j'ai un seul endroit pour rechercher tous mes journaux, mais j'obtiens des résultats presque instantanés sur mes recherches, des visualisations puissantes qui sont incroyablement utiles pour l'analyse et le dépannage, et de beaux tableaux de bord qui me donnent un aperçu utile du réseau.

La Suite Elastic publie constamment de nouvelles fonctionnalités étonnantes, en gardant un rythme de développement très actif, elle propose souvent deux nouvelles versions par mois. J'aime toujours garder mon environnement à jour pour m'assurer de pouvoir profiter des nouvelles fonctionnalités et améliorations. Aussi pour le garder exempt de bogues et de problèmes de sécurité. Mais cela m'oblige à constamment mettre à jour l'environnement.

Même si le site Web Elastic conserve une documentation claire et détaillée, y compris sur le processus de mise à niveau de leurs produits, la mise à niveau manuelle est une tâche complexe, en particulier le cluster Elasticsearch. Il y a beaucoup d'étapes impliquées, et un ordre très précis doit être suivi. C'est pourquoi j'ai décidé d'automatiser l'ensemble du processus, il y a longtemps, en utilisant Ansible Playbooks.

Dans ce didacticiel Ansible, je vais vous présenter une série de Playbooks Ansible qui ont été développés pour mettre à niveau automatiquement mon installation Elastic Stack.

Qu'est-ce que la pile élastique

La pile Elastic, anciennement connue sous le nom de pile ELK, est composée d'Elasticsearch, Logstash et Kibana, de la société open source Elastic, qui fournissent ensemble une plate-forme puissante pour l'indexation, la recherche et l'analyse de vos données. Il peut être utilisé pour une large gamme d'applications. De la journalisation et de l'analyse de la sécurité à la gestion des performances des applications et à la recherche de sites.

  • Elasticsearch est le cœur de la pile. Il s'agit d'un moteur de recherche et d'analyse distribué capable de fournir des résultats de recherche en temps quasi réel, même sur un énorme volume de données stockées.

  • Logstash est un pipeline de traitement qui obtient ou reçoit des données de nombreuses sources différentes (50 plugins d'entrée officiels au moment où j'écris), les analyse, les filtre et les transforme et les envoie à une ou plusieurs des sorties possibles. Dans notre cas, nous nous intéressons au plugin de sortie Elasticsearch.

  • Kibana est votre interface utilisateur et opérationnelle. Il vous permet de visualiser, de rechercher, de naviguer dans vos données et de créer des tableaux de bord qui vous donnent des informations incroyables à ce sujet.

Qu'est-ce qu'Ansible

Ansible est une plate-forme d'automatisation informatique qui peut être utilisée pour configurer des systèmes, déployer ou mettre à niveau des logiciels et orchestrer des tâches informatiques complexes. Ses principaux objectifs sont la simplicité et la facilité d'utilisation. Ma fonctionnalité préférée d'Ansible est qu'il est sans agent, ce qui signifie que je n'ai pas besoin d'installer et de gérer de logiciel supplémentaire sur les hôtes et les appareils que je souhaite gérer. Nous utiliserons la puissance de l'automatisation Ansible pour mettre à jour automatiquement notre Elastic Stack.

Clause de non-responsabilité et mise en garde

Les playbooks que je vais partager ici sont basés sur les étapes décrites dans la documentation officielle du produit. Il est destiné à être utilisé uniquement pour les mises à niveau de la même version majeure. Par exemple : 5.x5.y ou 6.x6.yx > y . Les mises à niveau entre les versions majeures nécessitent souvent des étapes supplémentaires et ces playbooks ne fonctionneront pas dans ces cas.

Quoi qu'il en soit, lisez toujours les notes de version, en particulier la section des changements de rupture avant d'utiliser les playbooks pour la mise à niveau. Assurez-vous que vous comprenez les tâches exécutées dans les playbooks et vérifiez toujours les instructions de mise à niveau pour vous assurer que rien d'important ne change.

Cela dit, j'utilise ces playbooks (ou des versions antérieures) depuis la version 2.2 d'Elasticsearch sans aucun problème. À l'époque, j'avais des playbooks complètement séparés pour chaque produit, car ils ne partageaient pas le même numéro de version qu'ils ne le savent.

Cela dit, je ne suis en aucun cas responsable de votre utilisation des informations contenues dans cet article.

Notre environnement fictif

L'environnement dans lequel nos playbooks fonctionneront sera composé de 6 serveurs CentOS 7 :

  • 1 serveur Logstash
  • 1 serveur Kibana
  • 4 nœuds Elasticsearch

Peu importe si votre environnement a un nombre différent de serveurs. Vous pouvez simplement le refléter en conséquence dans le fichier d'inventaire et les playbooks devraient fonctionner sans problème. Si, toutefois, vous n'utilisez pas une distribution basée sur RHEL, je vous laisserai un exercice pour modifier les quelques tâches spécifiques à la distribution (principalement les éléments du gestionnaire de packages)

L'exemple Elastic Stack que nos Playbooks Ansible déploieront

L'inventaire

Ansible a besoin d'un inventaire pour savoir sur quels hôtes il doit exécuter les playbooks. Pour notre scénario imaginaire, nous allons utiliser le fichier d'inventaire suivant :

 [logstash] server01 ansible_host=10.0.0.1 [kibana] server02 ansible_host=10.0.0.2 [elasticsearch] server03 ansible_host=10.0.0.3 server04 ansible_host=10.0.0.4 server05 ansible_host=10.0.0.5 server06 ansible_host=10.0.0.6

Dans un fichier d'inventaire Ansible, toute [section] représente un groupe d'hôtes. Notre inventaire comporte 3 groupes d'hôtes : logstash , kibana et elasticsearch . Vous remarquerez que je n'utilise que les noms de groupe dans les playbooks. Cela signifie que peu importe le nombre d'hôtes dans l'inventaire, tant que les groupes sont corrects, le playbook fonctionnera.

Le processus de mise à niveau

Le processus de mise à niveau comprendra les étapes suivantes :

1) Pré-téléchargez les packages

2) Mise à jour Logstash

3) Rolling Upgrade du cluster Elasticsearch

4) Mise à niveau Kibana

L'objectif principal est de minimiser les temps d'arrêt. La plupart du temps, l'utilisateur ne s'en apercevra même pas. Parfois, Kibana peut être indisponible pendant quelques secondes. C'est acceptable pour moi.

Playbook Ansible principal

Le processus de mise à niveau consiste en un ensemble de différents playbooks. J'utiliserai la fonctionnalité import_playbook d'Ansible pour organiser tous les playbooks dans un fichier playbook principal qui peut être appelé pour prendre en charge l'ensemble du processus.

 - name: pre-download import_playbook: pre-download.yml - name: logstash-upgrade import_playbook: logstash-upgrade.yml - name: elasticsearch-rolling-upgrade import_playbook: elasticsearch-rolling-upgrade.yml - name: kibana-upgrade import_playbook: kibana-upgrade.yml

Assez simple. C'est juste un moyen d'organiser l'exécution des playbooks dans un ordre spécifique.

Voyons maintenant comment nous utiliserions l'exemple de playbook Ansible ci-dessus. J'expliquerai comment nous l'implémenterons plus tard, mais voici la commande que j'exécuterais pour passer à la version 6.5.4 :

 $ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml

Pré-téléchargez les packages

Cette première étape est en fait facultative. La raison pour laquelle j'utilise ceci est que je considère généralement comme une bonne pratique d'arrêter un service en cours d'exécution avant de le mettre à niveau. Désormais, si vous disposez d'une connexion Internet rapide, le temps nécessaire à votre gestionnaire de packages pour télécharger le package peut être négligeable. Mais ce n'est pas toujours le cas et je souhaite minimiser la durée d'indisponibilité d'un service. C'est ainsi que mon premier playbook utilisera yum pour pré-télécharger tous les packages. De cette façon, lorsque les temps de mise à niveau arrivent, l'étape de téléchargement a déjà été prise en charge.

 - hosts: logstash gather_facts: no tasks: - name: Validate logstash Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get logstash current version command: rpm -q logstash --qf %{VERSION} args: warn: no changed_when: False register: version_found - name: Pre-download logstash install package yum: name: logstash-{{ elk_version }} download_only: yes when: version_found.stdout is version_compare(elk_version, '<')

La première ligne indique que ce jeu ne s'appliquera qu'au groupe logstash . La deuxième ligne indique à Ansible de ne pas prendre la peine de collecter des informations sur les hôtes. Cela accélérera le jeu, mais assurez-vous qu'aucune des tâches du jeu n'aura besoin de faits sur l'hôte.

La première tâche du jeu validera la variable elk_version . Cette variable représente la version de la Suite Elastic vers laquelle nous mettons à niveau. Cela est passé lorsque vous appelez la commande ansible-playbook. Si la variable n'est pas transmise ou n'est pas un format valide, le jeu s'arrêtera immédiatement. Cette tâche sera en fait la première tâche de toutes les pièces. La raison en est de permettre aux jeux d'être exécutés isolément si désiré ou nécessaire.

La deuxième tâche utilisera la commande rpm pour obtenir la version actuelle de Logstash et s'enregistrer dans la variable version_found . Ces informations seront utilisées dans la tâche suivante. Les lignes args: , warn: no et changed_when: False sont là pour rendre ansible-lint heureux, mais ne sont pas strictement nécessaires.

La tâche finale exécutera la commande qui pré-télécharge réellement le package. Mais uniquement si la version installée de Logstash est antérieure à la version cible. Pas de point de téléchargement et une version plus ancienne ou identique si elle ne sera pas utilisée.

Les deux autres jeux sont essentiellement les mêmes, sauf qu'au lieu de Logstash, ils pré-téléchargeront Elasticsearch et Kibana :

 - hosts: elasticsearch gather_facts: no tasks: - name: Validate elasticsearch Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get elasticsearch current version command: rpm -q elasticsearch --qf %{VERSION} args: warn: no changed_when: False register: version_found - name: Pre-download elasticsearch install package yum: name: elasticsearch-{{ elk_version }} download_only: yes when: version_found.stdout is version_compare(elk_version, '<') - hosts: kibana gather_facts: no tasks: - name: Validate kibana Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get kibana current version command: rpm -q kibana --qf %{VERSION} args: warn: no changed_when: False register: version_found - name: Pre-download kibana install package yum: name: kibana-{{ elk_version }} download_only: yes when: version_found.stdout is version_compare(elk_version, '<')

Mise à jour Logstash

Logstash devrait être le premier composant à être mis à niveau. En effet, Logstash est garanti de fonctionner avec une ancienne version d'Elasticsearch.

Les premières tâches du jeu sont identiques à la contrepartie pré-téléchargement :

 - name: Upgrade logstash hosts: logstash gather_facts: no tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get logstash current version command: rpm -q logstash --qf %{VERSION} changed_when: False register: version_found

Les deux tâches finales sont contenues dans un bloc :

 - block: - name: Update logstash yum: name: logstash-{{ elk_version }} state: present - name: Restart logstash systemd: name: logstash state: restarted enabled: yes daemon_reload: yes when: version_found.stdout is version_compare(elk_version, '<')

Le conditionnel when garantit que les tâches du bloc ne seront exécutées que si la version cible est plus récente que la version actuelle. La première tâche à l'intérieur du bloc effectue la mise à niveau de Logstash et la deuxième tâche redémarre le service.

Mise à niveau progressive du cluster Elasticsearch

Pour nous assurer qu'il n'y aura pas de temps d'arrêt du cluster Elasticsearch, nous devons effectuer une mise à niveau progressive. Cela signifie que nous mettrons à niveau un nœud à la fois, en ne démarrant la mise à niveau de n'importe quel nœud qu'après nous être assurés que le cluster est dans un état vert (entièrement sain).

Dès le début de la pièce, vous remarquerez quelque chose de différent :

 - name: Elasticsearch rolling upgrade hosts: elasticsearch gather_facts: no serial: 1

Ici nous avons la ligne serial: 1 . Le comportement par défaut d'Ansible est d'exécuter le jeu contre plusieurs hôtes en parallèle, le nombre d'hôtes simultanés étant défini dans la configuration. Cette ligne garantit que le jeu sera exécuté contre un seul hôte à la fois.

Ensuite, nous définissons quelques variables à utiliser tout au long du jeu :

 vars: es_disable_allocation: '{"transient":{"cluster.routing.allocation.enable":"none"}}' es_enable_allocation: '{"transient":{"cluster.routing.allocation.enable": "all","cluster.routing.allocation.node_concurrent_recoveries": 5,"indices.recovery.max_bytes_per_sec": "500mb"}}' es_http_port: 9200 es_transport_port: 9300

La signification de chaque variable sera claire au fur et à mesure qu'elles apparaîtront dans la pièce.

Comme toujours, la première tâche consiste à valider la version cible :

 tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")

La plupart des tâches suivantes consisteront à exécuter des appels REST sur le cluster Elasticsearch. L'appel peut être exécuté sur n'importe quel nœud. Vous pouvez simplement l'exécuter sur l'hôte actuel dans le jeu, mais certaines des commandes seront exécutées pendant que le service Elasticsearch est en panne pour l'hôte actuel. Ainsi, dans les tâches suivantes, nous nous assurons de sélectionner un hôte différent pour exécuter les appels REST. Pour cela, nous utiliserons le module set_fact et la variable groups de l'inventaire Ansible.

 - name: Set the es_host for the first host set_fact: es_host: "{{ groups.elasticsearch[1] }}" when: "inventory_hostname == groups.elasticsearch[0]" - name: Set the es_host for the remaining hosts set_fact: es_host: "{{ groups.elasticsearch[0] }}" when: "inventory_hostname != groups.elasticsearch[0]"

Ensuite, nous nous assurons que le service est actif dans le nœud actuel avant de continuer :

 - name: Ensure elasticsearch service is running systemd: name: elasticsearch enabled: yes state: started register: response - name: Wait for elasticsearch node to come back up if it was stopped wait_for: port: "{{ es_transport_port }}" delay: 45 when: response.changed == true

Comme dans les jeux précédents, nous vérifierons la version actuelle. Sauf pour cette fois, nous utiliserons l'API REST Elasticsearch au lieu d'exécuter le rpm. Nous aurions aussi pu utiliser la commande rpm, mais je veux montrer cette alternative.

 - name: Check current version uri: url: http://localhost:{{ es_http_port }} method: GET register: version_found retries: 10 delay: 10

Les tâches restantes se trouvent dans un bloc qui ne sera exécuté que si la version actuelle est antérieure à la version cible :

 - block: - name: Enable shard allocation for the cluster uri: url: http://localhost:{{ es_http_port }}/_cluster/settings method: PUT body_format: json body: "{{ es_enable_allocation }}"

Maintenant, si vous avez suivi mes conseils et lu la documentation, vous aurez remarqué que cette étape devrait être à l'opposé : désactiver l'allocation de fragments. J'aime placer cette tâche ici en premier au cas où les fragments auraient été désactivés auparavant pour une raison quelconque. Ceci est important car la prochaine tâche attendra que le cluster devienne vert. Si l'allocation de partition est désactivée, le cluster restera jaune et les tâches se bloqueront jusqu'à ce qu'il expire.

Ainsi, après nous être assurés que l'allocation de partition est activée, nous nous assurons que le cluster est dans un état vert :

 - name: Wait for cluster health to return to green uri: url: http://localhost:{{ es_http_port }}/_cluster/health method: GET register: response until: "response.json.status == 'green'" retries: 500 delay: 15

Après un redémarrage du service de nœud, le cluster peut mettre longtemps à revenir au vert. C'est la raison des retries: 500 et du delay: 15 . Cela signifie que nous attendrons 125 minutes (500 x 15 secondes) pour que le cluster revienne au vert. Vous devrez peut-être ajuster cela si vos nœuds contiennent une très grande quantité de données. Dans la majorité des cas, c'est largement suffisant.

Nous pouvons maintenant désactiver l'allocation de partition :

 - name: Disable shard allocation for the cluster uri: url: http://localhost:{{ es_http_port }}/_cluster/settings method: PUT body_format: json body: {{ es_disable_allocation }}

Et avant d'arrêter le service, nous exécutons le rinçage de synchronisation facultatif, mais recommandé. Il n'est pas rare d'obtenir une erreur 409 pour certains des indices lorsque nous effectuons un sync flush. Comme cela peut être ignoré en toute sécurité, j'ai ajouté 409 à la liste des codes d'état de réussite.

 - name: Perform a synced flush uri: url: http://localhost:{{ es_http_port }}/_flush/synced method: POST status_code: "200, 409"

Maintenant, ce nœud est prêt à être mis à jour :

 - name: Shutdown elasticsearch node systemd: name: elasticsearch state: stopped - name: Update elasticsearch yum: name: elasticsearch-{{ elk_version }} state: present

Le service étant arrêté, nous attendons que tous les fragments soient alloués avant de redémarrer le nœud :

 - name: Wait for all shards to be reallocated uri: url=http://{{ es_host }}:{{ es_http_port }}/_cluster/health method=GET register: response until: "response.json.relocating_shards == 0" retries: 20 delay: 15

Une fois les fragments réaffectés, nous redémarrons le service Elasticsearch et attendons qu'il soit complètement prêt :

 - name: Start elasticsearch systemd: name: elasticsearch state: restarted enabled: yes daemon_reload: yes - name: Wait for elasticsearch node to come back up wait_for: port: "{{ es_transport_port }}" delay: 35 - name: Wait for elasticsearch http to come back up wait_for: port: "{{ es_http_port }}" delay: 5

Nous nous assurons maintenant que le cluster est jaune ou vert avant de réactiver l'allocation de partition :

 - name: Wait for cluster health to return to yellow or green uri: url: http://localhost:{{ es_http_port }}/_cluster/health method: GET register: response until: "response.json.status == 'yellow' or response.json.status == 'green'" retries: 500 delay: 15 - name: Enable shard allocation for the cluster uri: url: http://localhost:{{ es_http_port }}/_cluster/settings method: PUT body_format: json body: "{{ es_enable_allocation }}" register: response until: "response.json.acknowledged == true" retries: 10 delay: 15

Et nous attendons que le nœud se rétablisse complètement avant de traiter le suivant :

 - name: Wait for the node to recover uri: url: http://localhost:{{ es_http_port }}/_cat/health method: GET return_content: yes register: response until: "'green' in response.content" retries: 500 delay: 15

Bien sûr, comme je l'ai déjà dit, ce bloc ne doit être exécuté que si nous mettons vraiment à jour la version :

 when: version_found.json.version.number is version_compare(elk_version, '<')

Mise à niveau Kibana

Le dernier composant à être mis à jour est Kibana.

Comme vous vous en doutez, les premières tâches ne sont pas différentes de la mise à niveau de Logstash ou des lectures de pré-téléchargement. Sauf pour la définition d'une variable :

 - name: Upgrade kibana hosts: kibana gather_facts: no vars: set_default_index: '{"changes":{"defaultIndex":"syslog"}}' tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get kibana current version command: rpm -q kibana --qf %{VERSION} args: warn: no changed_when: False register: version_found

J'expliquerai la variable set_default_index lorsque nous arriverons à la tâche qui l'utilise.

Le reste des tâches se trouvera dans un bloc qui ne s'exécutera que si la version installée de Kibana est plus ancienne que la version cible. Les deux premières tâches mettront à jour et redémarreront Kibana :

 - name: Update kibana yum: name: kibana-{{ elk_version }} state: present - name: Restart kibana systemd: name: kibana state: restarted enabled: yes daemon_reload: yes

Et pour Kibana, cela aurait dû suffire. Malheureusement, pour une raison quelconque, après la mise à niveau, Kibana perd sa référence à son modèle d'index par défaut. Cela l'amène à demander au premier utilisateur qui accède après la mise à niveau de définir le modèle d'index par défaut, ce qui peut prêter à confusion. Pour l'éviter, assurez-vous d'inclure une tâche pour réinitialiser le modèle d'index par défaut. Dans l'exemple ci-dessous, il s'agit de syslog , mais vous devez le remplacer par ce que vous utilisez. Avant de définir l'index, cependant, nous devons nous assurer que Kibana est opérationnel et prêt à répondre aux requêtes :

 - name: Wait for kibana to start listening wait_for: port: 5601 delay: 5 - name: Wait for kibana to be ready uri: url: http://localhost:5601/api/kibana/settings method: GET register: response until: "'kbn_name' in response and response.status == 200" retries: 30 delay: 5 - name: Set Default Index uri: url: http://localhost:5601/api/kibana/settings method: POST body_format: json body: "{{ set_default_index }}" headers: "kbn-version": "{{ elk_version }}"

Conclusion

L'Elastic Stack est un outil précieux et je vous recommande vivement d'y jeter un œil si vous ne l'utilisez pas encore. Il est génial tel quel et s'améliore constamment, à tel point qu'il peut être difficile de suivre la mise à niveau constante. J'espère que ces Ansible Playbooks pourront vous être aussi utiles qu'ils le sont pour moi.

Je les ai rendus disponibles sur GitHub à https://github.com/orgito/elk-upgrade. Je vous recommande de le tester dans un environnement hors production.

Si vous êtes un développeur Ruby on Rails et que vous cherchez à intégrer Elasticsearch dans votre application, consultez Elasticsearch pour Ruby on Rails : A Tutorial to the Chewy Gem par l'ingénieur logiciel Core Toptal Arkadiy Zabazhanov.