Ansible check mode i --diff: jak bezpiecznie testować
Dowiedz się, jak działają flagi --check oraz --diff w Ansible. Wyjaśniamy ograniczenia trybu dry run i dlaczego moduły bez wsparcia dla tego trybu mogą generować fałszywe wyniki.
Na czym polega tryb sprawdzania w Ansible
Tryb sprawdzania w Ansible to symulacja wykonania (dry run): ansible-playbook --check łączy się z każdym hostem w playbooku, pyta każdy moduł, czy bieżący stan jest zgodny z oczekiwanym, i raportuje planowane zmiany bez wprowadzania jakichkolwiek modyfikacji. Dodanie flagi --diff powoduje wyświetlenie zawartości plików przed i po zmianach, które zostałyby wprowadzone. Łącznie pozwalają one odpowiedzieć na kluczowe pytanie przed każdym rzeczywistym uruchomieniem: co dokładnie zmieni się na tych serwerach?
Tryb sprawdzania nie jest symulacją całego playbooka. Nie istnieje żaden model serwera. Każdy moduł jest po prostu proszony o sprawdzenie stanu zamiast o jego modyfikację. Moduł, który potrafi wykonać operację w trybie tylko do odczytu, raportuje changed i przechodzi dalej. Moduł, który nie potrafi odpowiedzieć, nie wykonuje żadnej akcji i niczego nie raportuje. Dokumentacja Ansible ujmuje to w jednym zdaniu: „Moduły, które nie wspierają trybu sprawdzania, nie raportują niczego i nie wykonują żadnych działań”. Ta luka sprawia, że symulacja może dostarczyć błędnych informacji, dlatego większość tego przewodnika poświęcona jest właśnie temu zagadnieniu.
Uruchomienie testu na sucho: --check oraz --diff
ansible-playbook -i inventory.ini site.yml --check --diff --limit web1-C oraz -D to skrócone formy tych dwóch flag. Użycie --limit jest celowe. Różnice (diff) dla jednego hosta są czytelne. Różnice dla dwudziestu hostów wymagają przewijania.
Cały raport opiera się na czterech słowach określających wynik.
ok: [web1]oznacza, że moduł sprawdził stan i jest on zgodny z oczekiwanym. Żadne zmiany nie zostaną wprowadzone.changed: [web1]oznacza, że moduł wprowadziłby zmiany. Przy użyciu--diff, powyższe linie pokazują, jakie to zmiany.skipping: [web1]oznacza, że zadanie nie zostało ocenione. Warunekwhenbył fałszywy lub moduł nie obsługuje trybu sprawdzania.fatal: [web1]oznacza, że zadanie nie powiodło się podczas sprawdzania. Przeczytaj komunikat przed założeniem, że playbook jest uszkodzony.
--diff wyświetla ujednolicony diff dla modułów plików, z usuniętymi liniami oznaczonymi - i dodanymi liniami oznaczonymi +, pod nagłówkiem, którego linie zaczynają się od --- before oraz +++ after i wskazują ścieżkę docelową. Moduły, które nie zapisują plików, wyświetlają własny stan przed i po, więc ansible.builtin.user pokazuje atrybuty, które zostałyby zmienione, zamiast zawartości pliku.
Włącz diff na stałe w ansible.cfg, aby nigdy nie zapomnieć o tej fladze:
[diff]
always = true
context = 5Dwie mniej kosztowne metody sprawdzania powinny poprzedzać tryb check. ansible-playbook site.yml --syntax-check analizuje składnię YAML oraz strukturę play bez łączenia się z jakimkolwiek hostem. ansible-playbook site.yml --list-tasks wyświetla zadania, które zostałyby wykonane; w ten sposób można sprawdzić, czy rola, która miała być otagowana, faktycznie jest. Żadne z tych poleceń nie nawiązuje połączenia, więc oba działają natychmiastowo.
Tryb check nawiązuje połączenie. Otwiera sesję SSH do każdego hosta w schemacie i zbiera fakty, więc host, który jest wyłączony, spowoduje niepowodzenie testu na sucho. Jest to użyteczny sygnał sam w sobie i dlatego decyzja o tym, jak playbook powinien traktować nieosiągalne hosty jest istotna przed umieszczeniem testu na sucho w CI.
Dlaczego tryb sprawdzania kończy się niepowodzeniem na nowym serwerze
Ten playbook jest poprawny. Uruchomienie go z flagą --check na serwerze, na którym nie ma jeszcze nginx, powoduje niepowodzenie większości zadań.
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Write the site config
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/conf.d/site.conf
- name: Start and enable nginx
ansible.builtin.service:
name: nginx
state: started
enabled: trueZadanie apt zgłasza changed i jest to poprawne zachowanie: pakiet nie jest zainstalowany, więc rzeczywiste uruchomienie spowodowałoby jego instalację. Tryb sprawdzania nie zainstalował go. Zadanie template kończy się niepowodzeniem, ponieważ /etc/nginx/conf.d/ nie istnieje na tym hoście i nic go nie utworzyło. Zadanie service również kończy się niepowodzeniem, ponieważ nie istnieje jednostka nginx, której stan można by sprawdzić. Żaden z tych błędów nie wynika z usterki w playbooku. Próba wykonania operacji w trybie suchym (dry run) nie powiodła się z powodu braku wymaganego stanu, co dokumentacja określa ostrzeżeniem, że tryb sprawdzania nie dostarcza użytecznych danych dla zadań, których wynik zależy od zmian wprowadzonych przez poprzednie zadania.
Oto uczciwa wersja tej zasady: tryb sprawdzania jest dokładny w odniesieniu do hosta, który został już skonfigurowany przez playbook, natomiast generuje dużo szumu w przypadku nowego hosta. Uruchomienie --check, w którym każde zadanie zgłasza ok, jest wiarygodną informacją o skonfigurowanym hoście, ponieważ oznacza, że żadne zmiany nie są wymagane. Na zupełnie nowym hoście --check informuje głównie o tym, że host jest nowy. Pisząc swój pierwszy playbook Ansible dla VPS, należy spodziewać się, że pierwszy testowy przebieg będzie pełen błędów, a ocenę playbooka należy przeprowadzić na podstawie drugiego uruchomienia.
Dlaczego zadania typu command i shell są pomijane w trybie sprawdzania
ansible.builtin.command oraz ansible.builtin.shell nie posiadają informacji o działaniu wykonywanego polecenia. Nie istnieje bezpieczny sposób na uruchomienie dowolnego pliku binarnego w trybie tylko do odczytu, dlatego w trybie sprawdzania (check mode) moduł odmawia jego wykonania. Wynik zadania zawiera skipped: true oraz komunikat Command would have run if not in check mode, a w danych wyjściowych widoczny jest status skipping: [web1].
Dokumentacja modułu określa wsparcie dla trybu sprawdzania jako „częściowe”, a wskazanym obejściem jest użycie creates oraz removes. Należy podać ścieżkę creates, aby tryb sprawdzania mógł przynajmniej ocenić test pliku:
- name: Extract the release bundle
ansible.builtin.command: /usr/bin/tar xf /tmp/app.tar.gz -C /opt/app
args:
creates: /opt/app/bin/appJeśli /opt/app/bin/app już istnieje, tryb sprawdzania zgłasza Would not run command since '/opt/app/bin/app' exists, co stanowi wiarygodną odpowiedź. Jeśli ścieżka nie istnieje, zwracany jest wynik Command would have run if not in check mode, co również jest wiarygodną odpowiedzią. Bez creates zadanie to pozostaje pustym punktem w symulacji wykonania.
Efekt domina jest poważniejszy niż samo pominięcie zadania. Pominięte zadanie nadal rejestruje wynik, jednak jest to wynik pominięcia, który nie zawiera klucza stdout. Warunek kolejnego zadania kończy się niepowodzeniem podczas ewaluacji, generując błąd zbliżony do 'dict object' has no attribute 'stdout'. Playbook działa poprawnie podczas rzeczywistego uruchomienia, ale zawodzi w trybie symulacji, co stanowi najbardziej mylącą awarię w ramach tej funkcjonalności.
check_mode: false oraz jedyne miejsce, w którym należy go użyć
check_mode: false przy zadaniu oznacza „wykonaj to naprawdę, nawet w trybie --check”. Jest to rozwiązanie problemu pomijanych poleceń i jest bezpieczne tylko w przypadku zadań typu odczyt.
- name: Read the installed app version
ansible.builtin.command: /usr/local/bin/app --version
register: app_version
check_mode: false
changed_when: falseTo zadanie jest uczciwe w obu trybach. Odczytuje wersję i nigdy nie zapisuje danych, changed_when: false zapobiega raportowaniu zmian, które nie zostały wprowadzone, a check_mode: false sprawia, że app_version.stdout istnieje podczas suchego przebiegu, dzięki czemu warunki oparte na tej zmiennej są nadal sprawdzane.
Przed wklejeniem tego słowa kluczowego w inne miejsce należy zrozumieć jego dosłowne znaczenie. Zadanie z check_mode: false zapisuje dane na serwerach podczas ansible-playbook --check. Użycie go w zadaniu apt lub template sprawia, że suchy przebieg wygląda czyściej, ale przestaje być suchym przebiegiem. Jeśli zadania zapisu nie da się uczynić bezpiecznym, należy je zabezpieczyć w inny sposób:
- name: Apply the database migration
ansible.builtin.command: /usr/local/bin/app migrate --apply
when: not ansible_check_modeansible_check_mode to magiczna zmienna, którą Ansible ustawia na true podczas uruchomienia w trybie sprawdzania. Istnieje również odwrotne słowo kluczowe. check_mode: true wymusza tryb sprawdzania dla zadania zawsze, nawet podczas rzeczywistego uruchomienia, co zmienia je w sondę wykrywającą rozbieżności: zarejestruj wynik, a raport changed oznacza, że stan hosta nie jest już zgodny z wymaganiami zadania.
Dlaczego zadanie zgłasza zmianę przy każdym uruchomieniu
Uruchom playbook dwukrotnie, jeden po drugim, bez żadnych zmian w międzyczasie. Przy drugim uruchomieniu każde zadanie powinno zgłosić ok. Jeśli jakiekolwiek zadanie nadal zgłasza changed, oznacza to jedną z dwóch rzeczy: moduł nie jest w stanie odczytać stanu, którym zarządza, lub dane wejściowe są niestabilne. Oba problemy można naprawić i żaden z nich nie jest szumem, który należy ignorować.
commandorazshellbezcreates,removeslubchanged_whenzgłaszająchangedza każdym razem, ponieważ moduł nie ma możliwości sprawdzenia, czy cokolwiek się wydarzyło. Dodajcreateslub ustawchanged_whenw odniesieniu do ciągu znaków w wyjściu.ansible.builtin.filezstate: touchzgłaszachangedprzy każdym uruchomieniu zgodnie z założeniem, ponieważ operacja dotknięcia pliku aktualizuje jego znaczniki czasu. Użyjstate: file, jeśli celem było jedynie ustawienie właściciela lub uprawnień.template, którego wygenerowana treść ulega zmianie, nadpisuje plik przy każdym uruchomieniu. Znacznik czasu zansible_date_time, wywołanienow()lub hasło generowane za każdym razem na nowo tworzą inne bajty, więc moduł poprawnie zgłasza zmianę. Usuń zmienną wartość z szablonu.ansible.builtin.userzpassword: "{{ pw | password_hash('sha512') }}"zmienia się przy każdym uruchomieniu, ponieważpassword_hashwybiera losową sól przy każdym wywołaniu, więc wynikowy skrót nigdy nie pasuje do tego, który znajduje się w/etc/shadow. Przekaż jawną sól pochodzącą ze stabilnego źródła.state: latestw module pakietów zgłaszachanged, gdy tylko dostępna jest aktualizacja. To zachowanie jest poprawne. Jest to również powód, dla któregostate: latesttworzy playbook, którego wyniku nie można przewidzieć. Użyjstate: presenti wykonuj aktualizacje celowo.ansible.builtin.unarchivewskazujący na adres URL bezcreatespobiera i rozpakowuje pliki ponownie. Podaj ścieżkęcreates.
--diff to najszybszy sposób na rozróżnienie tych przypadków. Jeśli zadanie zgłasza changed, a różnice (diff) pokazują odmienne bajty, dane wejściowe są niestabilne. Jeśli zgłasza changed, a różnice nie pokazują niczego, moduł nie jest w stanie wyrazić, co zmienił, co zazwyczaj oznacza zadanie command lub zapis wyłącznie metadanych, takich jak znacznik czasu.
Nie używaj changed_when: false, aby wyciszyć głośne zadanie. Powoduje to stłumienie raportu, więc notify nigdy nie zostanie wyzwolone, a handler restartujący usługę nie zostanie uruchomiony. Zamiast tego napraw zadanie.
Ograniczanie zasięgu awarii: --limit, --tags oraz --step
Tryb sprawdzania (check mode) informuje o planowanych zmianach. Poniższe flagi określają, ile maszyn zostanie przetworzonych jednocześnie.
--limit zawęża wykonanie playbooka do podzbioru inwentarza. Przyjmuje te same wzorce co hosts:, zatem działają zarówno --limit web1, jak i --limit 'webservers:!web3'. Wzorzec należy ująć w cudzysłów. Nieujęty w cudzysłów ! w interaktywnej sesji bash wywołuje rozwinięcie historii na wykrzykniku, przez co powłoka modyfikuje polecenie, zanim dotrze ono do Ansible.
Przed wykonaniem operacji należy zweryfikować wzorzec. ansible-playbook site.yml --limit 'webservers:!web3' --list-hosts wyświetla dopasowane hosty i kończy działanie bez nawiązywania połączeń. Wzorzec, który nie pasuje do żadnego hosta, jest bezpieczny, ponieważ Ansible nie wykonuje operacji na całym inwentarzu. Wyświetlane jest ostrzeżenie o braku dopasowania wzorca, a następnie błąd informujący, że hosty oraz --limit nie pasują do żadnego zdefiniowanego hosta. Znajomość sposobu definiowania grup w pliku inwentarza pozwala na przewidywalne stosowanie wzorców.
--tags deploy uruchamia tylko zadania z określonymi tagami, a --skip-tags packages uruchamia wszystkie pozostałe. --list-tags wyświetla dostępne tagi. Tagi stają się przydatne, gdy playbook staje się zbyt rozbudowany, by uruchamiać go w całości, co jest również jednym z powodów podziału długiego playbooka na role.
--start-at-task "Write the site config" wznawia nieudane wykonanie od wskazanego zadania. Należy używać tej opcji do odzyskiwania stanu, pamiętając o konsekwencjach: wszystkie zadania przed wskazanym punktem są pomijane, w tym zadania ustawiające fakty lub rejestrujące zmienne wymagane przez dalsze etapy.
--step wyświetla monit przed każdym zadaniem i oczekuje odpowiedzi: tak, nie lub kontynuuj. Jest to proces powolny, lecz stanowi właściwe narzędzie przy pierwszym uruchomieniu operacji destrukcyjnych, ponieważ pozwala zatrzymać działanie między poszczególnymi zadaniami, zamiast po wykonaniu dwudziestu z nich.
Wdrażanie zmian z użyciem serial
Domyślnie Ansible wykonuje jedno zadanie na wszystkich hostach w play przed przejściem do kolejnego. Jest to szybkie, ale oznacza, że błędne zadanie dociera do całej floty w tej samej sekundzie. Zanim administrator odczyta błąd i naciśnie Ctrl-C, zmiana jest już wszędzie.
serial dzieli play na partie. Cały play jest wykonywany najpierw na pierwszej partii, a następnie na kolejnych.
- name: Roll out the web tier
hosts: webservers
serial: [1, 5, "30%"]
max_fail_percentage: 0
tasks:
- name: Deploy the release
ansible.builtin.include_role:
name: webappPierwsza partia składa się z jednego hosta. Jeśli operacja zakończy się powodzeniem, druga partia obejmuje pięć hostów, a każda kolejna to 30 procent hostów w play. max_fail_percentage: 0 przerywa play, gdy tylko którykolwiek host w partii zgłosi błąd, dzięki czemu wadliwe wdrożenie zatrzymuje się na jednej maszynie. any_errors_fatal: true to bardziej radykalna wersja, która przerywa play dla wszystkich w momencie pierwszej awarii hosta.
Uruchamianie zadań najpierw na jednym hoście nie jest przejawem paranoi, a powód jest konkretny. Grupy w inwentarzu ulegają rozbieżnościom. Serwer dodany sześć miesięcy po pozostałych może mieć inną wersję dystrybucji, posiadać usługę zainstalowaną ręcznie lub mieć inaczej skonfigurowane dyski. Playbook jest poprawny dla grupy, ale błędny dla tego jednego hosta, a żaden testowy przebieg na już skonfigurowanym hoście tego nie wykaże. Zarządzanie flotą serwerów Linux to w dużej mierze praktyka wykrywania nietypowych hostów, zanim zrobi to wprowadzana zmiana.
Kolejność wykonywania zadań
ansible-playbook site.yml --syntax-checkwykrywa błędy w składni YAML i strukturze bez nawiązywania połączenia sieciowego.ansible-playbook site.yml --limit web1 --list-hostsweryfikuje, czy wzorzec odpowiada oczekiwaniom.ansible-playbook site.yml --limit web1 --check --diffto symulacja działania. Należy przeanalizować wynikowy diff.ansible-playbook site.yml --limit web1 --diffwprowadza zmiany na wybranym hoście.- Należy ponownie wykonać krok 4. Wszystkie zadania powinny zgłosić status
ok. Każdy element oznaczony jakochangedwymaga poprawy przed wdrożeniem zmian w pozostałej części infrastruktury. ansible-playbook site.yml --check --diffdla całego inwentarza zwraca teraz miarodajne wyniki, ponieważ skonfigurowane hosty nie zgłaszają zmian, a pozostałe wpisy stanowią rzeczywistą różnicę.
Ostrzeżenie dotyczące kroku 3. --diff wyświetla zawartość plików w terminalu oraz w logach zadań CI, co oznacza, że szablon renderujący hasło do bazy danych ujawni je w logach. Należy ustawić diff: false dla tego zadania, aby pominąć jego wynik, lub no_log: true, aby ukryć cały rezultat, a samą wartość przechowywać w zaszyfrowanym pliku Ansible Vault, zamiast w repozytorium.
FAQ
Czy ansible-playbook --check wprowadza jakiekolwiek zmiany na serwerze?
Nie, z jednym wyjątkiem, który podlega kontroli użytkownika. W trybie sprawdzania każdy moduł jest proszony o raportowanie zamiast zapisu, a moduły, które nie potrafią tego zrobić, nie raportują niczego i nie wykonują żadnych działań. Wyjątkiem jest słowo kluczowe zadania check_mode: false, które wymusza rzeczywiste wykonanie tego pojedynczego zadania nawet podczas uruchomienia z flagą --check. Przed zaufaniem wynikom testu (dry run) należy przeszukać playbooki i role pod kątem check_mode: false oraz upewnić się, że każde dopasowanie dotyczy zadania, które jedynie odczytuje stan.
Jaka jest różnica między --check a --diff?
--check decyduje o tym, czy cokolwiek zostanie wykonane w rzeczywistości. --diff decyduje o poziomie szczegółowości raportu. Samo --check informuje jedynie, że plik uległby zmianie. Samo --diff wprowadza zmianę i pokazuje zmodyfikowane linie. Należy używać ich razem podczas testów, a flagę --diff warto włączyć również dla rzeczywistych uruchomień, ustawiając always = true w sekcji [diff] w pliku ansible.cfg.
Dlaczego moje zadanie Ansible raportuje zmianę przy każdym uruchomieniu?
Dzieje się tak, ponieważ moduł nie jest w stanie rozpoznać stanu, którym zarządza, lub przekazana mu wartość jest za każdym razem inna. command oraz shell zawsze raportują changed, chyba że dodasz creates lub changed_when. file z użyciem state: touch zmienia się zgodnie z założeniem. Szablon, który generuje znacznik czasu lub świeżo utworzone hasło, tworzy za każdym razem inne bajty, więc plik jest faktycznie nadpisywany. Uruchom playbook dwukrotnie pod rząd: każde zadanie, które nadal wykazuje changed przy drugim przebiegu, wymaga poprawy.
Dlaczego moje zadania command i shell są pomijane podczas testu?
Ponieważ nie istnieje sposób na uruchomienie dowolnego polecenia w trybie tylko do odczytu. W trybie sprawdzania moduł command ustawia skipped: true z komunikatem Command would have run if not in check mode. Dodaj creates lub removes, aby tryb sprawdzania mógł ocenić test pliku. W przypadku zadania, które tylko odczytuje stan, ustaw check_mode: false wraz z changed_when: false, aby zarejestrowany wynik istniał podczas testu i aby warunki oparte na tym wyniku nadal działały.
Dlaczego tryb sprawdzania kończy się niepowodzeniem na nowym serwerze, a przechodzi na istniejącym?
Ponieważ tryb sprawdzania nie tworzy stanu, od którego zależą późniejsze zadania. Test na hoście bez zainstalowanego nginx raportuje instalację jako changed, a następnie kończy się błędem przy zadaniu zapisującym w /etc/nginx/conf.d/, ponieważ ten katalog nigdy nie został utworzony. Jest to oczekiwane zachowanie. Tryb sprawdzania służy do wykrywania rozbieżności (drift) na hostach, które playbook już skonfigurował. Nie służy do walidacji pierwszego uruchomienia. Na nowym hoście należy zastosować playbook na jednej maszynie i przeanalizować wynik drugiego uruchomienia.