Aggiorna automaticamente lo stack elastico con Ansible Playbooks
Pubblicato: 2022-03-11L'analisi dei log per una rete composta da migliaia di dispositivi era un'attività complessa, dispendiosa in termini di tempo e noiosa prima che decidessi di utilizzare Elastic Stack come soluzione di registrazione centralizzata. Si è rivelata una decisione molto saggia. Non solo ho un unico posto dove cercare tutti i miei log, ma ottengo risultati quasi istantanei sulle mie ricerche, visualizzazioni potenti che sono incredibilmente utili per l'analisi e la risoluzione dei problemi e bellissime dashboard che mi danno un'utile panoramica della rete.
L'Elastic Stack rilascia costantemente nuove e sorprendenti funzionalità, mantenendo un ritmo di sviluppo molto attivo, spesso fornisce due nuove versioni ogni mese. Mi piace mantenere sempre aggiornato il mio ambiente per assicurarmi di poter sfruttare le nuove funzionalità e i miglioramenti. Anche per mantenerlo libero da bug e problemi di sicurezza. Ma questo mi richiede di aggiornare costantemente l'ambiente.
Anche se il sito Web Elastic mantiene una documentazione chiara e dettagliata, incluso il processo di aggiornamento dei propri prodotti, l'aggiornamento manuale è un'attività complessa, in particolare il cluster Elasticsearch. Ci sono molti passaggi coinvolti e un ordine molto specifico deve essere seguito. Ecco perché ho deciso di automatizzare l'intero processo, molto tempo fa, utilizzando Ansible Playbooks.
In questo tutorial di Ansible, illustrerò una serie di Ansible Playbook sviluppati per aggiornare automaticamente la mia installazione di Elastic Stack.
Cos'è lo stack elastico
Lo stack elastico, precedentemente noto come stack ELK, è composto da Elasticsearch, Logstash e Kibana, della società open source Elastic, che insieme forniscono una potente piattaforma per l'indicizzazione, la ricerca e l'analisi dei dati. Può essere utilizzato per un'ampia gamma di applicazioni. Dalla registrazione e dall'analisi della sicurezza alla gestione delle prestazioni delle applicazioni e alla ricerca nel sito.
Elasticsearch è il cuore dello stack. È un motore di ricerca e analisi distribuito in grado di fornire risultati di ricerca quasi in tempo reale anche rispetto a un enorme volume di dati archiviati.
Logstash è una pipeline di elaborazione che ottiene o riceve dati da molte fonti diverse (50 plug-in di input ufficiali mentre scrivo), li analizza, filtra e trasforma e li invia a uno o più possibili output. Nel nostro caso, siamo interessati al plugin di output Elasticsearch.
Kibana è la tua interfaccia utente e operativa. Ti consente di visualizzare, cercare, navigare tra i tuoi dati e creare dashboard che ti offrono informazioni straordinarie al riguardo.
Cos'è Ansible
Ansible è una piattaforma di automazione IT che può essere utilizzata per configurare sistemi, distribuire o aggiornare software e orchestrare attività IT complesse. I suoi obiettivi principali sono la semplicità e la facilità d'uso. La mia caratteristica preferita di Ansible è che è agentless, il che significa che non ho bisogno di installare e gestire alcun software aggiuntivo sugli host e sui dispositivi che voglio gestire. Utilizzeremo la potenza dell'automazione Ansible per aggiornare automaticamente il nostro stack elastico.
Disclaimer e una parola di cautela
I playbook che condividerò qui si basano sui passaggi descritti nella documentazione ufficiale del prodotto. È pensato per essere utilizzato solo per gli aggiornamenti della stessa versione principale. Ad esempio: 5.x
→ 5.y
o 6.x
→ 6.y
dove x
> y
. Gli aggiornamenti tra le versioni principali spesso richiedono passaggi aggiuntivi e quei playbook non funzioneranno per quei casi.
Indipendentemente da ciò, leggi sempre le note di rilascio, in particolare la sezione delle modifiche sostanziali prima di utilizzare i playbook per l'aggiornamento. Assicurati di aver compreso le attività eseguite nei playbook e controlla sempre le istruzioni di aggiornamento per assicurarti che non cambi nulla di importante.
Detto questo, utilizzo quei playbook (o versioni precedenti) dalla versione 2.2 di Elasticsearch senza problemi. All'epoca avevo playbook completamente separati per ogni prodotto, poiché non condividevano lo stesso numero di versione come sanno.
Detto questo, non sono in alcun modo responsabile del tuo utilizzo delle informazioni contenute in questo articolo.
Il nostro ambiente fittizio
L'ambiente in cui verranno eseguiti i nostri playbook sarà composto da 6 server CentOS 7:
- 1 server Logstash
- 1 server Kibana
- 4 nodi Elasticsearch
Non importa se il tuo ambiente ha un numero diverso di server. Puoi semplicemente rifletterlo di conseguenza nel file di inventario e i playbook dovrebbero funzionare senza problemi. Se, tuttavia, non stai utilizzando una distribuzione basata su RHEL, lascerò come esercizio la modifica delle poche attività specifiche della distribuzione (principalmente le cose del gestore di pacchetti)
L'inventario
Ansible ha bisogno di un inventario per sapere su quali host dovrebbe eseguire i playbook. Nel nostro scenario immaginario utilizzeremo il seguente file di inventario:
[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
In un file di inventario Ansible, qualsiasi [section]
rappresenta un gruppo di host. Il nostro inventario ha 3 gruppi di host: logstash
, kibana
ed elasticsearch
. Noterai che uso solo i nomi dei gruppi nei playbook. Ciò significa che non importa il numero di host nell'inventario, finché i gruppi sono corretti, il playbook verrà eseguito.
Il processo di aggiornamento
Il processo di aggiornamento consisterà nei seguenti passaggi:
1) Pre-scarica i pacchetti
2) Aggiornamento di Logstash
3) Rolling Upgrade del cluster Elasticsearch
4) Aggiornamento Kibana
L'obiettivo principale è ridurre al minimo i tempi di fermo. Il più delle volte l'utente non se ne accorgerà nemmeno. A volte Kibana potrebbe non essere disponibile per alcuni secondi. Questo è accettabile per me.
Playbook principale di Ansible
Il processo di aggiornamento consiste in una serie di diversi playbook. Userò la funzione import_playbook di Ansible per organizzare tutti i playbook in un file di playbook principale che può essere chiamato per occuparsi dell'intero processo.
- 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
Abbastanza semplice. È solo un modo per organizzare l'esecuzione dei playbook in un ordine specifico.
Ora, consideriamo come utilizzeremmo l'esempio del playbook Ansible sopra. Spiegherò come implementarlo in seguito, ma questo è il comando che eseguirei per eseguire l'aggiornamento alla versione 6.5.4:
$ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml
Pre-scarica i Pacchetti
Questo primo passo è in realtà facoltativo. Il motivo per cui lo uso è che considero generalmente una buona pratica interrompere un servizio in esecuzione prima di aggiornarlo. Ora, se disponi di una connessione Internet veloce, il tempo necessario al tuo gestore di pacchetti per scaricare il pacchetto potrebbe essere trascurabile. Ma non è sempre così e voglio ridurre al minimo la quantità di tempo in cui qualsiasi servizio è inattivo. In questo modo il mio primo playbook utilizzerà yum per pre-scaricare tutti i pacchetti. In questo modo, quando arrivano i tempi di aggiornamento, la fase di download è già stata sbrigata.
- 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 prima riga indica che questa riproduzione si applicherà solo al gruppo logstash
. La seconda riga dice ad Ansible di non preoccuparsi di raccogliere fatti sugli host. Ciò accelererà il gioco, ma assicurati che nessuno dei compiti del gioco abbia bisogno di informazioni sull'host.
La prima attività nel gioco convaliderà la variabile elk_version
. Questa variabile rappresenta la versione dell'Elastic Stack a cui stiamo effettuando l'aggiornamento. Questo viene passato quando invochi il comando ansible-playbook. Se la variabile non viene passata o non è un formato valido, il gioco verrà immediatamente salvato. Quel compito sarà effettivamente il primo compito in tutte le commedie. Il motivo è consentire l'esecuzione isolata dei giochi, se lo si desidera o è necessario.
La seconda attività utilizzerà il comando rpm
per ottenere la versione corrente di Logstash e registrarsi nella variabile version_found
. Tali informazioni verranno utilizzate nell'attività successiva. Le righe args:
, warning warn: no
e changed_when: False
sono lì per rendere felice ansible-lint, ma non sono strettamente necessarie.
L'attività finale eseguirà il comando che effettivamente pre-scarica il pacchetto. Ma solo se la versione installata di Logstash è precedente alla versione di destinazione. Non puntare il download e la versione precedente o la stessa se non verrà utilizzata.
Le altre due riproduzioni sono essenzialmente le stesse, tranne per il fatto che al posto di Logstash pre-scaricano Elasticsearch e 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, '<')
Aggiornamento Logstash
Logstash dovrebbe essere il primo componente da aggiornare. Questo perché Logstash è garantito per funzionare con una versione precedente di Elasticsearch.
I primi compiti del gioco sono identici alla controparte pre-download:
- 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
I due compiti finali sono contenuti in un blocco:

- 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, '<')
Il condizionale when
garantisce che le attività nel blocco verranno eseguite solo se la versione di destinazione è più recente della versione corrente. La prima attività all'interno del blocco esegue l'aggiornamento di Logstash e la seconda attività riavvia il servizio.
Aggiornamento in sequenza del cluster Elasticsearch
Per assicurarci che non ci siano tempi di inattività per il cluster Elasticsearch, dobbiamo eseguire un aggiornamento in sequenza. Ciò significa che aggiorneremo un nodo alla volta, avviando l'aggiornamento di qualsiasi nodo solo dopo aver verificato che il cluster sia in uno stato verde (completamente integro).
Dall'inizio del gioco noterai qualcosa di diverso:
- name: Elasticsearch rolling upgrade hosts: elasticsearch gather_facts: no serial: 1
Qui abbiamo la linea serial: 1
. Il comportamento predefinito di Ansible consiste nell'eseguire il gioco su più host in parallelo, il numero di host simultanei definito nella configurazione. Questa linea assicura che il gioco venga eseguito contro un solo host alla volta.
Successivamente, definiamo alcune variabili da utilizzare durante il gioco:
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
Il significato di ogni variabile sarà chiaro quando appaiono nel gioco.
Come sempre il primo compito è convalidare la versione di destinazione:
tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")
Molte delle seguenti attività consisteranno nell'esecuzione di chiamate REST sul cluster Elasticsearch. La chiamata può essere eseguita su qualsiasi nodo. Potresti semplicemente eseguirlo sull'host corrente nella riproduzione, ma alcuni dei comandi verranno eseguiti mentre il servizio Elasticsearch è inattivo per l'host corrente. Quindi, nelle attività successive, ci assicuriamo di selezionare un host diverso su cui eseguire le chiamate REST. Per questo, utilizzeremo il modulo set_fact e la variabile groups dell'inventario 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]"
Successivamente, ci assicuriamo che il servizio sia attivo nel nodo corrente prima di continuare:
- 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
Come nei giochi precedenti, controlleremo la versione corrente. Fatta eccezione per questa volta, utilizzeremo l'API REST di Elasticsearch invece di eseguire rpm. Avremmo potuto anche usare il comando rpm, ma voglio mostrare questa alternativa.
- name: Check current version uri: url: http://localhost:{{ es_http_port }} method: GET register: version_found retries: 10 delay: 10
Le attività rimanenti si trovano all'interno di un blocco che verrà eseguito solo se la versione corrente è precedente a quella di destinazione:
- 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 }}"
Ora, se hai seguito il mio consiglio e hai letto la documentazione, avrai notato che questo passaggio dovrebbe essere l'opposto: disabilitare l'allocazione degli shard. Mi piace mettere questo compito qui prima nel caso in cui i frammenti fossero stati disabilitati in precedenza per qualche motivo. Questo è importante perché l'attività successiva attenderà che il cluster diventi verde. Se l'allocazione dello shard è disabilitata, il cluster rimarrà giallo e le attività si bloccheranno fino al timeout.
Quindi, dopo aver verificato che l'allocazione dello shard sia abilitata, ci assicuriamo che il cluster sia in uno stato verde:
- 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
Dopo il riavvio di un servizio del nodo, il cluster può impiegare molto tempo per tornare a verde. Questo è il motivo dei retries: 500
e delay: 15
. Significa che aspetteremo 125 minuti (500 x 15 secondi) affinché il cluster torni al verde. Potrebbe essere necessario modificarlo se i tuoi nodi contengono una quantità davvero enorme di dati. Per la maggior parte dei casi è più che sufficiente.
Ora possiamo disabilitare l'allocazione dello shard:
- 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 }}
E prima di chiudere il servizio, eseguiamo il flush di sincronizzazione facoltativo, ma consigliato. Non è raro ricevere un errore 409 per alcuni degli indici quando eseguiamo un flush di sincronizzazione. Poiché questo è sicuro da ignorare, ho aggiunto 409 all'elenco dei codici di stato di successo.
- name: Perform a synced flush uri: url: http://localhost:{{ es_http_port }}/_flush/synced method: POST status_code: "200, 409"
Ora, questo nodo è pronto per essere aggiornato:
- name: Shutdown elasticsearch node systemd: name: elasticsearch state: stopped - name: Update elasticsearch yum: name: elasticsearch-{{ elk_version }} state: present
Con il servizio fermo attendiamo l'allocazione di tutti gli shard prima di riavviare il nodo:
- 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
Dopo che gli shard sono stati riallocati, riavviamo il servizio Elasticsearch e attendiamo che sia completamente pronto:
- 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
Ora ci assicuriamo che il cluster sia giallo o verde prima di riattivare l'allocazione dello shard:
- 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
E aspettiamo che il nodo si riprenda completamente prima di elaborare quello successivo:
- 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
Ovviamente, come ho detto prima, questo blocco dovrebbe essere eseguito solo se stiamo davvero aggiornando la versione:
when: version_found.json.version.number is version_compare(elk_version, '<')
Aggiornamento Kibana
L'ultimo componente da aggiornare è Kibana.
Come ci si potrebbe aspettare, le prime attività non sono diverse dall'aggiornamento di Logstash o dalle riproduzioni di pre-download. Fatta eccezione per la definizione di una variabile:
- 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
Spiegherò la variabile set_default_index
quando arriviamo all'attività che la utilizza.
Il resto delle attività sarà all'interno di un blocco che verrà eseguito solo se la versione installata di Kibana è precedente alla versione di destinazione. Le prime due attività aggiorneranno e riavvieranno Kibana:
- name: Update kibana yum: name: kibana-{{ elk_version }} state: present - name: Restart kibana systemd: name: kibana state: restarted enabled: yes daemon_reload: yes
E per Kibana sarebbe dovuto bastare. Sfortunatamente, per qualche ragione, dopo l'aggiornamento, Kibana perde il riferimento al suo schema di indice predefinito. Ciò fa sì che venga chiesto al primo utente che accede dopo l'aggiornamento di definire il modello di indice predefinito, il che potrebbe causare confusione. Per evitarlo, assicurati di includere un'attività per reimpostare il modello di indice predefinito. Nell'esempio seguente, è syslog
, ma dovresti cambiarlo in qualunque cosa usi. Prima di impostare l'indice, però, dobbiamo assicurarci che Kibana sia attivo e pronto a soddisfare le richieste:
- 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 }}"
Conclusione
L'Elastic Stack è uno strumento prezioso e ti consiglio vivamente di dare un'occhiata se non lo usi ancora. È fantastico così com'è ed è in costante miglioramento, tanto che potrebbe essere difficile tenere il passo con il costante aggiornamento. Spero che quegli Ansible Playbook possano esserti utili quanto lo sono per me.
Li ho resi disponibili su GitHub all'indirizzo https://github.com/orgito/elk-upgrade. Ti consiglio di testarlo in un ambiente non di produzione.
Se sei uno sviluppatore di Ruby on Rails che cerca di incorporare Elasticsearch nella tua app, dai un'occhiata a Elasticsearch per Ruby on Rails: A Tutorial to the Chewy Gem del Core Toptal Software Engineer Arkadiy Zabazhanov.