Ansible playbook of role: wanneer gebruikt u welke?
Ontdek wanneer u kiest voor een enkel Ansible playbook en wanneer een role nodig is. Leer over de directorystructuur, ansible-galaxy init en de juiste variabele prioriteit.
Verschil tussen een Ansible playbook en een role
Een Ansible playbook is het bestand dat u uitvoert met ansible-playbook. Het koppelt een groep hosts aan de uit te voeren werkzaamheden. Een Ansible role is een map met een vaste structuur die taken, templates, handlers en standaardvariabelen bevat; een playbook roept deze aan bij de naam. De syntaxis voor taken is in beide gevallen identiek, dus het is geen vraag over wat u kunt uitdrukken. Het is een vraag over herbruikbaarheid.
Begin met een plat playbook. Eén site.yml met een lijst van tasks: is de juiste vorm voor uw eerste automatisering, en dit blijft langer de juiste keuze dan de meeste mensen verwachten. Stap over op een role wanneer hetzelfde blok taken voor een tweede groep hosts moet worden uitgevoerd, of wanneer het bestand groeit tot voorbij ongeveer 100 regels en u een taak niet meer kunt vinden door te scrollen.
Als u er nog geen heeft geschreven, begin dan met een eerste playbook voor een enkele VPS en kom terug wanneer het begint te groeien.
Wanneer een plat playbook de juiste keuze is
Een plat playbook is de juiste keuze wanneer het werk eenmalig wordt uitgevoerd, op één host plaatsvindt, of wanneer niemand anders het zal lezen. Het inrichten van een enkele applicatieserver of het patchen van een systeem voor een onderhoudsvenster: geen van beide rechtvaardigt een mappenstructuur. Een role voegt zeven mappen en een extra abstractielaag toe. Als de enige aanroeper het playbook is dat ernaast staat, levert die abstractie niets op en kost het u telkens een extra stap wanneer u wilt lezen wat er daadwerkelijk wordt uitgevoerd.
Het platte playbook is niet langer de juiste keuze op een specifiek moment, en dat moment is eenvoudig te herkennen. U kopieert een blok taken naar een tweede playbook. Die kopie is het signaal. Vanaf dat moment moet elke correctie twee keer worden doorgevoerd, en op een dag zal het slechts één keer gebeuren.
Wat een roldirectory daadwerkelijk bevat
roles/common/
defaults/main.yml
vars/main.yml
tasks/main.yml
handlers/main.yml
templates/99-hardening.conf.j2
files/
meta/main.ymltasks/main.ymlis het startpunt. Ansible voert dit bestand uit wanneer de rol wordt aangeroepen; elke andere directory is optioneel.defaults/main.ymlbevat de variabelen waarvan wordt verwacht dat de aanroeper ze overschrijft. Dit is de bron met de laagste prioriteit in Ansible, waardoor bijna alles voorrang krijgt.vars/main.ymlbevat variabelen waarvan de aanroeper niet wordt geacht ze te overschrijven. Deze staan in prioriteit boven de inventory, wat een sterke positie is. Gebruik dit zelden.handlers/main.ymlbevat taken die worden geactiveerd doornotify. Een handler wordt aan het einde van de play één keer uitgevoerd, ongeacht hoeveel taken deze hebben aangestuurd.files/bevat bestanden die letterlijk worden gekopieerd door decopy-module, entemplates/bevat Jinja2-templates die worden gerenderd door detemplate-module. Binnen een rol verwijst u naar beide met alleen de bestandsnaam zonder pad, omdat Ansible eerst in de eigen directories van de rol zoekt.meta/main.ymldeclareert rolafhankelijkheden en de metadata die Ansible Galaxy leest.
De structuur is geen kwestie van stijlvoorkeur. Ansible zoekt op deze exacte paden, dus een template die u in roles/common/template/ (enkelvoud) plaatst, wordt simpelweg nooit gevonden.
Bouw de algemene rol met ansible-galaxy init
mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles commonDit schrijft het volledige skelet onder roles/common, inclusief mappen die u niet zult gebruiken en main.yml-stubs die alleen --- bevatten. Verwijder de bestanden die u leeg laat. Een lege vars/main.yml is onschadelijk voor Ansible, maar het maskeert welke bestanden in de rol daadwerkelijk van belang zijn.
Vul nu de bestanden in die het werk verrichten. Begin met de defaults, aangezien deze de publieke interface van de rol vormen.
# roles/common/defaults/main.yml
---
common_packages:
- ufw
- fail2ban
- unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"Zet "no" en "yes" tussen aanhalingstekens. Ansible parseert YAML met PyYAML, dat een kale no leest als de boolean false. Hierdoor wordt de gegenereerde configuratieregel PermitRootLogin False en zal sshd deze weigeren. De aanhalingstekens zorgen ervoor dat de waarde een string blijft.
# 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 }}Op Debian en Ubuntu heet de systemd-unit ssh, en op systemen uit de RHEL-familie is dit sshd. Een handler die de verkeerde naam gebruikt, faalt alleen wanneer er daadwerkelijk iets in de template wijzigt; daarom komt dit probleem vaak pas weken later aan het licht.
De regel validate is het meest nuttige onderdeel van die taak. Ansible rendert de template naar een tijdelijk bestand, vervangt %s door het pad van dat bestand en voert het commando uit. De doelbestemming wordt alleen vervangen als het commando exitcode 0 teruggeeft. Plaats een onzinnige instructie in de template en voer het opnieuw uit: de taak faalt met failed to validate, de echte /etc/ssh/sshd_config.d/99-hardening.conf blijft ongewijzigd en u kunt nog steeds inloggen op de server. Houd er rekening mee dat de controle meer test dan alleen uw syntax. Als sshd -t de host keys niet kan lezen, sluit het af met sshd: no hostkeys available -- exiting. en rapporteert Ansible dezelfde failed to validate. Lees daarom de msg van de module voordat u de template de schuld geeft.
Hoe een playbook een role aanroept
# site.yml
---
- name: Base configuration for every server
hosts: all
become: true
roles:
- common# inventory.ini
[local]
localhost ansible_connection=localansible-playbook -i inventory.ini site.ymlHet play moet eindigen met failed=0 in de samenvatting. Geef parameters door op de aanroeplocatie met de uitgebreide vorm; dit is hoe één role twee groepen hosts bedient:
roles:
- role: common
common_admin_group: ops
common_permit_root_login: prohibit-passwordEr is één volgorderegel die bijna iedereen verrast. Een play kan pre_tasks, roles, tasks en post_tasks bevatten, en Ansible voert deze in die volgorde uit, ongeacht de volgorde waarin u ze in het bestand heeft geschreven. Plaats tasks: boven roles: en de roles worden nog steeds als eerste uitgevoerd. Als er dus iets moet gebeuren vóór een role, hoort dit thuis in pre_tasks:, niet bovenaan 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: postOm een role aan te roepen vanuit een task-lijst in plaats van via de roles:-sleutel, gebruikt u import_role of 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 is statisch. Ansible leest de role tijdens het parsen en de taken worden onderdeel van het play, waardoor ansible-playbook --list-tasks site.yml ze weergeeft en een tag op de import van toepassing is op elke taak binnen de role. include_role is dynamisch. Er wordt niets gelezen totdat de taak wordt uitgevoerd, wat het mogelijk maakt om de rolename aan te sturen via een variabele of een loop. Het nadeel is dat deze taken onzichtbaar zijn voor --list-tasks en voor --start-at-task.
Hier schuilt een valkuil. Een when: op een include_role-taak wordt geëvalueerd voordat de defaults/main.yml van de geïnclude role in scope is. Schrijf when: common_packages | length > 0 op de include en de uitvoering stopt met 'common_packages' is undefined, ook al is die variabele gedefinieerd in de role die u zojuist include. De oplossing is om de schakelaar uit de role te verplaatsen: plaats deze in group_vars/all.yml, waar deze overal in scope is, en laat de defaults van de role staan voor waarden die de role zelf verbruikt.
Welke variabele wint: defaults, group_vars, vars, extra vars
Ansible documenteert meer dan twintig niveaus van variabele-prioriteit. Vier daarvan beslechten bijna elke discussie in de praktijk; hier zijn ze, van zwakste naar sterkste.
roles/<name>/defaults/main.ymlstaat onderaan. Vrijwel alles wat u elders instelt, overschrijft deze waarde. Daarom is dit de juiste plek voor de instelbare parameters van een role.group_vars/enhost_vars/staan in het midden. Hier horen de specifieke waarden van uw eigen omgeving thuis; deze overschrijven netjes de defaults van de role.roles/<name>/vars/main.ymlstaat bovenhost_vars. Een waarde die u hier plaatst, kan niet worden overschreven vanuit de inventory. Reserveer dit voor zaken die de role nodig heeft om intern consistent te blijven, zoals een pakketnaam die moet overeenkomen met een servicenaam.- Een role-parameter die bij de aanroep wordt meegegeven, wint van
vars/main.yml, en-eop de command line wint van alles, inclusief role-parameters.
U kunt dit proces in ongeveer een minuut observeren. Geef een kleine role één default en één role-var, en stel vervolgens dezelfde namen in via 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-cliDe eerste run print tunable=from-hostvars internal=from-rolevars. De inventory won van de role-default, maar verloor van de role-var. De tweede run print internal=from-cli, omdat extra vars helemaal bovenaan staan en niets daaronder de waarde kan wijzigen. Dat is ook de reden waarom -e prima is voor een eenmalige run, maar onjuist in een script dat u bewaart: het overrulet stilletjes elke weloverwogen beslissing in uw repository.
De werkregel: als u wilt dat een waarde instelbaar is, plaatst u deze in defaults/. Door de waarde in vars/ te plaatsen, geeft u aan elke toekomstige gebruiker van de role aan dat de inventory deze niet mag wijzigen. Soms is dat de bedoeling, maar meestal is het een vergissing.
Bewijs dat de rol idempotent is: voer deze tweemaal uit
Een betrouwbare Ansible-run levert de tweede keer hetzelfde resultaat op en rapporteert dat er niets is gewijzigd. Voer de playbook tweemaal uit en lees de samenvatting.
ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.ymlDe tweede samenvatting hoort er als volgt uit te zien:
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0changed=0 betekent dat elke module de huidige status heeft gecontroleerd en vaststelde dat het werk al was verricht. changed=2 bij een tweede run betekent dat twee taken het verschil niet kunnen vaststellen, waardoor ze bestanden blijven overschrijven en services oneindig blijven herstarten. De gebruikelijke boosdoener is command of shell, omdat Ansible niet kan weten wat een willekeurig commando heeft uitgevoerd.
# 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.txtVoer die playbook tweemaal uit en tel vervolgens de regels met wc -l /tmp/grow.txt /tmp/guarded.txt. /tmp/grow.txt bevat twee regels en /tmp/guarded.txt bevat er één. Bij de tweede run werd de beveiligde taak helemaal niet uitgevoerd en het resultaat bevat de melding skipped, since /tmp/guarded.txt exists, omdat creates de module eerst een zichtbaar product geeft om naar te zoeken. Wanneer een commando geen dergelijk product achterlaat, registreer dan de uitvoer en beslis zelf met changed_when.
ansible-playbook --check --diff site.yml voorspelt wijzigingen zonder ze door te voeren en --diff print de exacte regels die een template zou overschrijven. Lees de uitvoer met één voorbehoud: shell en command taken worden overgeslagen in de check-modus, dus een plan dat er schoon uitziet kan nog steeds werk verbergen.
Waarom meldt Ansible dat de rol niet is gevonden
Ansible zoekt naar een roles/-map naast het playbook-bestand en vervolgens in roles_path. De zoekopdracht volgt het playbook, niet uw shell.
ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonelyDie melding betekent dat site.yml en roles/ niet langer overeenkomen; Ansible toont hierbij de paden die het heeft geprobeerd. Houd beide in dezelfde map. Het uitvoeren vanuit een bovenliggende map is geen probleem, omdat het pad van het playbook bepalend is:
ansible-playbook -i infra/inventory.ini infra/site.ymlEr bestaat een minder opvallende variant van dit probleem. Ansible negeert een ansible.cfg in de huidige map wanneer die map voor iedereen schrijfbaar is (world writable). Elke gebruiker op het systeem zou daar namelijk een configuratie kunnen plaatsen en zo het gedrag van uw uitvoering kunnen wijzigen.
[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.Uw roles_path- en inventory-instellingen zijn dan stilletjes afwezig, waardoor het opzoeken van de rol mislukt om een reden die niets met rollen te maken heeft. ansible --version toont het config file dat daadwerkelijk is geladen en ansible-config dump --only-changed toont elke instelling die afwijkt van de standaardwaarden. Controleer beide wanneer een uitvoering zich gedraagt alsof uw configuratie niet bestaat.
Rollen delen: requirements.yml en een vastgezette versie
Een rol die door iemand anders is geschreven, wordt geïnstalleerd en niet gekopieerd. Declareer deze eenmalig:
# requirements.yml
---
roles:
- name: postgres
src: https://github.com/example/ansible-role-postgres
scm: git
version: v1.4.0ansible-galaxy install -r requirements.yml -p galaxy_rolesStel altijd version in. Zonder deze instelling krijgt u de versie die op de dag van uitvoering de standaard branch is, waardoor een deployment die vorige maand werkte, plotseling faalt zonder dat u wijzigingen in uw eigen repository heeft aangebracht. Wijs roles_path naar de downloadmap en houd deze map buiten git:
# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_rolesRollen in roles/ naast de playbook worden nog steeds gevonden, omdat dat pad altijd wordt doorzocht naast roles_path. Zo blijven uw eigen rollen gecommit en gecontroleerd, terwijl rollen van derden reproduceerbare downloads zijn die aan een tag zijn vastgezet.
Waar rollen niet langer de oplossing zijn
Een rol is een eenheid voor hergebruik binnen één Ansible-run. Het creëert geen servers of DNS-records bij uw provider. Wanneer u probeert dit wel te doen, veranderen playbooks in iets dat niemand wil onderhouden. de taakverdeling tussen Ansible en Terraform is het lezen waard voordat u begint. Een rol vervangt ook niet het ontwerp van uw inventory: zodra u meer dan een handvol machines beheert, is hoe u die servers groepeert en bereikt belangrijker dan hoe de taken zijn opgeslagen.
De hardening die deze common rol installeert, vereist eveneens eigen beslissingen. De bovenstaande drop-in stelt slechts twee richtlijnen in en niet meer. Lees daarom welke SSH-instellingen daadwerkelijk de moeite waard zijn om te wijzigen en hoe u Ubuntu zelfstandig beveiligingsupdates laat toepassen voordat u besluit wat er in de rol moet staan voor elke host die u beheert.
FAQ
Wanneer moet ik een Ansible playbook omzetten naar een role?
Wanneer hetzelfde blok taken in een tweede play moet worden uitgevoerd, of op een tweede groep hosts. Het kopiëren van taken tussen playbooks is het teken dat dit moet gebeuren, omdat vanaf dat moment elke correctie twee keer moet worden doorgevoerd en er op een dag een keer vergeten zal worden. Een enkel playbook van ongeveer 100 regels dat altijd op slechts één groep is gericht, wint niets bij een role, en de extra mappen maken het alleen maar minder leesbaar.
Worden roles uitgevoerd vóór de taken in dezelfde play?
Ja. Ansible voert pre_tasks uit, daarna alles wat onder roles: staat, vervolgens tasks:, en daarna post_tasks:. De volgorde waarin deze sleutels in uw bestand staan, wordt genegeerd. Het schrijven van tasks: boven roles: zorgt er niet voor dat die taken als eerste worden uitgevoerd. Als er iets moet gebeuren vóór een role, plaats het dan in pre_tasks:.
Waarom overschrijft mijn group_vars waarde de role niet?
Controleer of de variabele is ingesteld in de vars/main.yml van de role in plaats van in defaults/main.yml. vars/ staat boven group_vars en host_vars in de prioriteitsvolgorde van Ansible, waardoor de inventory deze niet kan overschrijven. Verplaats de variabele naar defaults/main.yml, wat onderaan de volgorde staat en de juiste plek is voor alles wat een aanroepende partij moet kunnen wijzigen. Om te bevestigen dat prioriteit de oorzaak is en niet een typefout, voert u het playbook eenmaal uit met -e name=value, aangezien dit alle andere bronnen overstijgt.
Waarom zegt Ansible dat de role niet is gevonden?
De zoekopdracht begint naast het playbook-bestand, dus site.yml en roles/ moeten zich in dezelfde map bevinden. De foutmelding toont de paden die zijn geprobeerd, zoals in the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. Het uitvoeren van het playbook vanuit een bovenliggende map is geen probleem, omdat de zoekopdracht het pad van het playbook volgt en niet de werkmap van uw shell. Als u afhankelijk bent van roles_path uit ansible.cfg, bevestig dan dat het bestand is geladen met ansible --version, aangezien Ansible het bestand negeert als de werkmap voor iedereen schrijfbaar is.
Heb ik ansible-galaxy init nodig om een role aan te maken?
Nee. Een role is niets meer dan mappen met de verwachte namen; dus mkdir -p roles/common/tasks plus een tasks/main.yml is al een werkende role. ansible-galaxy init --init-path roles common bespaart typewerk en geeft u het volledige skelet, inclusief meta/main.yml en een README-sjabloon. Verwijder de mappen die u leeg laat, omdat een lege vars/main.yml verhult welke bestanden in de role daadwerkelijk iets doen.