Ansible základy, bez kterých budou vaše sešity hroudou lepivých těstovin

Dělám spoustu recenzí kódu Ansible jiných lidí a sám hodně píšu. V průběhu rozebírání chyb (cizí i mých vlastních), ale i řady rozhovorů jsem si uvědomil hlavní chybu, které se uživatelé Ansible dopouštějí – pouštějí se do složitých věcí, aniž by ovládali ty základní.

Abych napravil tuto univerzální nespravedlnost, rozhodl jsem se napsat úvod k Ansible pro ty, kteří to již znají. Varuji vás, toto není převyprávění mužů, toto je dlouhé čtení se spoustou dopisů a bez obrázků.

Očekávaná úroveň čtenáře je, že již bylo napsáno několik tisíc řádků yamly, něco se již vyrábí, ale „nějak je všechno pokřivené“.

Jména

Hlavní chybou, kterou uživatel Ansible dělá, je, že neví, jak se něco nazývá. Pokud neznáte názvy, nemůžete pochopit, co se v dokumentaci píše. Živý příklad: člověk, který během rozhovoru, jak se zdálo, řekl, že hodně píše v Ansible, nedokázal odpovědět na otázku „z jakých prvků se herní kniha skládá?“ A když jsem navrhl, že „odpověď se očekávala, že učebnice sestává ze hry“, následoval usvědčující komentář „to nepoužíváme“. Lidé píšou Ansible pro peníze a nepoužívají hru. Ve skutečnosti to používají, ale nevědí, co to je.

Začněme tedy něčím jednoduchým: jak se to jmenuje. Možná to víte, možná ne, protože jste tomu při čtení dokumentace nevěnovali pozornost.

ansible-playbook spustí playbook. Playbook je soubor s příponou yml/yaml, uvnitř kterého je něco takového:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Už jsme si uvědomili, že celý tento soubor je playbook. Můžeme ukázat, kde jsou role a kde úkoly. Ale kde se hraje? A jaký je rozdíl mezi hrou a rolí nebo playbookem?

Vše je v dokumentaci. A chybí jim to. Začátečníci - protože je toho příliš mnoho a nebudete si pamatovat vše najednou. Zkušený - protože „triviální věci“. Pokud máte zkušenosti, přečtěte si tyto stránky znovu alespoň jednou za šest měsíců a váš kód se stane špičkou ve své třídě.

Takže si pamatujte: Playbook je seznam skládající se z play a import_playbook.
Toto je jedna hra:

- hosts: group1
  roles:
    - role1

a to je také další hra:

- hosts: group2,group3
  tasks:
    - debug:

co je hra? proč je?

Hra je klíčovým prvkem playbooku, protože hra a pouze hra spojuje seznam rolí a/nebo úkolů se seznamem hostitelů, na kterých musí být provedeny. V hlubokých hloubkách dokumentace můžete najít zmínku o delegate_to, pluginy pro místní vyhledávání, nastavení specifická pro síťové rozhraní, hostitelé skoků atd. Umožňují mírně změnit místo plnění úkolů. Ale zapomeňte na to. Každá z těchto chytrých možností má velmi specifické použití a rozhodně nejsou univerzální. A to se bavíme o základních věcech, které by měl znát a používat každý.

Pokud chcete „něco“ „někde“ předvést, napište play. Ne role. Ne role s moduly a delegáty. Vezmi to a napiš hru. Ve kterém v poli hostitelů uvádíte, kde se má provést, a v rolích/úkolech - co provést.

Jednoduché, že? Jak by to mohlo být jinak?

Jedním z charakteristických momentů, kdy to lidé chtějí dělat ne hrou, je „role, která všechno nastavuje“. Chtěl bych mít roli, která konfiguruje servery prvního typu i servery druhého typu.

Typickým příkladem je monitorování. Chtěl bych mít monitorovací roli, která bude konfigurovat monitorování. Monitorovací role je přiřazena monitorovacím hostitelům (podle hry). Ukazuje se však, že pro monitorování potřebujeme doručovat balíčky hostitelům, které monitorujeme. Proč nepoužít delegát? Musíte také nakonfigurovat iptables. delegát? Chcete-li povolit monitorování, musíte také napsat/opravit konfiguraci pro DBMS. delegát! A pokud kreativita chybí, můžete delegovat include_role ve vnořené smyčce pomocí složitého filtru na seznamu skupin a uvnitř include_role můžete udělat víc delegate_to znovu. A jdeme pryč...

Dobré přání - mít jednu jedinou monitorovací roli, která „dělá všechno“ - nás vede do úplného pekla, ze kterého je nejčastěji jediné východisko: vše přepsat od nuly.

Kde se tady stala chyba? V okamžiku, kdy jste zjistili, že k provedení úkolu „x“ na hostiteli X musíte jít na hostitele Y a provést tam „y“, museli jste udělat jednoduché cvičení: jít a napsat hru, což na hostiteli Y dělá y. Nepřidávejte něco do "x", ale napište to od začátku. I s pevně zakódovanými proměnnými.

Zdá se, že vše v odstavcích výše je řečeno správně. Ale to není váš případ! Protože chcete psát opakovaně použitelný kód, který je SUCHÝ a podobný knihovně, a musíte hledat metodu, jak to udělat.

Zde se skrývá další závažná chyba. Chyba, která proměnila mnoho projektů ze snesitelně napsaných (mohlo by to být lepší, ale vše funguje a je snadné dokončit) v naprostý horor, na který nepřijde ani autor. Funguje to, ale nedej bože cokoliv měnit.

Chyba je: role je funkce knihovny. Tato analogie zničila tolik dobrých začátků, že je prostě smutné se na to dívat. Role není funkcí knihovny. Neumí počítat a nedokáže se rozhodovat na úrovni hry. Připomeňte mi, jaká rozhodnutí dělá hra?

Díky, máš pravdu. Play rozhoduje (přesněji řečeno obsahuje informace) o tom, jaké úkoly a role bude na kterých hostitelích plnit.

Pokud toto rozhodnutí delegujete na roli, a dokonce i s výpočty, odsoudíte sebe (a toho, kdo se bude snažit analyzovat váš kód) k bídné existenci. Role nerozhoduje o tom, kde se vykonává. Toto rozhodnutí je učiněno hrou. Role dělá to, co je řečeno, tam, kde je to řečeno.

Proč je nebezpečné programovat v Ansible a proč je COBOL lepší než Ansible si povíme v kapitole o proměnných a jinja. Prozatím si řekněme jednu věc – každý váš výpočet za sebou zanechává nesmazatelnou stopu změn globálních proměnných a vy s tím nemůžete nic dělat. Jakmile se obě „stopy“ protnuly, bylo vše pryč.

Poznámka pro háklivé: role může jistě ovlivnit tok řízení. Jíst delegate_to a má rozumné využití. Jíst meta: end host/play. Ale! Pamatujete si, že učíme základy? Zapomněl jsem delegate_to. Mluvíme o nejjednodušším a nejkrásnějším kódu Ansible. Což se snadno čte, snadno se píše, snadno se ladí, snadno se testuje a snadno se vyplňuje. Takže ještě jednou:

hrát a pouze hrát rozhoduje o tom, na kterých hostitelích se co provede.

V této části jsme se zabývali protikladem mezi hrou a rolí. Nyní si promluvme o vztahu úkoly a role.

Úkoly a role

Zvažte hru:

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

Řekněme, že musíte udělat foo. A vypadá to tak foo: name=foobar state=present. Kam to mám napsat? v před? pošta? Vytvořit roli?

...A kam se poděly úkoly?

Opět začínáme se základem – herním zařízením. Pokud se v této otázce vznesete, nemůžete hru použít jako základ pro všechno ostatní a váš výsledek bude „roztřesený“.

Play device: direktiva hosts, nastavení pro samotné hraní a pre_tasks, úkoly, role, sekce post_tasks. Zbývající parametry pro hru pro nás nyní nejsou důležité.

Pořadí jejich sekcí s úkoly a rolemi: pre_tasks, roles, tasks, post_tasks. Protože sémanticky je pořadí provádění mezi tasks и roles není jasné, pak osvědčené postupy říkají, že přidáváme sekci tasks, pouze pokud ne roles... Pokud existuje roles, pak jsou všechny připojené úkoly umístěny do sekcí pre_tasks/post_tasks.

Zbývá jen, aby bylo vše sémanticky jasné: za prvé pre_taskspak rolespak post_tasks.

Ale stále jsme neodpověděli na otázku: kde je volání modulu? foo napsat? Musíme pro každý modul napsat celou roli? Nebo je lepší mít na všechno tlustou roli? A pokud ne role, tak kam mám napsat - pre nebo post?

Pokud na tyto otázky neexistuje žádná rozumná odpověď, pak je to známka nedostatku intuice, tedy těch stejných „vratkých základů“. Pojďme na to přijít. Nejprve bezpečnostní otázka: Pokud hra ano pre_tasks и post_tasks (a nejsou tam žádné úkoly ani role), pak se může něco zlomit, když provedu první úkol z post_tasks Přesunu to na konec pre_tasks?

Znění otázky samozřejmě napovídá, že se to zlomí. Ale co přesně?

... Psovodi. Čtení základů odhalí důležitou skutečnost: všechny psovody jsou po každé sekci automaticky propláchnuty. Tito. všechny úkoly od pre_tasks, pak všechny obslužné osoby, které byly upozorněny. Poté se provedou všechny role a všechny handlery, které byly v rolích upozorněny. Po post_tasks a jejich manipulátory.

Pokud tedy přetáhnete úkol z post_tasks в pre_tasks, pak jej potenciálně spustíte dříve, než se spustí handler. například pokud v pre_tasks je nainstalován a konfigurován webový server a post_tasks něco se mu pošle, pak tento úkol přeneste do sekce pre_tasks povede k tomu, že v době „odeslání“ server ještě neběží a vše se porouchá.

Nyní se znovu zamysleme, proč to potřebujeme pre_tasks и post_tasks? Například proto, aby před plněním role dokončil vše potřebné (včetně handlerů). A post_tasks nám umožní pracovat s výsledky vykonávání rolí (včetně handlerů).

O co jde, nám řekne bystrý odborník z Ansible. meta: flush_handlers, ale proč potřebujeme flush_handlers, když se můžeme spolehnout na pořadí provádění sekcí ve hře? Navíc použití meta: flush_handlers nám může dát neočekávané věci s duplicitními handlery, což nám při použití dává podivná varování when у block atd. Čím lépe znáte ansible, tím více nuancí můžete pojmenovat pro „ošidné“ řešení. A jednoduché řešení – použití přirozeného rozdělení mezi pre/role/post – nezpůsobuje nuance.

A zpět k našemu 'foo'. Kam to mám dát? V pre, post nebo role? To samozřejmě závisí na tom, zda potřebujeme výsledky handlera pro foo. Pokud tam nejsou, pak foo nemusí být umístěno ani před, ani po příspěvku - tyto sekce mají zvláštní význam - provádění úkolů před a za hlavním tělem kódu.

Nyní odpověď na otázku „role nebo úkol“ přichází na to, co je již ve hře - pokud tam úkoly jsou, musíte je přidat do úkolů. Pokud existují role, musíte vytvořit roli (i z jednoho úkolu). Připomínám, že úkoly a role se nepoužívají současně.

Pochopení základů Ansible poskytuje rozumné odpovědi na zdánlivé otázky vkusu.

Úkoly a role (druhá část)

Nyní si proberme situaci, kdy teprve začínáte psát playbook. Potřebujete udělat foo, bar a baz. Jsou to tři úkoly, jedna role nebo tři role? Abychom shrnuli otázku: v jakém okamžiku byste měli začít psát role? Jaký má smysl psát role, když můžete psát úkoly?... Co je to role?

Jednou z největších chyb (už jsem o tom mluvil) je myslet si, že role je jako funkce v knihovně programu. Jak vypadá obecný popis funkce? Přijímá vstupní argumenty, interaguje s vedlejšími příčinami, má vedlejší účinky a vrací hodnotu.

Teď pozor. Co se z toho dá v roli udělat? Volání vedlejších efektů je vždy vítáno, to je podstata celého Ansible – vytvářet vedlejší efekty. Máte vedlejší příčiny? Základní. Ale s "předat hodnotu a vrátit ji" - to je místo, kde to nefunguje. Za prvé, nemůžete roli předat hodnotu. V sekci vars pro roli můžete nastavit globální proměnnou s celoživotní velikostí přehrávání. Uvnitř role můžete nastavit globální proměnnou s dobou trvání hry. Nebo dokonce s životností herních knih (set_fact/register). Ale nemůžete mít "místní proměnné". Nemůžete „vzít hodnotu“ a „vrátit ji“.

Z toho vyplývá hlavní věc: v Ansible nemůžete něco napsat, aniž by to způsobilo vedlejší účinky. Změna globálních proměnných je pro funkci vždy vedlejším efektem. V Rustu je například změna globální proměnné unsafe. A v Ansible je to jediná metoda, jak ovlivnit hodnoty pro roli. Všimněte si použitých slov: ne „předat roli roli“, ale „změnit hodnoty, které role používá“. Mezi rolemi neexistuje žádná izolace. Mezi úkoly a rolemi neexistuje žádná izolace.

Celkem: role není funkce.

Co je na roli dobrého? Za prvé, role má výchozí hodnoty (/default/main.yaml), za druhé, role má další adresáře pro ukládání souborů.

Jaké jsou výhody výchozích hodnot? Protože v Maslowově pyramidě, Ansibleově poněkud zkreslené tabulce proměnných priorit, mají výchozí role nejnižší prioritu (minus parametry příkazového řádku Ansible). To znamená, že pokud potřebujete poskytnout výchozí hodnoty a nemusíte se obávat, že přepíší hodnoty z inventáře nebo skupinových proměnných, pak jsou výchozí hodnoty rolí tím jediným správným místem pro vás. (Trochu lžu - je jich víc |d(your_default_here), ale pokud mluvíme o stacionárních místech, pak pouze výchozí role).

Co dalšího je na rolích skvělé? Protože mají své vlastní katalogy. Jedná se o adresáře pro proměnné, jak konstantní (tj. vypočítané pro roli), tak dynamické (existuje buď vzor nebo anti-vzor - include_vars s {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Toto jsou adresáře pro files/, templates/. Také vám umožňuje mít své vlastní moduly a pluginy (library/). Oproti úlohám v playbooku (který může mít také toto vše) je zde ale jedinou výhodou to, že soubory nejsou vysypány na jednu hromádku, ale několik samostatných hromádek.

Ještě jeden detail: můžete zkusit vytvořit role, které budou k dispozici pro opětovné použití (přes galaxii). S příchodem kolekcí lze rozdělení rolí považovat za téměř zapomenuté.

Role tedy mají dvě důležité vlastnosti: mají výchozí hodnoty (jedinečná vlastnost) a umožňují vám strukturovat kód.

Vrátím se k původní otázce: kdy dělat úkoly a kdy role? Úkoly v playbooku se nejčastěji používají buď jako „lepidlo“ před/po rolích, nebo jako samostatný stavební prvek (pak by v kódu neměly být žádné role). Hromada normálních úkolů smíchaných s rolemi je jednoznačná lajdáctví. Měli byste se držet konkrétního stylu – buď úkolu, nebo role. Role zajišťují oddělení entit a výchozích hodnot, úlohy umožňují rychlejší čtení kódu. Obvykle se do rolí vkládá „stacionárnější“ (důležitý a složitý) kód a pomocné skripty se píší ve stylu úloh.

Je možné provést import_role jako úkol, ale pokud to napíšete, buďte připraveni vysvětlit svému vlastnímu smyslu pro krásu, proč to chcete udělat.

Bystrý čtenář může říci, že role mohou importovat role, role mohou mít závislosti přes galaxy.yml a existuje také hrozná a hrozná include_role — Připomínám, že zlepšujeme dovednosti v základním Ansible, a ne v figurální gymnastice.

Psovodi a úkoly

Pojďme diskutovat o další zřejmé věci: psovodech. Umět je správně používat je téměř umění. Jaký je rozdíl mezi psovodem a tahačem?

Protože si pamatujeme základy, zde je příklad:

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

Obslužné rutiny role jsou umístěny v roliname/handlers/main.yaml. Obslužné osoby se přehrabují mezi všemi účastníky hry: úkoly před/po_úkolech mohou vytáhnout obsluhu rolí a role může vytáhnout obsluhu ze hry. Volání "cross-role" pro handlery však způsobují mnohem více wtf než opakování triviálního handleru. (Dalším prvkem osvědčených postupů je snažit se neopakovat názvy obslužných programů).

Hlavní rozdíl je v tom, že úloha se vždy provede (idempotentně) (značky plus/minus a when), a handler - změnou stavu (upozorňovat na požáry pouze v případě, že došlo ke změně). Co to znamená? Například skutečnost, že při restartu, pokud nedošlo k žádné změně, nebude žádný handler. Proč by se mohlo stát, že potřebujeme spustit handler, když v úloze generování nedošlo k žádné změně? Například proto, že se něco zlomilo a změnilo, ale provedení se k psovodovi nedostalo. Například proto, že síť byla dočasně mimo provoz. Konfigurace se změnila, služba nebyla restartována. Při příštím spuštění se konfigurace již nezmění a služba zůstane se starou verzí konfigurace.

Situaci s konfigurací nelze vyřešit (přesněji řečeno můžete si pro sebe vymyslet speciální restartovací protokol s příznaky souborů atd., ale to už není 'základní ansible' v žádné podobě). Ale je tu další společný příběh: nainstalovali jsme aplikaci, nahráli ji .service-soubor, a teď to chceme daemon_reload и state=started. A přirozeným místem pro to se zdá být psovod. Ale pokud z něj uděláte ne handler, ale úkol na konci seznamu úkolů nebo role, pak bude pokaždé vykonán idempotentně. I kdyby se playbook uprostřed zlomil. Tím se problém restartu vůbec neřeší (nemůžete dělat úkol s atributem restarted, protože se ztrácí idempotence), ale rozhodně se vyplatí udělat state=started, celková stabilita playbooků se zvyšuje, protože snižuje se počet spojení a dynamický stav.

Další pozitivní vlastností handleru je, že nezanáší výstup. Nedošlo k žádným změnám - žádné další přeskočení nebo ok ve výstupu - snadnější čtení. Je to také negativní vlastnost - pokud hned při prvním spuštění najdete překlep v lineárně prováděné úloze, pak se handlery provedou až při změně, tzn. za určitých podmínek - velmi zřídka. Například poprvé v životě o pět let později. A samozřejmě dojde k překlepu v názvu a vše se rozbije. A pokud je nespustíte podruhé, nic se nezmění.

Samostatně musíme mluvit o dostupnosti proměnných. Pokud například úlohu upozorníte smyčkou, co bude v proměnných? Můžete hádat analyticky, ale není to vždy triviální, zvláště pokud proměnné pocházejí z různých míst.

... Takže handleři jsou mnohem méně užiteční a mnohem problematičtější, než se zdá. Pokud dokážete napsat něco krásně (bez ozdůbek) bez ovladačů, je lepší to udělat bez nich. Když to nejde krásně, je to s nimi lepší.

Sžíravý čtenář správně podotýká, že jsme nediskutovali listenže handler může zavolat notify pro jiný handler, že handler může zahrnout import_tasks (což může dělat include_role s with_items), že systém handlerů v Ansible je Turing-complete, že handlery z include_role se zvláštním způsobem prolínají s handlery ze hry, atd. .d. - to vše zjevně není „základem“).

Ačkoli existuje jedno specifické WTF, které je ve skutečnosti funkcí, kterou musíte mít na paměti. Pokud je váš úkol proveden pomocí delegate_to a má notify, pak se odpovídající handler provede bez delegate_to, tj. na hostiteli, kde je přiřazena hra. (I když psovod samozřejmě může mít delegate_to Stejný).

Samostatně bych chtěl říci pár slov o opakovaně použitelných rolích. Než se objevily kolekce, existovala myšlenka, že byste mohli vytvořit univerzální role, které by mohly být ansible-galaxy install a šel. Funguje na všech OS všech variant ve všech situacích. Takže můj názor: nejde to. Jakákoli role s hmotností include_vars, podporující 100500 1 případů, je odsouzen k propasti rohových chyb. Mohou být pokryty masivním testováním, ale jako u každého testování máte buď kartézský součin vstupních hodnot a celkové funkce, nebo máte „pokryté jednotlivé scénáře“. Můj názor je, že je mnohem lepší, když je role lineární (cyklomatická složitost XNUMX).

Čím méně if (výslovných nebo deklarativních - ve formuláři when nebo formulář include_vars podle množiny proměnných), tím lepší role. Někdy je třeba udělat větve, ale opakuji, čím méně jich je, tím lépe. Takže to vypadá jako dobrá role s galaxií (funguje to!) s hromadou when může být méně výhodná než „vlastní“ role z pěti úkolů. Moment, kdy je role s galaxií lepší, je, když začnete něco psát. Moment, kdy se to zhorší, je, když se něco rozbije a máte podezření, že je to kvůli „roli s galaxií“. Otevřete jej a je tam pět inkluzí, osm listů úkolů a stoh when'ov... A musíme na to přijít. Místo 5 úkolů lineární seznam, ve kterém není co rozbíjet.

V následujících dílech

  • Něco málo o inventáři, skupinové proměnné, plugin host_group_vars, hostvars. Jak uvázat gordický uzel se špagetami. Proměnné rozsahu a priority, Ansible paměťový model. "Kde tedy uložíme uživatelské jméno pro databázi?"
  • jinja: {{ jinja }} — nosql notype nosense měkká plastelína. Je všude, i tam, kde to nečekáte. Něco málo o !!unsafe a lahodný yaml.

Zdroj: www.habr.com

Přidat komentář