Conceptes bàsics d'Ansible, sense els quals els vostres llibres de jocs seran un tros de pasta enganxosa

Faig moltes ressenyes del codi Ansible d'altres persones i escric molt jo mateix. Durant l'anàlisi d'errors (tant dels meus com d'altres persones), així com d'algunes entrevistes, em vaig adonar del principal error que cometen els usuaris d'Ansible: s'introdueixen en coses complexes sense dominar les bàsiques.

Per corregir aquesta injustícia universal, vaig decidir escriure una introducció a Ansible per a qui ja la coneixia. Us adverteixo que això no és un relat d'homes, és una lectura llarga amb moltes cartes i sense imatges.

El nivell esperat del lector és que ja s'han escrit diversos milers de línies de yamla, alguna cosa ja està en producció, però "d'alguna manera tot està tort".

Noms

El principal error que comet un usuari d'Ansible és no saber com es diu alguna cosa. Si no coneixeu els noms, no podeu entendre què diu la documentació. Un exemple viu: durant una entrevista, una persona que semblava dir que escrivia molt a Ansible no podia respondre a la pregunta "de quins elements consta un llibre de jocs?" I quan vaig suggerir que "la resposta s'esperava que el llibre de jocs consistia en jugar", va seguir el comentari condemnador "no fem servir això". La gent escriu Ansible per diners i no fa servir el joc. En realitat l'utilitzen, però no saben què és.

Així que comencem amb una cosa senzilla: com es diu. Potser ho sabeu, o potser no, perquè no us vau fer cas quan vau llegir la documentació.

ansible-playbook executa el playbook. Un llibre de jocs és un fitxer amb l'extensió yml/yaml, dins del qual hi ha alguna cosa com això:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Ja ens vam adonar que tot aquest fitxer és un llibre de jugades. Podem mostrar on són els rols i on són les tasques. Però on és jugar? I quina diferència hi ha entre joc i paper o llibre de jocs?

Tot està a la documentació. I ho troben a faltar. Principiants: perquè n'hi ha massa i no ho recordareu tot alhora. Amb experiència, perquè "coses trivials". Si tens experiència, torna a llegir aquestes pàgines almenys una vegada cada sis mesos i el teu codi es convertirà en el líder de la classe.

Per tant, recordeu: Playbook és una llista que consta de jocs i import_playbook.
Aquesta és una jugada:

- hosts: group1
  roles:
    - role1

i aquesta també és una altra obra de teatre:

- hosts: group2,group3
  tasks:
    - debug:

Què és jugar? Per què és ella?

El joc és un element clau per a un llibre de jocs, perquè el joc i només el joc associa una llista de rols i/o tasques amb una llista d'amfitrions en què s'han d'executar. En les profunditats de la documentació es pot trobar esment delegate_to, connectors de cerca local, configuracions específiques de la xarxa, servidors de salt, etc. Permeten canviar lleugerament el lloc on es realitzen les tasques. Però, oblida't. Cadascuna d'aquestes opcions intel·ligents té usos molt específics i, definitivament, no són universals. I estem parlant de coses bàsiques que tothom hauria de conèixer i utilitzar.

Si voleu interpretar "alguna cosa" "en algun lloc", escriviu joc. No un paper. No és un paper amb mòduls i delegats. L'agafes i escrius el joc. En el qual, al camp dels amfitrions, enumereu on executar i, en rols/tasques, què heu d'executar.

Simple, oi? Com no podria ser d'una altra manera?

Un dels moments característics en què la gent té el desig de fer-ho no a través del joc és el "rol que ho configura tot". M'agradaria tenir un rol que configura tant servidors del primer tipus com servidors del segon tipus.

Un exemple arquetípic és el seguiment. M'agradaria tenir una funció de supervisió que configurarà la supervisió. La funció de monitoratge s'assigna als hosts de monitoratge (segons el joc). Però resulta que per al seguiment hem de lliurar paquets als amfitrions que estem supervisant. Per què no utilitzar delegat? També heu de configurar iptables. delegat? També heu d'escriure/corregir una configuració per al SGBD per habilitar la supervisió. delegat! I si falta creativitat, podeu fer una delegació include_role en un bucle imbricat mitjançant un filtre complicat en una llista de grups i dins include_role pots fer més delegate_to de nou. I marxem...

Un bon desig - tenir un únic paper de monitoratge, que "ho faci tot" - ens porta a un infern complet del qual sovint només hi ha una sortida: reescriure-ho tot des de zero.

On va passar l'error aquí? En el moment que vas descobrir que per fer la tasca "x" a l'amfitrió X havies d'anar a l'amfitrió Y i fer-hi "y", vas haver de fer un exercici senzill: anar a escriure el joc, que a l'amfitrió Y fa y. No afegiu res a "x", sinó escriu-ho des de zero. Fins i tot amb variables codificades.

Sembla que tot en els paràgrafs anteriors està dit correctament. Però aquest no és el teu cas! Perquè voleu escriure codi reutilitzable que sigui SEC i semblant a una biblioteca, i heu de buscar un mètode sobre com fer-ho.

Aquí és on s'amaga un altre error greu. Un error que va convertir molts projectes d'escrits de manera tolerable (podria ser millor, però tot funciona i és fàcil d'acabar) en un horror complet que ni l'autor no pot esbrinar. Funciona, però Déu no us permeti canviar res.

L'error és: rol és una funció de biblioteca. Aquesta analogia ha arruïnat tants bons inicis que és senzillament trist veure-ho. El paper no és una funció de biblioteca. No pot fer càlculs i no pot prendre decisions a nivell de joc. Recordeu-me quines decisions pren el joc?

Gràcies, tens raó. Play pren una decisió (més precisament, conté informació) sobre quines tasques i rols ha de realitzar en quins amfitrions.

Si delegues aquesta decisió a un paper, i fins i tot amb càlculs, et condemnes a tu mateix (i a aquell que intentarà analitzar el teu codi) a una existència miserable. El paper no decideix on s'executa. Aquesta decisió es pren jugant. El paper fa el que se li diu, on es diu.

Per què és perillós programar a Ansible i per què COBOL és millor que Ansible, parlarem al capítol sobre variables i jinja. De moment, diguem una cosa: cadascun dels vostres càlculs deixa enrere un rastre indeleble de canvis en les variables globals i no podeu fer-hi res. Tan bon punt les dues "rastres" es van creuar, tot havia desaparegut.

Nota per als curiosos: el paper, sens dubte, pot influir en el flux de control. Menja delegate_to i té usos raonables. Menja meta: end host/play. Però! Recordeu que ensenyem les bases? M'he oblidat delegate_to. Estem parlant del codi Ansible més senzill i bonic. Que és fàcil de llegir, fàcil d'escriure, fàcil de depurar, fàcil de provar i fàcil de completar. Així, una vegada més:

jugar i només el joc decideix quin amfitrió s'executa.

En aquest apartat hem tractat l'oposició entre joc i paper. Ara parlem de la relació entre tasques i rols.

Tasques i rols

Considereu el joc:

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

Diguem que has de fer foo. I sembla foo: name=foobar state=present. On hauria d'escriure això? en pre? publicar? Creeu un rol?

...I on van anar les tasques?

Tornem a començar amb els conceptes bàsics: el dispositiu de joc. Si sures d'aquest problema, no pots utilitzar el joc com a base per a tota la resta, i el teu resultat serà "tremolós".

Dispositiu de reproducció: directiva d'amfitrions, configuració del joc en si i pre_tasks, tasques, rols, seccions post_tasks. La resta de paràmetres de joc no són importants per a nosaltres ara.

L'ordre de les seves seccions amb tasques i rols: pre_tasks, roles, tasks, post_tasks. Com que semànticament l'ordre d'execució està entre tasks и roles no està clar, llavors les millors pràctiques diuen que estem afegint una secció tasks, només si no roles... Si hi ha roles, llavors totes les tasques adjuntes es col·loquen en seccions pre_tasks/post_tasks.

Només queda que tot és semànticament clar: primer pre_tasksaleshores rolesaleshores post_tasks.

Però encara no hem respost la pregunta: on és la convocatòria del mòdul? foo escriure? Hem d'escriure un rol sencer per a cada mòdul? O és millor tenir un paper gruixut per a tot? I si no és un paper, aleshores on hauria d'escriure: en pre o post?

Si no hi ha una resposta raonada a aquestes preguntes, això és un signe de manca d'intuïció, és a dir, aquests mateixos "fonaments inestables". Anem a esbrinar-ho. En primer lloc, una pregunta de seguretat: si el joc té pre_tasks и post_tasks (i no hi ha tasques ni rols), llavors alguna cosa es pot trencar si faig la primera tasca post_tasks Ho traslladaré fins al final pre_tasks?

Per descomptat, la redacció de la pregunta indica que es trencarà. Però què exactament?

... Manipuladors. La lectura dels conceptes bàsics revela un fet important: tots els gestors s'esborren automàticament després de cada secció. Aquells. totes les tasques de pre_tasks, després tots els gestors que van ser notificats. A continuació, s'executen tots els rols i tots els controladors que s'han notificat als rols. Després post_tasks i els seus manipuladors.

Així, si arrossegueu una tasca des de post_tasks в pre_tasks, llavors potencialment l'executareu abans que s'executi el controlador. per exemple, si en pre_tasks el servidor web està instal·lat i configurat, i post_tasks s'hi envia alguna cosa i després transfereix aquesta tasca a la secció pre_tasks portarà al fet que en el moment d'"enviar" el servidor encara no s'executarà i tot es trencarà.

Ara pensem de nou, per què necessitem pre_tasks и post_tasks? Per exemple, per completar tot el necessari (inclosos els gestors) abans de complir la funció. A post_tasks ens permetrà treballar amb els resultats de l'execució de rols (inclosos els controladors).

Un astut expert d'Ansible ens dirà què és. meta: flush_handlers, però per què necessitem flush_handlers si podem confiar en l'ordre d'execució de les seccions en joc? A més, l'ús de meta: flush_handlers ens pot donar coses inesperades amb controladors duplicats, donant-nos avisos estranys quan s'utilitzen when у block etc. Com millor conegueu l'ansible, més matisos podreu anomenar una solució "difícil". I una solució senzilla -utilitzant una divisió natural entre pre/rols/post- no provoca matisos.

I tornem al nostre 'foo'. On l'he de posar? En pre, post o rols? Òbviament, això depèn de si necessitem els resultats del controlador per a foo. Si no hi són, aleshores no cal que el foo es col·loqui ni en pre ni en post (aquestes seccions tenen un significat especial) executant tasques abans i després del cos principal del codi.

Ara la resposta a la pregunta "rol o tasca" es redueix al que ja està en joc: si hi ha tasques, cal afegir-les a les tasques. Si hi ha rols, cal que creeu un rol (fins i tot a partir d'una tasca). Permeteu-me recordar-vos que les tasques i els rols no s'utilitzen al mateix temps.

Entendre els fonaments bàsics d'Ansible proporciona respostes raonables a preguntes aparentment de gust.

Tasques i rols (segona part)

Ara parlem de la situació quan esteu començant a escriure un llibre de jugades. Heu de fer foo, bar i baz. Són tres tasques, un rol o tres rols? Per resumir la pregunta: en quin moment hauries de començar a escriure rols? Quin sentit té escriure rols quan pots escriure tasques?... Què és un rol?

Un dels errors més grans (ja vaig parlar d'això) és pensar que un paper és com una funció a la biblioteca d'un programa. Com és una descripció de funció genèrica? Accepta arguments d'entrada, interacciona amb causes secundaries, fa efectes secundaris i retorna un valor.

Ara, atenció. Què es pot fer a partir d'això en el paper? Sempre podeu anomenar efectes secundaris, aquesta és l'essència de tot l'Ansible: crear efectes secundaris. Té causes secundaries? Elemental. Però amb "passar un valor i retornar-lo" - aquí és on no funciona. En primer lloc, no podeu passar un valor a un rol. Podeu establir una variable global amb una mida de joc de tota la vida a la secció vars per al rol. Podeu establir una variable global amb una vida útil en joc dins del paper. O fins i tot amb la vida útil dels llibres de jugades (set_fact/register). Però no podeu tenir "variables locals". No pots "agafar un valor" i "tornar-lo".

El més important es desprèn d'això: no podeu escriure alguna cosa a Ansible sense causar efectes secundaris. El canvi de variables globals és sempre un efecte secundari per a una funció. A Rust, per exemple, canviar una variable global és unsafe. I a Ansible és l'únic mètode per influir en els valors d'un rol. Tingueu en compte les paraules utilitzades: no "passar un valor al rol", sinó "canviar els valors que fa servir el rol". No hi ha aïllament entre rols. No hi ha aïllament entre tasques i rols.

Total: un rol no és una funció.

Què té de bo el paper? En primer lloc, el rol té valors per defecte (/default/main.yaml), en segon lloc, el rol té directoris addicionals per emmagatzemar fitxers.

Quins són els avantatges dels valors predeterminats? Com que a la piràmide de Maslow, la taula més aviat pervertida de prioritats variables d'Ansible, els valors per defecte són els de més baixa prioritat (menys els paràmetres de la línia d'ordres d'Ansible). Això vol dir que si necessiteu proporcionar valors predeterminats i no preocupar-vos que anul·lin els valors de l'inventari o de les variables de grup, els valors per defecte són l'únic lloc adequat per a vosaltres. (Estic mentint una mica, n'hi ha més |d(your_default_here), però si parlem de llocs estacionaris, només els rols per defecte).

Què més té de genial els papers? Perquè tenen catàlegs propis. Aquests són directoris per a variables, tant constants (és a dir, calculades per al rol) com dinàmiques (hi ha un patró o un anti-patró - include_vars amb {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Aquests són els directoris per a files/, templates/. A més, us permet tenir els vostres propis mòduls i connectors (library/). Però, en comparació amb les tasques d'un llibre de jugades (que també pot tenir tot això), l'únic avantatge aquí és que els fitxers no s'aboquen a una pila, sinó a diverses piles separades.

Un detall més: podeu provar de crear rols que estaran disponibles per a la seva reutilització (via galàxia). Amb l'arribada de les col·leccions, la distribució de rols es pot considerar gairebé oblidada.

Així, els rols tenen dues característiques importants: tenen valors per defecte (una característica única) i et permeten estructurar el teu codi.

Tornant a la pregunta original: quan fer tasques i quan fer rols? Les tasques d'un llibre de jocs s'utilitzen més sovint com a "cola" abans/després de rols o com a element de construcció independent (aleshores no hi hauria d'haver cap rol al codi). Un munt de tasques normals barrejades amb rols és un descuido inequívoc. Hauríeu d'adherir-vos a un estil específic, ja sigui una tasca o un rol. Els rols proporcionen separació d'entitats i valors predeterminats, les tasques us permeten llegir el codi més ràpidament. Normalment, es posa codi més "estacionari" (important i complex) en rols i els scripts auxiliars s'escriuen amb un estil de tasca.

És possible fer import_role com a tasca, però si escriviu això, estigueu preparats per explicar al vostre propi sentit de la bellesa per què voleu fer-ho.

Un lector astut pot dir que els rols poden importar rols, els rols poden tenir dependències a través de galaxy.yml, i també hi ha una terrible i terrible include_role — Us recordo que estem millorant habilitats a l'Ansible bàsic, i no a la gimnàstica artística.

Manipuladors i tasques

Parlem d'una altra cosa òbvia: els manipuladors. Saber utilitzar-los correctament és gairebé un art. Quina diferència hi ha entre un manipulador i un arrossegament?

Com que recordem els fonaments bàsics, aquí teniu un exemple:

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

Els controladors del rol es troben a rolename/handlers/main.yaml. Els gestors rebusquen entre tots els participants de la jugada: pre/post_tasks poden treure gestors de rols, i un rol pot treure gestors de l'obra. Tanmateix, les trucades de "rol creuat" als controladors causen molt més wtf que repetir un controlador trivial. (Un altre element de les millors pràctiques és intentar no repetir els noms dels controladors).

La diferència principal és que la tasca sempre s'executa (idempotentment) (etiquetes més/menys i when), i el controlador - per canvi d'estat (notifiqui els incendis només si s'ha canviat). Què vol dir això? Per exemple, el fet que quan reinicieu, si no hi ha hagut cap canvi, no hi haurà gestor. Per què pot ser que necessitem executar el controlador quan no hi ha hagut cap canvi en la tasca de generació? Per exemple, perquè alguna cosa es va trencar i va canviar, però l'execució no va arribar al gestor. Per exemple, perquè la xarxa estava temporalment inactiva. La configuració ha canviat, el servei no s'ha reiniciat. La propera vegada que l'inicieu, la configuració ja no canvia i el servei es manté amb la versió antiga de la configuració.

La situació amb la configuració no es pot resoldre (més precisament, podeu inventar vosaltres mateixos un protocol de reinici especial amb senyals de fitxer, etc., però això ja no és "ansible bàsic" en cap forma). Però hi ha una altra història comuna: vam instal·lar l'aplicació, la vam gravar .service-fitxer, i ara el volem daemon_reload и state=started. I el lloc natural per a això sembla ser el manejador. Però si no el converteixes en un controlador sinó en una tasca al final d'una llista de tasques o d'una funció, s'executarà de manera idempotent cada vegada. Fins i tot si el llibre de jugades es trencava pel mig. Això no soluciona gens el problema reiniciat (no podeu fer una tasca amb l'atribut reiniciat, perquè es perd la idempotència), però definitivament val la pena fer state=started, l'estabilitat global dels llibres de jugades augmenta, perquè el nombre de connexions i l'estat dinàmic disminueix.

Una altra propietat positiva del controlador és que no obstrueix la sortida. No hi va haver canvis - no hi ha cap salt addicional o bé a la sortida - més fàcil de llegir. També és una propietat negativa: si trobeu un error ortogràfic en una tasca executada linealment a la primera execució, els controladors només s'executaran quan es modifiquin, és a dir. en algunes condicions, molt poques vegades. Per exemple, per primera vegada a la meva vida cinc anys després. I, per descomptat, hi haurà una errada al nom i tot es trencarà. I si no els executeu la segona vegada, no hi ha cap canvi.

Per separat, hem de parlar de la disponibilitat de variables. Per exemple, si notifiqueu una tasca amb un bucle, què hi haurà a les variables? Podeu endevinar analíticament, però no sempre és trivial, sobretot si les variables provenen de llocs diferents.

... Per tant, els manipuladors són molt menys útils i molt més problemàtics del que semblen. Si podeu escriure alguna cosa bellament (sense floritures) sense manipuladors, és millor fer-ho sense ells. Si no funciona bé, és millor amb ells.

El lector corrosiu assenyala amb raó que no hem parlat listenque un gestor pot trucar a notificar per a un altre gestor, que un gestor pot incloure import_tasks (que pot fer include_role amb with_items), que el sistema de gestor d'Ansible és Turing-complete, que els gestors d'include_role s'entrecreuen d'una manera curiosa amb els gestors del joc, etc. .d. - Tot això clarament no és el "bàsic").

Tot i que hi ha un WTF específic que en realitat és una característica que cal tenir en compte. Si la vostra tasca s'executa amb delegate_to i ha notificat, llavors s'executa el controlador corresponent sense delegate_to, és a dir a l'amfitrió on s'assigna el joc. (Tot i que el gestor, per descomptat, pot tenir delegate_to Mateix).

Per separat, vull dir algunes paraules sobre els rols reutilitzables. Abans que apareguessin les col·leccions, hi havia la idea que podríeu fer papers universals que poguessin ser ansible-galaxy install i va anar. Funciona en tots els sistemes operatius de totes les variants en totes les situacions. Per tant, la meva opinió: no funciona. Qualsevol paper amb massa include_vars, que admet 100500 casos, està condemnat a l'abisme d'errors de casos de cantonada. Es poden cobrir amb proves massives, però com amb qualsevol prova, o bé teniu un producte cartesià de valors d'entrada i una funció total, o bé teniu "escenaris individuals coberts". La meva opinió és que és molt millor si el paper és lineal (complexitat ciclomàtica 1).

Com menys si (explícits o declaratius - en el formulari when o forma include_vars per conjunt de variables), millor serà el paper. A vegades cal fer branques, però, repeteixo, com menys n'hi hagi, millor. Així que sembla un bon paper amb galàxia (funciona!) amb un munt de when pot ser menys preferible que el "propi" paper de cinc tasques. El moment en què el paper amb la galàxia és millor és quan comences a escriure alguna cosa. El moment en què empitjora és quan alguna cosa es trenca i tens la sospita que és pel "paper amb la galàxia". L'obres i hi ha cinc inclusions, vuit fulls de tasques i una pila when'ov... I hem d'esbrinar això. En lloc de 5 tasques, una llista lineal en la qual no hi ha res a trencar.

En les parts següents

  • Una mica sobre l'inventari, les variables de grup, el connector host_group_vars, hostvars. Com fer un nus gordià amb espaguetis. Variables d'abast i precedència, model de memòria Ansible. "Llavors, on emmagatzemem el nom d'usuari de la base de dades?"
  • jinja: {{ jinja }} — plastilina suau nosql notype nosense. Està a tot arreu, fins i tot on no t'ho esperes. Una mica sobre !!unsafe i deliciós yaml.

Font: www.habr.com

Afegeix comentari