Lehetséges alapok, amelyek nélkül a játékkönyvek ragacsos tésztadarabok lesznek

Sok véleményt írok mások Ansible kódjáról, és sokat írok magam is. A hibák (mind mások, mind a saját hibáinak) elemzése során, valamint számos interjú során rájöttem az Ansible felhasználók fő hibájára - az alapvető hibák elsajátítása nélkül bonyolítanak bele bonyolult dolgokba.

Ennek az egyetemes igazságtalanságnak a kijavítására úgy döntöttem, hogy bevezetőt írok az Ansible-hez azok számára, akik már ismerik. Figyelmeztetlek, ez nem az emberek újramondása, ez egy hosszú olvasmány, sok betűvel és kép nélkül.

Az olvasó elvárt szintje, hogy már több ezer sor yamla íródott, valami már készül, de „valahogy minden ferde”.

nevek

Az Ansible-felhasználók fő hibája az, hogy nem tudja, mi a neve. Ha nem ismeri a neveket, nem érti, mit mond a dokumentáció. Egy élő példa: egy interjú során egy személy, aki azt mondta, hogy sokat írt az Ansible-be, nem tudott válaszolni arra a kérdésre, hogy „milyen elemekből áll egy játékkönyv?” És amikor azt javasoltam, hogy „az a válasz várható, hogy a játékkönyv játékból áll”, a „nem használunk ilyet” elmarasztaló megjegyzés következett. Az emberek pénzért írják az Ansible-t, és nem használják a játékot. Valójában használják, de nem tudják, mi az.

Tehát kezdjük valami egyszerűvel: mi a neve. Lehet, hogy tudja ezt, vagy talán nem, mert nem figyelt, amikor elolvasta a dokumentációt.

ansible-playbook végrehajtja a játékkönyvet. A játékkönyv egy yml/yaml kiterjesztésű fájl, amelyben valami ilyesmi található:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Már rájöttünk, hogy ez az egész fájl egy játékkönyv. Meg tudjuk mutatni, hol vannak a szerepek és hol a feladatok. De hol a játék? És mi a különbség a játék és a szerep vagy a játékkönyv között?

Minden benne van a dokumentációban. És hiányzik nekik. Kezdők - mert túl sok van, és nem fogsz egyszerre emlékezni mindenre. Tapasztalt - mert „triviális dolgok”. Ha tapasztalt, olvassa el újra ezeket az oldalakat legalább félévente, és kódja osztályvezetővé válik.

Tehát ne feledje: A Playbook egy lista, amely a játékból és a import_playbook.
Ez egy színdarab:

- hosts: group1
  roles:
    - role1

és ez is egy másik színdarab:

- hosts: group2,group3
  tasks:
    - debug:

Mi a játék? Miért van?

A játék kulcsfontosságú eleme a játékkönyvnek, mivel a játék és csak a játék a szerepek és/vagy feladatok listáját társítja azon gazdagépek listájához, amelyeken végrehajtani kell. A dokumentáció mélyén említést találhatunk delegate_to, helyi keresési bővítmények, hálózati kliens-specifikus beállítások, ugrásgazda stb. Lehetővé teszik, hogy kissé módosítsa a feladatok elvégzésének helyét. De felejtsd el. Ezen okos lehetőségek mindegyikének nagyon specifikus felhasználási területei vannak, és határozottan nem univerzálisak. És alapvető dolgokról beszélünk, amelyeket mindenkinek tudnia és használnia kell.

Ha „valamit” „valahol” szeretne előadni, akkor színdarabot ír. Nem szerep. Nem szerepkör modulokkal és delegáltakkal. Fogod és írsz játékot. Amelyekben a hosts mezőben megadod, hogy hol kell végrehajtani, a szerepekben/feladatokban pedig azt, hogy mit kell végrehajtani.

Egyszerű, igaz? Hogy is lehetne másképp?

Az egyik jellegzetes pillanat, amikor az emberek ezt nem játékon keresztül akarják megtenni, az a „szerep, amely mindent beállít”. Szeretnék egy olyan szerepkört, amely konfigurálja mind az első típusú szervereket, mind a második típusú szervereket.

Archetipikus példa a monitorozás. Szeretnék egy megfigyelő szerepet beállítani, amely konfigurálja a megfigyelést. A figyelő szerepkör a figyelő gazdagépekhez van hozzárendelve (játék szerint). De kiderül, hogy a megfigyeléshez csomagokat kell kézbesítenünk az általunk figyelt gazdagépeknek. Miért nem használja a delegált? Az iptables-t is be kell állítani. delegált? Ezenkívül meg kell írnia/javítania egy konfigurációt a DBMS-hez a megfigyelés engedélyezéséhez. delegált! És ha a kreativitás hiányzik, akkor delegációt készíthet include_role beágyazott hurokban egy trükkös szűrő használatával a csoportok listáján, és belül include_role többet tehetsz delegate_to újra. És elmegyünk...

Egy jó kívánság – hogy egyetlen ellenőrző szerepünk legyen, amely „mindent megtesz” – a teljes pokolba vezet, ahonnan legtöbbször csak egyetlen kiút van: mindent a nulláról átírni.

Hol történt itt a hiba? Abban a pillanatban, amikor rájöttél, hogy az "x" feladat végrehajtásához az X gazdagépen el kell menned Y állomásra, és ott meg kell csinálni az "y"-t, egy egyszerű gyakorlatot kellett végrehajtanod: menj és írj lejátszást, ami Y gépen y-t csinál. Ne adj hozzá valamit az "x"-hez, hanem írd le a nulláról. Még a kemény kódolt változókkal is.

Úgy tűnik, hogy a fenti bekezdésekben minden helyesen van elmondva. De ez nem a te eseted! Mert újrafelhasználható kódot akarsz írni, ami SZÁRAZ és könyvtárszerű, és keresned kell egy módszert, hogyan csináld.

Itt lapul egy újabb súlyos hiba. Egy hiba, ami sok projektet tűrhetően megírtból (lehetne jobb is, de minden működik és könnyen befejezhető) olyan horrorrá változott, amire még a szerző sem tud rájönni. Működik, de Isten ments, hogy bármit is változtass.

A hiba a következő: a szerep egy könyvtári függvény. Ez a hasonlat annyi jó kezdetet tönkretett, hogy egyszerűen szomorú nézni. A szerepkör nem könyvtári funkció. Nem tud számolni, és nem tud játékszintű döntéseket hozni. Emlékeztessen, milyen döntéseket hoz a játék?

Köszönöm, igazad van. A Play döntést hoz (pontosabban információkat tartalmaz), hogy mely feladatokat és szerepeket melyik gazdagépen látja el.

Ha ezt a döntést egy szerepre delegálod, és még számításokkal is, nyomorult létezésre ítéled magad (és azt is, aki megpróbálja elemezni a kódodat). A szerep nem dönti el, hogy hol játsszák. Ezt a döntést játék hozza meg. A szerep azt teszi, amit mondanak neki, ahol mondják.

Miért veszélyes Ansible-ben programozni, és miért jobb a COBOL, mint az Ansible, a változókról és a dzsindzsáról szóló fejezetben fogunk beszélni. Egyelőre mondjunk egyet: minden számítása kitörölhetetlen nyomot hagy maga után a globális változók változásaiban, és nem tehet ellene semmit. Amint a két „nyom” keresztezte egymást, minden eltűnt.

Megjegyzés a finnyásnak: a szerep minden bizonnyal befolyásolhatja az irányítási folyamatot. Eszik delegate_to és ésszerű felhasználása van. Eszik meta: end host/play. De! Emlékszel, az alapokat tanítjuk? Elfelejtettem delegate_to. A legegyszerűbb és legszebb Ansible kódról beszélünk. Amely könnyen olvasható, könnyen írható, könnyen hibakereshető, könnyen tesztelhető és könnyen kitölthető. Szóval még egyszer:

a játék és csak a játék dönti el, hogy melyik házigazdán mit hajtanak végre.

Ebben a részben a játék és a szerep ellentétével foglalkoztunk. Most beszéljünk a feladatok és a szerepek viszonyáról.

Feladatok és szerepek

Fontolja meg a játékot:

- hosts: somegroup
  pre_tasks:
    - some_tasks1:
  roles:
     - role1
     - role2
  post_tasks:
     - some_task2:
     - some_task3:

Tegyük fel, hogy meg kell tennie a foo-t. És úgy néz ki foo: name=foobar state=present. Ezt hova írjam? a pre? poszt? Szerepet hoz létre?

...És hova lettek a feladatok?

Ismét az alapokkal kezdjük – a játékeszközzel. Ha lebegsz ebben a kérdésben, akkor nem használhatod minden más alapját a játékra, és az eredményed "remegő" lesz.

Lejátszási eszköz: hosts direktíva, magának a játéknak a beállításai és a pre_tasks, feladatok, szerepek, post_tasks szakaszok. A játék többi paramétere most nem fontos számunkra.

A szekcióik sorrendje feladatokkal és szerepekkel: pre_tasks, roles, tasks, post_tasks. Mivel szemantikailag a végrehajtás sorrendje között van tasks и roles nem világos, akkor a bevált gyakorlatok szerint egy szakaszt adunk hozzá tasks, csak ha nem roles... Ha van roles, akkor az összes csatolt feladat szakaszokba kerül pre_tasks/post_tasks.

Csak az marad, hogy szemantikailag minden világos: először pre_tasks, akkor roles, akkor post_tasks.

De még mindig nem válaszoltunk a kérdésre: hol van a modulhívás? foo ír? Minden modulhoz meg kell írnunk egy teljes szerepet? Vagy jobb, ha mindennek vastag szerepe van? És ha nem szerep, akkor hova írjak - pre-ben vagy post-ban?

Ha ezekre a kérdésekre nincs megalapozott válasz, akkor ez az intuíció hiányának jele, vagyis ugyanazok a „remegő alapok”. Találjuk ki. Először is egy biztonsági kérdés: Ha a játék rendelkezik pre_tasks и post_tasks (és nincsenek feladatok vagy szerepek), akkor eltörhet valami, ha az első feladatot végrehajtom post_tasks átteszem a végére pre_tasks?

Természetesen a kérdés megfogalmazása sejteti, hogy törni fog. De mit is pontosan?

... Kezelők. Az alapok olvasása során kiderül egy fontos tény: minden kezelő automatikusan kiöblítésre kerül minden szakasz után. Azok. minden feladattól pre_tasks, majd az összes értesítőt. Ezután az összes szerepkör és az összes olyan kezelő végrehajtásra kerül, amely a szerepekben értesítést kapott. Után post_tasks és kezelőik.

Így ha egy feladatot innen húz post_tasks в pre_tasks, akkor valószínűleg a kezelő végrehajtása előtt hajtja végre. például ha be pre_tasks a webszerver telepítve és konfigurálva van, és post_tasks valamit elküldenek neki, majd vigye át ezt a feladatot a szakaszba pre_tasks ahhoz vezet, hogy a „küldés” időpontjában a szerver még nem fog futni, és minden elromlik.

Most gondoljuk át újra, miért van erre szükség pre_tasks и post_tasks? Például azért, hogy a szerepkör betöltése előtt minden szükségeset elvégezzen (beleértve a kezelőket is). A post_tasks lehetővé teszi számunkra, hogy a szerepek végrehajtásának eredményeivel dolgozzunk (beleértve a kezelőket is).

Egy ügyes Ansible-szakértő megmondja nekünk, mi az. meta: flush_handlers, de miért van szükségünk flush_handlerekre, ha a játékban lévő szakaszok végrehajtási sorrendjére hagyatkozhatunk? Sőt, a meta: flush_handlers használata váratlan dolgokat adhat nekünk duplikált kezelőkkel, furcsa figyelmeztetéseket adva használatkor when у block stb. Minél jobban ismeri a lehetséges megoldást, annál több árnyalatot tud megnevezni egy „trükkös” megoldáshoz. Egy egyszerű megoldás pedig - természetes felosztást használva az elő/szerepek/után - nem okoz árnyalatokat.

És vissza a "foo"-hoz. Hová tegyem? Előzőben, posztban vagy szerepekben? Nyilván ez attól függ, hogy szükségünk van-e a foo kezelőjének eredményeire. Ha ezek nincsenek ott, akkor a foo-t nem kell sem pre-, sem post-ba helyezni - ezeknek a szakaszoknak különleges jelentése van - a fő kódtörzs előtt és után végrehajtandó feladatok végrehajtásához.

Most a „szerep vagy feladat” kérdésre adott válasz a már játékban lévőre vonatkozik - ha vannak ott feladatok, akkor hozzá kell adni a feladatokhoz. Ha vannak szerepek, létre kell hozni egy szerepet (akár egy feladatból is). Hadd emlékeztesselek arra, hogy a feladatokat és a szerepköröket nem egyszerre használják fel.

Az Ansible alapjainak megértése ésszerű válaszokat ad a látszólagos ízlési kérdésekre.

Feladatok és szerepek (második rész)

Most beszéljük meg azt a helyzetet, amikor még csak most kezdesz egy játékkönyvet írni. Csinálnod kell foo, bar és baz. Ez a három feladat egy vagy három szerep? Összefoglalva a kérdést: mikortól érdemes elkezdeni a szerepek írását? Mi értelme van szerepeket írni, ha lehet feladatokat írni?... Mi a szerep?

Az egyik legnagyobb hiba (erről már beszéltem) az, hogy azt gondoljuk, hogy egy szerep olyan, mint egy függvény a program könyvtárában. Hogyan néz ki egy általános függvényleírás? Elfogadja a bemeneti argumentumokat, kölcsönhatásba lép a mellékes okokkal, mellékhatásokat hajt végre, és értéket ad vissza.

Most figyelem. Mit lehet ebből kezdeni a szerepben? Mindig szívesen hívja a mellékhatásokat, ez az egész Ansible lényege - mellékhatások létrehozása. Vannak mellékes okai? Alapvető. De az „adj át egy értéket és küldd vissza” esetén ez nem működik. Először is, nem adhat át értéket egy szerepkörnek. A szerephez tartozó vars szakaszban beállíthat egy globális változót egy élettartamra vetített játékmérettel. A szerepkörön belül beállíthat egy egész életen át tartó globális változót. Vagy akár a játékkönyvek élettartamával (set_fact/register). De nem lehetnek "helyi változók". Nem lehet „értéket venni” és „visszaadni”.

A lényeg ebből következik: nem írhatsz semmit az Ansible-be anélkül, hogy mellékhatásokat ne okoznál. A globális változók megváltoztatása mindig mellékhatása egy függvénynek. A Rustban például egy globális változó megváltoztatása az unsafe. Az Ansible-ben pedig ez az egyetlen módszer egy szerep értékeinek befolyásolására. Jegyezze meg a használt szavakat: nem "értéket adjon át a szerepnek", hanem "változtassa meg a szerepkör által használt értékeket". A szerepek között nincs elszigeteltség. A feladatok és a szerepek között nincs elszigeteltség.

Összesen: a szerep nem funkció.

Mi a jó a szerepben? Először is, a szerepkör alapértelmezett értékei (/default/main.yaml), másodszor a szerepkör további könyvtárakat tartalmaz a fájlok tárolására.

Milyen előnyei vannak az alapértelmezett értékeknek? Mert Maslow piramisában, az Ansible változó prioritásainak meglehetősen torz táblájában a szerep alapértelmezései a legalacsonyabb prioritásúak (mínusz az Ansible parancssori paraméterek). Ez azt jelenti, hogy ha alapértelmezett értékeket kell megadnia, és nem kell aggódnia amiatt, hogy ezek felülírják a készlet- vagy csoportváltozók értékeit, akkor az alapértelmezett szerepkör az egyetlen megfelelő hely az Ön számára. (Kicsit hazudok – van több is |d(your_default_here), de ha stacioner helyekről beszélünk, akkor csak szerep-alapértelmezettek).

Mi a jó még a szerepekben? Mert saját katalógusuk van. Ezek a változók könyvtárai, mind az állandó (azaz a szerephez számítva), mind a dinamikusak (van minta vagy anti-minta - include_vars -val {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Ezek a címtárak files/, templates/. Ezenkívül lehetővé teszi, hogy saját moduljait és bővítményeit (library/). De a játékfüzetben szereplő feladatokhoz képest (amelyben mindezek is szerepelhetnek), az egyetlen előny itt az, hogy a fájlok nem egy kupacba kerülnek, hanem több külön kupacba.

Még egy részlet: megpróbálhatsz olyan szerepeket létrehozni, amelyek újrafelhasználhatóak lesznek (a galaxison keresztül). A gyűjtemények megjelenésével a szereposztás szinte feledésbe merültnek tekinthető.

Így a szerepköröknek két fontos jellemzője van: vannak alapértelmezéseik (egyedi tulajdonság), és lehetővé teszik a kód felépítését.

Visszatérve az eredeti kérdéshez: mikor kell feladatokat és mikor szerepeket csinálni? A játékfüzetben szereplő feladatokat leggyakrabban vagy „ragasztóként” használják a szerepek előtt/után, vagy önálló építőelemként (akkor ne legyenek szerepek a kódban). A szerepekkel kevert normális feladatok halma egyértelmû pofátlanság. Egy adott stílushoz kell ragaszkodnia – akár egy feladathoz, akár egy szerephez. A szerepek biztosítják az entitások és az alapértelmezett értékek elkülönítését, a feladatok pedig lehetővé teszik a kód gyorsabb olvasását. Általában több „helyhez kötött” (fontos és összetett) kód kerül szerepkörökbe, és a segédszkriptek feladatstílusban íródnak.

Feladatként meg lehet csinálni az import_role-t, de ha ezt írod, akkor készülj fel arra, hogy saját szépérzékednek magyarázd el, miért akarod ezt csinálni.

Egy okos olvasó azt mondhatja, hogy a szerepek importálhatnak szerepeket, a szerepek függhetnek a galaxy.yml-n keresztül, és van egy szörnyű és szörnyű include_role — Emlékeztetlek arra, hogy az alap Ansible-ben fejlesztjük a képességeinket, nem pedig a figuratornában.

Kezelők és feladatok

Beszéljünk egy másik nyilvánvaló dologról: a kezelőkről. A helyes használatuk ismerete szinte művészet. Mi a különbség a kezelő és a vontató között?

Mivel emlékszünk az alapokra, íme egy példa:

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

A szerepkör kezelői a rolename/handlers/main.yaml fájlban találhatók. A felvezetők turkálnak a játék összes résztvevője között: a pre/post_tasks kihúzhatja a szerepkezelőket, a szerep pedig a felvezetőket a játékból. A kezelők "keresztszerepű" hívásai azonban sokkal több wtf-et okoznak, mint egy triviális kezelő ismétlése. (A bevált gyakorlatok másik eleme, hogy ne ismételjük meg a kezelők nevét).

A fő különbség az, hogy a feladatot mindig (idempotensen) hajtják végre (plusz/mínusz címkék és when), a kezelő pedig - állapotváltozással (csak akkor értesíti a tüzet, ha megváltozott). Mit is jelent ez? Például az a tény, hogy újraindításkor, ha nem történt változás, akkor nem lesz kezelő. Miért lehet, hogy végre kell hajtanunk a kezelőt, amikor nem történt változás a generáló feladatban? Például azért, mert valami elromlott és megváltozott, de a végrehajtás nem jutott el a kezelőhöz. Például azért, mert a hálózat átmenetileg nem működött. A konfiguráció megváltozott, a szolgáltatás nem indult újra. A következő indításakor a konfiguráció már nem változik, és a szolgáltatás a konfiguráció régi verziójával marad.

A konfiggal nem lehet megoldani a helyzetet (pontosabban kitalálhatsz magadnak egy speciális újraindítási protokollt file flagekkel stb., de ez már semmilyen formában nem 'alap lehetséges'). De van még egy közös történet: telepítettük az alkalmazást, rögzítettük .service-fájlt, és most szeretnénk daemon_reload и state=started. És ennek természetes helye a kezelő. De ha nem kezelőt, hanem feladatot teszel egy feladatlista vagy szerepkör végén, akkor minden alkalommal idempotensen végrehajtódik. Még akkor is, ha a játékkönyv középen eltört. Ez egyáltalán nem oldja meg az újraindított problémát (az újraindított attribútummal nem lehet feladatot csinálni, mert az idempotencia elveszik), de mindenképpen érdemes megtenni az állapot=kezdett állapotot, megnő a playbookok általános stabilitása, mert csökken a kapcsolatok száma és a dinamikus állapot.

A kezelő másik pozitív tulajdonsága, hogy nem tömíti el a kimenetet. Nem történt semmi változás - nincs extra kihagyott vagy rendben van a kimenetben - könnyebben olvasható. Ez is egy negatív tulajdonság - ha egy lineárisan végrehajtott feladatban a legelső futtatáskor elírási hibát talál, akkor a kezelők csak változtatáskor hajtódnak végre, pl. bizonyos körülmények között – nagyon ritkán. Például életemben először öt évvel később. És persze lesz elírás a névben és minden elromlik. És ha másodszor nem futtatja őket, nincs változás.

Külön kell beszélnünk a változók elérhetőségéről. Például, ha egy feladatot ciklussal értesít, mi lesz a változókban? Analitikusan lehet találgatni, de ez nem mindig triviális, különösen, ha a változók különböző helyekről származnak.

... Tehát a kezelők sokkal kevésbé hasznosak és sokkal problémásabbak, mint amilyennek látszanak. Ha tudsz valamit szépen ( sallangok nélkül) írni kezelők nélkül, akkor jobb, ha nélkülük csinálod. Ha nem sikerül szépen, jobb velük.

A maró olvasó helyesen mutat rá, hogy nem beszéltük meg listenhogy egy kezelő meghívhat értesítést egy másik kezelő számára, hogy egy kezelő tartalmazhat import_tasks-t (amely az include_role-t a with_items-szel teheti meg), hogy az Ansible kezelőrendszere Turing-teljes, hogy az include_role kezelői furcsa módon metszik egymást a játékból származó kezelőkkel, stb. .d. - mindez nyilvánvalóan nem az „alapok”).

Bár van egy konkrét WTF, amely valójában egy olyan funkció, amelyet szem előtt kell tartania. Ha a feladatot a delegate_to és értesíti, akkor a megfelelő kezelő végrehajtásra kerül anélkül delegate_to, azaz azon a gazdagépen, amelyhez a lejátszás hozzá van rendelve. (Bár a felvezetőnek persze lehet delegate_to Azonos).

Külön szeretnék néhány szót ejteni az újrafelhasználható szerepekről. A gyűjtemények megjelenése előtt volt egy ötlet, hogy univerzális szerepeket készíthetsz, amelyek lehetnek ansible-galaxy install és ment. Minden változat minden operációs rendszerén működik, minden helyzetben. Szóval az én véleményem: nem megy. Bármilyen szerep a tömeggel include_vars, 100500 1 esetet támogat, a saroktokok hibáinak szakadékára van ítélve. Masszív teszteléssel lefedhetők, de mint minden tesztelésnél, vagy a bemeneti értékek és a teljes függvény derékszögű szorzata van, vagy vannak „egyedi forgatókönyvek”. Az a véleményem, hogy sokkal jobb, ha a szerep lineáris (ciklomatikus komplexitás XNUMX).

Minél kevesebb if (explicit vagy deklaratív - a formában when vagy forma include_vars változók halmaza szerint), annál jobb a szerep. Néha ágakat kell készíteni, de ismétlem, minél kevesebb van, annál jobb. Szóval jó szerepnek tűnik a galaxy-val (működik!) egy csomóval when kevésbé előnyös lehet, mint a „saját” szerep öt feladatból. Az a pillanat, amikor jobb a szerep a galaxisban, amikor elkezdesz írni valamit. Az a pillanat, amikor rosszabb lesz, amikor valami eltörik, és felmerül a gyanú, hogy ez a „galaxisban betöltött szerep” miatt van. Kinyitja, és ott van öt zárvány, nyolc feladatlap és egy köteg when'ov... És ezt ki kell találnunk. 5 feladat helyett egy lineáris lista, amiben nincs mit törni.

A következő részekben

  • Egy kicsit a készletről, a csoportváltozókról, a host_group_vars bővítményről, a hostvars-ról. Hogyan kössünk gordiuszi csomót spagettivel. Hatókör és precedencia változók, lehetséges memóriamodell. "Tehát hol tároljuk az adatbázis felhasználónevét?"
  • jinja: {{ jinja }} — nosql notype nosense lágy gyurma. Mindenhol ott van, még ott is, ahol nem számítasz rá. Egy kicsit kb !!unsafe és finom yaml.

Forrás: will.com

Hozzászólás