
W poprzedniej części, stworzyliśmy prosty system do monitoringu metryk serwerów wykorzystując Ansible. Stworzyliśmy playbook instalujący Prometheusa i Grafanę na serwerze oraz drugi, instalujący Exportera na monitorowanej maszynie. Jednak metryki to nie wszystko. Aby szybko rozwiązywać problemy i zapewnić wysoką dostępność, musimy wiedzieć nie tylko “że coś się dzieje” na naszej maszynie, ale także “co się dzieje”. System do monitoringu serwera musi również monitorować logi – systemowe, jak i naszej aplikacji.
Użyte oprogramowanie do monitoringu serwera
W roli serwera zbierającego logi użyjemy Grayloga. W przeciwieństwie do Prometheusa, działa on na zasadzie push based, więc nie musimy otwierać dodatkowych portów na monitorowanych maszynach. Do monitorowania, filtrowania, a następnie wysyłania logów do Grayloga, użyjemy narzędzia Filebeat. Jest to co prawda część stosu ELK, jednak współpracuje z naszym serwera, a dodatkowo jest łatwe w instalacji i konfiguracji. Do samej instalacji i konfiguracji, podobnie jak poprzednio, użyjemy Ansible, jednak wykorzystamy więcej z jego możliwości. Więcej informacji o tych narzędziach można znaleźć w poprzedniej części.
Monitoring logów
O ile w przypadku metryk sprawa jest bardzo prosta i każda metryka daje nam informację o stanie serwera, w przypadku logów nie jest to aż takie proste. Po pierwsze musimy odpowiednio filtrować logi wysyłane do serwera. W przeciwnym razie, odnalezienie odpowiednich logów będzie bardzo utrudnione, ale również może dojść do tego że, właśnie nasza maszyna do monitoringu najbardziej go potrzebuje(oczywiście profesjonalny system do monitoringu powinien być redundantny i musimy być zabezpieczeni przed jego ewentualną awarią, ale nie będziemy się na tym dzisiaj skupiali). Drugą bardzo ważną kwestią są odpowiednie logi z naszych aplikacji. Odpowiednie ustrukturyzowanie logów, bardzo ułatwi nam ich filtrowanie oraz zrozumienie “co poszło nie tak”. Dziś skupimy się na najprostszym filtrowaniu, ponieważ nie ma “jedynego słusznego” sposobu i każdy może inaczej tworzyć logi, ważne żeby było one spójne w obrębie monitorowanych systemów.
Przygotowanie monitoringu serwera
Użyjemy środowiska stworzonego w poprzednim artykule. Przejdźmy do instalacji Grayloga.
W tym przypadku, podobnie jak poprzednio użyjemy gotowych roli z Ansible Galaxy (https://galaxy.ansible.com/Graylog2/graylog-ansible-role). Instalujemy ją poleceniem ansible-galaxy install graylog2.graylog-ansible-role. Podobnie jak wcześniej tworzymy plik ze zmiennymi konfiguracyjnymi host_playbook/vars/graylog_vars.yml. Podstawowa konfiguracja wymaga podania następujących pól:
- graylog_version – wersja Grayloga która ma być zainstalowana
- graylog_password_secret – sekret który będzie używany do szyfrowania haseł użytkowników, powinien mieć co najmniej 64 znaki
- graylog_root_password_sha2 – skrót sha2 hasła administratora, można go wygenerować poleceniem echo -n “password” | sha256sum
- graylog_http_publish_uri oraz graylog_http_external_uri – adresy używane do komunikacji z REST API, a także między instancjami Grayloga
Więcej pól konfiguracyjnych wraz z dokładnym opisem można znaleźć na stronie roli w Ansible Galaxy(link powyżej). W moim przypadku plik z konfiguracją wygląda następująco:
graylog_version: 4.0
graylog_password_secret: "NJIfeq25EY3IlzT50Duzj3esbS07PiXqomAtXP2ycGP3VZezMzObqzEt92R8zrfWTj4owOIuWZShd8j74AEbvG2eUNTdPusu"
graylog_root_password_sha2: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # echo -n "password" | sha256sum
graylog_http_publish_uri: "http://192.168.2.3:9000/"
graylog_http_external_uri: "http://192.168.2.3:9000/"
Następnie dodajemy plik ze zmiennymi do playbooka:
vars_files:
- vars/graylog_vars.yml
A następnie dodajemy wywołanie roli graylog2.graylog-ansible-role
- name: Install Graylog
include_role:
name: graylog2.graylog-ansible-role
Na tym etapie możemy uruchomić skrypt i sprawdzić, czy udało się zainstalować i skonfigurować Grayloga. Po krótkiej chwili naszym oczom powinien ukazać się następujący komunikat:
TASK [graylog2.graylog-ansible-role : Graylog server should be configured] ************************************************************************************************************************************
changed: [monitoring-host]
TASK [graylog2.graylog-ansible-role : Graylog server defaults should be configured] ***************************************************************************************************************************
ok: [monitoring-host]
TASK [graylog2.graylog-ansible-role : Graylog server should start after reboot] *******************************************************************************************************************************
ok: [monitoring-host]
RUNNING HANDLER [graylog2.graylog-ansible-role : restart graylog-server] **************************************************************************************************************************************
changed: [monitoring-host]
TASK [graylog2.graylog-ansible-role : Wait for Graylog server to startup] *************************************************************************************************************************************
FAILED - RETRYING: Wait for Graylog server to startup (60 retries left).
FAILED - RETRYING: Wait for Graylog server to startup (59 retries left).
FAILED - RETRYING: Wait for Graylog server to startup (58 retries left).
FAILED - RETRYING: Wait for Graylog server to startup (57 retries left).
FAILED - RETRYING: Wait for Graylog server to startup (56 retries left).
ok: [monitoring-host]
PLAY RECAP ****************************************************************************************************************************************************************************************************
monitoring-host : ok=80 changed=2 unreachable=0 failed=0 skipped=161 rescued=0 ignored=0
Używając Vagranta oraz obrazu debian/buster64 może wystąpić sytuacja, że po 60 próbach, zadanie sprawdzające czy Garylog działa zwróci błąd. Należy wtedy zmienić konfigurację maszyny Java, przez dodanie
graylog_server_java_opts: "-Djava.net.preferIPv4Stack=true -Xms{{ graylog_server_heap_size }} -Xmx{{ graylog_server_heap_size }} -XX:NewRatio=1 -server -XX:+ResizeTLAB -XX:-OmitStackTraceInFastThrow"
do pliku host_playbook/vars/graylog_vars.yml
Jeśli instalacja się powiodła możemy się zalogować, otwierając interfejs webowy Grayloga który jest na porcie 9000. Używamy nazwy użytkownika admin i hasła którego skrót podaliśmy w zmiennej graylog_root_password_sha2. Po poprawnym zalogowaniu powinien nam się ukazać ekran startowy Grayloga.
Żeby odbierać logi z naszych maszyn musimy stworzyć wejście. Dla Filebeat musi być to wejście typu Beat. Można to zrobić zarówno z interfejsu webowego, jak i przy użyciu API. My użyjemy tej 2 opcji. Dokumentacja API znajduje się w interfejsie webowym ze ścieżką /api/api-browser/. API Graylog posiada możliwość generowania kluczy do uwierzytelniania zapytań(co z resztą jest zalecane), jednak tutaj dla uproszczenia użyjemy loginu i hasła. Stwórzmy więc plik ze zmiennymi potrzebnymi do wykonywania zapytań host_playbook/vars/graylog_api_vars.yml. Potrzebne nam będą następujące zmienne:
- graylog_api_user – nazwa użytkownika, domyślnie admin
- graylog_api_password – hasło dla powyższego użytkownika
- graylog_api_url – adres url do API, czyli adres interfejsu webowego z sufiksem /api
W moim przypadku będzie on wyglądał następująco:
graylog_api_user: admin
graylog_api_password: password
graylog_api_url: "http://192.168.2.3:9000/api"
Następnie dodajemy go do sekcji vars_files. Kolejnym krokiem jest stworzenie zapytań. Potrzebujemy 2 zapytań. Najpierw musimy pobrać wszystkie wejścia i sprawdzić czy nasze nie zostało utworzone wcześniej, w przeciwnym razie każde uruchomienie playbooka, będzie skutkowało utworzeniem nowego wejścia. W przypadku gdy nie istnieje, użyjemy drugiego zapytania żeby je stworzyć. Do wysyłania zapytań będziemy używać modułu uri. Jego najważniejsze parametry to:
- url – url zapytania
- method – metoda zapytania
- body – treść zapytania
- body_format – format w którym ma zostać wysłana treść, powoduje również ustawienie odpowiedniego nagłówka Content-Type
- username – nazwa użytkownika
- password – hasło użytkownika
- force_basic_auth – flaga sygnalizująca że nagłówek “Basic authentication” ma zostać wysłany zawsze, a nie tylko po otrzymaniu w odpowiedzi kodu 401
- status_code – kody odpowiedzi oznaczające powodzenie zapytania
Zgodnie z dokumentacją API Grayloga do pobrania listy wejść musimy użyć zapytania GET na url /api/system/inputs. Tak więc wywołanie zapytania będzie wyglądać następująco:
- name: Get all Graylog inputs
uri:
url: "{{ graylog_api_url }}/system/inputs"
user: "{{ graylog_api_user }}"
password: "{{ graylog_api_password }}"
force_basic_auth: true
register: inputs_response
Ansible korzysta z silnika szablonów Jinja2, więc możemy w łatwy sposób dodać ścieżkę do url API. Parametr register powoduje zapisanie wyniku operacji do zmiennej inputs_response, dzięki temu sprawdzimy czy wejście jest już utworzone. Zrobimy to przez sprawdzenie czy wejście o danej nazwie znajduje się w zwróconej liście. Warunek ten wygląda następująco:
when: '"filebeat_ansible" in inputs_response.json.inputs | map(attribute="title") | list'
Żeby nie zaśmiecać naszego playbooka, treść zapytania stworzymy w osobnym pliku, a następnie użyjemy metody lookup. Treść zapytanie tworzącego wejście typu Beat wygląda następująco:
{
"title":"filebeat_ansible",
"type":"org.graylog.plugins.beats.Beats2Input",
"configuration":{
"bind_address":"0.0.0.0",
"port":5044,
"recv_buffer_size":1048576,
"number_worker_threads":2,
"tls_cert_file":"",
"tls_key_file":"",
"tls_enable":false,
"tls_key_password":"",
"tls_client_auth":"disabled",
"tls_client_auth_cert_file":"",
"tcp_keepalive":false,
"override_source":null,
"no_beats_prefix":false
},
"global":true
}
Plik zapisujemy jako host_playbook/requests/create_input.json. Zgodnie z dokumentacją do tego zapytania musimy dodać nagłówek X-Requested-By o dowolnej wartości. Następnie wysyłamy zapytanie:
- name: Add input if not exists
uri:
url: "{{ graylog_api_url }}/system/inputs"
method: POST
user: "{{ graylog_api_user }}"
password: "{{ graylog_api_password }}"
force_basic_auth: true
body: "{{ lookup('file','requests/create_input.json') }}"
body_format: json
status_code: 201
headers:
X-Requested-By: ansible
when: 'not ("filebeat_ansible" in (inputs_response.json.inputs | map(attribute="title") | list))'
failed_when: inputs_response.status != 201
Teraz możemy uruchomić playbooka. Po poprawnym wykonaniu w panelu Graylog, w zakładce System -> Inputs pojawi się nasze wejście.
Instalacja Filebeat
Skoro Graylog już działa, teraz musimy wysłać do niego jakieś logi. To jakie logi chcemy wysyłać podajemy w konfiguracji Filebeat. Ponieważ każdy projekt się różni, będziemy potrzebowali różnych konfiguracji dla różnych maszyn. Jako że chcemy kopiować za każdym razem naszego playbooka, przygotujemy rolę która będzie instalować Filebeat, a następnie go konfigurować.
Do zainicjowania roli używamy komendy ansible-galaxy init <nazwa roli> –offline. Flaga offline powoduje, że komenda nie korzysta z serwerów Ansible Galaxy podczas tworzenia roli.
ansible-galaxy init gbxsoft.filebeat --offline
Po zakończeniu powinna zostać utworzona następująca struktura katalogów:
Krótkie wyjaśnienie:
- defaults – zawiera pliki z domyślnymi wartościami
- files – zawiera inne pliki używane w roli
- handlers – zawiera “handlery” czyli zadania które mają być uruchomione jeśli jakieś zadanie zwróci wynik zmiana(np. jeśli zmienimy konfigurację Filebeat, to “handler” go zrestartuje)
- meta – zawiera metadane roli(licencja, twórca itp.)
- tasks – najważniejszy katalog, tu będą zadania które będzie wykonywać rola
- templates – zawiera szablony Jinja2
- tests – zawiera pliki z testami
- vars – zawiera pliki ze zmiennymi(np. dla konkretnych systemów operacyjnych)
Pierwszym krokiem jest oczywiście instalacja Filebeat. Dobrą praktyką jest umożliwienie wyboru wersji. Tak więc do defaults/main.yml dodajemy pole filebeat_version. Na ten moment jest to 7.10.0.
filebeat_version: 7.10.0
Rola powinna umożliwić nam używanie jej na różnych maszynach, a co za tym idzie, na różnych systemach operacyjnych. W tym przykładzie skupimy się na systemie Debian. Tak więc w tasks tworzymy plik install-debian.yml. Używając get_url pobierzemy plik dep, a następnie zainstalujemy go przy użyciu apt. Instalacja będzie wyglądała następująco:
---
- name: Download Filebeat
get_url:
url: "https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{{ filebeat_version }}-amd64.deb"
dest: "/tmp/filebeat-{{ filebeat_version }}-amd64.deb"
- name: Install Filebeat
apt:
deb: "/tmp/filebeat-{{ filebeat_version }}-amd64.deb"
Teraz w pliku tasks/main.yml musimy sprawdzić wersję systemu operacyjnego, a następnie uruchomić plik odpowiedzialny za instalację na tym systemie. Używamy do tego include, natomiast rodzinę systemu operacyjnego możemy pobrać ze zmiennej ansible_os_family:
---
- name: Download Filebeat
get_url:
url: "https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{{ filebeat_version }}-amd64.deb"
dest: "/tmp/filebeat-{{ filebeat_version }}-amd64.deb"
- name: Install Filebeat
apt:
deb: "/tmp/filebeat-{{ filebeat_version }}-amd64.deb"
Gdy mamy już zainstalowanego Filebeat na naszej maszynie, musimy przygotować plik konfiguracyjny. Jako baza posłuży nam domyślny plik konfiguracyjny. Można go pobrać z repozytorium GitHub (https://raw.githubusercontent.com/elastic/beats/master/filebeat/filebeat.yml). Pobieramy go, a następnie zapisujemy jako templates/filebeat.yml.j2. Na ten moment najbardziej interesują nas 2 sekcje: filebeat.inputs w której podajemy gdzie należy szukać logów, jak je filtrować itp., oraz output.logstash w której podajemy gdzie te logi mają być wysyłane. Tak więc w pliku defaults/main.yml tworzymy 2 kolejne zmienne: filebeat_graylog_hosts i filebeat_inputs. W tej pierwszej dodamy listę z jednym adresem localhost:5044. Natomiast do 2 dodamy jedno wejście, takie jak w domyślnej konfiguracji. Nasz plik powinien wyglądać w następujący sposób:
---
# defaults file for gbxsoft.filebeat
filebeat_version: 7.10.0
filebeat_graylog_hosts: [localhost:5044]
filebeat_inputs:
- type: log
enabled: false
paths:
- /var/log/*.log
Teraz musimy zmodyfikować szablon konfiguracji, tak aby nasze dane zostały dodane w odpowiednich miejscach. Najpierw musimy usunąć wszystkie elementy sekcji filebeat.inputs, a następnie w ich miejsce zawartość zmiennej filebeat_inputs w formacie yaml.
filebeat.inputs:
{{ filebeat_inputs | to_nice_yaml }}
Następnie musimy zmienić odkomentować sekcję output.logstash, a wszystkie pozostałe output usunąć. Następnie dodajemy do pola output.logstash.hosts treść zmiennej filebeat_graylog_hosts.
output.logstash:
# The Logstash hosts
hosts:
{{ filebeat_graylog_hosts | to_nice_yaml }}
Zanim wyślemy konfigurację na serwer musimy stworzyć handler, który wykona restart Filebeata, gdy wykryje że konfiguracja się zmieniła. Dodajemy go w pliku handlers/main.yml tak jak zwykłe zadanie. Wywoływać go będziemy natomiast przez podanie jego nazwy w liście notify dodanej do zadania. Restart usługi wykonujemy przez użycie service, ustawiając stan na restarted.
---
# handlers file for gbxsoft.filebeat
- name: Config changed
service:
name: filebeat
state: restarted
Teraz już możemy wysłać plik konfiguracyjny używając template:
- name: Upload Filebeat config
template:
src: filebeat.yml.j2
dest: /etc/filebeat/filebeat.yml
notify:
- Config changed
Ostatnim krokiem jest upewnienie się że Filebeat działa, i uruchamia się wraz ze startem systemu. Używamy do tego service, ustawiając stan na started i dodając flagę enabled.
- name: Ensure Filebeat is started
service:
name: filebeat
enabled: true
state: started
Mamy już gotową rolę do konfiguracji Filebeat, teraz pozostaje jej użyć na monitorowanej maszynie. Tworzymy więc katalog client_playbook/vars, a w nim plik filebeat_vars.yml. W nim ustawiamy filebeat_graylog_hosts na adres serwera Graylog(w moim przypadku 192.168.2.3:5044). Żeby sprawdzić czy wszystko działa spróbujemy wysłać logi z usługi sshd. Logi te znajdują się w pliku /var/log/auth.log, więc dodajemy tą ścieżkę do paths. Aby odfiltrować pozostałe wpisy użyjemy bardzo prostego wyrażenia regularnego ‘sshd\[\d+\]’. Możemy je dodać w polu include_lines, wtedy zostaną wysłane wszystkie logi pasujące do wydarzenia. W polu exclude_lines podajemy natomiast wyrażenia, które jeśli pasują, to logi nie zostaną wysłane. Wyrażenia w obu polach są łączone przez logiczne “lub” tzn. jeśli podamy kilka wyrażeń, wystarczy żeby jedno pasowało. Możemy także oznaczyć jakoś nasze wejście podając wartość fields. Podajemy tam elementy klucz-wartość, które umożliwią nam sortowanie i filtrowanie logów w panelu. Typ wejścia ustawiamy na log i oczywiście włączamy wejście.
filebeat_graylog_hosts: [192.168.2.3:5044]
filebeat_inputs:
- type: log
enabled: true
paths:
- /var/log/auth.log
include_lines:
- 'sshd\[\d+\]'
fields:
info: from_ansible
Mamy już gotowy plik konfiguracyjny, teraz wystarczy jedynie dodać go do pliku client_playbook/install.yml oraz wywołać rolę.
vars_files:
- vars/filebeat_vars.yml
- name: Install Filebeat
import_role:
name: gbxsoft.filebeat
Od teraz każdy log z usługi SSH będzie trafiał do Grayloga. Tak więc mamy już działający, scentralizowany system monitoringu serwerów. Jest on co prawda bardzo prosty, ale może posłużyć jako baza do rozwoju.