W świecie automatyzacji IT i zarządzania konfiguracją, Ansible stało się jednym z najważniejszych narzędzi. Jego prostota i elastyczność pozwalają na szybkie wdrażanie i zarządzanie infrastrukturą, co przyczyniło się do jego szerokiego przyjęcia. Jednakże, jak to często bywa z potężnymi narzędziami, istnieje tendencja do wykorzystywania ich w sposób, który z czasem może prowadzić do problemów. Jednym z takich problemów jest tworzenie monolitycznych playbooków, które mogą szybko stać się nieczytelne, trudne do zarządzania i podatne na błędy. Ansible to potężne narzędzie, które praktycznie bez końca można rozwijać. W tym artykule przedstawimy różnice między monolitami w playbookach, które z reguły spotykam w pracy u klientów, a podejściem nieco bardziej złożonym, czytelnym i profesjonalnym – tworzeniu playbooków opartych o role, zmienne i inne.

Monolit serwera LAMP

Wyobraźmy sobie sytuację, w której administrator systemu tworzy playbook Ansible do konfiguracji serwera. Na początku jest to prosta lista zadań, które muszą być wykonane. Jednak z czasem, gdy wymagania rosną, playbook staje się coraz bardziej skomplikowany. Dodawane są nowe zadania, zmienne, warunki, a nawet bloki kodu do obsługi błędów. W końcu, playbook przekształca się w monolityczny dokument o setkach, a nawet tysiącach linii kodu. Taki playbook może wyglądać tak:

Przykładowy playbook monolityczny dla tego zadania mógłby wyglądać tak
 

    

---
-name: Configure web server
  hosts: webservers
  become: yes
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present

    -name: Start and enable Apache service
      service:
        name: apache2
        state: started
        enabled: yes

    -name: Copy Apache config file
      copy:
        src: /path/to/local/apache2.conf
        dest: /etc/apache2/apache2.conf
        owner: root
        group: root
        mode: 0644

    - name: Ensure Apache is running
      command: systemctl status apache2
      register: apache_status
      failed_when: apache_status.rc != 0
      changed_when: false

    - name: Install MySQL
      apt:
        name: mysql-server
        state: present

    - name: Start and enable MySQL service
      service:
        name: mysql
        state: started
        enabled: yes

    - name: Install PHP and modules
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - php
        - php-mysql
        - libapache2-mod-php

    - name: Copy PHP config file
      copy:
        src: /path/to/local/php.ini
        dest: /etc/php/7.4/apache2/php.ini
        owner: root
        group: root
        mode: 0644

    - name: Restart Apache to apply changes
      service:
        name: apache2
        state: restarted

Ten przykładowy playbook, mimo że spełnia swoje zadanie, jest trudny do zarządzania i skalowania. W miarę dodawania kolejnych usług, konfiguracji i zależności, staje się coraz bardziej skomplikowany i podatny na błędy, ale i nieczytelny. W takim playbooku trudno jest śledzić zmiany (choć oczywiście można korzystać z narzędzi takich jak git), debugować problemy czy wprowadzać nowe funkcje bez ryzyka wprowadzenia nieoczekiwanych błędów.

Jak robić to jak doświadczeni admini?

Aby rozwiązać problemy związane z monolitycznymi playbookami, Ansible wprowadziło koncepcję ról (roles). Role pozwalają na podział konfiguracji na mniejsze, bardziej zarządzalne części, które mogą być łatwo ponownie używane i udostępniane. Role mogą zawierać zadania (tasks), zmienne (variables), szablony (templates), pliki (files), a nawet testy (tests). Dzięki temu playbooki stają się bardziej modularne, czytelne i łatwiejsze w utrzymaniu.

Aby zobrazować różnice, zmodyfikujemy nasz monolityczny przykład dla serwera LAMP, dzieląc go na role. Każda rola będzie odpowiedzialna za konkretną część konfiguracji: instalację Apache, instalację MySQL i instalację PHP.

Struktura katalogów:

Podsu    
LAMP/
├── roles/
│   ├── apache/
│   │   ├── tasks/
│   │   │   └── main.yml
│   │   ├── templates/
│   │   │   └── apache2.conf.j2
│   ├── mysql/
│   │   ├── tasks/
│   │   │   └── main.yml
│   ├── php/
│   │   ├── tasks/
│   │   │   └── main.yml
│   │   ├── templates/
│   │   │   └── php.ini.j2
├── site.yml
├── hosts.ini

Plik: site.yml

---
- hosts: serwery
  become: yes
  roles:
    - apache

    - mysql
    - php

Plik: roles/apache/tasks/main.yml

---
- name: Install Apache
  apt:
    name: apache2
    state: present

- name: Start and enable Apache service
  service:
    name: apache2
    state: started
    enabled: yes

- name: Copy Apache config file
  template:
    src: apache2.conf.j2
    dest: /etc/apache2/apache2.conf
    owner: root
    group: root
    mode: 0644

- name: Ensure Apache is running
  command: systemctl status apache2
  register: apache_status
  failed_when: apache_status.rc != 0
  changed_when: false

Plik: roles/mysql/tasks/main.yml

---
- name: Install MySQL
  apt:
    name: mysql-server
    state: present

- name: Start and enable MySQL service
  service:
    name: mysql
    state: started
    enabled: yes

Plik: roles/php/tasks/main.yml


---
- name: Install PHP and modules
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - php
    - php-mysql
    - libapache2-mod-php

- name: Copy PHP config file
  template:
    src: php.ini.j2
    dest: /etc/php/7.4/apache2/php.ini
    owner: root
    group: root
    mode: 0644

- name: Restart Apache to apply changes
  service:
    name: apache2
    state: restarted


Plik: roles/apache/templates/apache2.conf.j2

DefaultRuntimeDir ${APACHE_RUN_DIR}
PidFile ${APACHE_PID_FILE}
Timeout 30
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

HostnameLookups Off
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn

IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf

Include ports.conf

<Directory />
	Options FollowSymLinks
	AllowOverride None
	Require all denied
</Directory>

<Directory /usr/share>
	AllowOverride None
	Require all granted
</Directory>

<Directory /var/www/>
	Options Indexes FollowSymLinks
	AllowOverride None
	Require all granted
</Directory>

AccessFileName .htaccess

<FilesMatch "^\.ht">
	Require all denied
</FilesMatch>

LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf


Plik: roles/php/templates/php.ini.j2

## jakaś przykładowa konfiguracja PHP.ini (np. zwiększenie pamięci, zmiana timeout itd.)

Podsumowanie

Jak widać przejście od monolitycznych playbooków do modularnych ról przynosi wiele korzyści w tym dodatkowo podnosi prestiż naszej pracy. Przede wszystkim, playbooki stają się bardziej czytelne i łatwiejsze do zarządzania. Każda rola jest odpowiedzialna za określoną część konfiguracji, co ułatwia jej utrzymanie i rozwijanie. Modułowość pozwala również na ponowne użycie ról w różnych projektach, co zwiększa efektywność pracy. Ponadto, zmiany w konfiguracji są bardziej kontrolowane i mniej podatne na błędy, ponieważ każda rola jest testowana i rozwijana niezależnie.

Dzięki zastosowaniu zmiennych i szablonów, konfiguracja staje się bardziej elastyczna i dostosowana do specyficznych potrzeb środowiska. Użycie zmiennych umożliwia łatwe dostosowanie ustawień bez konieczności modyfikowania kodu, a szablony pozwalają na dynamiczne generowanie plików konfiguracyjnych. Wszystko to prowadzi do bardziej zorganizowanego i profesjonalnego podejścia do zarządzania konfiguracją. Podsumowując, tworzenie zaawansowanych playbooków opartych na rolach, zmiennych i szablonach jest najlepszą praktyką, która prowadzi do bardziej zrównoważonego, skalowalnego i efektywnego zarządzania infrastrukturą. Taka struktura nie tylko ułatwia pracę administratorom, ale również zwiększa niezawodność i bezpieczeństwo systemów, co jest kluczowe w dużych organizacjach, takich jak banki. Dzięki modularności i elastyczności, playbooki Ansible stają się potężnym narzędziem w rękach profesjonalistów.