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?