EuroLinux
rsyslog

Rsyslog część druga – konfiguracja centralnego systemu logów z Ansible

Linux, DevOps

Zgodnie z obietnicą z poprzedniego artykułu dziś skupimy się na stworzeniu dwóch ról Ansible, które pozwolą nam wyskalować ustawienia logowania na dziesiątki, a nawet setki hostów. Artykuł ten ma też za zadanie zasymulować rzeczywiste tworzenie ról Ansible, które mogą być w prosty sposób wersjonowane, zmieniane i skalowane.

Wygląd repozytorium

Mając na uwadze dobre praktyki związane z Ansible, finalna wersja repozytorium będzie wyglądać w następujący sposób:

.
├── ansible.cfg
├── inventories
│   └── prod
│       └── hosts
├── playbooks
│   ├── configure-rsyslog.yml
│   └── roles -> ../roles
├── README.md
└── roles
    ├── rsyslog_client
    │   ├── defaults
    │   │   └── main.yml
    │   ├── handlers
    │   │   └── main.yml
    │   ├── README.md
    │   └── tasks
    │       └── main.yml
    └── rsyslog_server
        ├── defaults
        │   └── main.yml
        ├── files
        │   └── external-rotate
        ├── handlers
        │   └── main.yml
        ├── README.md
        └── tasks
            └── main.yml

Tworzenie repozytorium gitowego

By przeprowadzić symulację tworzenia automatyzacji opartej na Ansible, pozwoliłem sobie stworzyć repozytorium Git. Znajduje się ono pod adresem: https://github.com/EuroLinux/ansible-rsyslog-example. Tworzenie repozytorium oraz używanie i zarządzanie Gitem wykracza poza zakres tego artykułu. Niemniej polecam kilka naszych artykułów na ten temat: Git podstawowe Narzędzie Pracy Developera część 1, Git podstawowe Narzędzie Pracy Developera część 2, Git podstawowe Narzędzie Pracy Developera część 3 czy Git Trójkątny Przepływ Pracy.

Przykładowe utworzenie repozytorium z dodaniem zdalnego serwera:

mkdir ansible-rsyslog-example
cd ansible-rsyslog-example
echo "# ansible-rsyslog-example" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@FIXME:FIXME/ansible-rsyslog-example.git
git push origin master

Ansible – konfiguracja pliku inventory

Przed przystąpieniem do pisania roli należy zdefiniować hosty (maszyny fizyczne i wirtualne), na których będzie ona uruchomiona. Plik inventory, w języku polskim nazywany czasem plikiem inwentarza, został szerzej omówiony w tym artykule. Nasz plik inventory zawiera 2 systemy wysyłające logi oraz jeden serwer zbierający logi. Znajduje się on na ścieżce inventories/prod/hosts i wygląda następująco:

[rsyslog-servers]
192.168.121.45
[rsyslog-clients]
192.168.121.24
192.168.121.132

Pozwala to na stworzenie własnej struktury z różnymi host_vars i group_vars ze względu na środowisko, na którym działamy. Biorąc pod uwagę wykładowy charakter tego artykułu, pozwoliłem sobie na użycie adresów IP zamiast nazw hostów.

Po zakończeniu konfiguracji inwentarza można dodać pliki do repozytorium, stworzyć migawkę i wysłać ją na zdalny serwer.

git add .
git commit -m "Add inventory"
git push origin master

Konfiguracja ansible.cfg

W pliku ansible.cfg znajdują się następujące dane konfiguracyjne, odpowiadające za ustawienie zdalnego użytkownika i eskalacji przywilejów:

[defaults]
remote_user = ansible
[privilege_escalation]
become = True

Po dodaniu ansible.cfg ponownie można utworzyć migawkę gita.

git add .
git commit -m "Add ansible.cfg"
git push origin master

Rola serwera

Po przygotowaniu konfiguracji możemy stworzyć rolę. W tym celu w katalogu naszego repozytorium o nazwie roles wywołujemy:

[Alex@Normandy ansible-rsyslog-example]$ mkdir roles
[Alex@Normandy ansible-rsyslog-example]$ cd roles
[Alex@Normandy roles]$ ansible-galaxy init rsyslog_server
- rsyslog_server was created successfully

Osobiście mam w zwyczaju usuwać domyślne pliki, by nie zaśmiecać repozytorium. W związku z tym proponuję usunąć katalogi i pliki, których nie używamy. Można je oczywiście później w łatwy sposób odtworzyć.

Oto prosty jednolinijkowiec, który rekursywnie kasuje nieużywane katalogi:

rm -r rsyslog_server/{tests,vars,templates,meta}

Jako że wiemy, jakie serwisy będziemy konfigurować, zacznę od napisania dwóch prostych handlerów (uchwytów), które będą wykonywane, jeśli nastąpi zmiana. Znajdują się one na ścieżce roles/rsyslog_server/handlers/main.yml.

# handlers file for rsyslog_server
- name: restart rsyslog
  service:
    name: rsyslog
    state: restarted
- name: restart firewalld 
  service:
    name: firewalld
    state: restarted

Następnie proponuję stworzyć informację o porcie, na którym ma działać Rsyslog wraz z listą definiującą szablony. Każdy szablon (w rozumieniu Rsyslog nie Ansible) trzymany jest w słowniku (inaczej nazywanym mapą). Pierwszy element o kluczu def trzyma regułę definiującą sam wzorzec. Drugi element dest określa, z jakich obiektów i priorytetów będą zbierane dane. W poniższym przykładzie linie będą takie same jak w pierwszym artykule tej serii. Dodatkowo w celu ułatwienia (lepszym rozwiązaniem w przypadku serwera Rsyslog z dużą ilością zmian w konfiguracji będzie stworzenie odpowiedniego wzorca) dodałem także zmienione linie plików konfiguracyjnych.

Plik rsyslog_server/defaults/main.yml:

---
# defaults file for rsyslog_server

rsyslog_port: 514
rsyslog_templates: [ 
  {def: "$template MojSzablon,\"/var/log/external/%HOSTNAME%.log\"", dest: "*.* -?MojSzablon"},
]
rsyslog_config_lines: [ 
  { 'regex': '^\$ModLoad imtcp', 'line': '$ModLoad imtcp'},
  { 'regex': '^\$InputTCPServerRun*', 'line': '$InputTCPServerRun {{rsyslog_port}}'},
]

Proponuję utworzyć także plik zawierający ustawienia rotacji logów. Jest on oparty na /etc/logrotate.d/syslog. Zawartość pliku roles/rsyslog_server/files/external-rotate.

/var/log/external/\*.log 
{
    missingok
    sharedscripts
    postrotate
  /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

Finalnie możemy przystąpić do napisania naszych zadań (tasks) dla Ansible. Oto plik z zadaniami znajdujący się na ścieżce roles/rsyslog_server/tasks/main.yml:

---
# tasks file for rsyslog_server
- name: Install rsyslog
  yum:
    state: present
    name: rsyslog

- name: Enable rsyslog
  service:
    name: rsyslog
    enabled: yes

- name: Configure tcp port on firewalld
  firewalld:
    port: "{{rsyslog_port}}/tcp"
    permanent: yes
    state: enabled
  notify: restart firewalld

- name: Configure the logrotation
  copy:
    src: external-rotate 
    dest: /etc/logrotate.d/external-rotate
    owner: root
    group: root
    mode: 0644


- name: Enable tcp listening in /etc/rsyslog.conf
  lineinfile:
    path: /etc/rsyslog.conf
    regexp: "{{item.regex}}"
    line: "{{item.line}}"
  loop: "{{rsyslog_config_lines}}"
  notify: restart rsyslog 

- name: Remove templates.conf
  file:
    path: /etc/rsyslog.d/templates.conf
    state: absent

- name: Add config lines
  blockinfile:
    create: yes
    path: /etc/rsyslog.d/templates.conf
    block: |
      {{item.def}}
      {{item.dest}}
    marker: ""
  loop: "{{rsyslog_templates}}"
  notify: restart rsyslog

By zakończyć tę część konfiguracji, pozostało nam jedynie napisanie playbooka. Będzie się on znajdował na ścieżce playbooks/rsyslog-server.yml.

---
- name: Configure rsyslog server
  hosts: rsyslog-servers
  roles:
    - rsyslog_server

Zanim jednak wykonamy naszego playbooka, należy stworzyć dowiązanie symboliczne do katalogu zawierającego role:

[Alex@Normandy roles]$ ln -s ../roles roles

oraz przetestować nasz kod Ansible. Uruchamianie kodu Ansible bez wykonania minimalnych testów to proszenie się o kłopoty.

Niestety nie ma dobrego sposobu na zrobienie tzw. dry-run. W teorii istnieje możliwość uruchomienia playbooka z opcją --check, by zebrać dane o wykonanych zadaniach oraz informacje o tym, czy zmienią one system. W praktyce jednak sposób ten z reguły nie działa i w wielu przypadkach nigdy nie zadziała poprawnie. Wynika to z prostego faktu, iż Ansible musiałoby wykonać kopię systemu, wprowadzić zmiany w celu zebrania danych, a następnie wrócić do oryginalnego stanu. Dla przykładu proszę sobie wyobrazić (playbook, który by to reprezentował, również byłby dziecinnie prosty) następujący scenariusz:

  1. Stwarzamy katalogi /www/riposty/ansible/jest/gupi.
  2. Przywracamy lub nadajemy odpowiedni kontekst SELinuksowy na www/riposty oraz podpliki.
  3. Stwarzamy plik /www/riposty/ansible/jest/gupi/riposta z zawartością No chyba Ty.
  4. Pobieramy dane z serwera http://localhost/ansible/jest/gupi/riposta.

W którym momencie Ansible zgłosi błąd? W zależności od tego, czy katalog /www/riposty/autor/jest/gupi istnieje w kroku 2. lub 4. Oczywiście jest też tutaj pewne założenie, że system posiada serwer WWW, który wykorzystuje katalog /www/ jako główny katalog serwera.

Bardziej skomplikowanych scenariuszy rozłożonych na wiele hostów, z instalacją i konfiguracją oprogramowania, będących dynamicznie od siebie zależnych (np. instalacja aplikacji, a następnie migracja bazy danych) nie warto z reguły nawet rozważać.

Do testowania ról polecam użycie projektu Molecule. Dzięki niemu możemy testować role Ansible i, co bardzo ważne, tworzyć testy regresji dla infrastruktury. Z kolei w celu lintowania (oceny) kodu Ansible polecam użycie ansible-lint. Więcej na temat tych narzędzi będzie można znaleźć na naszym blogu w najbliższych miesiącach. Zachęcam więc do jego regularnego odwiedzania lub zapisu na newsletter 🙂

Co zatem należy zrobić za każdym razem, kiedy chcemy przetestować playbooka? Otóż sprawdzić składnie. Odpowiada za to parametr --syntax-check.

ansible-playbook --inventory inventories/prod playbooks/rsyslog-server.yml --syntax-check

Po pomyślnym przejściu testu można uruchomić rolę konfigurującą serwer.

ansible-playbook --inventory inventories/prod playbooks/rsyslog-server.yml --syntax-check

Poniżej przykładowe uruchomienie playbooka.

ansible-playbook --inventory inventories/prod playbooks/rsyslog-server.yml 

PLAY [Configure rsyslog server] *********************************************************

TASK [Gathering Facts] ******************************************************************
ok: [192.168.121.45]

TASK [rsyslog_server : Install rsyslog] *************************************************
ok: [192.168.121.45]

TASK [rsyslog_server : Enable rsyslog] **************************************************
ok: [192.168.121.45]

TASK [rsyslog_server : Configure tcp port on firewalld] *********************************
changed: [192.168.121.45]

TASK [rsyslog_server : Configure the logrotation] ***************************************
changed: [192.168.121.45]

TASK [rsyslog_server : Enable tcp listening in /etc/rsyslog.conf] ***********************
changed: [192.168.121.45] => (item={u'regex': u'^\\$ModLoad imtcp', u'line': u'$ModLoad imtcp'})
changed: [192.168.121.45] => (item={u'regex': u'^\\$InputTCPServerRun*', u'line': u'$InputTCPServerRun 514'})

TASK [rsyslog_server : Remove templates.conf] *******************************************
ok: [192.168.121.45]

TASK [rsyslog_server : Add config lines] ************************************************
changed: [192.168.121.45] => (item={u'dest': u'*.* -?MojSzablon', u'def': u'$template MojSzablon,"/var/log/external/%HOSTNAME%.log"'})

RUNNING HANDLER [rsyslog_server : restart rsyslog] **************************************
changed: [192.168.121.45]

RUNNING HANDLER [rsyslog_server : restart firewalld] ************************************
changed: [192.168.121.45]

PLAY RECAP ******************************************************************************
192.168.121.45             : ok=10   changed=6    unreachable=0    failed=0

Po udanym zakończeniu playbooka proponuję zapisać zmiany w systemie kontroli wersji.

git add .
git commit -m "rsyslog server role and playbook"
git push origin master

Rola klienta

Rola klienta będzie ograniczać się do prostej reguły przekazywania logów po TCP. Jedyną ciekawą konstrukcją jest pętla po grupie pozwalająca na dodanie następnego serwera, do którego logi będą przekazywane, jeśli tylko pojawi się on w pliku inwentarza.

By stworzyć rolę rsyslog_client proponuję wejść do katalogu ./roles, a następnie stworzyć rolę i ponownie usunąć nieużywane pliki.

cd roles
ansible-galaxy init rsyslog_client
rm -rf rsyslog_client/{files/,meta/,templates/,tests/,vars/}

W pliku roles/rsyslog_client/defaults/main.yml umieszczamy domyślny port.

---
# defaults file for rsyslog_server
rsyslog_port: 514

W uchwytach (handlers) pod ścieżką roles/rsyslog_client/handlers/main.yml tworzymy
identyczną jak w poprzedniej roli funkcję restartu rsysloga.

---
# handlers file for rsyslog_client
- name: restart rsyslog
  service:
    name: rsyslog
    state: restarted

W końcu możemy napisać zadania w pliku roles/rsyslog_client/tasks/main.yml.

---
# tasks file for rsyslog_client
- name: Install rsyslog
  yum:
    state: present
    name: rsyslog

- name: Enable rsyslog
  service:
    name: rsyslog
    enabled: yes

- name: Remove forward.conf
  file:
    path: /etc/rsyslog.d/forward.conf
    state: absent

- name: Add config lines
  lineinfile:
    create: yes
    line: "*.* @@{{item}}:{{rsyslog_port}}" 
    path: /etc/rsyslog.d/forward.conf
  notify: restart rsyslog 
  loop: "{{groups['rsyslog-servers'] }}"

Po takim zabiegu należy tylko dopisać prostego playbooka, który wykorzysta naszą rolę. Będzie się on znajdował na ścieżce playbooks/rsyslog-client.yml.

---
- name: Configure rsyslog clients
  hosts: rsyslog-clients
  roles:
    - rsyslog_client

Następnie uruchamiany sprawdzenie składni:

ansible-playbook --inventory inventories/prod playbooks/rsyslog-client.yml  --syntax-check

playbook: playbooks/rsyslog-client.yml

By finalnie uruchomić playbooka:

ansible-playbook --inventory inventories/prod playbooks/rsyslog-client.yml  

PLAY [Configure rsyslog clients] ********************************************************

TASK [Gathering Facts] ******************************************************************
ok: [192.168.121.24]
ok: [192.168.121.132]

TASK [rsyslog_client : Install rsyslog] *************************************************
ok: [192.168.121.24]
ok: [192.168.121.132]

TASK [rsyslog_client : Enable rsyslog] **************************************************
ok: [192.168.121.24]
ok: [192.168.121.132]

TASK [rsyslog_client : Remove forward.conf] *********************************************
ok: [192.168.121.24]
ok: [192.168.121.132]

TASK [rsyslog_client : Add config lines] ************************************************
changed: [192.168.121.132] => (item=192.168.121.45)
changed: [192.168.121.24] => (item=192.168.121.45)

RUNNING HANDLER [rsyslog_client : restart rsyslog] **************************************
changed: [192.168.121.132]
changed: [192.168.121.24]

PLAY RECAP ******************************************************************************
192.168.121.132            : ok=6    changed=2    unreachable=0    failed=0   
192.168.121.24             : ok=6    changed=2    unreachable=0    failed=0

Po wprowadzonych zmianach możemy zapisać oraz wysłać zmiany na serwer gita.

git add .
git commit -m "add rsyslog_client role and playbook"
git push origin master

Test konfiguracji

Istnieje wiele narzędzi do testowania infrastruktury. Jednak ze względu na prostotę zagadnienia przetestujemy nasze rozwiązanie eksploracyjne. W tym celu wykorzystamy wiedzę o narzędziu logger poznanym w poprzednim artykule.

logger -p 0 "This is emergency from ${HOSTNAME}. Use Bat-Signal."

Na każdym z hostów (mają odpowiednio ustawione swoje nazwy loghost1, loghost2 i logserver) wykonujemy powyższą komendę. Następnie wystarczy sprawdzić, czy na serwerze logów znajdują się odpowiednie pliki.

[root@logserver ~]# ll /var/log/external/
total 200
-rw-------. 1 root root   692 May 22 10:25 loghost1.log
-rw-------. 1 root root   709 May 22 10:25 loghost2.log
-rw-------. 1 root root 67364 May 22 10:25 logserver.log

Playbook dla ról Ansible

Jak wcześniej wspomniałem, repozytorium znajduje się pod adresem: https://github.com/EuroLinux/ansible-rsyslog-example. Zdaję sobie sprawę, że ze względu na prostotę zagadnienia, wygląd repozytorium oraz szereg ustawień jest na wyrost. Niemniej jeszcze raz podkreślę, iż chciałem pokazać rzeczywisty sposób pracy z Ansible.

Jeśli artykuł Ci się spodobał, podziel się tym, co dobre 😉

Chcesz być na bieżąco? Obserwuj nasz profil w serwisie LinkedIn.

Zobacz artykuły o podobnej tematyce

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Fill out this field
Fill out this field
Proszę wprowadzić prawidłowy adres email.
You need to agree with the terms to proceed

Menu
+48 22 243 22 33
close slider