Ansible basics, uden hvilke dine spillebøger bliver en klump klistret pasta

Jeg laver mange anmeldelser af andres Ansible-kode og skriver meget selv. I løbet af at analysere fejl (både andres og mine egne), samt en række interviews, indså jeg den største fejl, som Ansible-brugere begår - de kommer ind i komplekse ting uden at mestre de grundlæggende.

For at rette op på denne universelle uretfærdighed besluttede jeg at skrive en introduktion til Ansible for dem, der allerede kender den. Jeg advarer dig, dette er ikke en genfortælling af mænd, dette er en longread med mange bogstaver og ingen billeder.

Det forventede niveau for læseren er, at der allerede er skrevet flere tusinde linjer med yamla, noget er allerede i produktion, men "på en eller anden måde er alt skævt."

navne

Den største fejl, en Ansible-bruger begår, er ikke at vide, hvad noget hedder. Hvis du ikke kender navnene, kan du ikke forstå, hvad der står i dokumentationen. Et levende eksempel: under et interview kunne en person, der syntes at sige, at han skrev meget i Ansible, ikke svare på spørgsmålet "hvilke elementer består en playbook af?" Og da jeg foreslog, at "svaret forventedes, at spillebogen består af leg", fulgte den forbandende kommentar "det bruger vi ikke". Folk skriver Ansible for penge og bruger ikke spil. De bruger det faktisk, men ved ikke hvad det er.

Så lad os starte med noget simpelt: hvad hedder det. Måske ved du det, eller måske gør du det ikke, fordi du ikke var opmærksom, da du læste dokumentationen.

ansible-playbook udfører playbook. En playbook er en fil med filtypenavnet yml/yaml, inde i hvilken der er noget som dette:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Vi har allerede indset, at hele denne fil er en spillebog. Vi kan vise, hvor rollerne er, og hvor opgaverne er. Men hvor er leg? Og hvad er forskellen på leg og rolle eller legebog?

Det hele står i dokumentationen. Og de savner det. Begyndere - fordi der er for meget, og du vil ikke huske alt på én gang. Erfaren - fordi "trivielle ting". Hvis du er erfaren, skal du genlæse disse sider mindst en gang hver sjette måned, og din kode bliver klasseledende.

Så husk: Playbook er en liste bestående af leg og import_playbook.
Dette er et skuespil:

- hosts: group1
  roles:
    - role1

og dette er også en anden leg:

- hosts: group2,group3
  tasks:
    - debug:

Hvad er leg? Hvorfor er hun det?

Leg er et nøgleelement i en playbook, fordi leg og kun leg forbinder en liste over roller og/eller opgaver med en liste over værter, som de skal udføres på. I de dybe dybder af dokumentationen kan du finde omtale af delegate_to, lokale opslagsplugins, netværks-cli-specifikke indstillinger, jump-værter osv. De giver dig mulighed for lidt at ændre det sted, hvor opgaver udføres. Men glem det. Hver af disse smarte muligheder har meget specifikke anvendelser, og de er bestemt ikke universelle. Og vi taler om grundlæggende ting, som alle bør vide og bruge.

Hvis du vil opføre "noget" "et eller andet sted", skriver du skuespil. Ikke en rolle. Ikke en rolle med moduler og delegerede. Du tager den og skriver skuespil. I hvilket, i værtsfeltet, du angiver, hvor der skal udføres, og i roller/opgaver - hvad der skal udføres.

Simpelt, ikke? Hvordan kunne det være anderledes?

Et af de karakteristiske øjeblikke, hvor folk har et ønske om at gøre dette ikke gennem leg, er "rollen, der sætter alt op". Jeg vil gerne have en rolle, der konfigurerer både servere af den første type og servere af den anden type.

Et arketypisk eksempel er overvågning. Jeg vil gerne have en overvågningsrolle, der konfigurerer overvågning. Overvågningsrollen er tildelt overvågningsværter (ifølge spil). Men det viser sig, at til overvågning er vi nødt til at levere pakker til de værter, som vi overvåger. Hvorfor ikke bruge delegeret? Du skal også konfigurere iptables. delegeret? Du skal også skrive/rette en konfiguration til DBMS'et for at aktivere overvågning. delegeret! Og mangler kreativiteten, så kan du lave en delegation include_role i en indlejret løkke ved hjælp af et vanskeligt filter på en liste over grupper og indenfor include_role du kan gøre mere delegate_to en gang til. Og så går vi...

Et godt ønske - at have én enkelt overvågningsrolle, som "gør alt" - fører os ind i det fuldstændige helvede, hvorfra der oftest kun er én udvej: at omskrive alt fra bunden.

Hvor skete fejlen her? I det øjeblik du opdagede, at for at udføre opgave "x" på vært X, skulle du gå til vært Y og lave "y" der, skulle du lave en simpel øvelse: gå og skrive et spil, hvilket på vært Y gør y. Tilføj ikke noget til "x", men skriv det fra bunden. Selv med hårdkodede variabler.

Det ser ud til, at alt i afsnittene ovenfor er sagt korrekt. Men dette er ikke din sag! Fordi du vil skrive genbrugelig kode, der er DRY og bibliotekslignende, og du skal lede efter en metode til, hvordan du gør det.

Det er her en anden alvorlig fejl lurer. En fejl, der forvandlede mange projekter fra tåleligt skrevet (det kunne være bedre, men alt fungerer og er nemt at afslutte) til en komplet rædsel, som selv forfatteren ikke kan finde ud af. Det virker, men Gud forbyde dig at ændre noget.

Fejlen er: rolle er en biblioteksfunktion. Denne analogi har ødelagt så mange gode begyndelser, at det simpelthen er trist at se. Rollen er ikke en biblioteksfunktion. Hun kan ikke lave beregninger, og hun kan ikke træffe beslutninger på legeniveau. Mind mig om, hvilke beslutninger leg træffer?

Tak, du har ret. Play træffer en beslutning (mere præcist indeholder den information) om hvilke opgaver og roller, der skal udføres på hvilke værter.

Hvis du uddelegerer denne beslutning til en rolle, og endda med beregninger, dømmer du dig selv (og den, der vil forsøge at analysere din kode) til en elendig tilværelse. Rollen bestemmer ikke, hvor den udføres. Denne beslutning træffes ved leg. Rollen gør, hvad den bliver fortalt, hvor den bliver fortalt.

Hvorfor er det farligt at programmere i Ansible, og hvorfor COBOL er bedre end Ansible, vil vi tale om i kapitlet om variabler og jinja. For nu, lad os sige én ting - hver af dine beregninger efterlader et uudsletteligt spor af ændringer i globale variabler, og du kan ikke gøre noget ved det. Så snart de to "spor" krydsede hinanden, var alt væk.

Bemærk til de sarte: rollen kan bestemt påvirke kontrolflowet. Spise delegate_to og det har rimelige anvendelser. Spise meta: end host/play. Men! Kan du huske, at vi underviser i det grundlæggende? glemte det delegate_to. Vi taler om den enkleste og smukkeste Ansible-kode. Som er let at læse, let at skrive, nem at fejlfinde, let at teste og nem at fuldføre. Så endnu en gang:

leg og kun leg afgør på hvilke værter, hvad der udføres.

I dette afsnit beskæftigede vi os med modsætningen mellem leg og rolle. Lad os nu tale om opgaver vs rolleforhold.

Opgaver og roller

Overvej at spille:

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

Lad os sige, at du skal gøre foo. Og det ligner foo: name=foobar state=present. Hvor skal jeg skrive dette? i før? stolpe? Skabe en rolle?

...Og hvor blev opgaverne af?

Vi starter med det grundlæggende igen - legeenheden. Hvis du flyder på denne problemstilling, kan du ikke bruge leg som grundlag for alt andet, og dit resultat bliver "rystende".

Play device: værtsdirektiv, indstillinger for selve spillet og pre_tasks, opgaver, roller, post_tasks sektioner. De resterende parametre for spil er ikke vigtige for os nu.

Rækkefølgen af ​​deres sektioner med opgaver og roller: pre_tasks, roles, tasks, post_tasks. Da semantisk rækkefølgen af ​​udførelse er mellem tasks и roles er ikke klart, så siger bedste praksis, at vi tilføjer et afsnit tasks, kun hvis ikke roles... Hvis der er roles, så er alle vedhæftede opgaver placeret i sektioner pre_tasks/post_tasks.

Tilbage er blot, at alt er semantisk klart: først pre_tasks, så roles, så post_tasks.

Men vi har stadig ikke besvaret spørgsmålet: hvor er modulopkaldet? foo skrive? Skal vi skrive en hel rolle for hvert modul? Eller er det bedre at have en tyk rolle til alt? Og hvis ikke en rolle, hvor skal jeg så skrive - i pre eller post?

Hvis der ikke er noget begrundet svar på disse spørgsmål, så er dette et tegn på mangel på intuition, det vil sige de samme "rystende grundlag". Lad os finde ud af det. Først et sikkerhedsspørgsmål: Hvis leg har pre_tasks и post_tasks (og der er ingen opgaver eller roller), så kan noget gå i stykker, hvis jeg udfører den første opgave fra post_tasks Jeg flytter det til slutningen pre_tasks?

Spørgsmålets ordlyd antyder selvfølgelig, at det vil bryde. Men hvad præcist?

... Håndtere. At læse det grundlæggende afslører en vigtig kendsgerning: alle handlere skylles automatisk efter hver sektion. De der. alle opgaver fra pre_tasks, derefter alle handlere, der blev underrettet. Derefter udføres alle rollerne og alle de handlere, der blev notificeret i rollerne. Efter post_tasks og deres handlere.

Altså hvis du trækker en opgave fra post_tasks в pre_tasks, så vil du potentielt udføre det, før handleren udføres. for eksempel hvis i pre_tasks webserveren er installeret og konfigureret, og post_tasks der sendes noget til den, så overfør denne opgave til sektionen pre_tasks vil føre til, at serveren på tidspunktet for "afsendelse" endnu ikke kører, og alt vil gå i stykker.

Lad os nu tænke igen, hvorfor har vi brug for det pre_tasks и post_tasks? For eksempel for at fuldføre alt det nødvendige (inklusive handlere), før de udfører rollen. EN post_tasks vil give os mulighed for at arbejde med resultaterne af udførende roller (inklusive handlere).

En kloge Ansible-ekspert vil fortælle os, hvad det er. meta: flush_handlers, men hvorfor har vi brug for flush_handlers, hvis vi kan stole på rækkefølgen for udførelse af sektioner i spil? Desuden kan brugen af ​​meta: flush_handlere give os uventede ting med duplicate handlers, hvilket giver os mærkelige advarsler, når de bruges when у block etc. Jo bedre du kender det ansible, jo flere nuancer kan du nævne for en "tricky" løsning. Og en simpel løsning - ved at bruge en naturlig opdeling mellem præ/roller/post - giver ikke nuancer.

Og tilbage til vores 'foo'. Hvor skal jeg placere den? I pre, post eller roller? Det afhænger naturligvis af, om vi har brug for resultaterne af handleren for foo. Hvis de ikke er der, så behøver foo ikke at blive placeret i hverken før eller efter - disse sektioner har en særlig betydning - udfører opgaver før og efter hoveddelen af ​​koden.

Nu kommer svaret på spørgsmålet "rolle eller opgave" ned til, hvad der allerede er i spil - hvis der er opgaver der, så skal du tilføje dem til opgaver. Hvis der er roller, skal du oprette en rolle (selv fra én opgave). Lad mig minde om, at opgaver og roller ikke bruges på samme tid.

At forstå det grundlæggende i Ansible giver rimelige svar på tilsyneladende spørgsmål om smag.

Opgaver og roller (del to)

Lad os nu diskutere situationen, når du lige er begyndt at skrive en legebog. Du skal lave foo, bar og baz. Er det tre opgaver, én rolle eller tre roller? For at opsummere spørgsmålet: på hvilket tidspunkt skal du begynde at skrive roller? Hvad er meningen med at skrive roller, når man kan skrive opgaver?... Hvad er en rolle?

En af de største fejl (jeg har allerede talt om dette) er at tro, at en rolle er som en funktion i et programs bibliotek. Hvordan ser en generisk funktionsbeskrivelse ud? Det accepterer input-argumenter, interagerer med sideårsager, laver bivirkninger og returnerer en værdi.

Nu, opmærksomhed. Hvad kan man gøre ud af dette i rollen? Du er altid velkommen til at ringe til bivirkninger, det er essensen af ​​hele Ansible – at skabe bivirkninger. Har du sideårsager? Elementære. Men med "giv en værdi og returner den" - det er der, det ikke virker. For det første kan du ikke overføre en værdi til en rolle. Du kan indstille en global variabel med en levetidsstørrelse for spillet i vars-sektionen for rollen. Du kan indstille en global variabel med en levetid i spil i rollen. Eller endda med legebøgers levetid (set_fact/register). Men du kan ikke have "lokale variabler". Du kan ikke "tage en værdi" og "returnere den".

Det vigtigste følger af dette: du kan ikke skrive noget i Ansible uden at forårsage bivirkninger. Ændring af globale variabler er altid en bivirkning for en funktion. I Rust er det for eksempel at ændre en global variabel unsafe. Og i Ansible er det den eneste metode til at påvirke værdierne for en rolle. Bemærk de anvendte ord: ikke "giv en værdi til rollen", men "ændr de værdier, som rollen bruger". Der er ingen isolation mellem rollerne. Der er ingen isolation mellem opgaver og roller.

Totalt: en rolle er ikke en funktion.

Hvad er godt ved rollen? For det første har rollen standardværdier (/default/main.yaml), for det andet har rollen yderligere mapper til lagring af filer.

Hvad er fordelene ved standardværdier? For i Maslows pyramide, Ansibles ret forvrængede tabel over variable prioriteter, er rollestandarder de mest lavprioriterede (minus Ansible kommandolinjeparametre). Dette betyder, at hvis du skal angive standardværdier og ikke bekymre dig om, at de tilsidesætter værdierne fra lager- eller gruppevariabler, så er rollestandarder det eneste rigtige sted for dig. (Jeg lyver lidt - der er flere |d(your_default_here), men hvis vi taler om stationære steder, så er det kun rollestandarder).

Hvad er der ellers godt ved rollerne? Fordi de har deres egne kataloger. Disse er mapper for variabler, både konstante (dvs. beregnet for rollen) og dynamiske (der er enten et mønster eller et anti-mønster - include_vars med {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Disse er mapperne til files/, templates/. Det giver dig også mulighed for at have dine egne moduler og plugins (library/). Men i sammenligning med opgaver i en playbook (som også kan have alt dette), er den eneste fordel her, at filerne ikke dumpes i én bunke, men flere separate bunker.

En detalje mere: du kan prøve at oprette roller, der vil være tilgængelige til genbrug (via galakse). Med indtoget af samlinger kan rollefordeling anses for næsten glemt.

Roller har således to vigtige funktioner: de har standarder (en unik funktion), og de giver dig mulighed for at strukturere din kode.

Tilbage til det oprindelige spørgsmål: hvornår skal man udføre opgaver, og hvornår man skal udføre roller? Opgaver i en playbook bruges oftest enten som "lim" før/efter roller, eller som et selvstændigt byggeelement (så skal der ikke være roller i koden). En bunke normale opgaver blandet med roller er entydig sjusk. Du bør holde dig til en bestemt stil - enten en opgave eller en rolle. Roller giver adskillelse af enheder og standardindstillinger, opgaver giver dig mulighed for at læse kode hurtigere. Normalt sættes mere "stationær" (vigtig og kompleks) kode i roller, og hjælpescripts er skrevet i opgavestil.

Det er muligt at lave import_role som en opgave, men hvis du skriver dette, så vær forberedt på at forklare din egen skønhedssans, hvorfor du vil gøre dette.

En klog læser kan sige, at roller kan importere roller, roller kan have afhængigheder via galaxy.yml, og der er også en forfærdelig og forfærdelig include_role — Jeg minder dig om, at vi forbedrer færdigheder i grundlæggende Ansible og ikke i figurgymnastik.

Håndtere og opgaver

Lad os diskutere en anden indlysende ting: handlere. At vide, hvordan man bruger dem korrekt, er næsten en kunst. Hvad er forskellen mellem en handler og et træk?

Da vi husker det grundlæggende, er her et eksempel:

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

Rollens handlere er placeret i rolename/handlers/main.yaml. Handlere roder mellem alle legedeltagere: pre/post_tasks kan trække rollebehandlere, og en rolle kan trække handlere fra legen. Men "cross-rolle" opkald til handlere forårsager meget mere wtf end at gentage en triviel handler. (Et andet element i bedste praksis er at prøve ikke at gentage handlernavne).

Den største forskel er, at opgaven altid udføres (idempotent) (plus/minus tags og when), og behandleren - efter tilstandsændring (underret kun brande, hvis den er blevet ændret). Hvad betyder det? For eksempel det faktum, at når du genstarter, hvis der ikke blev ændret, så vil der ikke være nogen handler. Hvorfor kan det være, at vi skal udføre handler, når der ikke var nogen ændring i genereringsopgaven? For eksempel fordi noget gik i stykker og ændrede sig, men udførelse nåede ikke handleren. For eksempel fordi netværket var midlertidigt nede. Konfigurationen er ændret, tjenesten er ikke blevet genstartet. Næste gang du starter det, ændres konfigurationen ikke længere, og tjenesten forbliver med den gamle version af konfigurationen.

Situationen med konfigurationen kan ikke løses (mere præcist kan du opfinde en speciel genstartsprotokol til dig selv med filflag osv., men dette er ikke længere 'basic ansible' i nogen form). Men der er en anden fælles historie: vi installerede applikationen, optog den .service-fil, og nu vil vi have den daemon_reload и state=started. Og det naturlige sted for dette ser ud til at være handleren. Men hvis du ikke gør det til en handler, men en opgave i slutningen af ​​en opgaveliste eller rolle, så vil den blive udført idempotent hver gang. Også selvom spillebogen gik i stykker på midten. Dette løser slet ikke det genstartede problem (du kan ikke udføre en opgave med attributten genstartet, fordi idempotens er tabt), men det er bestemt værd at gøre state=started, den generelle stabilitet af playbooks øges, fordi antallet af forbindelser og dynamisk tilstand falder.

En anden positiv egenskab ved handleren er, at den ikke tilstopper outputtet. Der var ingen ændringer - ingen ekstra sprunget over eller ok i outputtet - nemmere at læse. Det er også en negativ egenskab - hvis du finder en tastefejl i en lineært udført opgave ved den allerførste kørsel, så vil handlerne kun blive udført, når de ændres, dvs. under nogle forhold - meget sjældent. For eksempel for første gang i mit liv fem år senere. Og selvfølgelig vil der være en tastefejl i navnet, og alt går i stykker. Og hvis du ikke kører dem anden gang, er der ingen ændringer.

Separat skal vi tale om tilgængeligheden af ​​variabler. For eksempel, hvis du underretter en opgave med en loop, hvad vil der så være i variablerne? Du kan gætte analytisk, men det er ikke altid trivielt, især hvis variablerne kommer fra forskellige steder.

... Så handlere er meget mindre nyttige og meget mere problematiske, end de ser ud til. Hvis du kan skrive noget smukt (uden dikkedarer) uden handlere, er det bedre at gøre det uden dem. Hvis det ikke fungerer smukt, er det bedre med dem.

Den ætsende læser påpeger med rette, at vi ikke har diskuteret listenat en handler kan kalde notify for en anden handler, at en handler kan inkludere import_tasks (som kan inkludere include_role med with_items), at handlersystemet i Ansible er Turing-komplet, at handlere fra include_role krydser på en nysgerrig måde med handlere fra leg, osv. .d. - alt dette er tydeligvis ikke "det grundlæggende").

Selvom der er en specifik WTF, der faktisk er en funktion, som du skal huske på. Hvis din opgave udføres med delegate_to og den har notificeret, så udføres den tilsvarende handler uden delegate_to, dvs. på værten, hvor leg er tildelt. (Selvom handleren selvfølgelig kan have delegate_to Samme).

Separat vil jeg sige et par ord om genanvendelige roller. Før samlinger dukkede op, var der en idé om, at man kunne lave universelle roller, der kunne være ansible-galaxy install og gik. Virker på alle OS af alle varianter i alle situationer. Så min mening: det virker ikke. Enhver rolle med masse include_vars, der understøtter 100500 sager, er dømt til afgrunden af ​​hjørnekasse-fejl. De kan dækkes med massiv testning, men som med enhver test har du enten et kartesisk produkt af inputværdier og en samlet funktion, eller du har "individuelle scenarier dækket." Min mening er, at det er meget bedre, hvis rollen er lineær (cyklomatisk kompleksitet 1).

Jo færre hvis (eksplicit eller deklarativt - i formen when eller form include_vars efter sæt af variabler), jo bedre rolle. Nogle gange skal du lave grene, men jeg gentager, jo færre der er, jo bedre. Så det virker som en god rolle med galakse (det virker!) med en masse when kan være mindre at foretrække end "sin egen" rolle fra fem opgaver. Øjeblikket, hvor rollen med galakse er bedre, er, når du begynder at skrive noget. Øjeblikket, hvor det bliver værre, er, når noget går i stykker, og du har en mistanke om, at det er på grund af "rollen med galaksen". Du åbner den, og der er fem indeslutninger, otte opgaveark og en stak when'ov... Og vi skal finde ud af det. I stedet for 5 opgaver, en lineær liste, hvor der ikke er noget at bryde.

I de følgende dele

  • Lidt om inventar, gruppevariabler, host_group_vars plugin, hostvars. Sådan binder du en gordisk knude med spaghetti. Omfang og præcedens variabler, Ansible memory model. "Så hvor gemmer vi brugernavnet til databasen?"
  • jinja: {{ jinja }} — nosql notype nosense blød plasticine. Det er overalt, selv hvor du ikke forventer det. Lidt om !!unsafe og lækker yaml.

Kilde: www.habr.com

Tilføj en kommentar