System do monitoringu serwera przy użyciu Ansible cz. II

monitoring serwera

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.

Spodobał Ci się artykuł?

Masz pytania? Zacznijmy rozmowę! 🙂

Klikając wyślij, zgadzasz się na wykorzystanie Twojego adresu email oraz imienia do kontaktu elektronicznego, a także do przesyłania wiadomości marketingowych. Administratorem Twoich danych osobowych jest Graphicbox Sp. z o. o. ul. Lwowska 6/201, Rzeszów. Więcej w polityce prywatności.

Form Men