Ma teen palju ülevaateid teiste Ansible'i koodidest ja kirjutan ise ka palju. Veateavet analüüsides (nii oma kui ka teiste omi) ja mõnedel vestlustel olen aru saanud, et Ansible'i kasutajad teevad ühe peamise vea — nad sukeldusid keeruliseks, ilma põhitõdesid omandamata.
Selle universaalse ebaõigluse parandamiseks otsustasin kirjutada Ansible'i sissejuhatuse neile, kes seda juba tunnevad. Hoian ette, et see ei ole manuaalide kokkuvõte, vaid mahukas artikkel, kus on palju sõnu ja ei ole pilte.
Oodatav lugejatase — juba on kirjutatud mõned tuhanded YAML-read, juba midagi on tootmises, kuid "kõik on kuidagi viltu".
Pealkirjad
Ansible'i peamine kasutaja viga on mitte teada, kuidas asju nimetatakse. Kui te ei tea nimetusi, ei saa te aru dokumentatsioonis kirjutatust. Elav näide: intervjuul isik, kes väidetavalt ütles, et ta on Ansible'iga palju töötanud, ei osanud vastata küsimusele, 'millest playbook koosneb?'. Ja kui ma vihjasin, et 'oodati vastust, et playbook koosneb play'st', järgnes tappev kommentaar 'me ei kasuta seda'. Inimesed kirjutavad Ansible'is raha eest ja ei kasuta play'd. Tegelikult kasutatakse, aga ei teata, mis see on.
Alustame seega lihtsast: kuidas asju nimetatakse. Võib-olla teate seda, aga võib-olla ei tea, sest ei pannud tähele, kui lugesite dokumentatsiooni.
ansible-playbook täidab playbooki. Playbook on fail laiendiga yml/yaml, mille sees on midagi sellist:
---
- hosts: group1
roles:
- role1
- hosts: group2,group3
tasks:
- debug:Me oleme juba aru saanud, et kogu see fail on playbook. Saame näidata, kus on rolled (roles), kus on ülesanded (tasks). Aga kus on play? Ja kuidas erineb play rollist või playbookist?
Dokumentatsioonis on see kõik olemas. Ja seda ignoreeritakse. Algajad — sest informatsiooni on liiga palju, et kõik сразу запомнить. Kogenud — sest "lihtsad asjad". Kui olete kogenud, siis lugesite neid lehti vähemalt korra kuue kuu jooksul ja teie kood muutub palju paremaks.
Nii et pidage meeles: Playbook on nimekiri, mis koosneb mängudest ja import_playbook.
See on üks mäng:
- hosts: group1
roles:
- role1ja see on ka veel üks mäng:
- hosts: group2,group3
tasks:
- debug:Mis siis on mäng? Miks see on vajalik?
Mäng on playbooki võtmeelement, kuna mäng ja ainult mäng seob rollide ja/või ülesannete nimekirja hostide nimekirjaga, millel neid teha tuleb. Dokumentatsiooni sügavas nurgas võib leida mainimisi delegate_to, kohalike lookup-pluginide, võrgukliendi-spetsiifiliste seadistuste, hüppamis-hostide jne. kohta. Need võimaldavad ülesannete täitmise kohta natuke muuta. Kuid unustage see. Igal neist keerulistest valikutest on väga spetsiifilised rakendused, ja need ei ole universaalsed. Räägime põhilistest asjadest, mida peaksid teadma ja kasutama kõik.
Kui soovite "midagi" teostada "kuskil" — kirjutate play. Mitte rolli. Mitte rolli moodulitega ja delegaatidega. Te lihtsalt kirjutate play. Milles, valdkonnas hosts loetlete, kus teostada, ja roles/tasks — mida teostada.
Lihtne, eks? Kuidas saab olla teisiti?
Üks iseloomulik hetki, mil inimestel tekib soov seda mitte play kaudu teha, on "roll, mis kõik seadistab". Tahaks, et oleks roll, mis seadistab ja serverile esimese tüübi ja teise tüübi serverid.
Arhetüüpne näide on monitooring. Tahaks, et oleks roll monitoring, mis seadistab monitooringu. Rolli monitoring määratakse monitooringu hostidele (vastavas play's). Kuid selgub, et monitooringu jaoks tuleb installida paketid hostidele, mida me monitoorime. Miks mitte kasutada delegaati? Ja veel peab seadistama iptables. Delegaati? Ja veel peab kirjutama/korrigeerima andmebaasi konfiguratsiooni, et monitooring saaks töötada. Delegaati! Ja kui loominguline mõte hakkab voolama, võib teha delegatsiooni include_role sisemise tsükli kaudu keerulise filtri kaudu grupi nimekirja peale, ja sees include_role võib veel teha delegate_to uuesti. Ja see läheb…
Hea soov on omada ühte ainsat rolli monitoring, mis "teeb kõike" — viib meid põrgusse, kust sageli on ainus väljapääs: kirjutada kõik nullist ümber.
Kus siin juhtus viga? Just siis, kui avastasite, et ülesande "x" täitmiseks hostis X peate minema hosti Y juurde ja tegema seal "y", oleksite pidanud sooritama lihtsa harjutuse: minna ja kirjutada play, mis hostis Y teeb y. Mitte midagi "x" juurde kirjutada, vaid alustada nullist. Isegi kui need on kõvasti kooditud muutujad.
Tundub, et eelnevatel lõikudest on kõik õigesti öeldud. Aga see ei ole teie juhtum! Sest te soovite kirjutada taaskasutatavat koodi, mis on DRY ja sarnaneb raamatukogule, ning tuleb leida meetod, kuidas seda teha.
Siin peidus on veel üks tõsine viga. Viga, mis muutis mitmed projektid talutavalt kirjutatutest (võib teha paremini, aga kõik töötab ja on lihtne juurde kirjutada) täielikuks õuduseks, milles isegi autor ei suuda orienteeruda. See töötab, aga jumal hoidku kui midagi muuta.
See viga kõlab nii: roll on raamatukogufunktsioon. See analoogia on pööranud nii palju häid algatusi, et on lihtsalt kurb vaadata. Roll ei ole raamatukogufunktsioon. Ta ei saa teha arvutusi ja ei saa teha otsuseid tasemel play. Tuletage mulle meelde, milliseid otsuseid play teeb?
Aitäh, te olete õige. Play teeb otsuse (täpsemalt, sisaldab teavet) selle kohta, milliseid ülesandeid ja rolle millistel hostidel täita.
Kui määrate selle otsuse rollile, veel koos arvutuste tegemisega, siis määrate endale (ja sellele, kes teie koodi püüab lahti harutada) vaese eksistentsi. Roll ei otsusta, kus ta täidetakse. Selle otsuse teeb play. Roll teeb seda, mida talle öeldi, seal, kus talle öeldi.
Räägime peatükis muutujatest ja jinja's, miks on Ansible'i programmeerimine ohtlik ja miks on COBOL parem kui Ansible. Praegu öelda, et iga teie arvutus jätab endale kustutamatud jäljed globaalsete muutujate muutmisest, ja te ei saa sellega midagi teha. Niipea kui kaks "jälge" ristuvad — on kõik kadunud.
Tähelepanek täpsetele: roll võib mõjutada control flow'd. delegate_to ja on mõistlikud rakendused. On meta: lõpp host/play. Kuid! Kas mäletate, et õpime aluseid? Unustasite delegate_to. Räägime kõige lihtsamast ja kõige ilusamast koodist Ansible'is. Mis on lihtne lugeda, lihtne kirjutada, lihtne debugeerida, lihtne testida ja lihtne täiendada. Niisiis, veel kord:
play ja ainult play otsustab, millistel hostidel midagi täidetakse.
Selles jaos oleme arutanud play ja rolli vastandlikkust. Nüüd räägime ülesannete (tasks) ja rollide (role) suhetest.
Ülesanded ja rollid
Vaadakem play'd:
- hosts: somegroup
pre_tasks:
- some_tasks1:
roles:
- role1
- role2
post_tasks:
- some_task2:
- some_task3:Oletame, et peate tegema foo. Ja see näeb välja nagu foo: name=foobar state=present. Kuhu see kirjutada? pre? post? Luua roll?
… Ja kuhu jäid ülesanded?
Alustame taas alustest - play struktuurist. Kui te selles küsimuses ei kindel, ei saa te play'd kasutada kõigi teiste aluseks, ja teie tulemus on "värisev".
Play struktuur: direktiiv hosts, play seadistused ja sektsioonid pre_tasks, tasks, roles, post_tasks. Ülejäänud parameetrid play jaoks ei ole meile praegu olulised.
Nende sektsioonide järjestus ülesannete ja rollide osas: pre_tasks, roles, tasks, post_tasks. Kuna semantiliselt on täitmise järjekord nende vahel tasks ja roles ei ole selge, siis parim tava ütleb, et lisame sektsiooni tasks, ainult kui pole roles. Kui on olemas roles, siis kõik seotud ülesanded paigutatakse sektsiooni pre_tasks/post_tasks.
Jääb vaid see, mis on semantiliselt arusaadav: kõigepealt pre_tasks, seejärel roles, seejärel post_tasks.
Aga me ei ole endiselt vastanud küsimusele: kuhu kirjutada mudeli foo kasutamine? Kas peame iga mudeli jaoks kirjutama terve rolli? Või on parem omada üht suurt rolli kõigeks? Kui mitte roll, siis kuhu kirjutada – pre või post?
Kui nendele küsimustele ei ole argumenteeritud vastust, siis see on märk intuitsiooni puudumisest, see ongi need "ebasiledad alused". Alustame. Esiteks kontrollküsimus: kui play'l on pre_tasks ja post_tasks (ja ei ole ei ülesandeid ega rolle), kas midagi võib katki minna, kui ma viin esimese ülesande post_tasks lõppu pre_tasks?
Muidugi, küsimuse sõnastus viitab, et see võib puruneda. Aga mis täpselt?
… Käitlejad. Põhimõtete lugemine avab olulise fakti: kõik käitlejad flush’itakse automaatselt pärast iga sektsiooni. Tähendab, et kõik ülesanded pre_tasks, siis kõik käitlejad, mis on olnud notify. Seejärel täidetakse kõik rollid ja kõik käitlejad, mis on olnud notify rollides. Siis post_tasks ja nende käitlejad.
Seega, kui liigutate ülesande post_tasks ühes pre_tasks, siis potentsiaalselt täidate selle enne käitleja täitmist. Näiteks, kui pre_tasks paigaldatakse ja konfigureeritakse veebiserver, ja seejärel määratleme selle, takistades seeläbi kasutajal selgelt selle väljaid muuta. See on üks andmete peitmise mustritest post_tasks millesse midagi saadetakse, siis selle ülesande liigutamine sektsiooni pre_tasks toob kaasa selle, et "saadetamise" hetkel server ei tööta ja kõik puruneb.
Ja nüüd mõtleme veel kord, miks me vajame pre_tasks ja post_tasks? Например, для того, чтобы выполнить всё нужное (включая хэндлеры) до выполнения роли. А post_tasks aitab meil töötada rollide täitmise tulemustega (sealhulgas käitlejatega).
Tark Ansible'i tunner ütleb meile, et on olemas meta: flush_handlers, aga miks me vajame flush_handlers, kui me saame loota sektsioonide täitmise järjekorrale mängus? Veelgi enam, meta: flush_handlers'i kasutamine võib tuua meile ootamatuid ja korduvaid käitlejaid, tekitada kummalisi hoiatusi, kui kasutatakse when on blokki jne. Mida paremini te tunnete Ansible'i, seda rohkem nüansse suudate öelda "nutika" lahenduse kohta. Lihtne lahendus on looduslik eraldamine pre/roles/post — see ei tekita nüansse.
Ja naaseme meie ‘foo’ juurde. Kuhu see paigutada? Pre, post või rollidesse? Ilmselgelt sõltub see sellest, kas me vajame käitleja töö tulemusi foo jaoks. Kui neid ei ole, ei pea foo olema ei pre ega post — need sektsioonid on erilise tähendusega — ülesannete täitmine enne ja pärast peamise koodimassi.
Nüüd on vastus küsimusele "roll või ülesanne" seotud sellega, mis juba on play's — kui seal on ülesandeid, tuleb need ülesanded lisada. Kui on rolle — tuleb luua roll (isegi üksiku ülesande põhjal). Tuletan meelde, et ülesanded ja rollid ei tohi olla samal ajal kasutuses.
Ansible'i põhialuste mõistmine annab põhjendatud vastused, mis näivad olevat maitse küsimused.
Ülesanded ja rollid (teine osa)
Rääkigem nüüd olukorrast, kus te alles alustate playbooki kirjutamist. Teil on vaja teha foo, bar ja baz. Kas need on kolm ülesannet, üks roll või kolm rolli? Üldistades küsimust: millal tuleks hakata rolle kirjutama? Mis mõte on rollide kirjutamisel, kui saab kirjutada ülesandeid?… Mis on roll?
Üks suurimaid vigu (millest olen juba rääkinud) on arvata, et roll on nagu funktsioon programmis. Kuidas näeb välja funktsiooni üldine kirjeldus? Ta võtab sisenditeks argumendid, suheldes kõrvaltoimetega, tekitab kõrvalmõjusid ja tagastab väärtuse.
Nüüd tähelepanu. Mida saab rollis teha? Kutsu esile kõrvaltoimeid — alati hea meelega, see ongi kogu Ansible väljakutse — teha kõrvaltoimeid. Omada kõrvalpõhjusi? Lihtne. Aga "edastada väärtus ja tagastada see" — siin on asi juba keeruline. Esiteks, te ei saa rolli väärtust edastada. Saate määrata globaalset muutujat, mille eluiga on mängu jooksul, vars’i sektsioonis rolli jaoks. Saate määrata globaalset muutujat, mille eluiga on mängu jooksul, rolli sees. Või isegi plaanide eluiga,set_fact/register). Kuid te ei saa omada "kohalikku muutuja". Te ei saa "võtta väärtust" ja "tagastada seda".
Kandub välja peamine: Ansible'i jaoks ei saa midagi kirjutada ja mitte tekitada kõrvaltoimeid. Globaalsete muutujate muutmine on alati kõrvaltoime funktsioonis. Näiteks Rustis on globaalsete muutujate muutmine unsafe. Ja Ansible’is on ainus meetod, kuidas mõjutada rolli väärtusi. Pange tähele, milliseid sõnu kasutatakse: mitte "edastada väärtus rolli", vaid "muuta väärtusi, mida roll kasutab". Roolide vahel ei ole isolatsiooni. Ülesannete ja rollide vahel ei ole isolatsiooni.
Kokku: roll ei ole funktsioon.
Mis on head rollides? Esiteks, rollidel on default values (/default/main.yaml), teiseks on rollidel täiendavad kataloogid failide salvestamiseks.
Miks on default values head? Sest Maslow püramiidis on Ansible'i muutuja prioriteetide tabelis rolli default'id kõige vähem prioriteetsed (välja arvatud Ansible'i käsurea parameetrid). See tähendab, et kui soovite pakkuda vaikimisi väärtusi ja mitte muretseda, et need hägustavad inventari või grupivariable’id, on rollide default’id ainus õige koht, kuhu minna. (Ma valetan natuke — on veel |d(your_default_here), aga kui rääkida staatilisest kohast — siis ainult rollide default'id).
Mis veel on head rollides? Need, et neil on oma kataloogid. Need on kataloogid muutujatele, nii püsivatele (st rollile arvutatud) kui ka dünaamilistele (on olemas selline pattern või anti-pattern — include_vars (autoriseerimiskoodiga). {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Need on kataloogid files/, templates/. Samuti võimaldab see rollidel omada oma mooduleid ja pluginaid (library/). Kuid võrreldes playbookide ülesannetega (millel võivad olla ka kõik need omadused) on siin ainult üks kasu: failid on kokku kuhjatud mitmesse eraldi kuhja, mitte ühte suurde.
Veel üks detail: on võimalik proovida teha rolle, mis on kergesti taaskasutatavad (galaxy kaudu). Pärast kogude tekkimist võib rollide levikut peaaegu unustatuks pidada.
Seega on rollidel kaks olulist omadust: neil on vaikeseaded (ainulaadne omadus) ja nad võimaldavad koodi struktureerimist.
Tagasi algse küsimuse juurde: millal teha ülesandeid ja millal rolle? Ülesandeid kasutatakse playbookides kõige sagedamini kas kui "liimi" enne või pärast rolle, või iseseisva ehituskomponendina (siis ei tohiks koodis rolle olla). Palju normaalseid ülesandeid koos rollidega on üheselt soovimatu. Tuleks kinni pidada kindlast stiilist - kas ülesanded või rollid. Rollid pakuvad subjektide eraldamist ja vaikeseadeid, ülesanded võimaldavad koodi kiiremini lugeda. Tavaliselt kantakse rollidesse rohkem "statioonaarne" (oluline ja keeruline) kood, samas kui ülesannete stiilis kirjutatakse abiskripte.
On olemas võimalus import_role'i kasutada ülesandena, kuid kui te seda kirjutate, olge valmis oma esteetiliste tunnete õigustamiseks, miks te soovite seda teha.
Tüütud lugejad võivad öelda, et rollid võivad importida rolle, rollidel võib olla sõltuvus läbi galaxy.yml, ja lisaks on veel hirmus ja õudne. include_role — meenutan, et me arendame oma põhioskusi Ansible'is, mitte iluvõimlemises.
Handler'id ja ülesanded
Räägime ühest ilmsest asjast: handler'idest. Nende õigesti kasutamise oskus on peaaegu kunst. Mis vahe on handler'i ja ülesande vahel?
Kuna me meenutame põhialuseid, siis siin on näide:
- hosts: group1
tasks:
- foo:
notify: handler1
handlers:
- name: handler1
bar:Rollis asuvad handler'id failis rolename/handlers/main.yaml. Handler'id jagunevad kõikide mängijate vahel: pre/post_tasks võivad kutsuda rollide handler'e, ning roll võib kutsuda handler'e mängust. Siiski, "ristrollide" handler'ite kutsumine tekitab palju suuremat segadust kui triviaalsete handler'ite kordamine. (Veel üks parim praktika — vältida handler'ite nimede kordamist).
Põhiline erinevus seisneb selles, et ülesanne täidetakse (idempotentselt) alati (pluss/minus sildid ja when), ja käitleja — staatuse muutmise jaoks (notify aktiveerub ainult siis, kui on olnud muutus). Miks see on probleem? Näiteks, et korduva käivitamise korral, kui ei olnud muutust, ei tule ka käitlejat. Aga miks võib olla nii, et me peame käitlejat täitma, isegi kui genereeriva ülesande puhul ei olnud muutust? Näiteks sellepärast, et midagi läks valesti ja muutus oli olnud, kuid käitlejani ei jõudnud. Näiteks, kuna võrk oli ajutiselt maas. Konfigureerimine muutus, teenust ei käivitatud uuesti. Järgmisel käivitamisel konfigureerimine enam ei muutu ja teenus jääb vana konfigureerimise versiooni peale.
Konfigureerimise olukord ei ole lahendatav (täpsemalt, võime ise endale välja mõelda spetsiaalse protokolli taaskäivitamiseks faililippude jne. abil, kuid see ei ole enam 'basic ansible' mingil kujul). Siiski on veel üks sage lugu: oleme rakenduse installinud, selle .service-faili, ja nüüd tahame seda daemon_reload ja state=started. Ja loomulik koht selle jaoks näib olevat käitleja. Kuid kui muuta see käitlejast ülesandeks ülesannete loendi lõpus või rolliks, siis täidetakse see igal korral ideempotentselt. Isegi kui mänguraamat katkeb keskel. See ei lahenda probleemiga seotud restarted (ülesannet ei saa teha restarted atribuudi, kuna ideempotentsus kaob), kuid kindlasti tasub seadistada state=started, seetõttu tõuseb mänguraamatude üldine stabiilsus, kuna väheneb seoste ja dünaamilise oleku arv.
Veel üks positiivne omadus käitleja on see, et see ei ummista väljundit. Muudatusi polnud — ei ole üleliigseid skipped või ok väljundis — lihtsam lugeda. Samuti on see ka negatiivne omadus — kui leiate trükivea lineaarset ära täidetavas ülesandes esimesel käigul, siis käitlejad täidetakse ainult muutuste korral, st teatud tingimustel — väga harva. Näiteks esmakordselt elus viie aastase vahega. Ja loomulikult on seal trükiviga nimes ja kõik katki. Teisel korral neid enam ei käivitata — changed'it pole.
Erinevate muutujate kättesaadavusest tuleb kindlasti rääkida. Näiteks, kui teete notify tsükliülesande jaoks, siis mis on muutuja väärtused? Analüütiliselt on võimalik järeldada, kuid see ei ole alati triviaalne, eriti kui muutujad tulevad erinevatest kohtadest.
… Seega on handler'id palju vähem kasulikud ja palju probleemsemad, kui esmapilgul tundub. Kui on võimalik midagi ilusasti (ilma nippideta) kirjutada ilma handler'ideta, siis on parem seda teha. Kui ilusasti ei õnnestu, on parem neid kasutada.
Peenest lugija märgib õigusega, et me ei arutanud kuula, et handler võib kutsuda notify teise handler'i jaoks, et handler võib sisaldada import_tasks'i (mis võib teha include_role'i c with_items-iga), et handlerite süsteem Ansible'is on Turingi täiuslik, et include_role'i handler'id ristuvad huvitaval viisil mängu handler'itega jne — kõik see ei ole kindlasti "põhialused".
Kuid on üks konkreetne WTF, mis on tegelikult funktsioon ja mida tuleks meeles pidada. Kui teil on ülesanne, mis täitub delegate_to ja sellel on notify, siis vastav handler täidetakse ilma delegate_to, st hostis, kus mäng on määratud. (Kuigi handler'il võib loomulikult olla delegate_to ka).
Soovin rääkida natuke uuesti kasutatavatest rollidest. Enne kogude loomist oli idee, et võiks luua universaalsed rollid, mida saab ansible-galaxy install ja asusime teele. Töötab kõigis opsüsteemides igasugustes olukordades. Niisiis, minu arvamus: see ei toimi. Iga roll, mis katab tonni include_vars, toetades 100500 juhtumit, on ette määratud nurkade juhtumite vigade sügavustesse. Neid saab peita massilise testimisega, kuid nagu iga testimise puhul, kas on teil dekarteslik toode sisendite väärtustest ja totaalfunktsioon, või olete "katnud üksikuid stsenaariume". Minu arvamus — oleks parem, kui roll oleks lineaarne (tsüklomaatiline keerukus 1).
Mida vähem if-e (olgu need siis selged või deklareeritud — kujul when või kujul include_vars tüüpide kogum), seda parem on roll. Mõnikord tuleb harude loomine, kuid, pean kordama, mida vähem neid on, seda parem. Nii et näiliselt hea roll galaxy's (see töötab ju!) koos hulga when võib olla vähem eelistatav kui "oma" roll viiest ülesandest. Moment, mil galaxy roll on parem — kui hakkate midagi kirjutama. Moment, mil see halveneb — kui midagi läheb katki ja teil on kahtlus, et see on "galaxy rolli" tõttu. Avate selle, ja seal on viis kaasamist, kaheksa ülesande nimekirja ja hunnik... whenJa sellega on vaja tegeleda. Samuti mitte 5 ülesannet lineaarse nimekirjana, kus ei saa isegi katki minna.
Järgmistes osades
- Veidi inventeerimisest, grupimuutujatest, host_group_vars plugin'ist, hostvars. Kuidas spagetist korralikult siduda. Muutujate ulatus ja eelõigus, Ansible'i mälumudel. "Kus siis ikkagi hoida andmebaasi kasutajanime?"
jinja: {{ jinja }}— nosql notype nosense pehme plastiliin. See on igal pool, isegi seal, kus te seda ei oota. Veidi...!!unsafeja maitsev yaml.
Allikas: habr.com
