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 vagrant@localhost -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
test@mmazurek ~/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
Mateusz M.

Ostatnie wpisy

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

1 tydzień ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

2 miesiące ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

3 miesiące ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

4 miesiące ago

Praca zdalna – co z nią dalej?

Cześć, ostatnio w Internecie pojawiło się dużo artykułów, które nie były przychylne pracy zdalnej. Z drugiej strony większość komentarzy pod… Read More

4 miesiące ago

Podsumowanie: listopad 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W listopadzie dokończyłem cykl "Z mgły zrodzony" Sandersona. Tylko "Stop prawa" mi nie do… Read More

5 miesięcy ago