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.
Pytanie, czemu playbook za każdym razem musi coś instalować? Nie może się podkonfigurować pod istniejące środowisko np. MySQL?
Playbook Ansible instaluje rzeczy, bo działa deklaratywnie, zapewniając zgodność środowiska (idempotentność). Aby działać na istniejącym środowisku (np. MySQL), można:
– Użyć warunków (when) do pomijania instalacji.
– Oddzielić instalację od konfiguracji (np. tagi install/configure).
– Weryfikować stan (moduły stat, command).
– Sterować zmiennymi (-e install_mysql=false).
Dzięki temu playbook będzie bardziej elastyczny.