Mõistlikud põhitõed, ilma milleta on teie mänguraamatud kleepuva pasta tükk

Ma arvustan palju teiste inimeste Ansible koodi ja kirjutan palju ise. Vigade (nii teiste inimeste kui ka enda omade) analüüsimise, aga ka mitmete intervjuude käigus sain aru peamisest veast, mida Ansible kasutajad teevad – nad satuvad keerulistesse asjadesse põhilisi selgeks saamata.

Selle üleüldise ebaõigluse parandamiseks otsustasin kirjutada Ansible’i tutvustuse neile, kes seda juba teavad. Hoiatan, see ei ole meeste ümberjutustus, see on pikk lugemine, kus on palju tähti ja pilte pole.

Lugeja oodatav tase on see, et yamlat on juba mitu tuhat rida kirjutatud, midagi on juba tootmises, aga "kuidagi on kõik viltu".

Nimed

Peamine viga, mille Ansible kasutaja teeb, on see, et ta ei tea, kuidas midagi nimetatakse. Kui te nimesid ei tea, ei saa te dokumentatsioonist aru. Elav näide: intervjuu ajal ei osanud inimene, kes ütles, et kirjutas Ansible'is palju, vastata küsimusele „millistest elementidest koosneb mänguraamat?” Ja kui ma pakkusin, et "oodati vastust, et mänguraamat koosneb mängust", järgnes hukkamõistev kommentaar "me ei kasuta seda". Inimesed kirjutavad Ansible'i raha pärast ja ei kasuta mängu. Nad kasutavad seda tegelikult, kuid ei tea, mis see on.

Nii et alustame millestki lihtsast: kuidas seda nimetatakse. Võib-olla teate seda või ei tea, sest te ei pööranud dokumentatsiooni lugedes tähelepanu.

ansible-playbook käivitab mänguraamatu. Mänguraamat on yml/yaml laiendiga fail, mille sees on midagi sellist:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Oleme juba aru saanud, et kogu see fail on mänguraamat. Saame näidata, kus on rollid ja kus on ülesanded. Aga kus on mängimine? Ja mis vahe on näidendil ja rollil või mänguraamatul?

See kõik on dokumentatsioonis. Ja nad igatsevad seda. Algajad - sest seda on liiga palju ja te ei mäleta kõike korraga. Kogenud - sest "tühised asjad". Kui olete kogenud, lugege neid lehti uuesti vähemalt kord kuue kuu jooksul ja teie kood muutub klassi juhtivaks.

Niisiis, pidage meeles: Playbook on loend, mis koosneb mängust ja import_playbook.
See on üks näidend:

- hosts: group1
  roles:
    - role1

ja see on ka teine ​​näidend:

- hosts: group2,group3
  tasks:
    - debug:

Mis on mängimine? Miks ta on?

Mäng on mänguraamatu võtmeelement, sest mängimine ja ainult mängimine seostab rollide ja/või ülesannete loendi hostide loendiga, millel need tuleb täita. Dokumentatsiooni sügavatest sügavustest leiate mainimist delegate_to, kohaliku otsingu pistikprogrammid, võrgukliendipõhised sätted, hüppavad hostid jne. Need võimaldavad teil ülesannete täitmise kohta veidi muuta. Aga, unusta see ära. Igal neist nutikatest võimalustest on väga spetsiifilised kasutusalad ja need pole kindlasti universaalsed. Ja me räägime põhiasjadest, mida kõik peaksid teadma ja kasutama.

Kui tahad “midagi” “kuskil” esitada, siis kirjutad näidendit. Mitte roll. Pole moodulite ja delegaatidega roll. Sa võtad selle ja kirjutad näidendit. Milles väljal hosts loetlete, kus täita, ja rollides/ülesannetes - mida täita.

Lihtne, eks? Kuidas saakski teisiti olla?

Üks iseloomulikke hetki, mil inimestel on soov seda teha mitte mängu kaudu, on "roll, mis paneb paika kõik". Soovin rolli, mis konfigureerib nii esimest tüüpi servereid kui ka teist tüüpi servereid.

Arhetüüpne näide on monitooring. Soovin saada jälgimisrolli, mis konfigureerib jälgimist. Jälgimisroll on määratud jälgivatele hostidele (vastavalt mängule). Kuid selgub, et jälgimiseks peame toimetama pakid jälgitavatele hostidele. Miks mitte kasutada delegaati? Samuti peate konfigureerima iptablesi. delegeerida? Samuti peate jälgimise lubamiseks kirjutama/parandama DBMS-i konfiguratsiooni. delegaat! Ja kui loovust napib, siis saad delegatsiooni teha include_role pesastatud tsüklis, kasutades keerulist filtrit rühmade loendis ja sees include_role saate teha rohkem delegate_to uuesti. Ja me läheme minema...

Hea soov – omada ühtainsat jälgimisrolli, mis “teeb kõike” – viib meid täielikku põrgusse, millest enamasti on vaid üks väljapääs: kõik nullist ümber kirjutada.

Kus siin viga juhtus? Hetkel, kui avastasite, et ülesande "x" tegemiseks hostis X peate minema hosti Y juurde ja tegema seal "y", tuli teha lihtne harjutus: minna ja kirjutada esitus, mis hostis Y teeb y. Ärge lisage midagi "x"-le, vaid kirjutage see nullist. Isegi kõvakodeeritud muutujatega.

Näib, et ülaltoodud lõikudes on kõik õigesti öeldud. Kuid see pole teie juhtum! Kuna soovite kirjutada korduvkasutatavat koodi, mis on KUIV ja teegilaadne, ja peate otsima meetodit, kuidas seda teha.

Siin varitseb veel üks tõsine viga. Viga, mis muutis paljud projektid talutavalt kirjutatud (võiks olla parem, aga kõik töötab ja on lihtne lõpetada) täielikuks õudseks, millest isegi autor aru ei saa. See toimib, aga hoidku jumal, et sa midagi muudaksid.

Viga on järgmine: roll on raamatukogu funktsioon. See analoogia on rikkunud nii palju häid algusi, et seda on lihtsalt kurb vaadata. Roll ei ole raamatukogu funktsioon. Ta ei oska arvutada ega teha mängutasandi otsuseid. Tuletage mulle meelde, milliseid otsuseid mäng teeb?

Aitäh, sul on õigus. Play teeb otsuse (täpsemalt sisaldab teavet), milliseid ülesandeid ja rolle millistel hostidel täita.

Kui delegeerite selle otsuse mõnele rollile ja isegi arvutustega, hukatate end (ja selle, kes proovib teie koodi sõeluda) armetu eksistentsi. Roll ei otsusta, kus seda esitatakse. See otsus tehakse mängu teel. Roll teeb, mis kästakse, kus kästakse.

Miks on Ansible'is programmeerimine ohtlik ja miks on COBOL parem kui Ansible, räägime muutujatest ja jinjast. Ütleme praegu üht – iga teie arvutus jätab maha kustumatu jälje globaalsete muutujate muutustest ja te ei saa sellega midagi ette võtta. Niipea kui kaks "jälge" ristusid, oli kõik kadunud.

Märkus vingujatele: roll võib kindlasti mõjutada juhtimisvoogu. Sööma delegate_to ja sellel on mõistlik kasutusala. Sööma meta: end host/play. Aga! Pea meeles, et me õpetame põhitõdesid? Unustasid delegate_to. Me räägime kõige lihtsamast ja ilusamast Ansible koodist. Mida on lihtne lugeda, lihtne kirjutada, lihtne siluda, lihtne testida ja lihtne täita. Niisiis, veel kord:

mängida ja ainult mäng otsustab, millistel hostidel mida täidetakse.

Selles osas käsitlesime mängu ja rolli vastandust. Nüüd räägime ülesannete ja rollide suhetest.

Ülesanded ja rollid

Kaaluge mängimist:

- 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 foo: name=foobar state=present. Kuhu ma selle kirjutama peaksin? in pre? postitada? Kas luua roll?

...Ja kuhu läksid ülesanded?

Alustame taas põhitõdedest – mänguseadmest. Kui hõljute sellel teemal, ei saa te mängu kõige muu aluseks võtta ja teie tulemus jääb "raputama".

Esitusseade: majutab käskkirja, mängimise enda sätted ja pre_tasks, ülesanded, rollid, post_tasks jaotised. Ülejäänud mänguparameetrid pole meie jaoks praegu olulised.

Nende sektsioonide järjekord koos ülesannete ja rollidega: pre_tasks, roles, tasks, post_tasks. Kuna semantiliselt on täitmise järjekord vahemikus tasks и roles pole selge, siis heade tavade kohaselt lisame jaotise tasks, ainult siis, kui mitte roles... Kui seal on roles, siis paigutatakse kõik lisatud ülesanded osadesse pre_tasks/post_tasks.

Jääb vaid see, et kõik on semantiliselt selge: esiteks pre_tasks, siis roles, siis post_tasks.

Kuid me pole ikka veel vastanud küsimusele: kus on mooduli kutse? foo kirjutada? Kas me peame iga mooduli jaoks kirjutama terve rolli? Või on parem, kui kõigele on paks roll? Ja kui mitte roll, siis kuhu ma peaksin kirjutama - kas eel- või postituses?

Kui nendele küsimustele pole põhjendatud vastust, on see märk intuitsiooni puudumisest, see tähendab samadest "rappuvatest alustest". Selgitame välja. Esiteks turvaküsimus: kui mängul on pre_tasks и post_tasks (ja pole ülesandeid ega rolle), siis võib midagi katkeda, kui täidan esimese ülesande alates post_tasks Ma nihutan selle lõpuni pre_tasks?

Muidugi vihjab küsimuse sõnastus, et läheb katki. Aga mida täpselt?

... Käitlejad. Põhitõdede lugemisel ilmneb oluline tõsiasi: kõiki käitlejaid loputatakse automaatselt pärast iga sektsiooni. Need. kõik ülesanded alates pre_tasks, seejärel kõik töötlejad, keda teavitati. Seejärel täidetakse kõik rollid ja kõik käsitlejad, keda rollides teavitati. Pärast post_tasks ja nende käitlejad.

Seega, kui lohistate ülesande asukohast post_tasks в pre_tasks, siis tõenäoliselt käivitate selle enne töötleja käivitamist. näiteks kui sisse pre_tasks veebiserver on installitud ja konfigureeritud ning post_tasks talle saadetakse midagi, seejärel edastage see ülesanne jaotisesse pre_tasks viib selleni, et "saatmise" ajal server veel ei tööta ja kõik läheb katki.

Nüüd mõtleme uuesti, milleks seda vaja on pre_tasks и post_tasks? Näiteks selleks, et enne rolli täitmist kõik vajalik (sh käitlejad) läbi teha. A post_tasks võimaldab meil töötada rollide (sh käitlejate) täitmise tulemustega.

Nutikas Ansible ekspert räägib meile, mis see on. meta: flush_handlers, aga milleks me vajame flush_handlereid, kui saame loota mängus olevate sektsioonide täitmise järjekorrale? Lisaks võib meta: flush_handlers kasutamine anda meile ootamatuid asju duplikaatkäitlejate puhul, andes meile kasutamisel kummalisi hoiatusi when у block jne. Mida paremini teate võimalikku, seda rohkem nüansse saate nimetada "keerulise" lahenduse jaoks. Ja lihtne lahendus – kasutades loomulikku jaotust eel/rollide/posti vahel – ei tekita nüansse.

Ja tagasi meie "foo" juurde. Kuhu ma selle panema? Eel-, posti- või rollides? Ilmselt sõltub see sellest, kas me vajame foo käitleja tulemusi. Kui neid pole, siis ei pea foo'd asetama ei pre- ega post-ossa - neil jaotistel on eriline tähendus - ülesannete täitmine enne ja pärast koodi põhiosa.

Nüüd taandub vastus küsimusele "roll või ülesanne" sellele, mis on juba mängus - kui seal on ülesandeid, siis peate need ülesannetesse lisama. Kui on rolle, siis tuleb luua roll (kasvõi ühest ülesandest). Tuletan meelde, et ülesandeid ja rolle ei kasutata korraga.

Ansible’i põhitõdede mõistmine annab mõistlikud vastused näilistele maitseküsimustele.

Ülesanded ja rollid (teine ​​osa)

Nüüd arutleme olukorra üle, kui hakkate just mänguraamatut kirjutama. Peate tegema foo, baari ja bazi. Kas need kolm ülesannet on üks roll või kolm rolli? Küsimuse kokkuvõtteks: mis hetkel peaks hakkama rolle kirjutama? Mis mõtet on rolle kirjutada, kui saab kirjutada ülesandeid?... Mis on roll?

Üks suurimaid vigu (sellest juba rääkisin) on arvata, et roll on nagu funktsioon programmi teegis. Kuidas näeb välja üldine funktsiooni kirjeldus? See aktsepteerib sisendargumente, suhtleb kõrvalpõhjustega, avaldab kõrvalmõjusid ja tagastab väärtuse.

Nüüd tähelepanu. Mida saab sellest rollis teha? Alati on teretulnud helistada kõrvalmõjudeks, see on kogu Ansible’i olemus – kõrvalmõjude tekitamine. Kas teil on kõrvalpõhjuseid? Elementaarne. Kuid "andke väärtus ja tagastage see" - see on koht, kus see ei tööta. Esiteks ei saa te rollile väärtust edasi anda. Rolli jaotises Vars saate määrata globaalse muutuja kogu eluea jooksul. Saate määrata rolli sees globaalse muutuja, millel on kogu elu. Või isegi mänguraamatute elueaga (set_fact/register). Kuid teil ei saa olla "kohalikke muutujaid". Sa ei saa "väärtust võtta" ja "tagastada".

Sellest tuleneb peamine: te ei saa Ansiblesse midagi kirjutada, ilma et see põhjustaks kõrvalmõjusid. Globaalsete muutujate muutmine on alati funktsiooni kõrvalmõju. Näiteks Rustis on globaalse muutuja muutmine unsafe. Ja Ansibles on see ainus meetod rolli väärtuste mõjutamiseks. Pange tähele kasutatud sõnu: mitte "andke rollile väärtus edasi", vaid "muutke väärtusi, mida roll kasutab". Rollide vahel pole isolatsiooni. Tööülesannete ja rollide vahel puudub isolatsioon.

Kokku: roll ei ole funktsioon.

Mis on rollis head? Esiteks on rollil vaikeväärtused (/default/main.yaml), teiseks on rollil failide salvestamiseks täiendavad kataloogid.

Mis kasu on vaikeväärtustest? Kuna Maslow püramiidis, Ansible'i üsna moonutatud muutuvate prioriteetide tabelis, on rolli vaikeseaded kõige madalama prioriteediga (miinus Ansible käsurea parameetrid). See tähendab, et kui peate esitama vaikeväärtused ja mitte muretsema selle pärast, et need alistavad laoseisu või rühmamuutujate väärtused, on rolli vaikeväärtused teie jaoks ainuõige koht. (Ma valetan natuke - neid on rohkem |d(your_default_here), aga kui rääkida statsionaarsetest kohtadest, siis ainult rollivaikeväärtused).

Mis on rollide juures veel head? Sest neil on oma kataloogid. Need on muutujate kataloogid, nii konstantsed (st rolli jaoks arvutatud) kui ka dünaamilised (seal on kas muster või antimuster - include_vars koos {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Need on kataloogid files/, templates/. Samuti võimaldab see teil omada oma mooduleid ja pistikprogramme (library/). Kuid võrreldes mänguraamatu ülesannetega (milles võib ka see kõik olla), on siin ainsaks eeliseks see, et faile ei visata ühte hunnikusse, vaid mitmesse eraldi hunnikusse.

Veel üks detail: võite proovida luua rolle, mis on taaskasutamiseks saadaval (galaktika kaudu). Kollektsioonide tulekuga võib rollijaotuse pidada peaaegu unustatud.

Seega on rollidel kaks olulist funktsiooni: neil on vaikeseaded (ainulaadne funktsioon) ja need võimaldavad teil koodi struktureerida.

Tulles tagasi algse küsimuse juurde: millal teha ülesandeid ja millal rolle? Mänguraamatu ülesandeid kasutatakse kõige sagedamini kas "liimina" enne/pärast rolle või iseseisva ehituselemendina (siis ei tohiks koodis rolle olla). Hunnik tavalisi ülesandeid segamini rollidega on üheselt mõistetav lohakus. Peaksite kinni pidama kindlast stiilist – kas ülesandest või rollist. Rollid eraldavad olemid ja vaikeväärtused, ülesanded võimaldavad koodi kiiremini lugeda. Tavaliselt pannakse rollidesse "statsionaarsem" (olulisem ja keerulisem) kood ning abiskriptid kirjutatakse ülesande stiilis.

Ülesandeks on võimalik teha import_role, aga kui sa selle kirjutad, siis ole valmis selgitama enda ilumeelele, miks sa seda teha tahad.

Nutikas lugeja võib öelda, et rollid võivad rolle importida, rollidel võivad olla sõltuvused saidi galaxy.yml kaudu ning seal on ka kohutav ja kohutav include_role — Tuletan meelde, et parandame oskusi Ansible algklassides, mitte iluvõimlemises.

Käitlejad ja ülesanded

Arutleme veel ühe ilmse asja üle: käitlejad. Nende õige kasutamise teadmine on peaaegu kunst. Mis vahe on händleril ja lohistamisel?

Kuna me mäletame põhitõdesid, on siin näide:

- hosts: group1
  tasks:
    - foo:
      notify: handler1
  handlers:
     - name: handler1
       bar:

Rolli töötlejad asuvad failis rolename/handlers/main.yaml. Käsitlejad kobisevad kõigi mängus osalejate vahel: pre/post_tasks võivad tõmmata rollikäsitlejaid ja roll võib neid mängust välja tõmmata. Rollidevahelised kõned töötlejatele põhjustavad aga palju rohkem wtf-i kui triviaalse töötleja kordamine. (Teine parimate tavade element on püüda mitte korrata käitlejate nimesid).

Peamine erinevus seisneb selles, et ülesanne täidetakse alati (idempotentselt) (pluss/miinus sildid ja when) ja käitleja - oleku muutmise järgi (teavitage tulekahjusid ainult siis, kui seda on muudetud). Mida see tähendab? Näiteks see, et kui taaskäivitades ei tehtud muudatusi, siis pole ka töötlejat. Miks võib juhtuda, et peame käitleja käivitama, kui genereeritavas ülesandes muutusi ei toimunud? Näiteks sellepärast, et midagi läks katki ja muutus, kuid täitmine ei jõudnud käitlejani. Näiteks seetõttu, et võrk oli ajutiselt maas. Konfiguratsioon on muutunud, teenust pole taaskäivitatud. Järgmisel käivitamisel konfiguratsioon enam ei muutu ja teenus jääb konfiguratsiooni vana versiooni juurde.

Konfigiga olukorda ei saa lahendada (täpsemalt saab faililippudega vms endale spetsiaalse restartprotokolli leiutada, aga see pole enam ühelgi kujul 'põhiline'). Kuid on veel üks levinud lugu: installisime rakenduse, salvestasime selle .service-faili ja nüüd me tahame seda daemon_reload и state=started. Ja selle loomulik koht näib olevat käitleja. Kui aga muudate selle mitte töötlejaks, vaid ülesandeks ülesandeloendi või rolli lõpus, siis täidetakse seda iga kord idempotenselt. Isegi kui näidendiraamat läks keskelt katki. See ei lahenda üldse taaskäivitamise probleemi (restart atribuudiga ülesannet teha ei saa, kuna idempotentsus kaob), kuid kindlasti tasub teha state=started, mänguraamatute üldine stabiilsus suureneb, sest ühenduste arv ja dünaamiline olek väheneb.

Käsitleja teine ​​positiivne omadus on see, et see ei ummista väljundit. Mingeid muudatusi ei toimunud – väljundis ei jäetud lisa vahele ega ok – lihtsam lugeda. See on ka negatiivne omadus - kui avastad lineaarselt täidetavas ülesandes kirjavea kohe esimesel käivitamisel, siis käitlejad käivituvad alles muutmisel, s.t. teatud tingimustel - väga harva. Näiteks esimest korda elus viis aastat hiljem. Ja loomulikult tuleb nimes kirjaviga ja kõik läheb katki. Ja kui te neid teist korda ei käivita, pole midagi muutunud.

Eraldi peame rääkima muutujate saadavusest. Näiteks kui teavitate ülesandest tsükliga, siis mis on muutujates? Võite analüütiliselt arvata, kuid see pole alati triviaalne, eriti kui muutujad pärinevad erinevatest kohtadest.

... Seega on käitlejad palju vähem kasulikud ja palju problemaatilisemad, kui tundub. Kui suudate ilma käsitlejateta midagi ilusti (ilma satsikuteta) kirjutada, on parem seda teha ilma nendeta. Kui see ilusti välja ei tule, on nendega parem.

Söövitav lugeja juhib õigusega tähelepanu sellele, et me pole arutanud listenet töötleja saab helistada teavitustele teisele käitlejale, et töötleja võib sisaldada import_tasks (mis võib teha include_role'i ​​funktsiooniga with_items), et Ansible'i käitlejasüsteem on Turingi-täielik, et loendi include_role töötlejad ristuvad kummalisel viisil mängu käitlejatega, jne .d. - see kõik pole ilmselgelt "põhitõed").

Kuigi on üks konkreetne WTF, mis on tegelikult funktsioon, mida peate meeles pidama. Kui teie ülesanne täidetakse koos delegate_to ja sellel on teavitus, siis käivitatakse vastav töötleja ilma delegate_to, st. hostil, kus mängimine on määratud. (Kuigi käitlejal võib see muidugi olla delegate_to sama).

Eraldi tahan öelda paar sõna korduvkasutatavate rollide kohta. Enne kollektsioonide ilmumist tekkis idee, et võiks teha universaalseid rolle, mis võiksid olla ansible-galaxy install ja läks. Töötab kõigi variantide kõigi operatsioonisüsteemidega ja igas olukorras. Niisiis, minu arvamus: see ei tööta. Mis tahes roll massiga include_vars, mis toetab 100500 1 juhtumit, on määratud nurgakorpuse vigade kuristikku. Neid saab katta ulatusliku testimisega, kuid nagu iga testimise puhul, on teil kas sisendväärtuste ja kogufunktsiooni Descartes'i korrutis või "kaetud üksikud stsenaariumid". Minu arvamus on, et palju parem on, kui roll on lineaarne (tsüklomaatiline keerukus XNUMX).

Mida vähem kui (selgesõnaline või deklaratiivne - vormis when või vorm include_vars muutujate komplekti järgi), seda parem roll. Mõnikord tuleb oksi teha, aga, kordan, mida vähem neid on, seda parem. Nii et see tundub hea roll galaxyga (see töötab!) koos hunnikuga when võib olla vähem eelistatav kui "oma" roll viie ülesande hulgast. Hetk, mil roll galaxyga on parem, on siis, kui hakkad midagi kirjutama. Hullemaks läheb siis, kui miski puruneb ja tekib kahtlus, et selle põhjuseks on roll galaktikaga. Avate selle ja seal on viis lisamist, kaheksa ülesandelehte ja virn when'ov... Ja me peame selle välja mõtlema. 5 ülesande asemel lineaarne nimekiri, milles pole midagi murda.

Järgmistes osades

  • Natuke laoseisust, rühmamuutujatest, host_group_vars pluginast, hostvarsist. Kuidas siduda spagettidega Gordiuse sõlm. Ulatus- ja tähtsusmuutujad, võimalik mälumudel. "Niisiis kuhu me andmebaasi kasutajanime salvestame?"
  • jinja: {{ jinja }} — nosql notype nosense pehme plastiliin. See on kõikjal, isegi seal, kus te seda ei oota. Natuke umbes !!unsafe ja maitsev yaml.

Allikas: www.habr.com

Lisa kommentaar