Ansible: szablony Jinja2 i handlery w praktyce
Dowiedz się jak renderować pliki konfiguracyjne nginx za pomocą szablonów Jinja2 oraz używać handlerów do restartu usług. Sprawdź jak zapewnić idempotencję uruchomień.
Co szablony i handlery Ansible wnoszą do pierwszego playbooka
Szablony i handlery Ansible to dwa elementy, które zmieniają statyczny playbook w użyteczne narzędzie. Szablon generuje plik konfiguracyjny na podstawie zmiennych, dzięki czemu jeden plik obsługuje każdy host. Handler uruchamia się tylko wtedy, gdy zadanie faktycznie wprowadziło zmiany, więc usługa przeładowuje się przy rzeczywistej modyfikacji konfiguracji, a w pozostałych przypadkach pozostaje bez zmian.
Ten przewodnik kontynuuje temat dokładnie w miejscu, w którym kończy się twój pierwszy playbook Ansible na VPS. Posiadasz już play, który instaluje pakiet i uruchamia usługę. Wszystkie poniższe operacje wykonuje się na jednej maszynie, ponieważ play jest kierowany na localhost poprzez połączenie lokalne. Nie potrzebujesz drugiego serwera, aby wykonać te kroki. Ten sam play zadziała na rzeczywistych hostach z inwentarza bez zmian w zadaniach, a ostatnia sekcja opisuje, co ulega zmianie.
Konfiguracja katalogu roboczego
sudo apt update
sudo apt install -y ansible nginx
ansible --version
mkdir -p ~/ansible-templates/templates
cd ~/ansible-templatesnginx znajduje się tutaj tylko dlatego, że jest rzeczywistą usługą z plikiem konfiguracyjnym i poleceniem przeładowania, co stanowi wszystko, czego wymaga ten przykład. ansible --version wyświetla wersję ansible-core oraz używany interpreter Python. Należy zanotować obie te wartości. Poniższy playbook używa w pełni kwalifikowanych nazw modułów, takich jak ansible.builtin.template, które wymagają Ansible 2.10 lub nowszego; każdy obecnie dostępny pakiet dystrybucyjny spełnia ten wymóg.
Utwórz inventory.ini:
[local]
localhost ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}"ansible_connection=local instruuje Ansible, aby uruchamiał każde zadanie jako proces lokalny zamiast otwierać sesję SSH do samego siebie. Drugie ustawienie nie jest zbędne. Gdy w pliku inwentarza wpiszesz localhost, staje się on zwykłym hostem i traci interpreter, który Ansible domyślnie przypisuje do niejawnego localhost. W rezultacie system wraca do automatycznego wykrywania interpretera i może wybrać inną wersję języka Python niż ta, która uruchamia playbook. ansible_playbook_python to interpreter uruchamiający obecnie ansible-playbook, co zapewnia spójność obu środowisk.
Utwórz ansible.cfg:
[defaults]
inventory = inventory.iniBez tego pliku konieczne jest przekazywanie -i inventory.ini w każdym poleceniu. Przy braku inwentarza Ansible wyświetla [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all', a playbook z hosts: all nie dopasuje żadnego hosta. Jeszcze jedna uwaga dotycząca ansible.cfg: Ansible ignoruje ten plik, jeśli znajduje się on w katalogu z uprawnieniami zapisu dla wszystkich użytkowników, dlatego projekt należy przechowywać w katalogu domowym. Plik inwentarza zawiera więcej niż tylko listę hostów, a to jest najmniejszy możliwy plik, który spełnia to zadanie.
template kontra copy – kiedy stosować poszczególne rozwiązania
ansible.builtin.copy przesyła plik w niezmienionej postaci. ansible.builtin.template najpierw przetwarza plik za pomocą Jinja2, a następnie przesyła wynik. Dokumentacja modułu opisuje template jako „moduł wirtualny, który jest w całości zaimplementowany jako wtyczka akcji i uruchamiany na kontrolerze”. Ma to istotną konsekwencję: renderowanie odbywa się na maszynie, na której wywołano ansible-playbook. Host docelowy nigdy nie otrzymuje zmiennych i nie wymaga zainstalowanego Jinja2.
Używaj copy, gdy plik jest identyczny na każdym hoście. Używaj template, gdy choć jedna wartość różni się w zależności od hosta lub gdy wymagana jest pętla {% for %} albo blok {% if %}. copy posiada parametr content:, a zmienne wewnątrz niego są podstawiane tak samo jak w przypadku innych argumentów zadań, jednak nie obsługuje on pętli ani instrukcji warunkowych. Z tego powodu wszelkie pliki o złożonej strukturze powinny być obsługiwane jako szablony. Oba moduły przyjmują te same opcje plików, ponieważ korzystają z tych samych fragmentów dokumentacji, dzięki czemu owner, group, mode, backup oraz validate działają w nich w identyczny sposób.
Przygotowanie szablonu: jedna zmienna, jedna pętla
Zapisz to jako templates/app.conf.j2:
# {{ ansible_managed }}
upstream {{ app_name }}_backend {
{% for backend in app_backends %}
server {{ backend.host }}:{{ backend.port }} weight={{ backend.weight }};
{% endfor %}
}
server {
listen {{ app_listen_port }};
server_name {{ app_server_name }};
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
}
}Dwa rodzaje znaczników Jinja2 wykonują tutaj pracę. {{ ... }} to wyrażenie, które wypisuje swoją wartość. {% ... %} to instrukcja, która sama w sobie niczego nie wypisuje. app_backends jest listą słowników, więc backend.host odczytuje jeden klucz z każdego wpisu, a pętla zapisuje jedną linię server dla każdego wpisu, niezależnie od ich liczby.
Jeden szczegół dotyczący białych znaków, ponieważ zaskakuje on osoby znające Jinja2 z innych środowisk. Ansible domyślnie ustawia trim_blocks na yes, czego samo Jinja2 nie robi, więc znak nowej linii bezpośrednio po znaczniku {% ... %} jest usuwany, a pętla nie pozostawia po sobie pustej linii. Ansible pozostawia lstrip_blocks na no, więc wszelkie spacje umieszczone przed znacznikiem {% są zachowywane i pojawiają się w wygenerowanym pliku. Jeśli wynik zawiera niepożądane wcięcia, ustaw lstrip_blocks: true w zadaniu szablonu.
{{ ansible_managed }} domyślnie renderuje się jako tekst dosłowny Ansible managed. Pozostaw to ustawienie bez zmian. Użytkownicy często redefiniują ansible_managed w ansible.cfg, aby uwzględnić datę, a w momencie gdy to robią, wygenerowany plik różni się przy każdym uruchomieniu, zadanie zgłasza zmianę przy każdym uruchomieniu, a usługa przeładowuje się przy każdym uruchomieniu. To jedno ustawienie niszczy właściwość, której dotyczy reszta tego przewodnika. Rozszerzenie .j2 jest konwencją, a Ansible go nie weryfikuje.
Playbook
Zapisz to jako site.yml:
- name: Render an nginx site from a template
hosts: local
become: true
vars:
app_name: learn
app_listen_port: 8080
app_server_name: learn.example.com
app_backends:
- host: 127.0.0.1
port: 9001
weight: 3
- host: 127.0.0.1
port: 9002
weight: 1
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Render the site configuration
ansible.builtin.template:
src: templates/app.conf.j2
dest: "/etc/nginx/conf.d/{{ app_name }}.conf"
owner: root
group: root
mode: '0644'
backup: true
notify: nginx config changed
- name: Make sure nginx is enabled and running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Test the nginx configuration
ansible.builtin.command:
cmd: /usr/sbin/nginx -t
changed_when: false
listen: nginx config changed
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
listen: nginx config changedmode: '0644' jest celowo ujęte w cudzysłów. Dokumentacja opcji plików wskazuje na konieczność cytowania liczb ósemkowych, „aby Ansible otrzymało ciąg znaków i mogło samodzielnie wykonać konwersję z ciągu na liczbę”. Bez cudzysłowu parser YAML odczytuje 0644 jako zwykłą liczbę, co może prowadzić do uzyskania nieoczekiwanych uprawnień.
notify: nginx config changed wskazuje temat, a nie konkretny handler. Oba handlery posiadają listen: nginx config changed, więc jedno powiadomienie (notify) aktywuje oba. Dodanie w przyszłości trzeciego handlera z tą samą linią listen nie wymaga edycji zadania szablonu. cache_valid_time: 3600 zapobiega ponownemu łączeniu się z serwerami lustrzanymi pakietów, jeśli uruchomienie nastąpiło w ciągu tej samej godziny.
Uruchomienie jednorazowe i analiza wyjścia
ansible-playbook site.ymlJeśli polecenie sudo wymaga hasła, należy dodać flagę -K, co spowoduje, że Ansible poprosi o jego podanie.
Należy najpierw przeanalizować wiersze dotyczące poszczególnych zadań, a następnie podsumowanie PLAY RECAP na dole. Każde zadanie wyświetla changed:, gdy Ansible musiało wykonać operację, lub ok:, gdy host znajdował się już w pożądanym stanie; podsumowanie zlicza te wartości dla każdego hosta. Dopiero po zakończeniu wszystkich zadań w ramach play wyświetlane jest RUNNING HANDLER [Test the nginx configuration], a po nim RUNNING HANDLER [Reload nginx].
Teraz należy sprawdzić stan maszyny, zamiast polegać wyłącznie na wyjściu polecenia:
sudo cat /etc/nginx/conf.d/learn.conf
sudo /usr/sbin/nginx -t
curl -sI http://127.0.0.1:8080/Polecenie nginx -t wyświetla nginx: configuration file /etc/nginx/nginx.conf test is successful, jeśli złożona konfiguracja jest poprawna składniowo. Polecenie curl zwraca linię statusu z nginx, a 502 Bad Gateway jest tutaj poprawną odpowiedzią, ponieważ blok serwera jest aktywny, a na portach 9001 lub 9002 nie nasłuchuje żaden proces. Komunikat sudo tail /var/log/nginx/error.log podaje przyczynę w sposób bezpośredni: connect() failed (111: Connection refused) while connecting to upstream.
Uruchomienie po raz drugi w celu weryfikacji idempotencji
ansible-playbook site.ymlTo uruchomienie jest kluczowe, dlatego należy porównać jego wynik z pierwszym, wiersz po wierszu. Zadanie szablonu powinno teraz wyświetlić ok: w miejscu, w którym wcześniej widniało changed:, a żaden z handlerów nie powinien pojawić się w danych wyjściowych.
Mechanizm jest prosty i warto go znać, ponieważ stanowi podstawę debugowania. template renderuje plik na kontrolerze i porównuje sumę kontrolną wyniku z sumą kontrolną pliku znajdującego się już w dest. Zgodność zawartości, właściciela i uprawnień oznacza brak konieczności wykonania jakichkolwiek działań, dlatego zadanie raportuje ok, przez co notify nigdy nie jest wyzwalane, a handler nie zostaje uruchomiony. Handlery uruchamiają się wyłącznie w przypadku changed i w żadnym innym.
Należy również zweryfikować działanie w drugą stronę. Zmień weight: 3 na weight: 1 w vars, uruchom ponownie play, a zadanie szablonu zaraportuje changed, oba handlery zostaną wykonane, a sudo cat /etc/nginx/conf.d/learn.conf pokaże nową wartość.
Jeśli drugie, identyczne uruchomienie nadal raportuje zmianę, renderowanie nie jest stabilne. W pierwszej kolejności należy poszukać w danych wyjściowych elementów zależnych od czasu, ponieważ jest to częsta przyczyna, a za taki stan rzeczy zazwyczaj odpowiada niestandardowy ansible_managed. Następnie należy sprawdzić, czy mode oraz owner w zadaniu są zgodne z tym, co faktycznie znajduje się na dysku, ponieważ rozbieżność w tym zakresie stanowi zmianę, nawet jeśli bajty są identyczne.
Podgląd zmian przed ich wprowadzeniem
ansible-playbook site.yml --check --diff--check uruchamia playbook bez wprowadzania zmian na hoście. --diff wyświetla przewidywane skutki każdego zadania, co w przypadku template oznacza prezentację różnic wiersz po wierszu między wygenerowaną zawartością a plikiem na dysku. Łącznie pozwalają one odpowiedzieć na pytanie „co wykona to polecenie” bez faktycznego wprowadzania zmian. Tryb sprawdzania (check mode) ma swoje ograniczenia, głównie w przypadku zadań, których wynik zależy od wcześniejszego zadania, które w trybie sprawdzania nie zostało faktycznie wykonane.
Dlaczego handlery czekają do końca play
Dokumentacja handlerów określa to wprost: "Domyślnie handlery uruchamiane są po zakończeniu wszystkich zadań w danej sekcji play. Powiadomione handlery są wykonywane automatycznie po każdej z następujących sekcji, w podanej kolejności: pre_tasks, roles/tasks oraz post_tasks."
Powodem jest przetwarzanie wsadowe. Play, który generuje cztery pliki konfiguracyjne dla jednej usługi, powinien zrestartować tę usługę tylko raz, na końcu, gdy wszystkie cztery pliki są już na miejscu. Restart po każdym pliku spowodowałby cztery restarty, z których trzy ładowałyby niekompletną konfigurację. Ta sama strona jasno określa gwarancję: "Powiadomienie tego samego handlera wielokrotnie spowoduje jego wykonanie tylko raz, niezależnie od tego, ile zadań go powiadomiło".
Kolejność również jest ustalona: "Handlery są wykonywane w kolejności, w jakiej zdefiniowano je w sekcji handlers, a nie w kolejności wymienionej w instrukcji notify". Dlatego Test the nginx configuration znajduje się powyżej Reload nginx w playbooku. Test uruchamia się jako pierwszy, ponieważ został zapisany jako pierwszy, a żaden element w linii notify nie ma na to wpływu.
Wymuszanie wcześniejszego uruchomienia handlerów oraz ich działanie po awarii
Czasami kolejne zadanie w tym samym play wymaga, aby usługa działała już z nową konfiguracją. W takim przypadku należy wymusić wykonanie powiadomionych handlerów za pomocą modułu meta, który według dokumentacji sprawia, że "Ansible wykonuje wszystkie zadania handlerów, które zostały do tej pory powiadomione".
- name: Run the notified handlers now instead of at the end of the play
ansible.builtin.meta: flush_handlers
- name: Wait for the new listener to accept connections
ansible.builtin.wait_for:
host: 127.0.0.1
port: 8080
timeout: 10Usunięcie linii meta spowoduje, że zadanie wait_for zostanie wykonane, podczas gdy nginx wciąż obsługuje starą konfigurację. Przy pierwszym uruchomieniu na porcie 8080 nie ma jeszcze żadnego procesu nasłuchującego, więc zadanie czeka pełne 10 sekund, a następnie kończy się błędem.
Drugi przypadek dotyczy awarii. "Jeśli zadanie powiadamia handler, ale inne zadanie zawiedzie później w tym samym play, domyślnie handler nie zostanie uruchomiony na danym hoście, co może pozostawić hosta w nieoczekiwanym stanie". Play, który generuje konfigurację, a następnie przerywa działanie z powodu niepowiązanego zadania, pozostawia na dysku nowy plik, podczas gdy w działającej usłudze wciąż załadowana jest stara konfiguracja. Można to nadpisać za pomocą --force-handlers w linii poleceń lub za pomocą force_handlers: true wewnątrz play. Ten sam przełącznik istnieje jako force_handlers = True w sekcji [defaults] pliku ansible.cfg oraz jako zmienna środowiskowa ANSIBLE_FORCE_HANDLERS. Wartością domyślną jest False.
Konflikt nazw handlerów i ciche pominięcie
Dokumentacja określa następującą zasadę: „Każdy handler powinien mieć globalnie unikalną nazwę. Jeśli zdefiniowano wiele handlerów o tej samej nazwie, tylko ostatni załadowany do play może zostać powiadomiony i wykonany”. Handlery zdefiniowane wewnątrz roli nie są ograniczone do zakresu tej roli. Są one dodawane do jednej globalnej listy handlerów dla całego play, więc dwie role definiujące Restart nginx pozostawiają użytkownika z nazwą, która wskazuje tylko na jeden z nich, a o tym, który zostanie wybrany, decyduje kolejność ładowania, a nie rola, z której wysłano powiadomienie.
Przetestuj tę zasadę przed jej zastosowaniem. Zapisz poniższą treść jako handlers-dup.yml:
- name: Two handlers, one name
hosts: local
gather_facts: false
tasks:
- name: Notify the duplicated name
ansible.builtin.command:
cmd: /bin/true
changed_when: true
notify: Duplicated handler
handlers:
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-first
state: touch
mode: '0644'
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-second
state: touch
mode: '0644'rm -f /tmp/dup-first /tmp/dup-second
ansible-playbook handlers-dup.yml
ls -l /tmp/dup-first /tmp/dup-secondPlay kończy się powodzeniem, RUNNING HANDLER [Duplicated handler] pojawia się raz, a ls wypisuje linię dla /tmp/dup-first oraz ls: cannot access '/tmp/dup-second': No such file or directory dla drugiego z nich. Uruchomiony handler to ten zdefiniowany jako pierwszy, a nie ostatni załadowany, co jest przeciwieństwem tego, co sugeruje dokumentacja.
Warto zrozumieć tę różnicę, ponieważ udokumentowana zasada dotyczy bloków handlerów, a nie linii w pliku. Handlery pochodzące z różnych miejsc, na przykład z dwóch różnych ról, stanowią oddzielne bloki, a późniejszy blok przesłania wcześniejszy. Zwykła lista handlers: w play stanowi pojedynczy blok, a wyszukiwanie wewnątrz bloku przebiega od góry do dołu i zatrzymuje się na pierwszej pasującej nazwie. Wewnątrz jednego pliku pierwsza definicja odpowiada, a druga jest nieosiągalna, podczas gdy między rolami przesłanianie działa zgodnie z opisem w dokumentacji. W obu przypadkach nie można wywołać obu handlerów, a żadne z tych zachowań nie powinno stanowić podstawy konfiguracji.
Istnieją dwa poprawne rozwiązania. Należy nadać każdej nazwie handlera przedrostek specyficzny dla danej roli lub powiadomić kwalifikowaną formę role_name : handler_name, co według dokumentacji jest sposobem „na zapewnienie, że powiadomiony zostanie handler z roli, a nie ten spoza roli o tej samej nazwie”. Spacje wokół dwukropka są częścią tej składni. Problem ten staje się istotny w momencie korzystania z ról, których nie jest się autorem.
Kolejna zasada z tej samej strony: „Unikaj umieszczania zmiennych w nazwie handlera. Ponieważ nazwy handlerów są przetwarzane przez szablon na wczesnym etapie, Ansible może nie mieć dostępnej wartości dla nazwy handlera w takiej postaci”. Handler o nazwie Restart {{ service_name }} powoduje przerwanie całego play, jeśli zmienna nie jest zdefiniowana w momencie przetwarzania nazwy. Utrzymywanie nazw handlerów jako stałych ciągów znaków i grupowanie ich za pomocą listen pozwala uniknąć tego problemu.
validate: odmowa instalacji uszkodzonego pliku
validate uruchamia polecenie względem wygenerowanego pliku, zanim Ansible przeniesie go do docelowej lokalizacji. Dokumentacja podaje: „Polecenie sprawdzające, które ma zostać wykonane przed skopiowaniem zaktualizowanego pliku do miejsca docelowego. Do walidacji używana jest ścieżka pliku tymczasowego, przekazywana przez %s, która musi być obecna, jak w poniższych przykładach. Ponadto polecenie jest przekazywane w sposób bezpieczny, więc funkcje powłoki, takie jak rozwijanie nazw czy potoki, nie będą działać”.
Z tego tekstu wynikają dwie zasady. Użycie %s jest obowiązkowe, a ciąg walidacyjny bez tego znacznika kończy zadanie błędem validate must contain %s. Ponadto powłoka nie jest dostępna, więc potoki, przekierowania, globbing oraz && nie działają. Jedno polecenie, jeden argument plikowy.
Oficjalne przykłady modułu to dwa przypadki, w których rozwiązanie to sprawdza się idealnie:
- name: Copy a new sudoers file into place, after passing validation with visudo
ansible.builtin.template:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -cf %s
- name: Update sshd configuration safely, avoid locking yourself out
ansible.builtin.template:
src: etc/ssh/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
validate: /usr/sbin/sshd -t -f %s
backup: yesOba przykłady działają, ponieważ każdy program sprawdzający przyjmuje jeden plik i ocenia go na własnych zasadach. visudo -cf odczytuje plik sudoers. sshd -t -f odczytuje kompletny sshd_config.
Dlaczego walidacja nie może sprawdzić pliku nginx w tym przewodniku
Dodanie validate: /usr/sbin/nginx -t -c %s do powyższego zadania szablonu powoduje jego niepowodzenie. Komunikat wskazuje przyczynę:
nginx: [emerg] "upstream" directive is not allowed here in <ansible temporary path>:2nginx -t -c oczekuje pełnej konfiguracji, która rozpoczyna się na najwyższym poziomie od bloków events oraz http. Plik generowany przez ten playbook jest fragmentem, włączanym do bloku http za pomocą include /etc/nginx/conf.d/*.conf; wewnątrz /etc/nginx/nginx.conf. Rozpatrywany samodzielnie, poza tym kontekstem, upstream jest dyrektywą umieszczoną w niewłaściwym miejscu, więc nginx odrzuca plik, który w swoim docelowym położeniu jest całkowicie poprawny. Narzędzie sprawdzające otrzymało fragment, a potraktowało go jak kompletną konfigurację.
Działającym rozwiązaniem jest to, które już znajduje się w playbooku. Należy zainstalować fragment, a następnie sprawdzić scaloną konfigurację w handlerze zdefiniowanym przed handlerem przeładowania. Ponieważ handlery są uruchamiane w kolejności ich definicji, nginx -t widzi właściwy plik /etc/nginx/nginx.conf z dołączonym fragmentem, a błąd w tym miejscu przerywa playbook, zanim zostanie wywołany systemctl reload. Należy mieć świadomość kosztów: uszkodzony plik znajduje się na dysku w momencie niepowodzenia testu, a nginx kontynuuje obsługę ostatniej załadowanej konfiguracji do czasu restartu.
Właśnie dlatego backup: true jest niezbędne. Zapisuje ono kopię poprzedniego pliku obok oryginału przed nadpisaniem, nadając mu nazwę basename.PID.YYYY-MM-DD@HH:MM:SS~, dzięki czemu w katalogu znajdują się wpisy takie jak learn.conf.4127.2026-08-20@11:42:09~. Uruchomienie sudo ls -l /etc/nginx/conf.d/ po zmianie pozwoli taki plik odnaleźć.
Ten szczegół dotyczący nazewnictwa jest ważniejszy, niż się wydaje. Kopia zapasowa jest nieszkodliwa w /etc/nginx/conf.d/, ponieważ główna konfiguracja zawiera tylko conf.d/*.conf, a nazwa kopii kończy się tyldą. Nie jest ona jednak nieszkodliwa w katalogu dołączanym za pomocą zwykłego *, a w systemach Debian i Ubuntu /etc/nginx/nginx.conf zawiera /etc/nginx/sites-enabled/* dokładnie w ten sposób. Użycie szablonu w sites-enabled z backup: true spowodowałoby, że nginx załadowałby kopię zapasową jako drugi aktywny blok serwera, dlatego ten playbook zapisuje plik w conf.d.
Uruchamianie tego samego playbooka na rzeczywistych hostach z inwentarza
Zmień hosts: local na nazwę grupy, której używasz; żadne inne elementy playbooka nie wymagają zmian. Szablon jest renderowany raz dla każdego hosta, więc app_listen_port oraz app_backends mogą pochodzić z group_vars i host_vars, podczas gdy sam plik szablonu pozostaje jeden. To główna korzyść z umieszczania wartości w zmiennych zamiast wpisywania ich bezpośrednio w pliku.
Dwie rzeczy ulegają zmianie. become: true wymaga teraz hasła sudo na każdym hoście docelowym, chyba że skonfigurowano tam sudo bez hasła, więc dodaj -K. Każdy sekret w tym szablonie, taki jak hasło do bazy danych lub token API, nie może znajdować się w postaci jawnej vars: w pliku, który przesyłasz do repozytorium. Zaszyfruj te wartości za pomocą Ansible Vault i odwołuj się do nich po nazwie dokładnie tak, jak robisz to teraz, ponieważ szablon nie sprawdza źródła pochodzenia zmiennej.
Gdy playbook rozrasta się poza jedną usługę, vars:, templates/ oraz handlers: mają już przygotowane standardowe miejsce. Przeniesienie ich tam jest istotą podziału na playbook i rolę.
FAQ
Dlaczego mój handler Ansible nie został uruchomiony?
Prawie zawsze wynika to z faktu, że zadanie, które go wywołuje, zgłosiło ok zamiast changed. Handlery uruchamiają się tylko w przypadku wystąpienia zmiany, więc zadanie typu template, którego wynik renderowania jest identyczny z plikiem już obecnym na dysku, nie wywołuje żadnego handlera. Następnie należy sprawdzić cztery kwestie. Ciąg znaków w notify musi dokładnie odpowiadać nazwie handlera name lub tematowi listen, z uwzględnieniem wielkości liter i spacji. Późniejsze zadanie, które zakończyło się niepowodzeniem na danym hoście, blokuje wywołane handlery, chyba że użyto flagi --force-handlers. Handler zdefiniowany w innym playu nie jest widoczny dla bieżącego. Zadanie wywołujące, które zostało pominięte przez warunek when, nigdy nie wysyła powiadomienia.
Dlaczego mój playbook zgłasza zmianę przy każdym uruchomieniu?
Wyrenderowany tekst nie jest stabilny pomiędzy uruchomieniami. Najczęstszą przyczyną jest znacznik czasu w danych wyjściowych; niestandardowy ciąg ansible_managed zawierający datę powoduje właśnie takie zachowanie. Kolejną rzeczą do sprawdzenia są parametry mode oraz owner zadania: jeśli nie odpowiadają one plikowi już znajdującemu się na dysku, Ansible poprawia je i zgłasza zmianę, mimo że zawartość jest identyczna. Należy uruchomić ansible-playbook site.yml --check --diff, aby sprawdzić, która z tych dwóch przyczyn występuje, ponieważ --diff pokazuje różnicę, którą zadanie zamierza wprowadzić.
Jaka jest różnica między template a copy w Ansible?
ansible.builtin.copy przesyła plik bez zmian. ansible.builtin.template najpierw renderuje go za pomocą Jinja2 na kontrolerze, a następnie przesyła wynik, dzięki czemu zmienne i pętle są rozwiązywane, zanim plik dotrze do hosta docelowego. Należy używać copy dla plików, które mają być identyczne pod względem bajtów w każdym miejscu. Należy używać template dla wszystkiego, co różni się w zależności od hosta. Oba moduły współdzielą te same opcje plików, więc mode, owner, backup oraz validate działają w nich w ten sam sposób.
Jak uruchomić handler w trakcie trwania playa?
Należy dodać ansible.builtin.meta: flush_handlers jako zadanie w miejscu, w którym mają zostać uruchomione handlery. Wyzwala to wszystkie dotychczas zgłoszone handlery, a następnie play jest kontynuowany w normalny sposób. Należy tego użyć, gdy późniejsze zadanie w tym samym playu zależy od usługi już działającej z nową konfiguracją, na przykład wait_for na porcie, który istnieje dopiero po przeładowaniu. Jest to wspierany sposób uruchamiania handlera przed zakończeniem playa.
Czy mogę użyć validate z fragmentem konfiguracji nginx?
Nie przy użyciu nginx -t -c %s. To polecenie oczekuje kompletnej konfiguracji rozpoczynającej się od bloków najwyższego poziomu events oraz http, więc odrzuca fragment conf.d z komunikatem typu "upstream" directive is not allowed here. Fragment jest poprawny wewnątrz bloku http, ale niepoprawny jako samodzielny plik. Należy zainstalować plik, a następnie uruchomić nginx -t względem złożonej konfiguracji w handlerze zdefiniowanym przed handlerem przeładowania. Handlery uruchamiają się w kolejności ich zdefiniowania, więc błędna konfiguracja przerywa play przed próbą przeładowania. Należy ustawić backup: true w zadaniu template, aby poprzedni plik pozostał dostępny do przywrócenia.