Šta je Ansible, kako radi i kako radi za mene?

“Keep it simple, stupid” (KISS) je lajtmotiv koji su tvorci Ansible-a, alata za orkestraciju i konfigurisanje servera, imali u glavi od samog začeća ideje o ovom alatu. Da kao Đorđe Balašević ne bismo bili luzeri, počećemo od početka.

Ivica Kolenkaš - 8. Jun, 2016.

Upravljanje konfiguracijom (eng: configuration management) se prema Wikipediji odnosi na kreiranje i održavanje zapisa o svim komponentama infrastrukture. To uključuje detalje konfiguracije, dokumentaciju i druge informacije. U pogledu softvera pre svega se misli na verzije, patcheve i drugo, a u pogledu hardvera na lokaciju servera i njegovu namenu. Servere i softver na njima je moguće konfigurisati ručno i automatski, a osnovne prednosti potonjeg su veća agilnost i brži oporavak nakon neželjenih događaja.

Koliko puta se desilo da je kolega prešao u drugu firmu, a njegov javni ključ je ostao na serverima? Koliko puta se desilo da ostanem duže na poslu jer softver koji sam juče podesio na baš ovaj način (majke mi) iz nekog razloga danas ne radi. Ansible je besplatan i open source alat za konfigurisanje jednog ili hiljadu servera. Ono što Ansible-u daje prednost u odnosu na ručno konfigurisanje su:

Konfigurisanje servera je prilično širok pojam; uključuje upravljanje korisnicima, softverom, servisima, firewallom (upravljač vatrenog zida je vrlo bitna komponenta operativnog sistema) itd. Ti serveri mogu biti fizički ili virtuelni, može biti lokalna ili virtuelna mašina u cloudu, na njoj može da bude Linux ili Windows, može biti jedan ili jedna hiljada – Ansible ne pravi pitanje. Ukoliko može da komunicira sa serverom, može i da ga konfiguriše.

Ansible naravno nije prvi niti jedini alat za konfigurisanje (configuration management). Konkurenti su Puppet, Chef, Salt i naravno shell skripte. Ono što Ansible izdvaja je:

Inventar

Ansible za komunikaciju sa Linux serverima koristi SSH, a za komunikaciju sa Windows serverima WinRM protokol. Kako bi znao sa kojim serverima će uspostaviti komunikaciju, potrebno je definisati inventarni fajl koji može imati bilo koje ime, ali po nekoj konvenciji inventarni fajl ima naziv ‘hosts’. Ovo ne treba mešati sa /etc/hosts fajlom. Inventory fajl ima strukturu kao klasičan .ini fajl, a primer jednog možemo videti ispod:

loadbalancer

[webservers]
web1.ans
web2.ans

Primer iznad prikazuje jednostavan inventory fajl sa tri servera od kojih su dva u grupi “webservers”. U inventaru mogu biti definisani samostalni serveri, možemo ih grupisati, a grupe mogu da sadrže druge grupe. Nazivi grupa se nalaze u uglastim zagradama. Na primeru ispod vidimo grupe loadbalancers, webservers i all_servers koja sadrži dve prethodno definisane grupe.

[loadbalancers]
loadbalancer1
loadbalancer2

[webservers]
web1.ans
web2.ans

[all_servers:children]
loadbalancers
webservers

Inventar je moguće generisati i dinamički. Ovo je izuzetno korisno ako se radi o virtuelnim serverima na nekom od public cloud servisa kao što su Amazon Web Services (AWS), DigitalOcean, Azure itd. Dinamički inventar predstavlja program napisan u bilo kom jeziku koji kao svoj izlaz ispisuje validan JSON koji mora imati specifičnu strukturu koju Ansible očekuje. Dinamički inventar prevazilazi okvire ovog članka, a više o ovoj temi možete pronaći na stranici dinamički inventar zvanične Ansible dokumentacije.

Moduli

Do sada smo videli kako se Ansible povezuje sa serverima i kako radi. Ono šta Ansible radi je određeno modulima. Moduli su komponente koje dolaze uz Ansible i koje sadrže funkcionalan kod. Postoje dve grupe modula, a to su osnovni i dodatni moduli. Razlika među njima je minimalna; osnovni (eng: core) moduli dolaze uz Ansible i na njihovom razvoju učestvuju zaposleni u kompanijama Ansible Inc. i RedHat. Dodatni moduli (eng: extras) se relativno lako instaliraju koristeći paket-menadžer Linux distribucije na kojoj je Ansible instaliran ili direktno sa GitHuba. Ansible je open source program i u njegovom razvoju mogu da učestvuju svi, a isto važi i za module. Za očekivati je da zaposleni u kompanijama zaduženi za održavanje Ansible-a imaju vodeću reč po pitanju koje izmene će uvažiti a koje ne, no to je tema za neko od narednih okupljanja Ansible zajednice Novog Sada.

U osnovne module spadaju copy, user, npm, ping, setup, cron, hostname i mnogi drugi. Po samom imenu modula je moguće zaključiti koju funkciju obavlja. Detaljnije možete videti u tabeli:

Screen Shot 2016-06-06 at 14.55.38

Modulima je moguće proslediti argumente koji bliže određuju šta će modul da radi; npr. copy modul prihvata argumente src i dest koji određuju putanju izvornog fajla i udaljenu putanju gde će fajl biti kopiran. Izvršavanje ovog modula bi izgledalo ovako:Listu svih dostupnih modula možete pronaći u zvaničnoj Ansible dokumentaciji.

ansible -m copy -a “src=moj_sajt.zip dest=/var/www/html”

Prikazana naredba poziva copy modul sa dva obavezna argumenta, a akcija koju će taj modul izvršiti jeste kopiranje fajla moj_sajt.zip iz trenutnog direktorijuma u direktorijum /var/www/html na konfigurisanom serveru. Naredba iznad nije preterano udobna za upotrebu. Na svu sreću Ansible omogućava organizovanje modula u veće celine.

Taskovi

Videli smo da je module moguće izvršavati direktno sa komandne linije, međutim to nije čest slučaj. Taskovi su atomične akcije koje imaju svoj početak, trajanje i kraj. Svaki task je određen nazivom i modulom koji poziva:

- name: install mysql
yum: name=mysql state=installed

Anatomija prikazanog taska je prilično prosta. Naziv mu je “install mysql”, koristi yum modul sa dva osnovna parametra; name parametar određuje ime paketa čije stanje treba da bude installed. Ovde dolazimo do vrlo bitno Ansible koncepta; Ansible u taskovima ne očekuje naredbe ili funkcije koje nešto rade – Ansible taskovi služe da opišu stanje u kom želimo da se sistem nalazi. Ukoliko je paket mysql instaliran Ansible ga neće instalirati ponovo. Istom analogijom, ako imamo task koji kreira lokalni korisnički nalog Ansible će prvo proveriti da li takav nalog postoji, i kreiraće ga samo ako ne postoji. Ovo znači da je potpuno ispravno pokrenuti jedan task više puta jer na konfigurisanom serveru ništa neće biti izmenjeno ukoliko je trenutno stanje sistema isto kao opisano stanje u tasku. Ovo je dijametralno suprotno od pristupa sa shell skriptama gde provera ne postoji nego moramo da je napišemo ručno.

Taskovi mogu da koriste samo jedan modul; ako želim da instaliram MySQL i startujem mysqld servis onda su to dva odvojena taska. Pomenuti yum modul, kao i njegov Debian/Ubuntu “kolega” apt mogu biti upotrebljeni na sledeći način:

- name: install common packages
yum: name={{ item }} state=present
with_items:
   - mysql
   - httpd
   - php
   - phpmyadmin

U navedenom tasku je prikazan with_items konstrukt sa elementima mysql, httpd, php i phpmyadmin. Ansible će interno od ovih elemenata da kreira niz koji će yum modulu biti prosleđen kao jedan argument. Ovim je omogućeno da se yum naredba na RHEL/CentOS ili apt-get naredba na Debian/Ubuntu sistemima pokrene samo jednom sa svim potrebnim argumentima, čime izbegavamo osvežavanje liste paketa za svaki paket koji instaliramo. Prikazan task je ekvivalentan naredbi:

yum -y install mysql httpd php phpmyadmin

Ansible vs. shell

Pre nekoliko godina sam sa kolegama radio na jednom freelance projektu koji se sastojao od Python web API-ja i MongoDB-a za skladištenje nestruktuiranih podataka zbog odlične skalabilnosti. Jedan od mojih zadataka je bio deployment i upgrade aplikacije na serverima koje je klijent obezbedio. U to vreme je izašla 2.6 verzija MongoDB-a, a mi smo do tada koristili 2.4. Nije bilo razloga da i dalje koristimo stariju verziju pa smo odlučili da izvršimo upgrade cele aplikacije. To sam mogao da uradim ručno ili kroz shell skripte, i naravno odabrao sam shell. Unapred se izvinjavam BASH majstorima:

which=`which mongod 2>&1 >/dev/null`
   if [ $? -eq 0 ]; then
     if [ "$INSTALLED_MONGO" == "$MONGO_VERSION" ]; then
       echo "Mongo Server version is current and up to date"
   fi
   if [ "$INSTALLED_MONGO" != "$MONGO_VERSION" ]; then
       remove_mongo_server
       install_mongo_server
   fi
   else
       install_mongo_server
   fi

Isečak koda prikazan iznad proverava da li je MongoDB paket instaliran i da li je odgovarajuće verzije. Ukoliko je instaliran i odgovarajuće verzije biće ispisana poruka, a u suprotnom će biti pozvane dodatne funkcije koje brišu postojeći MongoDB i instaliraju određenu, noviju verziju. Ekvivalent ovih akcija u Ansible-u je daleko kraći i čitljiviji

- name: install mongodb
yum: name=mongodb-server-2.6 state=installed

Playbook

Taskovi pomenuti u prethodnom pasusu sami za sebe ne znače mnogo. Kao i u narodnoj priči o sedam prutova, i u svetu konfigurisanja i automatizacije stvari funkcionišu mnogo bolje kada ih povežemo. Ansible nam omogućava da taskove organizujemo u playbookove. Playbook je dakle skup taskova koji opisuju stanje sistema, vrše njegovo konfigurisanje i orkestraciju. Skelet jednog playbooka izgleda ovako:    

- name: Common tasks
  hosts: webservers
  become: true
  tasks:
    - name: task 1
...
  handlers:
    - name: handler 1

U samom zaglavlju je ime playbooka za lakšu identifikaciju, nakon čega sledi naziv grupe servera ili pojedinačnog servera koji konfigurišemo a koji je definisan u inventarnom fajlu. Become linija u Ansible svetu označava da će sve akcije na konfigurisanom serveru koristiti sudo elevaciju prava. Nakon ovog uvoda sledi lista taskova, a posle njih su definisani svi handleri o kojima će biti priče u nekom od narednih pasusa. Ansible za playbookove koristi YAML sintaksu zbog svoje izuzetne čitljivosti i lakog parsiranja ili generisanja. Taskovi se izvršavaju jedan po jedan, onim redom kojim su definisani. Bitno je pomenuti da, ukoliko se playbook izvršava nad više konfigurisanih servera, taskovi će biti pokrenuti u paraleli nad svim konfigurisanim serverima. Naravno, desiće se da neki od taskova ne uspe da se izvrši usled greške. Dovoljno je ispraviti task ili grešku i ponovo pokrenuti playbook – Ansible neće menjati konfigurisani sistem ukoliko se stanje slaže sa stanjem opisanim u playbooku.

Handleri

Tokom konfigurisanja sistema javlja se potreba za uzročno-posledičnim vezama između akcija. Jednostavan primer je izmena konfiguracionog fajla nekog servisa i njegovo restartovanje kako bi se konfiguracija primenila. Ansible za ove potrebe koristi koncept handlera; handleri su taskovi kojima treba okidač kako bi bili pokrenuti. U pomenutom primeru izmene konfiguracije, okidač je task koji menja konfiguracioni fajl:

- name: change mysql max_connections
  copy: src=edited_my.cnf dest=/etc/my.cnf
  notify:
    - restart_mysql

Ako izuzmemo ključnu reč notify, kod prikazan iznad nije drugačiji od ranije prikazanih taskova. Notify je okidač pomenut u prethodnim rečenicama koji označava da će ovaj task nakon svog izvršenja pozvati handler koji se zove restart_mysql. Handleri su u osnovi taskovi sa nekoliko razlika u odnosu na taskove:

Bitno je pomenuti da će handler samo jednom da se izvrši čak i ako ga pozove više taskova. Ovim izbegavamo višestruke restarte servisa pri višestrukim izmenama jednog fajla, što potpuno ima smisla – izmeni fajl deset puta i na kraju restartuj servis samo jednom. Handleri najveću primenu imaju baš u navedenom slučaju restartovanja servisa, restartovanju servera i eventualno brisanju privremenih fajlova na kraju playbooka.

Promenljive

Ansible nije programski jezik ali ima nekoliko funkcionalnosti pozajmljenih iz programskih jezika, a jedna od njih su promenljive. U programskim jezicima, promenljive su memorijske lokacije koje imaju svoje ime i neku dodeljenu vrednost, ime = ‘Marija’ na primer.

Promenljive su vrlo korisna stvar i zbog toga mogu biti definisane na nekoliko mesta; u playbooku, inventarnom fajlu, u posebnim fajlovima za promenljive, mogu biti prosleđene sa komandne linije ili mogu biti prikupljene automatski. Promenljive možemo koristiti u playbook-ovima i template-ovima tako što njihovo ime napišemo unutar para vitičastih zagrada, kao na primeru ispod:

- name: Common tasks
  hosts: db_servers
  become: true
  vars:
    mysql_config_path: /etc/my.cnf
  tasks:
   - name: change mysql max_connections
     copy: src=edited_my.cnf dest=”{{ mysql_config_path }}”

Novina u prikazanom kodu jeste “vars” sekcija u kojoj se definišu promenljive. Prilikom izvršavanja ovog taska, naziv promenljive unutar vitičastih zagrada će biti zamenjen vrednošću promenljive što je u ovom slučaju putanja do konfiguracionog fajla. Iz sigurnosnih razloga, Ansible zahteva da stavimo navodnike ispred i iza vitičastih zagrada pošto unutar promenljive može biti neželjeni kod, koji će biti interpretiran kao string ako ga postavimo unutar navodnika čime se izbegava njegovo direktno izvršavanje.

Promenljive mogu nastati kombinacijom više promenljivih:

file_name: my_app.zip
server_name: http://myhost.com
file_location: {{ server_name }}/files/{{ file_name }}

Rezultujuća promenljiva će imati vrednost http://myhost.com/files/my_app.zip. Promenljive je moguće generisati dinamički tokom izvršavanja playbooka što je izuzetno korisno za prenos vrednosti sa taska na neki drugi task ili playbook. Za generisanje promenljivih od izlazne vrednosti nekog taska se koristi ključna reč register :

- name: get current logged in user
  command: whoami
  register: logged_in_user

Prikazan task će generisati promenljivu logged_in_user koja nastaje kao izlazna vrednost whoami naredbe koja u terminalu ispisuje trenutno ulogovanog korisnika na Linux sistemu.

Generisane promenljive – fakti

Prilikom konektovanja na konfigurisani server, Ansible automatski pokrene jedan od ugrađenih modula pod nazivom setup. Ovaj modul služi za sakupljanje informacija o konfigurisanom serveru kao što su operativni sistem, hardverske i mrežne specifikacije i mnoge druge. Prikupljene informacije ostaju sačuvane za vreme izvršavanja playbooka i mogu biti upotrebljene na isti način kao promenljive opisane u prethodnom poglavlju. Razni autori i tutorijai fakte opisuju kao automatski generisane promenljive sa čime se i ja slažem.

Količina prikupljenih informacija je prilična:

. . .
"ansible_devices": {
        "sda": {
            "model": "VBOX HARDDISK",
            "partitions": {
                "sda1": {
                    "sectors": "1024000",
                    "sectorsize": 512,
                    "size": "500.00 MB",
                    "start": "2048"
                },
                "sda2": {
                    "sectors": "166746112",
                    "sectorsize": 512,
                    "size": "79.51 GB",
                    "start": "1026048"
                }
            },
            "removable": "0",
            "rotational": "1",
            "scheduler_mode": "cfq",
            "sectors": "167772160",
            "sectorsize": "512",
            "size": "80.00 GB",
            "support_discard": "0",
            "vendor": "ATA"
      }
  },
  "ansible_distribution": "CentOS",
  "ansible_distribution_major_version": "7",
  "ansible_distribution_release": "Core",
  "ansible_distribution_version": "7.2.1511",
. . .

Literature i reference

Skoro svi članci koji opisuju Ansible ili ga porede sa sličnim alatima navode da je Ansible izuzetno lak za učenje sa čime bih se složio. Mišljenja sam da je knjiga u bilo kom formatu vrednija od ‘quick-and-easy’ tutorijala ili vodiča jer pruža više informacija, a i format je takav da omogućava zalaženje u teoriju i dubinu materije. Toplo preporučujem knjigu Ansible: Up and Running autora Lorin Hochstein kao i čitanje zvanične Ansible dokumentacije dostupne na adresi docs.ansible.com/ansible.

* * *

Kolege i poznanici iz nekog razloga preferiraju da servere podešavaju ručno, i to potpuno ima smisla ako će na serveru instalirati agenta za monitoring i nekoliko servisa. U takvim slučajevima se učenje Ansible-a čini kao nepotrebno trošenje vremena. Da li je to tako zaista? Moramo biti svesni činjenice da sve u IT svetu napreduje izuzetnom brzinom, a menadžment želi rezultate što je pre moguće (vrlo često “za juče”). Da li ću jednu te istu stvar da uradim ručno nebrojeno mnogo puta ili ću to prepustiti alatu, a svoje vreme upotrebiti na bolji način? Za mene je izbor lak.