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:
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.
Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More
Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More
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
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
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
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