Ansible grunnleggende, uten hvilke lekebøkene dine vil være en klump klebrig pasta

Jeg gjør mange anmeldelser av andres Ansible-kode og skriver mye selv. I løpet av å analysere feil (både andres og mine egne), samt en rekke intervjuer, innså jeg hovedfeilen som Ansible-brukere gjør – de går inn i komplekse ting uten å mestre de grunnleggende.

For å rette opp denne universelle urettferdigheten bestemte jeg meg for å skrive en introduksjon til Ansible for de som allerede vet det. Jeg advarer deg, dette er ikke en gjenfortelling av menn, dette er en longread med mange bokstaver og ingen bilder.

Det forventede nivået til leseren er at flere tusen linjer med yamla allerede er skrevet, noe er allerede i produksjon, men "på en eller annen måte er alt skjevt."

navnene

Den største feilen en Ansible-bruker gjør er å ikke vite hva noe heter. Hvis du ikke kan navnene, kan du ikke forstå hva dokumentasjonen sier. Et levende eksempel: under et intervju kunne en person som så ut til å si at han skrev mye i Ansible ikke svare på spørsmålet "hvilke elementer består en lekebok av?" Og da jeg foreslo at "svaret var forventet at lekeboken består av lek," fulgte den fordømte kommentaren "det bruker vi ikke". Folk skriver Ansible for penger og bruker ikke spill. De bruker det faktisk, men vet ikke hva det er.

Så la oss starte med noe enkelt: hva heter det? Kanskje du vet dette, eller kanskje ikke, fordi du ikke tok hensyn da du leste dokumentasjonen.

ansible-playbook kjører playbook. En playbook er en fil med filtypen yml/yaml, inne i hvilken det er noe sånt som dette:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Vi har allerede innsett at hele denne filen er en spillebok. Vi kan vise hvor rollene er og hvor oppgavene er. Men hvor er leken? Og hva er forskjellen på lek og rolle eller lekebok?

Alt står i dokumentasjonen. Og de savner det. Nybegynnere - fordi det er for mye, og du vil ikke huske alt på en gang. Erfaren - fordi "trivielle ting". Hvis du er erfaren, les disse sidene på nytt minst en gang hver sjette måned, og koden din vil bli klasseledende.

Så husk: Playbook er en liste som består av lek og import_playbook.
Dette er ett skuespill:

- hosts: group1
  roles:
    - role1

og dette er også et annet skuespill:

- hosts: group2,group3
  tasks:
    - debug:

Hva er lek? Hvorfor er hun det?

Lek er et nøkkelelement for en lekebok, fordi lek og bare lek knytter en liste over roller og/eller oppgaver til en liste over verter som de må utføres på. I dypet av dokumentasjonen kan du finne omtale av delegate_to, plugins for lokale oppslag, nettverks-cli-spesifikke innstillinger, hoppverter, etc. De lar deg endre stedet der oppgavene utføres litt. Men, glem det. Hver av disse smarte alternativene har veldig spesifikke bruksområder, og de er definitivt ikke universelle. Og vi snakker om grunnleggende ting som alle bør kunne og bruke.

Hvis du vil fremføre "noe" "et sted", skriver du skuespill. Ikke en rolle. Ikke en rolle med moduler og delegater. Du tar den og skriver skuespill. I hvilken, i vertsfeltet, viser du hvor du skal utføre, og i roller/oppgaver - hva du skal utføre.

Enkelt, ikke sant? Hvordan kunne det være annerledes?

Et av de karakteristiske øyeblikkene når folk har et ønske om å gjøre dette ikke gjennom lek, er «rollen som setter alt opp». Jeg vil gjerne ha en rolle som konfigurerer både servere av den første typen og servere av den andre typen.

Et arketypisk eksempel er overvåking. Jeg vil gjerne ha en overvåkingsrolle som vil konfigurere overvåking. Overvåkingsrollen er tildelt overvåkingsverter (i henhold til lek). Men det viser seg at for overvåking må vi levere pakker til vertene som vi overvåker. Hvorfor ikke bruke delegat? Du må også konfigurere iptables. delegat? Du må også skrive/korrigere en konfigurasjon for DBMS for å aktivere overvåking. delegat! Og hvis kreativiteten mangler, så kan du lage en delegasjon include_role i en nestet sløyfe ved hjelp av et vanskelig filter på en liste over grupper, og inne include_role du kan gjøre mer delegate_to en gang til. Og vi går...

Et godt ønske - å ha én enkelt overvåkingsrolle, som "gjør alt" - fører oss inn i et fullstendig helvete som det oftest bare er én vei ut fra: å skrive om alt fra bunnen av.

Hvor skjedde feilen her? I det øyeblikket du oppdaget at for å gjøre oppgaven "x" på vert X, måtte du gå til vert Y og gjøre "y" der, måtte du gjøre en enkel øvelse: gå og skrive et spill, som på vert Y gjør y. Ikke legg til noe i "x", men skriv det fra bunnen av. Selv med hardkodede variabler.

Det ser ut til at alt i avsnittene over er sagt riktig. Men dette er ikke din sak! Fordi du vil skrive gjenbrukbar kode som er TØRR og biblioteklignende, og du må se etter en metode for hvordan du gjør det.

Det er her en annen alvorlig feil lurer. En feil som gjorde mange prosjekter fra tålelig skrevet (det kunne vært bedre, men alt fungerer og er enkelt å fullføre) til en fullstendig skrekk som selv forfatteren ikke kan finne ut av. Det fungerer, men Gud forby deg å endre noe.

Feilen er: rolle er en bibliotekfunksjon. Denne analogien har ødelagt så mange gode begynnelser at det rett og slett er trist å se på. Rollen er ikke en bibliotekfunksjon. Hun kan ikke gjøre beregninger og hun kan ikke ta avgjørelser på lekenivå. Minn meg på hvilke avgjørelser leken tar?

Takk, du har rett. Play tar en beslutning (mer presist, den inneholder informasjon) om hvilke oppgaver og roller som skal utføres på hvilke verter.

Hvis du delegerer denne avgjørelsen til en rolle, og til og med med beregninger, dømmer du deg selv (og den som vil prøve å analysere koden din) til en elendig tilværelse. Rollen bestemmer ikke hvor den skal utføres. Denne avgjørelsen tas ved lek. Rollen gjør det den blir fortalt, der den blir fortalt.

Hvorfor er det farlig å programmere i Ansible og hvorfor COBOL er bedre enn Ansible skal vi snakke om i kapittelet om variabler og jinja. For nå, la oss si én ting - hver av beregningene dine etterlater et uutslettelig spor av endringer i globale variabler, og du kan ikke gjøre noe med det. Så snart de to "sporene" krysset hverandre, var alt borte.

Merknad til de pysete: rollen kan sikkert påvirke kontrollflyten. Spise delegate_to og den har rimelige bruksområder. Spise meta: end host/play. Men! Husker du at vi lærer det grunnleggende? glemte delegate_to. Vi snakker om den enkleste og vakreste Ansible-koden. Som er lett å lese, lett å skrive, lett å feilsøke, lett å teste og lett å fullføre. Så, nok en gang:

lek og kun lek bestemmer på hvilke verter hva som blir henrettet.

I dette avsnittet tok vi for oss motsetningen mellom lek og rolle. La oss nå snakke om oppgavene vs rolleforhold.

Oppgaver og roller

Vurder å spille:

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

La oss si at du må gjøre foo. Og det ser ut som foo: name=foobar state=present. Hvor skal jeg skrive dette? i før? post? Skape en rolle?

...Og hvor ble det av oppgavene?

Vi begynner med det grunnleggende igjen - lekeapparatet. Hvis du flyter på dette problemet, kan du ikke bruke spill som grunnlag for alt annet, og resultatet ditt vil være "shaky".

Spilleenhet: vertsdirektiv, innstillinger for selve spillet og pre_tasks, oppgaver, roller, post_tasks-seksjoner. De resterende parametrene for spill er ikke viktige for oss nå.

Rekkefølgen på seksjonene deres med oppgaver og roller: pre_tasks, roles, tasks, post_tasks. Siden semantisk er rekkefølgen for utførelse mellom tasks и roles er ikke klart, så sier beste praksis at vi legger til en seksjon tasks, bare hvis ikke roles... Hvis det er det roles, så er alle vedlagte oppgaver plassert i seksjoner pre_tasks/post_tasks.

Alt som gjenstår er at alt er semantisk klart: først pre_tasks, deretter roles, deretter post_tasks.

Men vi har fortsatt ikke svart på spørsmålet: hvor er modulanropet? foo skrive? Trenger vi å skrive en hel rolle for hver modul? Eller er det bedre å ha en tykk rolle for alt? Og hvis ikke en rolle, hvor skal jeg da skrive - i pre eller post?

Hvis det ikke er noe begrunnet svar på disse spørsmålene, er dette et tegn på mangel på intuisjon, det vil si de samme "vaklende grunnlagene." La oss finne ut av det. Først et sikkerhetsspørsmål: Hvis lek har pre_tasks и post_tasks (og det er ingen oppgaver eller roller), så kan noe gå i stykker hvis jeg utfører den første oppgaven fra post_tasks Jeg flytter det til slutten pre_tasks?

Spørsmålets ordlyd antyder selvfølgelig at det vil gå i stykker. Men hva egentlig?

... Håndtere. Å lese det grunnleggende avslører et viktig faktum: alle behandlere skylles automatisk etter hver seksjon. De. alle oppgaver fra pre_tasks, deretter alle behandlerne som ble varslet. Deretter utføres alle rollene og alle behandlerne som ble varslet i rollene. Etter post_tasks og deres behandlere.

Altså, hvis du drar en oppgave fra post_tasks в pre_tasks, så vil du potensielt kjøre den før behandleren kjøres. for eksempel hvis i pre_tasks webserveren er installert og konfigurert, og post_tasks noe sendes til den, overfør deretter denne oppgaven til seksjonen pre_tasks vil føre til at serveren ennå ikke kjører på tidspunktet for "sending", og alt vil gå i stykker.

La oss nå tenke igjen, hvorfor trenger vi pre_tasks и post_tasks? For eksempel for å fullføre alt nødvendig (inkludert behandlere) før du oppfyller rollen. EN post_tasks vil tillate oss å jobbe med resultatene av utførende roller (inkludert behandlere).

En klok Ansible-ekspert vil fortelle oss hva det er. meta: flush_handlers, men hvorfor trenger vi flush_handlers hvis vi kan stole på rekkefølgen for utførelse av seksjoner i spill? Dessuten kan bruken av meta: flush_handlere gi oss uventede ting med dupliserte behandlere, og gi oss merkelige advarsler når de brukes when у block etc. Jo bedre du kjenner det mulige, desto flere nyanser kan du gi en "vanskelig" løsning. Og en enkel løsning – å bruke en naturlig deling mellom pre/roller/post – gir ikke nyanser.

Og tilbake til vår 'foo'. Hvor skal jeg sette den? I før, post eller roller? Dette avhenger selvsagt av om vi trenger resultatene til handleren for foo. Hvis de ikke er der, trenger ikke foo å plasseres i verken pre eller post - disse seksjonene har en spesiell betydning - utføre oppgaver før og etter hoveddelen av koden.

Nå kommer svaret på spørsmålet "rolle eller oppgave" ned til det som allerede er i spill - hvis det er oppgaver der, må du legge dem til oppgaver. Hvis det er roller, må du opprette en rolle (selv fra én oppgave). La meg minne om at oppgaver og roller ikke brukes samtidig.

Å forstå det grunnleggende i Ansible gir rimelige svar på tilsynelatende spørsmål om smak.

Oppgaver og roller (del to)

La oss nå diskutere situasjonen når du akkurat begynner å skrive en lekebok. Du må lage foo, bar og baz. Er dette tre oppgaver, én rolle eller tre roller? For å oppsummere spørsmålet: på hvilket tidspunkt bør du begynne å skrive roller? Hva er vitsen med å skrive roller når du kan skrive oppgaver?... Hva er en rolle?

En av de største feilene (jeg har allerede snakket om dette) er å tro at en rolle er som en funksjon i et programs bibliotek. Hvordan ser en generisk funksjonsbeskrivelse ut? Den aksepterer input-argumenter, samhandler med sideårsaker, gjør bivirkninger og returnerer en verdi.

Nå, oppmerksomhet. Hva kan gjøres av dette i rollen? Du er alltid velkommen til å ringe bivirkninger, dette er essensen av hele Ansible - for å skape bivirkninger. Har du sideårsaker? Elementær. Men med "pass en verdi og returner den" - det er der det ikke fungerer. For det første kan du ikke overføre en verdi til en rolle. Du kan angi en global variabel med en levetidsstørrelse for spillet i vars-delen for rollen. Du kan angi en global variabel med en levetid i spill i rollen. Eller til og med med levetiden til lekebøker (set_fact/register). Men du kan ikke ha "lokale variabler". Du kan ikke "ta en verdi" og "returnere den".

Det viktigste følger av dette: du kan ikke skrive noe i Ansible uten å forårsake bivirkninger. Å endre globale variabler er alltid en bivirkning for en funksjon. I Rust, for eksempel, er endring av en global variabel unsafe. Og i Ansible er det den eneste metoden for å påvirke verdiene for en rolle. Legg merke til ordene som brukes: ikke "gi en verdi til rollen", men "endre verdiene som rollen bruker". Det er ingen isolasjon mellom rollene. Det er ingen isolasjon mellom oppgaver og roller.

totalt: en rolle er ikke en funksjon.

Hva er bra med rollen? For det første har rollen standardverdier (/default/main.yaml), for det andre har rollen ekstra kataloger for lagring av filer.

Hva er fordelene med standardverdier? Fordi i Maslows pyramide, Ansibles ganske perverterte tabell over variable prioriteter, er rollestandarder de mest lavprioriterte (minus Ansible kommandolinjeparametere). Dette betyr at hvis du trenger å oppgi standardverdier og ikke bekymre deg for at de overstyrer verdiene fra inventar- eller gruppevariabler, så er rollestandarder det eneste rette stedet for deg. (Jeg lyver litt - det er flere |d(your_default_here), men hvis vi snakker om stasjonære steder, er det bare rollestandarder).

Hva annet er bra med rollene? Fordi de har sine egne kataloger. Dette er kataloger for variabler, både konstante (dvs. beregnet for rollen) og dynamiske (det er enten et mønster eller et antimønster - include_vars med {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Dette er katalogene for files/, templates/. Det lar deg også ha dine egne moduler og plugins (library/). Men sammenlignet med oppgaver i en lekebok (som også kan ha alt dette), er den eneste fordelen her at filene ikke dumpes i én haug, men flere separate hauger.

En detalj til: du kan prøve å lage roller som vil være tilgjengelige for gjenbruk (via galakse). Med inntoget av samlinger kan rollefordelingen anses som nesten glemt.

Dermed har roller to viktige funksjoner: de har standarder (en unik funksjon) og de lar deg strukturere koden din.

Tilbake til det opprinnelige spørsmålet: når skal man gjøre oppgaver og når man skal gjøre roller? Oppgaver i en lekebok brukes oftest enten som «lim» før/etter roller, eller som et selvstendig byggeelement (da skal det ikke være noen roller i koden). En haug med vanlige oppgaver blandet med roller er entydig slurv. Du bør holde deg til en bestemt stil – enten en oppgave eller en rolle. Roller gir separasjon av enheter og standarder, oppgaver lar deg lese kode raskere. Vanligvis settes mer "stasjonær" (viktig og kompleks) kode inn i roller, og hjelpeskript skrives i oppgavestil.

Det er mulig å gjøre import_rolle som en oppgave, men hvis du skriver dette, så vær forberedt på å forklare din egen skjønnhetssans hvorfor du vil gjøre dette.

En skarpsindig leser kan si at roller kan importere roller, roller kan ha avhengigheter via galaxy.yml, og det er også en forferdelig og forferdelig include_role — Jeg minner om at vi forbedrer ferdighetene i grunnleggende Ansible, og ikke i figurgymnastikk.

Håndtere og oppgaver

La oss diskutere en annen åpenbar ting: handlere. Å vite hvordan du bruker dem riktig er nesten en kunst. Hva er forskjellen mellom en handler og en drag?

Siden vi husker det grunnleggende, er her et eksempel:

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

Rollens behandlere er plassert i rollenavn/handlere/main.yaml. Behandlere roter mellom alle lekedeltakere: pre/post_tasks kan trekke rollebehandlere, og en rolle kan trekke behandlere fra stykket. Imidlertid forårsaker "cross-rolle"-kall til behandlere mye mer wtf enn å gjenta en triviell behandler. (Et annet element i beste praksis er å prøve å ikke gjenta behandlernavn).

Hovedforskjellen er at oppgaven alltid utføres (idempotent) (pluss/minus-tagger og when), og behandleren - etter tilstandsendring (varsle branner bare hvis den er endret). Hva betyr dette? For eksempel det faktum at når du starter på nytt, hvis det ikke ble endret, vil det ikke være noen behandler. Hvorfor kan det være at vi trenger å utføre behandler når det ikke var noen endring i genereringsoppgaven? For eksempel fordi noe gikk i stykker og endret seg, men utførelse nådde ikke behandleren. For eksempel fordi nettverket var midlertidig nede. Konfigurasjonen er endret, tjenesten er ikke startet på nytt. Neste gang du starter den, endres ikke konfigurasjonen lenger, og tjenesten forblir med den gamle versjonen av konfigurasjonen.

Situasjonen med konfigurasjonen kan ikke løses (mer presist kan du finne opp en spesiell omstartsprotokoll for deg selv med filflagg osv., men dette er ikke lenger "grunnleggende mulig" i noen form). Men det er en annen vanlig historie: vi installerte applikasjonen, registrerte den .service-fil, og nå vil vi ha den daemon_reload и state=started. Og det naturlige stedet for dette ser ut til å være handleren. Men hvis du ikke gjør det til en behandler, men en oppgave på slutten av en oppgaveliste eller rolle, vil den bli utført idempotent hver gang. Selv om spilleboken gikk i stykker på midten. Dette løser ikke det omstartede problemet i det hele tatt (du kan ikke gjøre en oppgave med restarted-attributtet, fordi idempotens går tapt), men det er definitivt verdt å gjøre state=started, den generelle stabiliteten til playbooks øker, fordi antall tilkoblinger og dynamisk tilstand reduseres.

En annen positiv egenskap til behandleren er at den ikke tetter til utgangen. Det var ingen endringer - ingen ekstra hoppet eller ok i utgangen - lettere å lese. Det er også en negativ egenskap - hvis du finner en skrivefeil i en lineært utført oppgave på den aller første kjøringen, vil behandlerne kun bli utført når de endres, dvs. under noen forhold - svært sjelden. For eksempel for første gang i mitt liv fem år senere. Og selvfølgelig vil det være en skrivefeil i navnet og alt vil gå i stykker. Og hvis du ikke kjører dem andre gang, er det ingen endring.

Separat må vi snakke om tilgjengeligheten av variabler. For eksempel, hvis du varsler en oppgave med en loop, hva vil være i variablene? Du kan gjette analytisk, men det er ikke alltid trivielt, spesielt hvis variablene kommer fra forskjellige steder.

... Så behandlere er mye mindre nyttige og mye mer problematiske enn de ser ut til. Hvis du kan skrive noe vakkert (uten dikkedarer) uten behandlere, er det bedre å gjøre det uten dem. Hvis det ikke fungerer vakkert, er det bedre med dem.

Den etsende leser påpeker med rette at vi ikke har diskutert listenat en behandler kan ringe notify for en annen behandler, at en behandler kan inkludere import_tasks (som kan gjøre include_role med with_items), at behandlersystemet i Ansible er Turing-komplett, at behandlere fra include_role skjærer seg på en nysgjerrig måte med behandlere fra spill, osv. .d. - alt dette er tydeligvis ikke det "grunnleggende").

Selv om det er en spesifikk WTF som faktisk er en funksjon du må huske på. Hvis oppgaven din utføres med delegate_to og den har varslet, så utføres den tilsvarende behandleren uten delegate_to, dvs. på verten der spill er tildelt. (Selv om føreren selvfølgelig kan ha det delegate_to Samme).

Hver for seg vil jeg si noen ord om gjenbrukbare roller. Før samlinger dukket opp, var det en idé om at man kunne lage universelle roller som kunne være det ansible-galaxy install og gikk. Fungerer på alle OS av alle varianter i alle situasjoner. Så, min mening: det fungerer ikke. Enhver rolle med masse include_vars, som støtter 100500 1 tilfeller, er dømt til avgrunnen av hjørnekofferter. De kan dekkes med massiv testing, men som med enhver testing, enten har du et kartesisk produkt av inngangsverdier og en totalfunksjon, eller du har "individuelle scenarier dekket." Min mening er at det er mye bedre om rollen er lineær (syklomatisk kompleksitet XNUMX).

Jo færre hvis (eksplisitt eller deklarativt - i skjemaet when eller form include_vars etter sett med variabler), jo bedre rolle. Noen ganger må du lage grener, men jeg gjentar, jo færre det er, jo bedre. Så det virker som en god rolle med galakse (det fungerer!) med en haug med when kan være mindre å foretrekke enn "sin egen" rolle fra fem oppgaver. Øyeblikket når rollen med galakse er bedre er når du begynner å skrive noe. Øyeblikket når det blir verre er når noe går i stykker og du har en mistanke om at det er på grunn av "rollen med galaksen". Du åpner den, og det er fem inneslutninger, åtte oppgaveark og en stabel when'ov... Og vi må finne ut av dette. I stedet for 5 oppgaver, en lineær liste der det ikke er noe å bryte.

I de følgende delene

  • Litt om inventar, gruppevariabler, host_group_vars plugin, hostvars. Hvordan knytte en gordisk knute med spaghetti. Omfang og prioritetsvariabler, Ansible minnemodell. "Så hvor lagrer vi brukernavnet for databasen?"
  • jinja: {{ jinja }} — nosql notype nosense myk plasticine. Det er overalt, selv der du ikke forventer det. Litt om !!unsafe og deilig yaml.

Kilde: www.habr.com

Legg til en kommentar