Automatycznie aktualizuj Elastic Stack za pomocą podręczników Ansible
Opublikowany: 2022-03-11Analiza logów dla sieci złożonej z tysięcy urządzeń była kiedyś złożonym, czasochłonnym i nudnym zadaniem, zanim zdecydowałem się użyć Elastic Stack jako scentralizowanego rozwiązania do rejestrowania. Okazało się to bardzo mądrą decyzją. Nie tylko mam jedno miejsce do przeszukiwania wszystkich moich dzienników, ale otrzymuję niemal natychmiastowe wyniki wyszukiwania, potężne wizualizacje, które są niezwykle pomocne w analizie i rozwiązywaniu problemów oraz piękne pulpity nawigacyjne, które dają mi przydatny przegląd sieci.
Elastic Stack stale udostępnia nowe i niesamowite funkcje, utrzymując bardzo aktywne tempo rozwoju, często co miesiąc wydaje dwie nowe wersje. Lubię zawsze aktualizować moje środowisko, aby mieć pewność, że mogę korzystać z nowych funkcji i ulepszeń. Ponadto, aby był wolny od błędów i problemów z bezpieczeństwem. Ale to wymaga ode mnie ciągłego aktualizowania środowiska.
Mimo że witryna Elastic przechowuje przejrzystą i szczegółową dokumentację, w tym dotyczącą procesu aktualizacji produktów, ręczna aktualizacja jest złożonym zadaniem, zwłaszcza klastra Elasticsearch. W grę wchodzi wiele kroków i należy postępować zgodnie z bardzo konkretną kolejnością. Dlatego już dawno postanowiłem zautomatyzować cały proces za pomocą Ansible Playbooks.
W tym samouczku Ansible przeprowadzę nas przez serię podręczników Ansible Playbook, które zostały opracowane w celu automatycznej aktualizacji mojej instalacji Elastic Stack.
Co to jest elastyczny stos
Elastic Stack, wcześniej znany jako stos ELK, składa się z Elasticsearch, Logstash i Kibana firmy Elastic, oferującej potężną platformę do indeksowania, wyszukiwania i analizowania danych. Może być używany do szerokiego zakresu zastosowań. Od rejestrowania i analizy bezpieczeństwa po zarządzanie wydajnością aplikacji i wyszukiwanie w witrynie.
Elasticsearch jest rdzeniem stosu. Jest to rozproszony silnik wyszukiwania i analizy, który może dostarczać wyniki wyszukiwania w czasie zbliżonym do rzeczywistego, nawet w przypadku ogromnej ilości przechowywanych danych.
Logstash to potok przetwarzania, który pobiera lub odbiera dane z wielu różnych źródeł (50 oficjalnych wtyczek wejściowych, jak piszę), analizuje je, filtruje i przekształca oraz wysyła do jednego lub więcej możliwych wyjść. W naszym przypadku interesuje nas wtyczka wyjścia Elasticsearch.
Kibana to frontend użytkownika i operacji. Umożliwia wizualizację, wyszukiwanie, nawigację po danych i tworzenie pulpitów nawigacyjnych, które dają niesamowity wgląd w te dane.
Co to jest Ansible
Ansible to platforma automatyzacji IT, której można używać do konfigurowania systemów, wdrażania lub aktualizowania oprogramowania oraz organizowania złożonych zadań informatycznych. Jego głównymi celami są prostota i łatwość użytkowania. Moją ulubioną cechą Ansible jest to, że nie wymaga agenta, co oznacza, że nie muszę instalować żadnego dodatkowego oprogramowania na hostach i urządzeniach, którymi chcę zarządzać, ani zarządzać nimi. Wykorzystamy moc automatyzacji Ansible do automatycznego uaktualnienia naszego elastycznego stosu.
Zastrzeżenie i słowo ostrzeżenia
Poradniki, które tutaj udostępnię, są oparte na krokach opisanych w oficjalnej dokumentacji produktu. Ma być używany tylko do aktualizacji tej samej głównej wersji. Na przykład: 5.x
→ 5.y
lub 6.x
→ 6.y
gdzie x
> y
. Aktualizacje między głównymi wersjami często wymagają dodatkowych kroków, a te podręczniki nie będą działać w takich przypadkach.
Niezależnie od tego, zawsze czytaj informacje o wydaniu, zwłaszcza sekcję dotyczącą istotnych zmian, zanim użyjesz podręczników do aktualizacji. Upewnij się, że rozumiesz zadania wykonane w playbookach i zawsze sprawdzaj instrukcje aktualizacji, aby upewnić się, że nic ważnego się nie zmieni.
To powiedziawszy, używam tych podręczników (lub wcześniejszych wersji) od wersji 2.2 Elasticsearch bez żadnych problemów. W tamtym czasie dla każdego produktu miałem zupełnie osobne podręczniki, ponieważ nie miały one takiego samego numeru wersji, jaki znają.
Powiedziawszy to, nie jestem w żaden sposób odpowiedzialny za wykorzystanie przez Ciebie informacji zawartych w tym artykule.
Nasze fikcyjne środowisko
Środowisko, na którym będą działać nasze playbooki, będzie składało się z 6 serwerów CentOS 7:
- 1 x serwer Logstash
- 1 x serwer Kibana
- 4 x węzły Elasticsearch
Nie ma znaczenia, czy Twoje środowisko ma inną liczbę serwerów. Możesz po prostu odpowiednio odzwierciedlić to w pliku ekwipunku, a podręczniki powinny działać bez problemu. Jeśli jednak nie używasz dystrybucji opartej na RHEL, zostawię to jako ćwiczenie, abyś zmienił kilka zadań, które są specyficzne dla dystrybucji (głównie rzeczy związane z menedżerem pakietów)
Ekwipunek
Ansible potrzebuje inwentarza, aby wiedzieć, na których hostach ma uruchamiać podręczniki. W naszym wyobrażonym scenariuszu użyjemy następującego pliku inwentaryzacji:
[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
W pliku inwentaryzacji Ansible dowolna [section]
reprezentuje grupę hostów. W naszym ekwipunku znajdują się 3 grupy hostów: logstash
, kibana
, i elasticsearch
. Zauważysz, że w podręcznikach używam tylko nazw grup. Oznacza to, że nie ma znaczenia liczba hostów w ekwipunku, o ile grupy są poprawne, playbook będzie działał.
Proces aktualizacji
Proces aktualizacji będzie się składał z następujących kroków:
1) Wstępnie pobierz pakiety
2) Ulepszenie Logstasha
3) Stopniowa aktualizacja klastra Elasticsearch
4) Ulepszenie Kibany
Głównym celem jest zminimalizowanie przestojów. Przez większość czasu użytkownik nawet tego nie zauważy. Czasami Kibana może być niedostępna przez kilka sekund. To jest dla mnie do przyjęcia.
Główny podręcznik Ansible
Proces aktualizacji składa się z zestawu różnych podręczników. Użyję funkcji import_playbook programu Ansible, aby uporządkować wszystkie podręczniki w jednym głównym pliku podręcznika, który można wywołać, aby zająć się całym procesem.
- 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
Dość proste. To tylko sposób na uporządkowanie wykonania podręczników w jednej określonej kolejności.
Zastanówmy się teraz, jak wykorzystalibyśmy powyższy przykład z podręcznika Ansible. Później wyjaśnię, jak to zaimplementujemy, ale to polecenie, które wykonałbym, aby zaktualizować do wersji 6.5.4:
$ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml
Wstępnie pobierz pakiety
Ten pierwszy krok jest właściwie opcjonalny. Powodem, dla którego tego używam, jest to, że uważam za ogólnie dobrą praktykę zatrzymanie działającej usługi przed jej aktualizacją. Teraz, jeśli masz szybkie połączenie z Internetem, czas na pobranie pakietu przez menedżera pakietów może być znikomy. Ale nie zawsze tak jest i chcę zminimalizować czas, w którym jakakolwiek usługa nie działa. W ten sposób mój pierwszy playbook użyje yum do wstępnego pobrania wszystkich pakietów. W ten sposób, gdy nadejdą czasy aktualizacji, etap pobierania został już wykonany.
- 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, '<')
Pierwsza linia wskazuje, że ta gra będzie dotyczyć tylko grupy logstash
. Drugi wiersz mówi Ansible, aby nie zawracał sobie głowy zbieraniem faktów na temat gospodarzy. Przyspieszy to grę, ale upewnij się, że żadne z zadań w grze nie będzie wymagało żadnych informacji o gospodarzu.
Pierwsze zadanie w sztuce sprawdzi poprawność zmiennej elk_version
. Ta zmienna reprezentuje wersję elastycznego stosu, do której aktualizujemy. Jest to przekazywane po wywołaniu polecenia ansible-playbook. Jeśli zmienna nie zostanie przekazana lub nie ma prawidłowego formatu, gra zostanie natychmiast wycofana. To zadanie będzie właściwie pierwszym zadaniem we wszystkich sztukach. Powodem tego jest umożliwienie, aby sztuki były wykonywane w odosobnieniu, jeśli jest to pożądane lub konieczne.
Drugie zadanie użyje polecenia rpm
, aby pobrać aktualną wersję Logstash i zarejestrować się w zmiennej version_found
. Te informacje zostaną wykorzystane w następnym zadaniu. Linie args:
, warn: no
i changed_when: False
są po to, by uszczęśliwić ansible-lint, ale nie są bezwzględnie konieczne.
Ostatnie zadanie wykona polecenie, które faktycznie wstępnie pobierze pakiet. Ale tylko wtedy, gdy zainstalowana wersja Logstasha jest starsza niż wersja docelowa. Nie wskaż pobierania i starszej lub tej samej wersji, jeśli nie będzie ona używana.
Pozostałe dwie gry są zasadniczo takie same, z wyjątkiem tego, że zamiast Logstash będą pobierać wstępnie 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, '<')
Aktualizacja Logstash
Logstash powinien być pierwszym komponentem do aktualizacji. Dzieje się tak, ponieważ Logstash gwarantuje współpracę ze starszą wersją Elasticsearch.
Pierwsze zadania w grze są identyczne jak w przedpobranym odpowiedniku:
- 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
Dwa ostatnie zadania zawarte są w bloku:

- 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, '<')
Warunkowe when
gwarantuje, że zadania w bloku zostaną wykonane tylko wtedy, gdy wersja docelowa jest nowsza niż wersja bieżąca. Pierwsze zadanie wewnątrz bloku wykonuje aktualizację Logstasha, a drugie zadanie ponownie uruchamia usługę.
Rolling Upgrade klastra Elasticsearch
Aby mieć pewność, że klaster Elasticsearch nie będzie przestoju, musimy wykonać aktualizację kroczącą. Oznacza to, że będziemy aktualizować jeden węzeł na raz, rozpoczynając aktualizację dowolnego węzła dopiero po upewnieniu się, że klaster jest w stanie zielonym (w pełni zdrowy).
Od początku gry zauważysz coś innego:
- name: Elasticsearch rolling upgrade hosts: elasticsearch gather_facts: no serial: 1
Tutaj mamy linię serial: 1
. Domyślnym zachowaniem Ansible jest wykonywanie gry na wielu hostach równolegle, liczba równoczesnych hostów zdefiniowana w konfiguracji. Ta linia zapewnia, że gra zostanie wykonana tylko przeciwko jednemu hostowi na raz.
Następnie definiujemy kilka zmiennych, które będą używane podczas zabawy:
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
Znaczenie każdej zmiennej będzie jasne, gdy pojawią się w sztuce.
Jak zawsze pierwszym zadaniem jest walidacja wersji docelowej:
tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")
Wiele z poniższych zadań będzie polegało na wykonywaniu wywołań REST względem klastra Elasticsearch. Wywołanie można wykonać na dowolnym węźle. Możesz po prostu wykonać go na bieżącym hoście w grze, ale niektóre polecenia zostaną wykonane, gdy usługa Elasticsearch nie działa dla bieżącego hosta. Tak więc w następnych zadaniach upewniamy się, że wybraliśmy innego hosta, na którym będą uruchamiane wywołania REST. W tym celu wykorzystamy moduł set_fact oraz zmienną groups z inwentarza 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]"
Następnie upewniamy się, że usługa działa w bieżącym węźle, zanim przejdziemy dalej:
- 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
Podobnie jak w poprzednich sztukach, sprawdzimy obecną wersję. Z wyjątkiem tego czasu, zamiast uruchamiać rpm, użyjemy Elasticsearch REST API. Mogliśmy również użyć polecenia rpm, ale chcę pokazać tę alternatywę.
- name: Check current version uri: url: http://localhost:{{ es_http_port }} method: GET register: version_found retries: 10 delay: 10
Pozostałe zadania znajdują się w bloku, który zostanie wykonany tylko wtedy, gdy bieżąca wersja jest starsza niż wersja docelowa:
- 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 }}"
Teraz, jeśli zastosowałeś się do moich rad i przeczytałeś dokumentację, zauważyłeś, że ten krok powinien być odwrotny: wyłączyć alokację fragmentów. Chciałbym umieścić to zadanie tutaj na pierwszym miejscu, na wypadek gdyby shardy zostały wcześniej wyłączone z jakiegoś powodu. Jest to ważne, ponieważ następne zadanie będzie czekać, aż klaster stanie się zielony. Jeśli alokacja fragmentu jest wyłączona, klaster pozostanie żółty, a zadania będą zawieszone do czasu przekroczenia limitu czasu.
Tak więc po upewnieniu się, że alokacja fragmentów jest włączona, upewniamy się, że klaster jest w stanie zielonym:
- 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
Po ponownym uruchomieniu usługi węzła powrót do stanu zielonego w klastrze może zająć dużo czasu. To jest powód retries: 500
i delay: 15
. Oznacza to, że będziemy czekać 125 minut (500 x 15 sekund), aż klaster powróci do stanu zielonego. Być może będziesz musiał to dostosować, jeśli twoje węzły przechowują naprawdę ogromną ilość danych. W większości przypadków to zdecydowanie więcej niż wystarczające.
Teraz możemy wyłączyć alokację fragmentu:
- 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 }}
A przed zamknięciem usługi wykonujemy opcjonalną, ale zalecaną, synchronizację opróżniania. Nierzadko zdarza się, że podczas wykonywania opróżniania synchronizacji pojawia się błąd 409 dla niektórych indeksów. Ponieważ można to bezpiecznie zignorować, dodałem 409 do listy kodów statusu sukcesu.
- name: Perform a synced flush uri: url: http://localhost:{{ es_http_port }}/_flush/synced method: POST status_code: "200, 409"
Teraz ten węzeł jest gotowy do aktualizacji:
- name: Shutdown elasticsearch node systemd: name: elasticsearch state: stopped - name: Update elasticsearch yum: name: elasticsearch-{{ elk_version }} state: present
Po zatrzymaniu usługi czekamy na przydzielenie wszystkich fragmentów przed ponownym uruchomieniem węzła:
- 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
Po ponownym przydzieleniu shardów ponownie uruchamiamy usługę Elasticsearch i czekamy, aż będzie całkowicie gotowa:
- 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
Teraz upewniamy się, że klaster jest żółty lub zielony przed ponownym włączeniem alokacji fragmentów:
- 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 czekamy, aż węzeł w pełni się zregeneruje, zanim przetworzymy następny:
- 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
Oczywiście, jak powiedziałem wcześniej, ten blok powinien być wykonywany tylko wtedy, gdy naprawdę aktualizujemy wersję:
when: version_found.json.version.number is version_compare(elk_version, '<')
Ulepszenie Kibany
Ostatnim komponentem do ulepszenia jest Kibana.
Jak można się spodziewać, pierwsze zadania nie różnią się od aktualizacji Logstasha czy odtworzeń przed pobraniem. Z wyjątkiem definicji jednej zmiennej:
- 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
Wyjaśnię zmienną set_default_index
, kiedy przejdziemy do zadania, które jej używa.
Pozostałe zadania będą znajdować się w bloku, który zostanie wykonany tylko wtedy, gdy zainstalowana wersja Kibany jest starsza niż wersja docelowa. Pierwsze dwa zadania zaktualizują i zrestartują Kibanę:
- name: Update kibana yum: name: kibana-{{ elk_version }} state: present - name: Restart kibana systemd: name: kibana state: restarted enabled: yes daemon_reload: yes
A dla Kibany to powinno wystarczyć. Niestety, z jakiegoś powodu po aktualizacji Kibana traci odniesienie do domyślnego wzorca indeksu. Powoduje to, że pierwszy użytkownik, który uzyska dostęp po aktualizacji, prosi o zdefiniowanie domyślnego wzorca indeksu, co może powodować zamieszanie. Aby tego uniknąć, upewnij się, że zawiera zadanie zresetowania domyślnego wzorca indeksu. W poniższym przykładzie jest to syslog
, ale powinieneś go zmienić na taki, jakiego używasz. Jednak przed ustawieniem indeksu musimy upewnić się, że Kibana jest gotowa do obsługi żądań:
- 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 }}"
Wniosek
Elastic Stack to cenne narzędzie i zdecydowanie polecam zajrzeć, jeśli jeszcze go nie używasz. Jest świetny tak, jak jest i stale się poprawia, tak bardzo, że może być trudno nadążyć za ciągłym ulepszaniem. Mam nadzieję, że te Poradniki Ansible mogą być dla Ciebie równie przydatne, jak dla mnie.
Udostępniłem je na GitHubie pod adresem https://github.com/orgito/elk-upgrade. Zalecam przetestowanie go w środowisku nieprodukcyjnym.
Jeśli jesteś programistą Ruby on Rails i chcesz włączyć Elasticsearch do swojej aplikacji, sprawdź Elasticsearch for Ruby on Rails: A Tutorial to the Chewy Gem autorstwa Core Toptal Software Engineer Arkadiy Zabazhanov.