SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-01

Ansible: playbook czy rola? Kiedy użyć każdego

Sprawdź, kiedy wystarczy płaski playbook, a kiedy potrzebna jest rola: struktura katalogów, ansible-galaxy init, wywołania ról i pierwszeństwo zmiennych.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Ansible: playbook a rola — jaka jest różnica

Ansible playbook to plik uruchamiany za pomocą ansible-playbook. Przypisuje grupę hostów do zadań, które należy na nich wykonać. Rola Ansible to katalog o ustalonej strukturze, zawierający zadania, szablony, handlery i zmienne domyślne. Playbook wywołuje rolę według nazwy. Składnia zadań jest w obu przypadkach identyczna, więc nie chodzi o zakres możliwych operacji. Chodzi o możliwość ponownego użycia.

Należy zacząć od płaskiego playbooka. Jeden plik site.yml zawierający listę tasks: to właściwa struktura dla pierwszej automatyzacji. Taka struktura pozostaje właściwa dłużej, niż zwykle się oczekuje. Na rolę należy przejść, gdy ten sam blok zadań musi zostać uruchomiony dla drugiej grupy hostów albo gdy plik przekroczy około 100 wierszy i nie można już znaleźć zadania przez przewijanie.

Jeśli nie utworzono jeszcze playbooka, zacznij od pierwszego playbooka dla pojedynczego VPS i wróć do tego tematu, gdy zacznie się rozrastać.

Kiedy płaski playbook jest właściwym rozwiązaniem

Płaski playbook jest właściwym rozwiązaniem, gdy zadanie jest wykonywane raz, na jednym hoście albo nikt inny nie będzie go czytać. Inicjowanie pojedynczego serwera aplikacji lub instalowanie poprawek na serwerze przed oknem serwisowym nie uzasadnia tworzenia drzewa katalogów. Rola dodaje siedem katalogów i jedną warstwę pośrednią. Jeśli jedynym elementem wywołującym jest playbook znajdujący się obok, ta warstwa niczego nie zapewnia, a za każdym razem, gdy trzeba sprawdzić rzeczywiście wykonywane zadania, wymaga dodatkowego przejścia.

Płaski playbook przestaje być właściwym rozwiązaniem w określonym momencie. Łatwo go rozpoznać. Blok zadań zostaje skopiowany do drugiego playbooka. Jest to sygnał, że od tej chwili każdą poprawkę trzeba wprowadzać dwukrotnie, a pewnego dnia zostanie ona wprowadzona tylko raz.

Co faktycznie zawiera katalog roli

roles/common/
  defaults/main.yml
  vars/main.yml
  tasks/main.yml
  handlers/main.yml
  templates/99-hardening.conf.j2
  files/
  meta/main.yml
  • tasks/main.yml jest punktem wejścia. Ansible uruchamia ten plik, gdy rola jest wywoływana. Wszystkie pozostałe katalogi są opcjonalne.
  • defaults/main.yml zawiera zmienne, które wywołujący powinien nadpisać. Jest to źródło o najniższym priorytecie w Ansible, dlatego niemal każde inne źródło ma pierwszeństwo.
  • vars/main.yml zawiera zmienne, których wywołujący nie powinien nadpisywać. Mają wyższy priorytet niż inventory. To zdecydowane założenie. Należy używać tego katalogu rzadko.
  • handlers/main.yml zawiera zadania wywoływane przez notify. Handler jest uruchamiany na końcu play, jeden raz, niezależnie od liczby zadań, które go powiadomiły.
  • files/ zawiera pliki kopiowane bez zmian przez moduł copy, a templates/ zawiera szablony Jinja2 przetwarzane przez moduł template. W obrębie roli do obu odwołuje się za pomocą samej nazwy pliku, bez ścieżki, ponieważ Ansible w pierwszej kolejności przeszukuje własne katalogi roli.
  • meta/main.yml deklaruje zależności roli oraz metadane odczytywane przez Ansible Galaxy.

Układ nie jest kwestią preferencji dotyczących stylu. Ansible szuka plików dokładnie w tych ścieżkach, dlatego szablon umieszczony w roles/common/template/ (liczba pojedyncza) po prostu nie zostanie znaleziony.

Utworzenie wspólnej roli za pomocą ansible-galaxy init

mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles common

Polecenie tworzy cały szkielet w katalogu roles/common, w tym katalogi, które nie będą używane, oraz puste pliki main.yml zawierające tylko ---. Należy usunąć te elementy, które pozostaną puste. Pusty katalog vars/main.yml nie powoduje problemów w Ansible, ale utrudnia ustalenie, które pliki w roli mają znaczenie.

Następnie należy uzupełnić pliki wykonujące właściwe działania. Najpierw należy skonfigurować wartości domyślne, ponieważ stanowią one publiczny interfejs roli.

# roles/common/defaults/main.yml
---
common_packages:
  - ufw
  - fail2ban
  - unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"

Należy ująć "no" i "yes" w cudzysłowy. Ansible analizuje YAML za pomocą PyYAML, który interpretuje niecytowany zapis no jako wartość logiczną false. W rezultacie wygenerowany wiersz konfiguracji ma postać PermitRootLogin False, a sshd go odrzuca. Cudzysłowy zachowują tę wartość jako ciąg znaków.

# roles/common/tasks/main.yml
---
- name: Install the base packages
  ansible.builtin.apt:
    name: "{{ common_packages }}"
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: Create the admin group
  ansible.builtin.group:
    name: "{{ common_admin_group }}"
    state: present

- name: Install the sshd hardening drop-in
  ansible.builtin.template:
    src: 99-hardening.conf.j2
    dest: /etc/ssh/sshd_config.d/99-hardening.conf
    owner: root
    group: root
    mode: "0644"
    validate: /usr/sbin/sshd -t -f %s
  notify: Restart sshd
# roles/common/handlers/main.yml
---
- name: Restart sshd
  ansible.builtin.service:
    name: ssh
    state: restarted
# roles/common/templates/99-hardening.conf.j2
# Managed by Ansible. Local edits are overwritten on the next run.
PermitRootLogin {{ common_permit_root_login }}
PasswordAuthentication {{ common_password_authentication }}

W systemach Debian i Ubuntu jednostka systemd nosi nazwę ssh, a w systemach z rodziny RHEL — sshd. Handler z nieprawidłową nazwą jednostki kończy się błędem dopiero wtedy, gdy coś rzeczywiście zmieni szablon. Z tego powodu problem zwykle ujawnia się dopiero po kilku tygodniach.

Wiersz validate jest najważniejszym elementem tego zadania. Ansible renderuje szablon do pliku tymczasowego, podstawia ścieżkę tego pliku w miejsce %s i uruchamia polecenie. Plik docelowy zostaje zastąpiony tylko wtedy, gdy polecenie zakończy się kodem 0. Należy dodać do szablonu nieprawidłową dyrektywę i uruchomić zadanie ponownie. Zadanie zakończy się błędem z komunikatem failed to validate, rzeczywisty plik /etc/ssh/sshd_config.d/99-hardening.conf pozostanie niezmieniony, a dostęp do serwera nadal będzie możliwy. Należy pamiętać, że sprawdzenie obejmuje więcej niż poprawność składni. Jeśli sshd -t nie może odczytać kluczy hosta, kończy działanie z kodem sshd: no hostkeys available -- exiting., a Ansible zgłasza ten sam failed to validate. Przed uznaniem szablonu za przyczynę problemu należy zapoznać się z sekcją msg dokumentacji modułu.

Jak playbook wywołuje rolę

# site.yml
---
- name: Base configuration for every server
  hosts: all
  become: true
  roles:
    - common
# inventory.ini
[local]
localhost ansible_connection=local
ansible-playbook -i inventory.ini site.yml

Play powinien kończyć się wpisem failed=0 w sekcji podsumowania. Parametry należy przekazywać w miejscu wywołania w rozwiniętej formie. Dzięki temu jedna rola może obsługiwać dwie grupy hostów:

  roles:
    - role: common
      common_admin_group: ops
      common_permit_root_login: prohibit-password

Istnieje jedna reguła kolejności, która zaskakuje prawie wszystkich. Play może zawierać pre_tasks, roles, tasks i post_tasks, a Ansible uruchomi je w tej kolejności, niezależnie od kolejności zapisania w pliku. Umieszczenie tasks: przed roles: nie zmieni faktu, że role zostaną uruchomione najpierw. Jeśli coś musi nastąpić przed rolą, należy umieścić to w pre_tasks:, a nie na początku tasks:.

- name: Ordering demonstration
  hosts: local
  gather_facts: false
  pre_tasks:
    - name: Runs first
      ansible.builtin.debug:
        msg: pre
  roles:
    - common
  tasks:
    - name: Runs after the role
      ansible.builtin.debug:
        msg: task
  post_tasks:
    - name: Runs last
      ansible.builtin.debug:
        msg: post

Aby wywołać rolę z listy zadań zamiast użycia klucza roles:, należy użyć import_role lub include_role.

  tasks:
    - name: Static, read when the playbook is parsed
      ansible.builtin.import_role:
        name: common

    - name: Dynamic, resolved when the task runs
      ansible.builtin.include_role:
        name: postgres
      when: "'db' in group_names"

import_role jest statyczne. Ansible odczytuje rolę podczas analizy składni, a jej zadania stają się częścią play, dlatego ansible-playbook --list-tasks site.yml wyświetla te zadania, a znacznik przypisany do importu ma zastosowanie do każdego zadania wewnątrz roli. include_role jest dynamiczne. Odczyt następuje dopiero podczas wykonywania zadania. Umożliwia to określenie nazwy roli na podstawie zmiennej lub pętli. Wadą jest to, że te zadania nie są widoczne dla --list-tasks ani dla --start-at-task.

W tym miejscu występuje jedna pułapka. when: w zadaniu include_role jest oceniane, zanim defaults/main.yml dołączonej roli znajdzie się w zakresie. Umieszczenie when: common_packages | length > 0 w wywołaniu kończy wykonanie błędem 'common_packages' is undefined, mimo że ta zmienna jest zdefiniowana w dołączanej roli. Należy przenieść przełącznik poza rolę: umieścić go w group_vars/all.yml, gdzie będzie dostępny w każdym miejscu, a wartości używane przez samą rolę pozostawić w jej wartościach domyślnych.

Która zmienna ma pierwszeństwo: defaults, group_vars, vars, extra vars

Ansible opisuje ponad dwadzieścia poziomów pierwszeństwa zmiennych. Cztery z nich rozstrzygają niemal każdy rzeczywisty spór. Poniżej przedstawiono je od najsłabszego do najsilniejszego.

  • roles/<name>/defaults/main.yml znajduje się niemal na dole. Prawie wszystko ustawione w innym miejscu ma przed nim pierwszeństwo. Dlatego jest właściwym miejscem na parametry konfiguracyjne roli.
  • group_vars/ i host_vars/ znajdują się pośrodku. Tu należy umieszczać ustawienia właściwe dla danego środowiska. Wartości te prawidłowo zastępują wartości domyślne roli.
  • roles/<name>/vars/main.yml ma pierwszeństwo przed host_vars. Wartość umieszczonej tu zmiennej nie może zostać zastąpiona z poziomu inventory. Należy zachować to miejsce dla wartości, które muszą pozostać wewnętrznie spójne w roli, na przykład nazwy pakietu, która musi odpowiadać nazwie usługi.
  • Parametr roli przekazany w miejscu jej wywołania ma pierwszeństwo przed vars/main.yml, a -e przekazane w wierszu poleceń ma pierwszeństwo przed wszystkim, w tym przed parametrami roli.

Można to sprawdzić w około minutę. Należy utworzyć niewielką rolę z jedną wartością domyślną i jedną zmienną roli, a następnie ustawić te same nazwy w host_vars.

# roles/prec/defaults/main.yml
---
prec_tunable: from-defaults
prec_internal: from-defaults
# roles/prec/vars/main.yml
---
prec_internal: from-rolevars
# host_vars/localhost.yml
---
prec_tunable: from-hostvars
prec_internal: from-hostvars
# roles/prec/tasks/main.yml
---
- name: Show which value survived
  ansible.builtin.debug:
    msg: "tunable={{ prec_tunable }} internal={{ prec_internal }}"
ansible-playbook -i inventory.ini prec.yml
ansible-playbook -i inventory.ini prec.yml -e prec_internal=from-cli

Pierwsze uruchomienie wyświetla tunable=from-hostvars internal=from-rolevars. Inventory ma pierwszeństwo przed wartością domyślną roli, ale ustępuje zmiennej roli. Drugie uruchomienie wyświetla internal=from-cli, ponieważ extra vars znajdują się na samym szczycie hierarchii i żadna niższa warstwa nie może ich zastąpić. Z tego samego powodu -e jest właściwe podczas jednorazowego uruchomienia, ale nie w utrwalanym skrypcie: ma niejawnie pierwszeństwo przed każdą rozważaną decyzją zapisaną w repozytorium.

Praktyczna zasada jest następująca: jeśli wartość ma podlegać konfiguracji, należy umieścić ją w defaults/. Umieszczenie jej w vars/ informuje każdego przyszłego użytkownika roli, że inventory nie może jej zmienić. Czasami jest to zamierzone, ale zazwyczaj wynika z pomyłki.

Sprawdzenie idempotencji roli: uruchomienie jej dwukrotnie

Godne zaufania wykonanie Ansible daje ten sam wynik za drugim razem i zgłasza, że niczego nie zmieniono. Należy uruchomić playbook dwukrotnie i odczytać podsumowanie.

ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.yml

Drugie podsumowanie powinno wyglądać następująco:

PLAY RECAP *********************************************************************
localhost   : ok=4  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0 oznacza, że każdy moduł sprawdził bieżący stan i stwierdził, że zadanie zostało już wykonane. changed=2 podczas drugiego uruchomienia oznacza, że dwa zadania nie potrafią rozpoznać różnicy, więc będą stale ponownie zapisywać pliki i uruchamiać usługi. Zwykle przyczyną jest command lub shell, ponieważ Ansible nie ma sposobu, aby ustalić, co zrobiło dowolne polecenie.

# traps.yml
---
- name: Command modules do not know what they changed
  hosts: local
  gather_facts: false
  tasks:
    - name: This appends a line on every run
      ansible.builtin.shell: "echo run >> /tmp/grow.txt"

    - name: This appends a line only once
      ansible.builtin.shell: "echo run >> /tmp/guarded.txt"
      args:
        creates: /tmp/guarded.txt

Należy uruchomić ten playbook dwukrotnie, a następnie policzyć wiersze zawierające wc -l /tmp/grow.txt /tmp/guarded.txt. /tmp/grow.txt zawiera dwa wiersze, a /tmp/guarded.txt zawiera jeden. Podczas drugiego uruchomienia zadanie z warunkiem ochronnym w ogóle się nie wykonało, a jego wynik zawiera komunikat skipped, since /tmp/guarded.txt exists, ponieważ creates zapewnia modułowi widoczny rezultat, którego może najpierw szukać. Jeśli polecenie nie pozostawia takiego rezultatu, należy zarejestrować jego dane wyjściowe i samodzielnie podjąć decyzję za pomocą changed_when.

ansible-playbook --check --diff site.yml przewiduje zmiany bez ich wprowadzania, a --diff wyświetla dokładne wiersze, które szablon przepisałby ponownie. Dane wyjściowe należy interpretować z uwzględnieniem jednego zastrzeżenia: zadania shell i command są pomijane w trybie check mode, dlatego plan, który wygląda na pozbawiony zmian, może nadal ukrywać działania do wykonania.

Dlaczego Ansible informuje, że rola nie została znaleziona

Ansible szuka katalogu roles/ obok pliku playbooka, a następnie w roles_path. Wyszukiwanie odbywa się względem playbooka, a nie powłoki.

ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely

Ten komunikat oznacza, że site.yml i roles/ przestały być ze sobą zgodne. Komunikat zawiera także ścieżki, które zostały sprawdzone. Należy umieścić oba elementy w tym samym katalogu. Uruchomienie polecenia z katalogu nadrzędnego jest prawidłowe, ponieważ decydująca jest ścieżka playbooka:

ansible-playbook -i infra/inventory.ini infra/site.yml

Istnieje także mniej widoczna wersja tego samego problemu. Ansible ignoruje plik ansible.cfg w bieżącym katalogu, jeżeli katalog ten jest zapisywalny dla wszystkich użytkowników. Każdy użytkownik serwera mógłby wówczas umieścić w nim konfigurację i zmienić działanie uruchomienia.

[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.

Ustawienia roles_path i inventory są wtedy niejawnie pomijane, a wyszukiwanie roli kończy się niepowodzeniem z przyczyny niezwiązanej z rolami. Polecenie ansible --version wyświetla config file, które zostało faktycznie wczytane, a polecenie ansible-config dump --only-changed wyświetla każde ustawienie różniące się od wartości domyślnych. Należy sprawdzić oba wyniki zawsze, gdy uruchomienie przebiega tak, jakby konfiguracja nie istniała.

Udostępnianie ról: requirements.yml i przypięta wersja

Rola napisana przez inną osobę jest instalowana, a nie kopiowana. Należy zadeklarować ją raz:

# requirements.yml
---
roles:
  - name: postgres
    src: https://github.com/example/ansible-role-postgres
    scm: git
    version: v1.4.0
ansible-galaxy install -r requirements.yml -p galaxy_roles

Zawsze należy ustawić version. Bez tego używana jest zawartość domyślnej gałęzi dostępna w dniu wykonania polecenia. W efekcie wdrożenie, które działało w zeszłym miesiącu, przestaje działać bez żadnych zmian we własnym repozytorium. roles_path należy wskazać jako katalog pobierania. Katalog ten należy wykluczyć z git:

# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_roles

Role w roles/, znajdującym się obok playbooka, nadal są wykrywane, ponieważ ta ścieżka jest zawsze przeszukiwana dodatkowo względem roles_path. Dzięki temu własne role pozostają zatwierdzone i poddawane przeglądom, a role innych dostawców są powtarzalnymi pobraniami przypiętymi do znacznika.

Gdy role przestają być właściwym rozwiązaniem

Rola jest jednostką ponownego użycia w ramach jednego uruchomienia Ansible. Nie tworzy serwerów ani rekordów DNS u dostawcy. Próba wykorzystania jej do tego celu prowadzi do powstania playbooków, których nikt nie chce utrzymywać. Przed rozpoczęciem warto przeczytać podział zadań między Ansible i Terraform. Rola nie zastępuje również projektu inventory: po przekroczeniu kilku maszyn sposób grupowania tych serwerów i uzyskiwania do nich dostępu ma większe znaczenie niż sposób uporządkowania zadań.

Zabezpieczenia instalowane przez tę rolę common również wymagają osobnych decyzji. Powyższy plik drop-in ustawia tylko dwie dyrektywy. Przed określeniem, co powinno znaleźć się w roli dla każdego zarządzanego hosta, należy przeczytać które ustawienia SSH rzeczywiście warto zmienić oraz jak skonfigurować Ubuntu, aby samodzielnie stosował aktualizacje zabezpieczeń.

FAQ

Kiedy należy przekształcić playbook Ansible w rolę?

Gdy ten sam blok zadań musi zostać uruchomiony w drugim playu lub dla drugiej grupy hostów. Kopiowanie zadań między playbookami jest sygnałem ostrzegawczym, ponieważ od tego momentu każdą poprawkę trzeba stosować dwukrotnie, a pewnego dnia zostanie ona zastosowana tylko raz. Pojedynczy playbook o długości około 100 wierszy, który jest zawsze kierowany tylko do jednej grupy, nic nie zyskuje dzięki roli. Dodatkowe katalogi utrudniają jego odczytanie.

Czy role są uruchamiane przed zadaniami w tym samym playu?

Tak. Ansible uruchamia pre_tasks, następnie wszystkie elementy wymienione w roles:, potem tasks:, a na końcu post_tasks:. Kolejność tych kluczy w pliku nie ma znaczenia. Umieszczenie tasks: przed roles: nie powoduje wcześniejszego uruchomienia tych zadań. Jeśli coś musi zostać wykonane przed rolą, należy umieścić to w pre_tasks:.

Dlaczego wartość group_vars nie zastępuje wartości z roli?

Należy sprawdzić, czy zmienna jest ustawiona w vars/main.yml roli, a nie w defaults/main.yml. vars/ ma wyższy priorytet niż group_vars i host_vars w kolejności pierwszeństwa Ansible, dlatego inventory nie może zastąpić tej wartości. Zmienną należy przenieść do defaults/main.yml. Ten poziom znajduje się blisko dołu kolejności i jest właściwym miejscem dla wartości, którą powinien móc zmieniać wywołujący. Aby potwierdzić, że przyczyną jest kolejność pierwszeństwa, a nie literówka, należy jednorazowo uruchomić playbook z -e name=value, który ma wyższy priorytet niż każde inne źródło.

Dlaczego Ansible informuje, że nie znaleziono roli?

Wyszukiwanie rozpoczyna się obok pliku playbooka, dlatego site.yml i roles/ muszą znajdować się w tym samym katalogu. Komunikat o błędzie zawiera wypróbowane ścieżki, jak w the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. Uruchomienie playbooka z katalogu nadrzędnego jest prawidłowe, ponieważ wyszukiwanie odbywa się na podstawie ścieżki playbooka, a nie bieżącego katalogu roboczego powłoki. Jeśli używane jest roles_path z ansible.cfg, należy potwierdzić załadowanie tego pliku za pomocą ansible --version, ponieważ Ansible ignoruje plik znajdujący się w katalogu z prawem zapisu dla wszystkich użytkowników.

Czy do utworzenia roli potrzebne jest ansible-galaxy init?

Nie. Rola składa się wyłącznie z katalogów o oczekiwanych nazwach, dlatego mkdir -p roles/common/tasks wraz z tasks/main.yml tworzy już działającą rolę. ansible-galaxy init --init-path roles common ogranicza liczbę czynności wymaganych do wpisania i tworzy pełny szkielet, w tym meta/main.yml oraz szablon pliku README. Należy usuwać puste katalogi, ponieważ pusty vars/main.yml utrudnia ustalenie, które pliki w roli faktycznie wykonują działania.

#ansible#roles#playbook#structure#automation