Едноставни основи, без кои вашите книги за играње ќе бидат грутка леплива тестенина

Правам многу прегледи на туѓиот Ansible код и многу пишувам самиот. Во текот на анализирањето на грешките (и туѓи и мои), како и голем број интервјуа, ја сфатив главната грешка што ја прават корисниците на Ansible - навлегуваат во сложени работи без да ги совладаат основните.

За да ја исправам оваа универзална неправда, решив да напишам вовед во Ansible за оние кои веќе го знаат. Ве предупредувам, ова не е прераскажување на Ман, ова е долгогодишно со многу букви и без слики.

Очекуваното ниво на читателот е дека веќе се напишани неколку илјади линии јамла, нешто е веќе во производство, но „некако сè е криво“.

Имиња

Главната грешка што ја прави корисникот на Ansible е тоа што не знае како се вика нешто. Ако не ги знаете имињата, не можете да разберете што пишува во документацијата. Жив пример: за време на едно интервју, лице кое се чинеше дека рече дека напишал многу во Ansible не можеше да одговори на прашањето „од кои елементи се состои книгата за игри? И кога сугерирав дека „одговорот се очекуваше дека играчката се состои од игра“, следеше проклетниот коментар „ние не го користиме тоа“. Луѓето пишуваат Ansible за пари и не користат игра. Тие всушност го користат, но не знаат што е тоа.

Значи, да почнеме со нешто едноставно: како се вика. Можеби го знаете ова, а можеби и не, затоа што не сте обрнале внимание кога ја читавте документацијата.

ansible-playbook ја извршува Playbook. Playbook е датотека со екстензија yml/yaml, во која има нешто вакво:

---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Веќе сфативме дека целата оваа датотека е книга за игри. Можеме да покажеме каде се улогите и каде се задачите. Но, каде е играта? А која е разликата помеѓу игра и улога или играна?

Сето тоа е во документацијата. И им недостасува. Почетници - затоа што има премногу и нема да запомните сè одеднаш. Искусни - затоа што „тривијални работи“. Ако сте искусни, повторно читајте ги овие страници барем еднаш на секои шест месеци и вашиот код ќе стане водечки во класата.

Значи, запомнете: Playbook е листа што се состои од игра и import_playbook.
Ова е една претстава:

- hosts: group1
  roles:
    - role1

и ова е уште една претстава:

- hosts: group2,group3
  tasks:
    - debug:

Што е игра? Зошто е таа?

Играта е клучен елемент за игротека, бидејќи играта и само игра поврзува список на улоги и/или задачи со список на домаќини на кои тие мора да се извршат. Во длабоките длабочини на документацијата можете да најдете спомнување на delegate_to, приклучоци за локално пребарување, поставки специфични за мрежата, хостови за скокови итн. Тие ви дозволуваат малку да го промените местото каде што се извршуваат задачите. Но, заборави на тоа. Секоја од овие паметни опции има многу специфични намени и дефинитивно не се универзални. А ние зборуваме за основни работи кои секој треба да ги знае и користи.

Ако сакате да изведете „нешто“ „некаде“, пишувате игра. Не улога. Не е улога со модули и делегати. Земи го и пишува драма. Во кое, во полето за домаќини наведувате каде да се изврши, а во улоги/задачи - што да се изврши.

Едноставно, нели? Како би можело да биде поинаку?

Еден од карактеристичните моменти кога луѓето имаат желба да го прават тоа не преку игра е „улогата што поставува сè“. Би сакал да имам улога која ги конфигурира и серверите од првиот тип и серверите од вториот тип.

Архетипски пример е следењето. Би сакал да имам улога на мониторинг која ќе го конфигурира мониторингот. Улогата на мониторинг е доделена на мониторинг домаќините (според игра). Но, излегува дека за мониторинг треба да доставиме пакети до домаќините што ги следиме. Зошто да не користите делегат? Исто така, треба да ги конфигурирате iptables. делегат? Исто така, треба да напишете/корегирате конфигурација за DBMS за да овозможите следење. делегат! А ако недостига креативност, тогаш можете да направите делегација include_role во вгнездена јамка користејќи незгоден филтер на список на групи и внатре include_role можеш повеќе delegate_to повторно. И си одиме...

Добрата желба - да имаме една единствена улога на набљудување, која „прави сè“ - нè води во целосен пекол од кој најчесто има само еден излез: да се преработи сè од нула.

Каде се случи грешката овде? Во моментот кога откривте дека за да ја извршите задачата „x“ на домаќинот X треба да отидете кај домаќинот Y и да направите „y“ таму, требаше да направите едноставна вежба: одете и напишете игра, која кај домаќинот Y прави y. Не додавајте нешто на „x“, туку напишете го од нула. Дури и со хардкодирани променливи.

Се чини дека сè во параграфите погоре е кажано правилно. Но, ова не е ваш случај! Затоа што сакате да напишете код за повеќекратна употреба кој е СУВ и налик на библиотека, и треба да барате метод како да го направите тоа.

Тука демне уште една сериозна грешка. Грешка што претвори многу проекти од толерантно напишани (може да биде подобро, но сè функционира и лесно се завршува) во целосен хорор што ни авторот не може да го сфати. Работи, но не дај Боже да промениш нешто.

Грешката е: улогата е библиотечна функција. Оваа аналогија уништи толку многу добри почетоци што едноставно е тажно да се гледа. Улогата не е библиотечна функција. Таа не може да прави калкулации и не може да носи одлуки на ниво на игра. Потсети ме какви одлуки носи играта?

Фала, во право си. Play донесува одлука (поточно, содржи информации) за тоа кои задачи и улоги да ги извршува на кои домаќини.

Ако ја делегираш оваа одлука на улога, па дури и со калкулации, се осудуваш себеси (и тој што ќе се обиде да ти го анализира кодот) на мизерно постоење. Улогата не одлучува каде се изведува. Оваа одлука се носи со игра. Улогата го прави она што и е кажано, онаму каде што е кажано.

Зошто е опасно програмирањето во Ansible и зошто COBOL е подобар од Ansible ќе зборуваме во поглавјето за променливи и џинџа. Засега, да кажеме едно - секоја ваша пресметка остава зад себе неизбришлива трага на промени во глобалните променливи, а вие не можете да направите ништо за тоа. Штом се вкрстија двете „траги“, сè исчезна.

Забелешка за сквернавите: улогата секако може да влијае на контролниот тек. Јадете delegate_to и има разумна употреба. Јадете meta: end host/play. Но! Запомнете дека ги учиме основите? Заборавив за delegate_to. Зборуваме за наједноставниот и најубавиот Ansible код. Кој е лесен за читање, лесен за пишување, лесен за отстранување грешки, лесен за тестирање и лесен за комплетирање. Значи, уште еднаш:

игра и само игра одлучува за кои домаќини што ќе се изврши.

Во овој дел се занимававме со спротивставеноста помеѓу играта и улогата. Сега ајде да зборуваме за задачите наспроти односот на улогата.

Задачи и улоги

Размислете за играње:

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

Да речеме дека треба да правиш будала. И изгледа како foo: name=foobar state=present. Каде да го напишам ова? во пред? пост? Создаде улога?

...А каде отидоа задачите?

Повторно започнуваме со основите - уредот за играње. Ако лебдите по ова прашање, не можете да ја користите играта како основа за се останато, а резултатот ќе ви биде „треслив“.

Уред за репродукција: директива за домаќини, поставки за самата игра и пред_задачи, задачи, улоги, секции за пост_задачи. Сега не ни се важни преостанатите параметри за игра.

Редоследот на нивните делови со задачи и улоги: pre_tasks, roles, tasks, post_tasks. Бидејќи семантички редоследот на извршување е помеѓу tasks и roles не е јасно, тогаш најдобрите практики велат дека додаваме дел tasks, само ако не roles... Ако има roles, потоа сите приложени задачи се сместени во делови pre_tasks/post_tasks.

Останува само сè да е семантички јасно: прво pre_tasksтогаш rolesтогаш post_tasks.

Но, сè уште не одговоривме на прашањето: каде е повикот на модулот? foo пишувам? Дали треба да напишеме цела улога за секој модул? Или е подобро да имаш дебела улога за се? И ако не улога, тогаш каде да пишувам - пред или пост?

Ако нема образложен одговор на овие прашања, тогаш ова е знак на недостаток на интуиција, односно истите тие „нетресени темели“. Ајде да го сфатиме. Прво, безбедносно прашање: Ако игра има pre_tasks и post_tasks (и нема задачи или улоги), тогаш може ли нешто да пукне ако ја изведам првата задача од post_tasks Ќе го преместам до крај pre_tasks?

Се разбира, формулацијата на прашањето навестува дека ќе се скрши. Но, што точно?

... Ракувачи. Читањето на основите открива важен факт: сите ракувачи автоматски се исплакнуваат по секој дел. Оние. сите задачи од pre_tasks, потоа сите ракувачи кои беа известени. Потоа се извршуваат сите улоги и сите управувачи кои биле известени во улогите. По post_tasks и нивните ракувачи.

Така, ако повлечете задача од post_tasks в pre_tasks, тогаш потенцијално ќе го извршите пред да се изврши управувачот. на пример, ако во pre_tasks веб-серверот е инсталиран и конфигуриран, и post_tasks нешто се испраќа до него, а потоа префрлете ја оваа задача во делот pre_tasks ќе доведе до фактот дека во моментот на „испраќање“ серверот сè уште нема да работи и сè ќе се скрши.

Сега да размислиме повторно, зошто ни треба pre_tasks и post_tasks? На пример, за да завршите сè што е потребно (вклучувајќи ги и ракувачите) пред да ја исполните улогата. А post_tasks ќе ни овозможи да работиме со резултатите од извршувањето на улогите (вклучувајќи ги и управувачите).

Добриот експерт Ansible ќе ни каже што е тоа. meta: flush_handlers, но зошто ни се потребни flush_handlers ако можеме да се потпреме на редоследот на извршување на деловите во игра? Покрај тоа, употребата на мета: flush_handlers може да ни даде неочекувани работи со дупликат ракувачи, давајќи ни чудни предупредувања кога се користи when у block итн. Колку подобро го знаете возбудливото, толку повеќе нијанси можете да именувате за „незгодно“ решение. И едноставно решение - користење на природна поделба помеѓу пред/улоги/пост - не предизвикува нијанси.

И, да се вратиме на нашето „фу“. Каде да го ставам? Во пред, пост или улоги? Очигледно, ова зависи од тоа дали ни требаат резултатите од управувачот за foo. Ако ги нема, тогаш foo не треба да се става ниту во пред, ниту во пост - овие делови имаат посебно значење - извршување задачи пред и по главното тело на код.

Сега одговорот на прашањето „улога или задача“ се сведува на она што е веќе во игра - ако има задачи таму, тогаш треба да ги додадете во задачите. Ако има улоги, треба да креирате улога (дури и од една задача). Дозволете ми да ве потсетам дека задачите и улогите не се користат истовремено.

Разбирањето на основите на Ansible дава разумни одговори на навидум прашања за вкус.

Задачи и улоги (втор дел)

Сега да разговараме за ситуацијата кога штотуку почнувате да пишувате книга за игри. Треба да направите фу, бар и баз. Дали се овие три задачи, една улога или три улоги? Да го резимираме прашањето: во кој момент треба да започнете да пишувате улоги? Која е поентата да пишувате улоги кога можете да пишувате задачи?... Што е улога?

Една од најголемите грешки (веќе зборував за ова) е да се мисли дека улогата е како функција во библиотеката на програмата. Како изгледа описот на генеричката функција? Прифаќа влезни аргументи, комуницира со несакани причини, прави несакани ефекти и враќа вредност.

Сега, внимание. Што може да се направи од ова во улогата? Секогаш сте добредојдени да повикате несакани ефекти, ова е суштината на целиот Ansible - да создадете несакани ефекти. Дали има несакани причини? Основно. Но, со „подадете вредност и вратете ја“ - тоа е местото каде што не функционира. Прво, не можете да пренесете вредност на улогата. Може да поставите глобална променлива со доживотна големина на игра во делот vars за улогата. Можете да поставите глобална променлива со доживотна игра во улогата. Или дури и со животниот век на книгите за играње (set_fact/register). Но, не можете да имате „локални променливи“. Не можете да „земете вредност“ и „да ја вратите“.

Главната работа произлегува од ова: не можете да напишете нешто во Ansible без да предизвикате несакани ефекти. Промената на глобалните променливи е секогаш несакан ефект за некоја функција. Во Rust, на пример, промената на глобалната променлива е unsafe. И во Ansible тоа е единствениот метод за влијание врз вредностите за некоја улога. Забележете ги употребените зборови: не „пренесете вредност на улогата“, туку „променете ги вредностите што ги користи улогата“. Не постои изолација помеѓу улогите. Не постои изолација помеѓу задачите и улогите.

Вкупно: улогата не е функција.

Што е добро во улогата? Прво, улогата има стандардни вредности (/default/main.yaml), второ, улогата има дополнителни директориуми за складирање датотеки.

Кои се придобивките од стандардните вредности? Бидејќи во пирамидата на Маслоу, прилично искривената табела на променливи приоритети на Ансибл, стандардните улоги се со најнизок приоритет (минус параметрите на командната линија Ansible). Ова значи дека ако треба да наведете стандардни вредности и да не се грижите дека тие ги надминуваат вредностите од залихите или групните променливи, тогаш стандардните улоги се единственото вистинско место за вас. (Малку лажам - ги има повеќе |d(your_default_here), но ако зборуваме за неподвижни места, тогаш само стандардните улоги).

Што друго е одлично за улогите? Затоа што имаат свои каталози. Ова се директориуми за променливи, и константни (т.е. пресметани за улогата) и динамични (постои или шема или анти-шаблон - include_vars заедно со {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Ова се директориумите за files/, templates/. Исто така, ви овозможува да имате свои модули и приклучоци (library/). Но, во споредба со задачите во Playbook (која исто така може да го има сето ова), единствената придобивка овде е што датотеките не се фрлаат во еден куп, туку неколку одделни купови.

Уште еден детал: може да се обидете да креирате улоги кои ќе бидат достапни за повторна употреба (преку галаксија). Со доаѓањето на колекциите, распределбата на улогите може да се смета за речиси заборавена.

Така, улогите имаат две важни карактеристики: имаат стандардни поставки (уникатна карактеристика) и ви дозволуваат да го структурирате вашиот код.

Враќање на првобитното прашање: кога да се прават задачи и кога да се прават улоги? Задачите во игротеката најчесто се користат или како „лепак“ пред/по улоги или како независен елемент за градење (тогаш не треба да има улоги во кодот). Куп нормални задачи измешани со улоги е недвосмислена невештина. Треба да се придржувате до одреден стил - или задача или улога. Улогите обезбедуваат одвојување на ентитетите и стандардните, задачите ви овозможуваат побрзо да го читате кодот. Обично, повеќе „стационарни“ (важни и сложени) кодови се ставаат во улогите, а помошните скрипти се пишуваат во стил на задача.

Можно е да се изврши import_role како задача, но ако го напишете ова, тогаш подгответе се да му објасните на сопственото чувство за убавина зошто сакате да го направите ова.

Умешен читател може да каже дека улогите можат да увезуваат улоги, улогите може да имаат зависност преку galaxy.yml, а има и ужасно и страшно include_role — Потсетувам дека ги усовршуваме вештините во основната Ansible, а не во фигурата гимнастика.

Ракувачи и задачи

Ајде да разговараме за друга очигледна работа: ракувачи. Да знаете како правилно да ги користите е речиси уметност. Која е разликата помеѓу управувачот и влечењето?

Бидејќи се сеќаваме на основите, еве еден пример:

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

Ракувачите на улогите се наоѓаат во rolename/handlers/main.yaml. Ракувачите пребаруваат помеѓу сите учесници во играта: пред/пост_задачите може да ги повлечат ракувачите со улоги, а улогата може да ги повлече ракувачите од играта. Сепак, повиците со „вкрстени улоги“ до управувачите предизвикуваат многу повеќе wtf отколку повторувањето на тривијален управувач. (Друг елемент на најдобрите практики е да се обидете да не ги повторувате имињата на управувачите).

Главната разлика е во тоа што задачата секогаш се извршува (идемпотентно) (ознаки плус/минус и when), а управувачот - со промена на состојбата (известете го пожарот само ако е променет). Што значи тоа? На пример, фактот дека кога ќе се рестартира, ако немало променето, тогаш нема да има управувач. Зошто можеби треба да извршиме управувач кога немаше промена во генерирачката задача? На пример, затоа што нешто се скрши и се смени, но извршувањето не стигна до управувачот. На пример, затоа што мрежата беше привремено исклучена. Конфигурацијата е променета, услугата не е рестартирана. Следниот пат кога ќе го стартувате, конфигурацијата повеќе не се менува, а услугата останува со старата верзија на конфигурацијата.

Ситуацијата со конфигурацијата не може да се реши (поточно, можете да измислите специјален протокол за рестартирање за себе со знаменце на датотеки итн., но ова веќе не е „основно ансибилно“ во која било форма). Но, постои уште една заедничка приказна: ја инсталиравме апликацијата, ја снимивме .service-датотека, и сега го сакаме daemon_reload и state=started. И природното место за ова се чини дека е управувачот. Но, ако го направите не управувач, туку задача на крајот од списокот со задачи или улогата, тогаш таа ќе се извршува идемпотентно секој пат. Дури и да се скрши игротеката на средина. Ова воопшто не го решава рестартираниот проблем (не можете да направите задача со рестартираниот атрибут, бидејќи идемпотенцијата е изгубена), но дефинитивно вреди да се направи state=started, севкупната стабилност на Playbooks се зголемува, бидејќи се намалува бројот на врски и динамичката состојба.

Друго позитивно својство на управувачот е тоа што не го затнува излезот. Немаше промени - нема дополнителни прескокнати или во ред на излезот - полесни за читање. Тоа е, исто така, негативно својство - ако најдете печатна грешка во линеарно извршената задача уште при првото извршување, тогаш управувачите ќе бидат извршени само кога ќе се сменат, т.е. под некои услови - многу ретко. На пример, за прв пат во мојот живот пет години подоцна. И, секако, ќе има печатна грешка во името и се ќе пукне. И ако не ги активирате по втор пат, нема промена.

Одделно, треба да зборуваме за достапноста на променливите. На пример, ако известите задача со циклус, што ќе има во променливите? Можете да погодите аналитички, но тоа не е секогаш тривијално, особено ако променливите доаѓаат од различни места.

... Значи, ракувачите се многу помалку корисни и многу попроблематични отколку што изгледаат. Ако можете да напишете нешто убаво (без важничене) без ракувачи, подобро е да го направите без нив. Ако не функционира убаво, подобро е со нив.

Корозивниот читател со право истакнува дека не сме разговарале listenдека управувачот може да повика известување за друг управувач, дека управувачот може да вклучи import_tasks (кои може да вклучуваат_роле со with_items), дека системот за ракување во Ansible е Turing-complete, дека ракувачите од include_role на чуден начин се вкрстуваат со ракувачи од игра, итн. .d. - сето ова очигледно не е „основните“).

Иако постои еден специфичен WTF што всушност е карактеристика што треба да ја имате на ум. Ако вашата задача е извршена со delegate_to и има известување, потоа соодветниот управувач се извршува без delegate_to, т.е. на домаќинот каде што е доделена игра. (Иако управувачот, се разбира, може да има delegate_to Исто).

Одделно, сакам да кажам неколку зборови за улогите за повеќекратна употреба. Пред да се појават колекциите, постоеше идеја дека можете да направите универзални улоги што би можеле да бидат ansible-galaxy install и отиде. Работи на сите ОС од сите варијанти во сите ситуации. Значи, мое мислење: не функционира. Секоја улога со маса include_vars, поддржувајќи 100500 случаи, е осуден на бездна од бубачки од аголни куќишта. Тие можат да бидат покриени со масовно тестирање, но како и со секое тестирање, или имате Декартов производ на влезни вредности и вкупна функција, или имате „покриени индивидуални сценарија“. Мое мислење е дека е многу подобро улогата да е линеарна (цикломатска сложеност 1).

Колку помалку ако (експлицитни или декларативни - во форма when или форма include_vars според збир на променливи), толку е подобра улогата. Понекогаш треба да направите гранки, но, повторувам, колку помалку има, толку подобро. Значи, изгледа како добра улога со галаксијата (функционира!) со еден куп when може да биде помалку претпочитана од „сопствената“ улога од пет задачи. Моментот кога улогата со галаксија е подобра е кога ќе почнете да пишувате нешто. Моментот кога станува полошо е кога нешто ќе се скрши и ќе се посомневате дека тоа е поради „улогата со галаксијата“. Го отворате и има пет подмножества, осум листови со задачи и стек when„Ов... И ние треба да го сфатиме ова. Наместо 5 задачи, линеарна листа во која нема што да се скрши.

Во следните делови

  • Малку за инвентар, групни променливи, приклучок host_group_vars, hostvars. Како да врзете Гордиев јазол со шпагети. Опсег и предност променливи, Ansible мемориски модел. „Значи, каде го складираме корисничкото име за базата на податоци?
  • jinja: {{ jinja }} — nosql notype nosense мека пластелин. Го има насекаде, дури и таму каде што не го очекуваш. Малку за !!unsafe и вкусен јамл.

Извор: www.habr.com

Додадете коментар