SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-21

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. Warunek when był 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 = 5

Dwie 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: true

Zadanie 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/app

Jeś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: false

To 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_mode

ansible_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ć.

  • command oraz shell bez creates, removes lub changed_when zgłaszają changed za każdym razem, ponieważ moduł nie ma możliwości sprawdzenia, czy cokolwiek się wydarzyło. Dodaj creates lub ustaw changed_when w odniesieniu do ciągu znaków w wyjściu.
  • ansible.builtin.file z state: touch zgłasza changed przy każdym uruchomieniu zgodnie z założeniem, ponieważ operacja dotknięcia pliku aktualizuje jego znaczniki czasu. Użyj state: 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 z ansible_date_time, wywołanie now() 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.user z password: "{{ pw | password_hash('sha512') }}" zmienia się przy każdym uruchomieniu, ponieważ password_hash wybiera 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: latest w module pakietów zgłasza changed, gdy tylko dostępna jest aktualizacja. To zachowanie jest poprawne. Jest to również powód, dla którego state: latest tworzy playbook, którego wyniku nie można przewidzieć. Użyj state: present i wykonuj aktualizacje celowo.
  • ansible.builtin.unarchive wskazujący na adres URL bez creates pobiera 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: webapp

Pierwsza 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ń

  1. ansible-playbook site.yml --syntax-check wykrywa błędy w składni YAML i strukturze bez nawiązywania połączenia sieciowego.
  2. ansible-playbook site.yml --limit web1 --list-hosts weryfikuje, czy wzorzec odpowiada oczekiwaniom.
  3. ansible-playbook site.yml --limit web1 --check --diff to symulacja działania. Należy przeanalizować wynikowy diff.
  4. ansible-playbook site.yml --limit web1 --diff wprowadza zmiany na wybranym hoście.
  5. Należy ponownie wykonać krok 4. Wszystkie zadania powinny zgłosić status ok. Każdy element oznaczony jako changed wymaga poprawy przed wdrożeniem zmian w pozostałej części infrastruktury.
  6. ansible-playbook site.yml --check --diff dla 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.

#ansible#check-mode#idempotency#automation#safety