Ansible playbook of role gebruiken: wat is het verschil?
Ontdek wanneer u kiest voor een plat Ansible playbook en wanneer een role de juiste keuze is. Leer over de directorystructuur, ansible-galaxy init en variabele prioriteit.
Ansible playbook versus role: wat is het verschil
Een Ansible playbook is het bestand dat u uitvoert met ansible-playbook. Het koppelt een groep hosts aan het werk dat zij moeten uitvoeren. Een Ansible role is een map met een vaste structuur die taken, templates, handlers en standaardvariabelen bevat; een playbook roept deze aan bij naam. De taaksyntaxis 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 tasks:-lijst 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 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 provisioneren van een enkele applicatieserver of het patchen van een systeem vóór een onderhoudsvenster rechtvaardigt geen mappenstructuur. Een rol 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 inzien wat er daadwerkelijk wordt uitgevoerd.
Een plat playbook is op een specifiek moment niet langer de juiste keuze, 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 dit nog maar éé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 een aanroeper deze overschrijft. Dit is de bron met de laagste prioriteit in Ansible, dus bijna alles overrulet deze waarden.vars/main.ymlbevat variabelen waarvan niet wordt verwacht dat een aanroeper deze overschrijft. 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 weigert sshd deze. 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 bij de RHEL-familie is dit sshd. Een handler die de verkeerde naam gebruikt, faalt pas wanneer er daadwerkelijk iets in het 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 het template naar een tijdelijk bestand, vervangt %s door het pad van dat bestand en voert het commando uit. De bestemming wordt alleen vervangen als het commando exitcode 0 teruggeeft. Plaats een onzinnige instructie in het template en voer het opnieuw uit: de taak faalt met failed to validate, het werkelijke /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. Raadpleeg daarom de msg van de module voordat u het 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; dit stelt u in staat om de role-naam 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 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 hanteert meer dan twintig niveaus van variabele-precedentie. Vier daarvan beslechten bijna elk praktisch geschil; hieronder staan ze van zwakste naar sterkste.
roles/<name>/defaults/main.ymlbevindt zich 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/bevinden zich in het midden. Hier horen de specifieke waarden van uw 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 binnen 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 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 behoudt: 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 deze 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 twee keer uit
Een Ansible-run die het vertrouwen waard is, levert de tweede keer hetzelfde resultaat op en rapporteert dat er niets is gewijzigd. Voer de playbook twee keer uit en lees de samenvatting.
ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.ymlDe tweede samenvatting zou er als volgt uit moeten 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 heeft vastgesteld dat het werk al is gedaan. changed=2 bij een tweede run betekent dat twee taken het verschil niet kunnen zien, waardoor ze bestanden blijven overschrijven en services eeuwig blijven herstarten. De gebruikelijke boosdoener is command of shell, omdat Ansible geen manier heeft om te weten wat een willekeurig commando heeft gedaan.
# 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 twee keer uit en tel daarna 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 bevat het resultaat 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 output 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 output met één voorbehoud in gedachten: shell en command taken worden overgeslagen in de check-modus, dus een plan dat er schoon uitziet, kan nog steeds werk verbergen.
Eén kolom in die samenvatting verdient dezelfde aandacht: een host waarmee Ansible geen verbinding kon maken, wordt geteld onder unreachable in plaats van failed en geen van de taken is uitgevoerd, dus bepaal vooraf of één onbereikbare host de hele run moet stoppen voordat u deze rol op meer dan een paar machines loslaat.
Waarom geeft Ansible aan dat de rol niet is gevonden
Ansible zoekt naar een roles/-directory naast het playbook-bestand en vervolgens in roles_path. Het zoekpad 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/ uit elkaar zijn gelopen; Ansible toont behulpzaam de paden die het heeft geprobeerd. Houd beide in dezelfde directory. Het uitvoeren vanuit een bovenliggende directory is geen probleem, omdat het pad van het playbook bepalend is:
ansible-playbook -i infra/inventory.ini infra/site.ymlEr bestaat een stillere variant van hetzelfde probleem. Ansible negeert een ansible.cfg in de huidige directory wanneer die directory voor iedereen schrijfbaar is (world writable), omdat elke gebruiker op de server daar een configuratiebestand zou kunnen plaatsen en zo het verloop van uw run zou 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 ontbreken dan stilletjes, en 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 standaardinstellingen. Controleer beide wanneer een run 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 er iets in uw eigen repository is gewijzigd. Wijs roles_path naar de downloadmap en houd die map buiten git:
# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_rolesRollen in roles/ naast het playbook worden nog steeds gevonden, omdat dat pad altijd wordt doorzocht naast roles_path. Uw eigen rollen blijven dus gecommit en gecontroleerd, terwijl rollen van derden reproduceerbare downloads zijn die zijn vastgezet op een specifieke tag.
Wanneer 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 bovendien 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, verdient eveneens eigen afwegingen. 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 in de rol thuishoort voor elke host die u bezit.
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 signaal, 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:, dan post_tasks:, en het negeert de volgorde waarin deze keys in uw bestand staan. 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 zich onderaan de volgorde bevindt en de juiste plek is voor alles wat een aanroepende partij moet kunnen wijzigen. Om te bevestigen dat de prioriteit de oorzaak is en niet een typefout, voert u het playbook eenmaal uit met -e name=value, wat boven elke andere bron staat.
Waarom zegt Ansible dat de role niet is gevonden?
Het zoeken 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.