Ansible – automatyzacja zarządzania serwerami

Witajcie ludzie :)
Ogromnie dawno tu nie pisałem, ale bardzo dużo pracy w pracy, a i po pracy sporo rzeczy do robienia co spowodowało, że blog odszedł na drugi tor… Ale, ale! W zakładce O mnie dodałem nową fotkę ;) Tak żeby blog nabrał jeszcze bardziej wyrazistego charakteru. Nie wiem czy liczy się to jako aktywność na blogu, więc postanowiłem, że część wolnego wykorzystam do napisania kilku słów o automatyzacji i programie o nazwie Ansible.

Zazwyczaj środowiska produkcyjne, chociaż w sumie developerskie również, są złożone z większej ilości maszyn, przez co szybko zachodzi konieczność obsługi dużej ich ilości, tj. dodawania nowych, aktualizacji oprogramowania itp. Nie ma problemu jeśli mamy trzy serwery do aktualizacji, ale robienie tego samego na trzydziestu serwerach… Może być lekko irytujące. Z odsieczą przychodzi nam Ansible.

Ansible umożliwia spisanie wszystkiego co należy zrobić na serwerze w formie playbooka’a, którego potem odpalamy, wskazując uprzednio jeszcze adresy IP serwerów, na których Ansible ma wykonać owego playbooka.

Aby zademonstrować działanie Ansible użyłem Vagranta aby postawić u siebie (Fedora 25) pięć osobnych maszyn wirtualnych z Centosem 7.
Oczywiście Vagrant dostarcza nam prosty provisioning tworzonych wirtualek, ale tutaj z niego nie będziemy korzystać, gdyż nie to jest naszym celem.

Vagrantfile:

1
2
3
4
5
6
7
8
Vagrant.configure("2") do |config|
    config.vm.box = "centos/7"
    ssh_pub_key = File.readlines("#{Dir.home}/.ssh/id_rsa.pub").first.strip
    config.vm.provision 'shell', inline: "mkdir -p /root/.ssh"
    config.vm.provision 'shell', inline: "echo #{ssh_pub_key} >> /root/.ssh/authorized_keys"
    config.vm.provision 'shell', inline: "echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys",
    privileged: false
end

Taki kawałek konfiguracji postawi nam czystego Centosa 7 i skopiuje nam do authorized_keys nasz klucz publiczny (na potrzeby tego wpisu na innym użytkowniku wygenerowałem sobie taki klucz).

Takim kawałkiem basha stawiam 5 wirtualek:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
DIR=vrts
NUMBER_OF_VRTS=5
cd $DIR
for (( n=1; n<=NUMBER_OF_VRTS; n++ ))
do  
    echo "creating directory vrt$n"
    mkdir -p "vrt$n"
    cd "vrt$n"
    vagrant up --provider virtualbox;
    echo "$n st Centos 7 up!"
    cd ".."
done

Ansible przy łączeniu się z serwerami wykonuje polecenia z użytkownika na który się logujemy. Tak jak polecenie ssh – korzysta z klucza z lokalizacji ~/.ssh.

Aby korzystać z Ansible konieczne jest stworzenie playbook’a. Takie pliki piszemy używając składni języka YAML. Playbook wyświetlający wersję interpretera Pythona na ekranie może wyglądać tak:

1
2
3
4
5
6
7
8
9
10
---
- hosts: ips
  tasks:
    - name: get version
      command: uname -a
      register: linux_version

    - name: show version
      debug:
          msg: "Version is {{ linux_version.stdout }} ! :)"

Strefa hosts odnosi się do pliku podanego przy uruchamianiu, w którym trzyma się adresy IP serwerów. Uruchomienie playbooka robi się poleceniem:

1
ansible-playbook playbooks/install_lighttp.yml -K -i inventory

gdzie po poleceniu ansible-playbook podajemy nazwę pliku z naszą receptą i po przełączniku „i”, plik z naszymi hostami. Zawartość pliku inventory wygląda tak:

1
2
3
4
5
6
7
8
9
[ips]
host1 ansible_ssh_port=2222 ansible_ssh_host=127.0.0.1
host2 ansible_ssh_port=2200 ansible_ssh_host=127.0.0.1
host3 ansible_ssh_port=2201 ansible_ssh_host=127.0.0.1
host4 ansible_ssh_port=2202 ansible_ssh_host=127.0.0.1
host5 ansible_ssh_port=2202 ansible_ssh_host=127.0.0.1

[all:vars]
ansible_ssh_user=vagrant

Z tym, że ja mam tutaj specyficzną sytuację – wszystkie moje serwery są na tym samym IP co powoduje konieczność wskazania tego w taki specyficzny sposób jak w sekcji ips. Przy normalnym układzie trzeba tutaj wpisać po enterze nazwy hostów/adresy IP. Sekcja all:vars to zestaw ustawień dla wszystkich sekcji. Ustawienie zmiennej ansbile_ssh_user każe Ansiblowi wykonać polecenie ssh [email protected] -P 2222 zamiast ssh localhost -P 2222 :)

Wracając do naszego playbooka, pod sekcją hosts mamy sekcję tasks i to tutaj piszemy naszą receptę, czyli ten zestaw kroków co i jak zrobić. Ustawiamy nazwę, następnie dzięki poleceniu command wykonujemy polecenie uname -a co zwróci nam wersję jądra i wynik tej operacji rejestrujemy do zmiennej o nazwie linux_version. Drugim i ostatnim krokiem jest wypisanie tego co polecenie zwróciło na standardowe wyjście na ekran w przyjaznej formie. W zmiennej linux_version jest też standardowe wyjście błędów itp. ogólnie użyteczne funkcje. Odpalając tego playbooka zobaczymy coś takiego:

I pod tym podsumowanie:

Więc wszystkie ładnie :) Ale samo uzyskiwanie informacji jest mało dynamiczne. Zróbmy coś innego – zainstalujmy Pythonowego pipa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
- hosts: ips
  become: true
  tasks:
    - name: check python version
      command: python --version
      register: python_version

    - name: show version
      debug:
          msg: "Python is in  {{ python_version.stderr }} ! :)"

    - name: install wget
      yum:
         name: wget
         state: latest

    - name: get pip
      command: wget https://bootstrap.pypa.io/get-pip.py

    - name: install pip
      command: python get-pip.py

    - name: remove downloaded file
      command: rm -f get-pip.py

Tutaj dochodzi pod sekcją hosts, sekcja become która pozwala uruchamiać polecenia z użytkownika root. Tutaj po kolei – sprawdzamy i wyświetlamy wersję Pythona, następne używając modułu yum instalujemy program wget. Pobieramy nim pliczek „get-pip.py”, uruchamiamy go i sprzątamy po sobie, usuwając ten plik. Takie operacje już by chwilkę zajeły, gdybyśmy mieli je robić ręcznie na 30 serwerach. Podgląd efektu:

I od teraz każdy z naszych pięciu serwerów posiada zainstalowanego pipa :)

Ważną rzeczą jest też kopiowanie plików na serwery zdalne. W ansiblu możemy napisać taki playbook który na każdy serwer skopiuje wskazane przez nas pliki do wskazanego przez nas miejsca. A przy okazji pokażę jak używać zmiennych dołączanych do playbooka.
A więc YAML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---
- hosts: ips
  tasks:
    - name: include variables
      include_vars:
          dir: 'vars'
          files_matching: 'file_names.yml'

    - debug:
          msg: 'We will upload file: {{ item }}'
      with_items: "{{ to_upload }}"

    - copy:
          src: ../to_upload/{{ item }}
          dest: ~/{{ item }}
      with_items: "{{ to_upload }}"

Pierwszy task to dołączenie zmiennych z pliku znajdującego się w katalogu „vars” o nazwie „file_names.yml”. Wygląda on tak:

1
2
3
4
5
[email protected] ~/ansible-post> cat playbooks/vars/file_names.yml
to_upload:
    - index.html
    - readme.txt
    - smile.jpg

Definiujemy tutaj tablicę o nazwie „to_upload” o elementach index.html, readme.txt i smile.jpg.

W naszych playbooku, podczas taska numer 2 wypisujemy na ekran nazwy plików. Dyrektywa with_items pozwala na iterację po zmiennej, która jest w zewnętrznym pliku. Każdy element zmiennej to_upload jest dostępny pod nazwą item.

Trzeci i ostatni task to właściwe kopiowanie – kopiujemy elementy tablicy to_upload z folderu o tej samej nazwie do katalogu domowego serwera zdalnego. W pseudokodzie można by to zapisać tak:

1
2
foreach (to_upload as item)
    copy('to_upload/' + item, '~/' + item)

Jeszcze taka mała dygresja – przed każdym naszym pierwszym taskiem jest wykonywany zawsze task o nazwie Gathering Facts. Zbiera on kilkanaście przydatnych informacji o każdym z hostów, takich jak nazwa dystrybucji systemu, procesor, wersja jądra itp. Całość można podejrzeć wykonując

1
ansible localhost -m setup | less

przedstawione informacje będą odnosiły się do localhosta.

No i czas zrobić coś co ma wartość konkretną – załóżmy, że posiadamy stronę, która jest postawiona za loadballancerem. No i nagle klient odnotowuje nagły wzrost odwiedzin i każe nam szybciutko zwiększyć wydolność strony. Daje nam kasę, błogosławieństwo i termin na dziś wieczór. Idziemy więc do OVH, kupujemy cztery wirtualki, każda z innego Data Center, dostajemy cztery adresy IP i siadamy do napisania playbooka:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
---
- hosts: ips
  become: true
  tasks:
    - name: Add repo
      yum:
          name: http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm
          state: present

    - name: Install lighttpd
      yum:
          name: lighttpd
          state: latest

    - name: include variables
      include_vars:
          dir: 'vars'
          files_matching: 'file_names.yml'

    - debug:
          msg: 'We will upload file: {{item}}'
      with_items: "{{ to_upload }}"

    - copy:
          src: ../to_upload/{{ item }}
          dest: /var/www/lighttpd/{{ item }}
      with_items: "{{ to_upload }}"

    - service:
          name: lighttpd
          state: restarted

Dodajemy potrzebne nam repozytorium i instalujemy lighttpd. Wczytujemy listę plików do podgrania, wrzucamy je do katalogu głównego lighttpd i restartujemy usługę. I koniec.

A jak przeforwardujemy sobie port 80 z którejkolwiek wirtualki na hosta to możemy otworzyć przeglądarkę i zobaczyć np. coś takiego:

Ale idźmy dalej z automatyzacją tego!

Skoro odpalamy playbooka takim poleceniem:

1
 ansible-playbook playbooks/install-websrv.yml -K -i inventory

to możemy pokusić się o przeniesienie playbooków do bardziej stronniczego miejsca, np. do /opt/playbooks/ co zmieni naszą komendę na

1
 ansible-playbook /opt/playbooks/install-websrv.yml -K -i inventory

Dalej wrzućmy to w plik bash:

1
2
#!/bin/bash
ansible-playbook /opt/playbooks/install-websrv.yml -K -i $1

Zmieniliśmy nazwę pliku z hostami na „$1” co powoduje, że nazwę tę zaczyta z wywołania pliku bash:

1
 ./install.sh inventory

ale idźmy dalej, przenieśmy plik install.sh do folderu /opt i stwórzmy na niego alias – tzn. w pliku .bashrc dopiszmy na końcu linijkę

1
alias install_srv="/opt/install.sh"

i wykonajmy polecenie:

1
source .bashrc

co spowoduje zaaplikowanie stworzonego właśnie aliasu, a naszego ansible’a będziemy mogli uruchomić jednym, krótkim poleceniem:

1
install_srv inventory

gdzie inventory to nazwa pliku z hostami.

Takie magie są fajne, bo skracają czas na ewentualne późniejsze użycia takiej funkcjonalności, a i ukrywają implementację co zabezpiecza przed ewentualnym zepsuciem tego procesu. Ale ma to też drugą stronę medalu, porobiliśmy sobie dwadzieścia takich komend, zatrudniamy Juniora i nagle dajemy mu dwadzieścia komend, które robią dosłownie wszystko iiii… Szok! Czarna dziura dla nowego gościa. Dostaje automagiczną komendę, która robi wszystko bez jakiegokolwiek zrozumienia całego procesu, bez świadomości jak on przebiega. I teraz mogą się stać dwie rzeczy, albo gość do końca swojego życia będzie używał tych poleceń bez zrozumienia, ale zacznie szukać jak to działa i albo ogranie (kosztem czasu) albo nie ogarnie. Chcę przez to powiedzieć, że takie skróty powinny być tłumaczone nowym osobom. Świadomość tego co się robi jest serio bardzo ważna i ogromnie poszerza wiedzę na temat systemu, przestaje on być ogromnie magicznym bytem, a staje się po prostu tym czym jest… Ogromną ilością sprytnych ifów, forów i while’ów.

Ansible to potężne narzędzie, które nie raz pozwoli Wam na zaoszczędzenie ogromnej ilości głupiej pracy. Tym miłym akcentem zakończę dzisiejszy wpis.

Dzięki za wizytę,
Mateusz Mazurek
Podziel się na:
    Facebook email PDF Wykop Twitter

Dodaj komentarz

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subskrybuj  
Powiadom o