Ansible pozwala zapisać administrację serwerem jako zestaw czytelnych instrukcji. Zamiast pamiętać, jakie polecenia wykonano na każdej maszynie, przechowujemy opis oczekiwanego stanu w repozytorium i uruchamiamy go w kontrolowany sposób. Automatyzacja nie usuwa ryzyka, ale ułatwia przegląd zmian, test na jednym hoście, powtarzalne wdrożenie i późniejsze odtworzenie konfiguracji.
W tym artykule przechodzimy od instalacji i pierwszego połączenia do playbooka wdrażanego na produkcji. Każdy element wyjaśniamy w ten sam sposób: co robi, po co jest potrzebny, jaki wynik zobaczymy i na co uważać.
Spis treści
Czym jest Ansible?
Ansible służy do automatyzowania konfiguracji serwerów i wdrożeń. Wyobraźmy sobie trzy serwery WWW. Na każdym trzeba utworzyć użytkownika, zainstalować nginx, wgrać konfigurację i uruchomić usługę. Możemy zrobić to ręcznie trzy razy albo opisać te czynności raz w pliku YAML i zlecić ich wykonanie Ansible.
Komputer, z którego uruchamiamy Ansible, nazywa się węzłem sterującym (control node). Serwery, na których wykonywane są zadania, to węzły zarządzane (managed nodes). Ansible zwykle łączy się z nimi przez SSH. Nie wymaga własnego agenta działającego stale na serwerze, ale większość modułów dla Linuksa potrzebuje dostępnego interpretera Pythona. Wyjątkiem jest między innymi moduł raw, którym można wykonać proste polecenie jeszcze przed zainstalowaniem Pythona.
W playbooku najczęściej opisujemy stan, który ma istnieć: pakiet ma być zainstalowany, plik ma mieć określoną treść, a usługa ma działać. To ważna różnica względem listy poleceń powłoki. Moduł najpierw sprawdza stan serwera i zmienia tylko to, co jest potrzebne.
Instalacja i pierwsze połączenie
Na węźle sterującym można zainstalować samo ansible-core, czyli silnik i podstawowe moduły. Pakiet ansible zawiera dodatkowo zestaw kolekcji społecznościowych. Do nauki podstaw wystarczy ansible-core:
pipx install ansible-core
ansible --version
pipx umieszcza aplikację w osobnym środowisku Pythona, dzięki czemu jej zależności nie mieszają się z pakietami systemowymi. Polecenie ansible --version potwierdza instalację i pokazuje między innymi używaną wersję oraz ścieżkę do pliku konfiguracyjnego.
Zanim powstanie playbook, sprawdzamy zwykłe połączenie SSH. Użytkownik powinien logować się kluczem i mieć tylko takie uprawnienia sudo, jakich wymaga automatyzacja:
ssh deploy@web01.przyklad.pl
Jeżeli SSH działa, Ansible możemy sprawdzić modułem ping:
ansible webservers -i inventory/hosts.ini \
-m ansible.builtin.ping
To nie jest sieciowy ping ICMP. Wynik pong oznacza, że Ansible zalogował się do hosta i uruchomił tam kod modułu przy użyciu Pythona. UNREACHABLE zwykle wskazuje problem z adresem, SSH, kluczem lub użytkownikiem, a FAILED oznacza, że połączenie powstało, lecz samo zadanie się nie udało.
Inventory — lista serwerów i grup
Inventory mówi Ansible, jakie hosty istnieją i do jakich grup należą. Nazwa po lewej może być czytelnym aliasem używanym w raportach, a ansible_host wskazuje prawdziwy adres DNS lub IP:
# inventory/hosts.ini
[webservers]
web01 ansible_host=192.0.2.11
web02 ansible_host=192.0.2.12
[databases]
db01 ansible_host=192.0.2.21
[production:children]
webservers
databases
[production:vars]
ansible_user=deploy
Grupa production zawiera dwie grupy podrzędne. Dzięki temu można skierować playbook do samych serwerów WWW albo do całego środowiska. Przed wykonaniem zmian warto sprawdzić, jak Ansible odczytał strukturę:
ansible-inventory -i inventory/hosts.ini --graph
ansible webservers -i inventory/hosts.ini --list-hosts
Pierwsze polecenie pokazuje drzewo grup, drugie — dokładną listę hostów wybraną przez wzorzec webservers. To prosty sposób, aby nie uruchomić zadania na szerszej grupie, niż zamierzaliśmy.
Proste parametry połączenia mogą znajdować się w inventory, ale ustawienia usług wygodniej trzymać w plikach group_vars/ i host_vars/. Haseł i kluczy nie wpisujemy otwartym tekstem do inventory; do sekretów wrócimy w części o Vault.
Playbook — czytelny opis pracy
Playbook jest plikiem YAML zawierającym jeden lub więcej bloków nazywanych plays. Każdy taki blok odpowiada na trzy pytania: na jakich hostach pracujemy, z jakimi uprawnieniami i jakie zadania wykonujemy.
# playbook.yml
- name: Konfiguracja serwerów WWW
hosts: webservers
become: true
tasks:
- name: Zainstaluj nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Upewnij się, że nginx działa i startuje z systemem
ansible.builtin.service:
name: nginx
state: started
enabled: true
hosts: webserverswybiera grupę z inventory;become: trueprosi o podniesienie uprawnień, zazwyczaj przezsudo;taskszawiera zadania wykonywane po kolei na wybranych hostach.
Jeśli sudo wymaga hasła, opcja -K poprosi o nie na początku. Nie należy wpisywać hasła bezpośrednio do playbooka:
ansible-playbook -i inventory/hosts.ini playbook.yml -K
Najpierw jednak sprawdzamy składnię i listę hostów. Kontrola składni wykryje na przykład złe wcięcie YAML, ale nie potwierdzi, że konfiguracja nginx jest poprawna ani że wdrożenie zadziała w danym środowisku:
ansible-playbook -i inventory/hosts.ini playbook.yml --syntax-check
ansible-playbook -i inventory/hosts.ini playbook.yml --list-hosts
Taski i moduły — z czego składa się playbook
Task to jeden nazwany krok, a moduł jest narzędziem wykonującym konkretną operację. Moduł ansible.builtin.apt zarządza pakietami w Debianie i Ubuntu, ansible.builtin.service usługami, ansible.builtin.copy plikami, a ansible.builtin.user kontami użytkowników.
Pełna nazwa, taka jak ansible.builtin.apt, wskazuje kolekcję i moduł. Jest dłuższa od samego apt, ale od razu wiadomo, skąd moduł pochodzi, a dokumentację jego parametrów można otworzyć lokalnie:
ansible-doc ansible.builtin.apt
Podczas wykonania Ansible pokazuje stan każdego zadania dla każdego hosta:
ok— stan już był prawidłowy, więc nic nie zmieniono;changed— zadanie wprowadziło zmianę albo w check mode przewiduje jej wprowadzenie;failed— połączenie działało, ale moduł nie wykonał zadania;unreachable— Ansible nie mógł połączyć się z hostem;skipped— zadanie pominięto, na przykład dlatego, że warunekwhenbył fałszywy.
Moduły opisujące stan są zwykle lepsze od uruchamiania surowych poleceń. ansible.builtin.command wykonuje program bez powłoki, a ansible.builtin.shell uruchamia polecenie przez powłokę i jest potrzebny dopiero dla takich konstrukcji jak potoki czy przekierowania. Samo użycie command lub shell nie daje automatycznie idempotencji. Parametry creates i removes mogą ograniczyć wykonanie na podstawie istnienia pliku, lecz jeśli istnieje właściwy moduł, zwykle lepiej użyć właśnie jego.
Idempotencja i próbne wykonanie
Idempotencja oznacza, że ponowne wykonanie tej samej operacji nie zmienia poprawnego już wyniku. Gdy nginx nie jest zainstalowany, moduł go zainstaluje i pokaże changed. Przy następnym przebiegu zobaczy zainstalowany pakiet i zwróci ok.
# Powłoka dopisze tę linię przy każdym uruchomieniu:
echo "127.0.0.1 cache.local" >> /etc/hosts
# Moduł dopilnuje, aby linia była obecna, ale jej nie powiela:
- name: Dodaj adres cache do /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "127.0.0.1 cache.local"
state: present
Idempotentny playbook pomaga usuwać configuration drift, czyli zwykły rozjazd między konfiguracją zapisaną w kodzie a faktycznym stanem serwera. Taki rozjazd powstaje na przykład wtedy, gdy ktoś ręcznie zmieni parametr tylko na jednej maszynie. Ponowne wykonanie playbooka może przywrócić wartość opisaną w repozytorium.
Ważne: idempotencja nie jest własnością każdego zadania. Moduły takie jak apt, template czy lineinfile potrafią porównać stan. Dowolny skrypt uruchomiony przez shell może przy każdym przebiegu zrobić coś ponownie. Bezpieczny playbook trzeba zaprojektować i przetestować — sama nazwa „Ansible” nie stanowi gwarancji.
Check mode symuluje wykonanie, a --diff pokazuje różnice w treści obsługiwanych plików. Najrozsądniej zacząć od jednego hosta:
ansible-playbook -i inventory/hosts.ini playbook.yml \
--check --diff --limit web01
To bardzo przydatna zapowiedź zmian, ale nie kopia rzeczywistego wykonania. Moduł bez obsługi check mode może niczego nie zrobić i niczego nie zgłosić. Zadanie zależne od wyniku wcześniejszego polecenia również może zachować się inaczej. Ponadto --diff może wypisać poufne dane z pliku; przy zadaniach z sekretami ustawiamy diff: false. Wynik próbnego przebiegu zawsze oceniamy, a potem obserwujemy wykonanie właściwe.
Zmienne, fakty i szablony Jinja2
Zmienna pozwala użyć tego samego zadania z różnymi wartościami. Zamiast kopiować rolę nginx dla każdego środowiska, możemy ustawić inną liczbę połączeń dla grupy produkcyjnej i testowej. Wartości wspólne dla serwerów WWW zapisujemy na przykład tak:
# inventory/group_vars/webservers.yml
nginx_worker_processes: auto
nginx_worker_connections: 2048
enable_gzip: true
Ansible pozwala definiować tę samą zmienną w wielu miejscach i stosuje reguły pierwszeństwa. To daje elastyczność, ale może też utrudnić znalezienie źródła wartości. Dlatego w projekcie ustalamy proste zasady i każdą zwykłą wartość definiujemy w jednym, przewidywalnym miejscu. W roli wartości przeznaczone do łatwego nadpisania umieszczamy w defaults/main.yml.
Fakty to informacje zebrane z hosta na początku play: system operacyjny, interfejsy, pamięć czy procesory. Możemy dzięki nim wykonać zadanie tylko dla konkretnej rodziny systemów:
- name: Pokaż rodzinę systemu
ansible.builtin.debug:
msg: "System należy do rodziny {{ ansible_facts['os_family'] }}"
Szablon Jinja2 łączy stałą treść pliku ze zmiennymi i warunkami. Z jednego szablonu powstaje osobna konfiguracja dla każdego hosta:
# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
}
http {
server_tokens off;
{% if enable_gzip | default(true) %}
gzip on;
{% endif %}
}
- name: Przygotuj i sprawdź konfigurację nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
backup: true
validate: nginx -t -c %s
notify: Przeładuj nginx
Moduł najpierw tworzy plik tymczasowy, a %s w poleceniu validate zostaje zastąpione jego ścieżką. Dopiero po udanej kontroli składni plik trafia pod dest. Jeżeli test się nie powiedzie, zadanie kończy się błędem i nie podmienia docelowej konfiguracji. To chroni ten konkretny etap, ale nadal warto sprawdzić zachowanie usługi na jednym hoście.
Handlery — uruchamiaj reakcję tylko po zmianie
Handler jest zadaniem uruchamianym po powiadomieniu przez notify, ale tylko wtedy, gdy zadanie powiadamiające miało stan changed. Jeśli szablon nginx nie zmienił treści pliku, usługa nie zostanie niepotrzebnie przeładowana.
handlers:
- name: Przeładuj nginx
ansible.builtin.service:
name: nginx
state: reloaded
Domyślnie handlery wykonują się pod koniec danego play. Ten sam handler uruchomi się tylko raz, nawet jeśli wcześniej powiadomiło go kilka zmienionych zadań. To pozwala wgrać kilka plików i dopiero potem raz przeładować usługę.
Jest też ważny skutek uboczny: jeśli późniejsze zadanie zakończy się błędem, oczekujący handler może się nie wykonać na tym hoście. Gdy reakcja musi nastąpić wcześniej, można świadomie użyć ansible.builtin.meta: flush_handlers. Opcja force_handlers wymusza handlery mimo późniejszego błędu, ale nie naprawi hosta, który stał się nieosiągalny. Te mechanizmy trzeba dopasować do sposobu działania konkretnej usługi.
Role — porządek i ponowne użycie
Kilkudziesięcioliniowy playbook jest czytelny, lecz duży plik obejmujący nginx, monitoring, użytkowników i kopie zapasowe szybko staje się trudny w utrzymaniu. Rola zbiera zadania, handlery, szablony i wartości domyślne dotyczące jednego obszaru:
roles/
└── nginx/
├── tasks/main.yml # zadania roli
├── handlers/main.yml # reakcje na zmiany
├── templates/nginx.conf.j2
├── files/ # pliki kopiowane bez zmian
├── defaults/main.yml # wartości łatwe do nadpisania
├── vars/main.yml # wartości trudniejsze do nadpisania
└── meta/main.yml # metadane i zależności
Pusty szkielet roli można utworzyć poleceniem:
ansible-galaxy role init roles/nginx
Następnie wpinamy role do playbooka:
- name: Konfiguracja serwerów WWW
hosts: webservers
become: true
roles:
- nginx
- hardening
- monitoring
defaults/main.yml jest dobrym miejscem na rozsądne ustawienia domyślne, które użytkownik roli ma zmieniać. vars/main.yml ma znacznie wyższy priorytet, dlatego nie umieszczamy tam wartości, które normalnie powinny różnić się między środowiskami.
Rola ułatwia wdrożenie tej samej sprawdzonej konfiguracji w wielu miejscach, ale poprawka nie „rozchodzi się” sama. Trzeba zaktualizować wersję roli w projekcie, przejrzeć zmianę, uruchomić playbook i sprawdzić wynik na każdym środowisku.
Ansible Vault — szyfrowanie danych w repozytorium
Hasła do baz i tokeny API bywają potrzebne jako zmienne, lecz nie mogą leżeć w Git w postaci jawnego tekstu. Ansible Vault szyfruje cały plik albo pojedynczą wartość. Bez hasła zamiast sekretu widać zaszyfrowaną treść.
# Utwórz zaszyfrowany plik
ansible-vault create group_vars/production/vault.yml
# Podejrzyj lub edytuj bez trwałego zapisywania jawnej kopii
ansible-vault view group_vars/production/vault.yml
ansible-vault edit group_vars/production/vault.yml
# Zaszyfruj istniejący plik
ansible-vault encrypt secrets.yml
Polecenie ansible-vault decrypt odszyfrowuje plik na dysku, więc łatwo przypadkowo dodać jego jawną wersję do repozytorium lub kopii zapasowej. Do zwykłego odczytu i zmiany bezpieczniejsze są view oraz edit.
# Hasło podawane interaktywnie dla środowiska production
ansible-playbook playbook.yml --vault-id production@prompt
# Plik lub skrypt dostarczający hasło, np. w automatyzacji
ansible-playbook playbook.yml \
--vault-id production@/bezpieczna/sciezka/vault-pass
Plik z hasłem Vault sam nie jest automatycznie szyfrowany. Powinien znajdować się poza repozytorium, mieć ograniczone uprawnienia, a w CI/CD sekret najlepiej pobierać z przeznaczonego do tego magazynu. Etykieta production pomaga rozdzielić hasła dla różnych środowisk.
chmod 600 /bezpieczna/sciezka/vault-pass
Uprawnienia 600 pozwalają czytać i zmieniać ten plik tylko jego właścicielowi. Nadal trzeba zadbać o kopie zapasowe, dostęp administratorów i miejsce przechowywania — same prawa pliku nie zastępują zarządzania sekretami.
Vault chroni dane zapisane na dysku, nie cały proces. W czasie wykonania Ansible odszyfrowuje wartości. Sekret może trafić do wyniku zadania, logu albo jawnego pliku na serwerze docelowym. Dla zadań operujących na sekretach stosujemy między innymi no_log: true, diff: false, właściwe uprawnienia pliku i kontrolę logów. no_log utrudnia diagnostykę, dlatego ustawiamy je tylko tam, gdzie jest potrzebne.
Od testu do bezpiecznego wdrożenia
Automatyzacja może szybko poprawić wiele serwerów, ale błędna zmiana również może szybko objąć wiele serwerów. Dlatego ograniczamy zasięg pierwszego wykonania. Przykładowa kolejność wygląda tak:
# 1. Składnia i rzeczywisty zakres hostów
ansible-playbook -i inventory/hosts.ini playbook.yml --syntax-check
ansible-playbook -i inventory/hosts.ini playbook.yml --list-hosts
# 2. Symulacja na jednym hoście
ansible-playbook -i inventory/hosts.ini playbook.yml \
--check --diff --limit web01
# 3. Rzeczywiste wykonanie na jednym hoście
ansible-playbook -i inventory/hosts.ini playbook.yml --limit web01
# 4. Dopiero po weryfikacji wykonanie na grupie
ansible-playbook -i inventory/hosts.ini playbook.yml --limit webservers
--limit nie zmienia zawartości playbooka. Tylko zawęża hosty w bieżącym uruchomieniu. Po etapie trzecim sprawdzamy działanie aplikacji, stan usługi, logi i monitoring — sam zielony recap Ansible potwierdza wykonanie zadań, a nie poprawne działanie całego systemu z punktu widzenia użytkownika.
Przy wielu równoważnych serwerach można wpisać w play serial: 1. Ansible wykona wtedy cały play kolejno na partiach po jednym hoście, zamiast zmieniać kilka maszyn równocześnie:
- name: Stopniowa aktualizacja serwerów WWW
hosts: webservers
serial: 1
become: true
roles:
- nginx
Takie wdrożenie kroczące zmniejsza zakres skutków błędu, ale samo nie gwarantuje braku przerwy. Aplikacja musi tolerować czasowe wyłączenie jednego węzła, a load balancer i testy zdrowia powinny być częścią procesu. Dla większej infrastruktury partia może mieć kilka hostów lub określony procent.
Jak wykorzystujemy Ansible w LinuxLab
W praktyce łączymy opisane mechanizmy w jeden proces:
- inventory i zmienne mówią, gdzie oraz z jakimi ustawieniami pracujemy;
- role i moduły opisują jaki stan chcemy uzyskać;
- Git pozwala przejrzeć historię i zaakceptować zmianę przed wdrożeniem;
- kontrola składni, check mode i walidacja plików wykrywają część problemów przed wykonaniem;
--limitlubserialograniczają zasięg pierwszego wdrożenia;- monitoring i test usługi potwierdzają, czy po zmianie działa nie tylko playbook, lecz także system.
Największą korzyścią nie jest samo wykonywanie poleceń na wielu hostach. Jest nią możliwość powtórzenia znanego procesu, przejrzenia go jak kodu i zastosowania tej samej poprawki bez polegania na pamięci administratora. To ogranicza ręczne pomyłki i skraca czas potrzebny na przywrócenie zgodnej konfiguracji, choć nie zastępuje kopii zapasowych, testów ani monitoringu.
Podsumowanie
Inventory odpowiada na pytanie „na których serwerach?”, playbook i taski — „co wykonać?”, a moduły — „jak doprowadzić zasób do wskazanego stanu?”. Zmienne i szablony pozwalają używać tego samego kodu w różnych środowiskach, handlery uruchamiają reakcje tylko po zmianie, role porządkują większy projekt, a Vault szyfruje sekrety przechowywane na dysku.
Bezpieczne użycie Ansible polega na czymś więcej niż napisaniu poprawnego YAML. Trzeba znać zakres hostów, rozumieć wynik zadań, uwzględnić ograniczenia check mode, zacząć od jednego serwera i zweryfikować usługę po wdrożeniu. Wtedy automatyzacja staje się nie tylko szybsza od pracy ręcznej, ale przede wszystkim bardziej przewidywalna.
Dokumentacja
Potrzebujesz automatyzacji i stabilnych serwerów?
Wdrażamy Ansible, monitoring i dobre praktyki, które ograniczają awarie. Porozmawiajmy o Twojej infrastrukturze — bezpłatnie i bez zobowiązań.