Actualizați automat Elastic Stack cu Ansible Playbooks
Publicat: 2022-03-11Analiza jurnalelor pentru o rețea compusă din mii de dispozitive era o sarcină complexă, consumatoare de timp și plictisitoare înainte de a decide să folosesc Elastic Stack ca soluție de înregistrare centralizată. S-a dovedit a fi o decizie foarte înțeleaptă. Nu numai că am un singur loc pentru a căuta toate jurnalele mele, dar obțin rezultate aproape instantanee la căutările mele, vizualizări puternice care sunt incredibil de utile pentru analiză și depanare și tablouri de bord frumoase care îmi oferă o imagine de ansamblu utilă asupra rețelei.
Elastic Stack lansează în mod constant funcții noi și uimitoare, păstrând un ritm foarte activ de dezvoltare, de multe ori oferă două versiuni noi în fiecare lună. Îmi place să-mi păstrez întotdeauna mediul la zi pentru a mă asigura că pot profita de noile funcții și îmbunătățiri. De asemenea, pentru a-l menține fără erori și probleme de securitate. Dar asta îmi cere să actualizez constant mediul.
Chiar dacă site-ul web Elastic menține o documentație clară și detaliată, inclusiv cu privire la procesul de actualizare a produselor lor, actualizarea manuală este o sarcină complexă, în special clusterul Elasticsearch. Sunt o mulțime de pași implicați și trebuie urmată o ordine foarte specifică. De aceea am decis să automatizez întregul proces, cu mult timp în urmă, folosind Ansible Playbooks.
În acest tutorial Ansible, ne voi prezenta o serie de Ansible Playbooks care au fost dezvoltate pentru a-mi actualiza automat instalarea Elastic Stack.
Ce este Elastic Stack
Elastic Stack, cunoscut anterior ca ELK stack, este compus din Elasticsearch, Logstash și Kibana, de la compania open-source Elastic, care oferă împreună o platformă puternică pentru indexarea, căutarea și analiza datelor dvs. Poate fi folosit pentru o gamă largă de aplicații. De la înregistrare și analiză de securitate până la gestionarea performanței aplicațiilor și căutarea pe site.
Elasticsearch este nucleul stivei. Este un motor de căutare și analiză distribuit capabil să furnizeze rezultate de căutare aproape în timp real, chiar și pentru un volum uriaș de date stocate.
Logstash este o conductă de procesare care primește sau primește date din mai multe surse diferite (50 de pluginuri oficiale de intrare în timp ce scriu), le analizează, filtrează și transformă și le trimite către una sau mai multe dintre posibilele ieșiri. În cazul nostru, suntem interesați de pluginul de ieșire Elasticsearch.
Kibana este utilizatorul și interfața dvs. de operare. Vă permite să vizualizați, să căutați, să navigați în datele dvs. și să creați tablouri de bord care vă oferă informații uimitoare despre acestea.
Ce este Ansible
Ansible este o platformă de automatizare IT care poate fi utilizată pentru configurarea sistemelor, implementarea sau actualizarea software-ului și orchestrarea sarcinilor IT complexe. Obiectivele sale principale sunt simplitatea și ușurința în utilizare. Caracteristica mea preferată a Ansible este că este fără agent, ceea ce înseamnă că nu trebuie să instalez și să gestionez niciun software suplimentar pe gazdele și dispozitivele pe care vreau să le gestionez. Vom folosi puterea automatizării Ansible pentru a actualiza automat Elastic Stack.
Disclaimer și un cuvânt de precauție
Manualele pe care le voi împărtăși aici se bazează pe pașii descriși în documentația oficială a produsului. Este menit să fie utilizat numai pentru upgrade-uri ale aceleiași versiuni majore. De exemplu: 5.x
→ 5.y
sau 6.x
→ 6.y
unde x
> y
. Upgrade-urile între versiunile majore necesită adesea pași suplimentari, iar acele manuale nu vor funcționa pentru acele cazuri.
Oricum, citiți întotdeauna notele de lansare, în special secțiunea privind modificările de ruptură înainte de a utiliza manualele pentru a face upgrade. Asigurați-vă că înțelegeți sarcinile executate în manuale și verificați întotdeauna instrucțiunile de actualizare pentru a vă asigura că nimic important nu se schimbă.
Acestea fiind spuse, am folosit acele manuale (sau versiuni anterioare) de la versiunea 2.2 a Elasticsearch fără probleme. La acea vreme aveam manuale complet separate pentru fiecare produs, deoarece nu aveau același număr de versiune pe care îl știau ei.
Acestea fiind spuse, nu sunt responsabil în niciun fel pentru utilizarea de către dvs. a informațiilor conținute în acest articol.
Mediul nostru fictiv
Mediul în care vor rula cărțile noastre de joc va consta din 6 servere CentOS 7:
- 1 x server Logstash
- 1 x server Kibana
- 4 x noduri Elasticsearch
Nu contează dacă mediul tău are un număr diferit de servere. Puteți pur și simplu să o reflectați în mod corespunzător în fișierul de inventar și cărțile de joc ar trebui să ruleze fără probleme. Dacă, totuși, nu utilizați o distribuție bazată pe RHEL, vă voi lăsa ca un exercițiu să schimbați câteva sarcini care sunt specifice distribuției (în principal chestiile managerului de pachete)
Inventarul
Ansible are nevoie de un inventar pentru a ști pe ce gazde ar trebui să ruleze manualele. Patru scenariul nostru imaginar, vom folosi următorul fișier de inventar:
[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
Într-un fișier de inventar Ansible, orice [section]
reprezintă un grup de gazde. Inventarul nostru are 3 grupuri de gazde: logstash
, kibana
și elasticsearch
. Veți observa că folosesc doar numele grupurilor în manuale. Asta înseamnă că nu contează numărul de gazde din inventar, atâta timp cât grupurile sunt corecte, manualul de joc va rula.
Procesul de actualizare
Procesul de actualizare va consta din următorii pași:
1) Predescărcați pachetele
2) Upgrade Logstash
3) Actualizare continuă a clusterului Elasticsearch
4) Upgrade Kibana
Scopul principal este de a minimiza timpul de nefuncționare. De cele mai multe ori utilizatorul nici nu va observa. Uneori, Kibana ar putea fi indisponibilă pentru câteva secunde. Asta e acceptabil pentru mine.
Principalul manual Ansible
Procesul de actualizare constă dintr-un set de manuale diferite. Voi folosi funcția import_playbook din Ansible pentru a organiza toate playbook-urile într-un singur fișier principal playbook care poate fi apelat pentru a avea grijă de întregul proces.
- 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
Destul de simplu. Este doar o modalitate de a organiza execuția manualelor într-o anumită ordine.
Acum, să luăm în considerare modul în care am folosi exemplul Ansible Playbook de mai sus. Voi explica cum îl implementăm mai târziu, dar aceasta este comanda pe care aș executa-o pentru a face upgrade la versiunea 6.5.4:
$ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml
Pre-descărcați pachetele
Primul pas este de fapt opțional. Motivul pentru care folosesc acest lucru este că consider o practică în general bună de a opri un serviciu care rulează înainte de a-l actualiza. Acum, dacă aveți o conexiune rapidă la Internet, timpul pentru managerul dvs. de pachete pentru a descărca pachetul ar putea fi neglijabil. Dar nu este întotdeauna cazul și vreau să reduc la minimum timpul în care orice serviciu este întrerupt. Astfel, primul meu playbook va folosi yum pentru a pre-descărca toate pachetele. În acest fel, când vin timpii de actualizare, pasul de descărcare a fost deja ocupat.
- 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, '<')
Prima linie indică faptul că acest joc se va aplica numai grupului de logstash
. Al doilea rând îi spune lui Ansible să nu se deranjeze să colecteze informații despre gazde. Acest lucru va accelera jocul, dar asigurați-vă că niciuna dintre sarcinile din joc nu va avea nevoie de informații despre gazdă.
Prima sarcină din joc va valida variabila elk_version
. Această variabilă reprezintă versiunea Elastic Stack la care facem upgrade. Aceasta este transmisă atunci când invocați comanda ansible-playbook. Dacă variabila nu este trecută sau nu este un format valid, jocul se va salva imediat. Această sarcină va fi de fapt prima sarcină din toate piesele. Motivul pentru aceasta este acela de a permite ca piesele să fie executate izolat, dacă se dorește sau este necesar.
A doua sarcină va folosi comanda rpm
pentru a obține versiunea curentă a Logstash și pentru a se înregistra în variabila version_found
. Aceste informații vor fi utilizate în următoarea sarcină. Rândurile args:
, warn: no
și changed_when: False
sunt acolo pentru a face ansible-lint fericit, dar nu sunt strict necesare.
Sarcina finală va executa comanda care pre-descărcă pachetul. Dar numai dacă versiunea instalată de Logstash este mai veche decât versiunea țintă. Descărcarea nu este indicată și versiunea mai veche sau aceeași, dacă nu va fi folosită.
Celelalte două piese sunt în esență aceleași, cu excepția faptului că, în loc de Logstash, vor pre-descărca Elasticsearch și 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, '<')
Upgrade Logstash
Logstash ar trebui să fie prima componentă care trebuie actualizată. Asta pentru că Logstash este garantat că va funcționa cu o versiune mai veche a Elasticsearch.
Primele sarcini ale piesei sunt identice cu omologul pre-descărcare:
- 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
Cele două sarcini finale sunt cuprinse într-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, '<')
Condiționalul when
garantează că sarcinile din bloc vor fi executate numai dacă versiunea țintă este mai nouă decât versiunea curentă. Prima sarcină din interiorul blocului realizează upgrade-ul Logstash, iar a doua sarcină repornește serviciul.
Actualizare continuă a clusterului Elasticsearch
Pentru a ne asigura că nu va exista timp de nefuncționare pentru clusterul Elasticsearch, trebuie să efectuăm o actualizare continuă. Aceasta înseamnă că vom face upgrade câte un nod, începând doar actualizarea oricărui nod după ce ne asigurăm că clusterul este într-o stare verde (pe deplin sănătos).
De la începutul piesei vei observa ceva diferit:
- name: Elasticsearch rolling upgrade hosts: elasticsearch gather_facts: no serial: 1
Aici avem serial: 1
. Comportamentul implicit al Ansible este de a executa jocul împotriva mai multor gazde în paralel, numărul de gazde simultane definit în configurație. Această linie se asigură că jocul va fi executat împotriva unei singure gazde la un moment dat.
În continuare, definim câteva variabile care vor fi folosite de-a lungul jocului:
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
Semnificația fiecărei variabile va fi clară pe măsură ce apar în joc.
Ca întotdeauna, prima sarcină este validarea versiunii țintă:
tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")
Multe dintre următoarele sarcini vor consta în executarea apelurilor REST împotriva clusterului Elasticsearch. Apelul poate fi executat împotriva oricăruia dintre noduri. Puteți să-l executați pur și simplu împotriva gazdei curente în joc, dar unele dintre comenzi vor fi executate în timp ce serviciul Elasticsearch este oprit pentru gazda actuală. Deci, în următoarele sarcini, ne asigurăm că selectăm o altă gazdă pentru a rula apelurile REST. Pentru aceasta, vom folosi modulul set_fact și variabila groups din inventarul 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]"
În continuare, ne asigurăm că serviciul este activ în nodul curent înainte de a continua:
- 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
Ca și în jocurile anterioare, vom verifica versiunea actuală. Cu excepția acestui timp, vom folosi API-ul REST Elasticsearch în loc să rulăm rpm. Am fi putut folosi și comanda rpm, dar vreau să arăt această alternativă.
- name: Check current version uri: url: http://localhost:{{ es_http_port }} method: GET register: version_found retries: 10 delay: 10
Sarcinile rămase sunt într-un bloc care va fi executat numai dacă versiunea curentă este mai veche decât versiunea țintă:
- 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 }}"
Acum, dacă mi-ai urmat sfatul și ai citit documentația, vei fi observat că acest pas ar trebui să fie invers: să dezactivezi alocarea shard-urilor. Îmi place să pun această sarcină aici mai întâi în cazul în care fragmentele au fost dezactivate înainte dintr-un motiv oarecare. Acest lucru este important deoarece următoarea sarcină va aștepta ca clusterul să devină verde. Dacă alocarea fragmentelor este dezactivată, clusterul va rămâne galben și sarcinile se vor bloca până când expiră.
Deci, după ce ne asigurăm că alocarea fragmentelor este activată, ne asigurăm că clusterul este într-o stare 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
După repornirea unui serviciu de nod, clusterul poate dura mult timp să revină la verde. Acesta este motivul pentru retries: 500
și delay: 15
. Înseamnă că vom aștepta 125 de minute (500 x 15 secunde) pentru ca clusterul să revină la verde. S-ar putea să fie nevoie să o ajustați dacă nodurile dvs. dețin o cantitate foarte mare de date. Pentru majoritatea cazurilor, este mult mai mult decât suficient.
Acum putem dezactiva alocarea fragmentelor:
- 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 }}
Și înainte de a închide serviciul, executăm fluxul de sincronizare opțional, dar recomandat. Nu este neobișnuit să obținem o eroare 409 pentru unii dintre indici atunci când efectuăm un flux de sincronizare. Deoarece acest lucru este sigur de ignorat, am adăugat 409 la lista de coduri de stare de succes.
- name: Perform a synced flush uri: url: http://localhost:{{ es_http_port }}/_flush/synced method: POST status_code: "200, 409"
Acum, acest nod este gata pentru a fi actualizat:
- name: Shutdown elasticsearch node systemd: name: elasticsearch state: stopped - name: Update elasticsearch yum: name: elasticsearch-{{ elk_version }} state: present
Cu serviciul oprit, așteptăm ca toate fragmentele să fie alocate înainte de a porni din nou nodul:
- 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
După ce fragmentele sunt realocate, repornim serviciul Elasticsearch și așteptăm ca acesta să fie complet gata:
- 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
Acum ne asigurăm că clusterul este galben sau verde înainte de a reactiva alocarea fragmentelor:
- 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
Și așteptăm ca nodul să se recupereze complet înainte de a procesa următorul:
- 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
Desigur, așa cum am spus mai devreme, acest bloc ar trebui să fie executat numai dacă realizăm cu adevărat versiunea:
when: version_found.json.version.number is version_compare(elk_version, '<')
Upgrade Kibana
Ultima componentă care trebuie actualizată este Kibana.
După cum v-ați putea aștepta, primele sarcini nu sunt diferite de upgrade-ul Logstash sau de redările de pre-descărcare. Cu excepția definiției unei 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
Voi explica variabila set_default_index
când vom ajunge la sarcina care o folosește.
Restul sarcinilor vor fi într-un bloc care se va executa numai dacă versiunea instalată de Kibana este mai veche decât versiunea țintă. Primele două sarcini vor actualiza și reporni Kibana:
- name: Update kibana yum: name: kibana-{{ elk_version }} state: present - name: Restart kibana systemd: name: kibana state: restarted enabled: yes daemon_reload: yes
Și pentru Kibana asta ar fi trebuit să fie suficient. Din păcate, din anumite motive, după upgrade, Kibana își pierde referința la modelul de index implicit. Acest lucru îl face să solicite primului utilizator care accesează după actualizare să definească modelul de index implicit, ceea ce poate provoca confuzie. Pentru a evita acest lucru, asigurați-vă că includeți o sarcină pentru a reseta modelul de index implicit. În exemplul de mai jos, este syslog
, dar ar trebui să îl schimbați în orice folosiți. Totuși, înainte de a seta indexul, trebuie să ne asigurăm că Kibana este gata și gata să servească cereri:
- 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 }}"
Concluzie
Elastic Stack este un instrument valoros și vă recomand cu siguranță să aruncați o privire dacă nu îl folosiți încă. Este grozav așa cum este și se îmbunătățește în mod constant, atât de mult încât ar putea fi greu să țineți pasul cu actualizarea constantă. Sper că acele Ansible Playbooks ar putea fi la fel de utile pentru tine ca și pentru mine.
Le-am pus la dispoziție pe GitHub la https://github.com/orgito/elk-upgrade. Vă recomand să îl testați într-un mediu care nu este de producție.
Dacă sunteți un dezvoltator Ruby on Rails care dorește să încorporeze Elasticsearch în aplicația dvs., consultați Elasticsearch pentru Ruby on Rails: Un tutorial la Chewy Gem de la inginerul de software Core Toptal Arkadiy Zabazhanov.