Ansible: Pętle, wyrażenia warunkowe i uchwyty
 

We wcześniejszym artykule mówiliśmy o zbieraniu faktów na temat zarządzanych węzłów oraz możliwości ich wykorzystania w naszych playbook-ach. Pokazaliśmy także, jak można się do nich potem odwołać. Idąc o krok dalej, zajmiemy się teraz ich wykorzystaniem do warunkowego wykonywania zadań. Stosowana jest do tego celu klauzula "when". Można nią także warunkować wykonywanie kolejnych zadań, od poprzednich rezultatów.

Klauzula "when" wykorzystuje bezpośrednią składnię (ang. raw syntax) Jinja2, dając w ten sposób możliwość stosowania rozbudowanych wyrażeń z użyciem testów (porównywanie) i filtrów (manipulacja danymi) Jinja2 oraz wyrażeń logicznych jak m.in. "OR" czy "AND". Oznacza to też, że mamy bezpośredni dostęp do zmiennych, bez potrzeby stosowania podwójnych nawiasów klamrowych "{{ }}".


[msleczek@vm0-net projekt_A]$ cat playbook.yaml
---
- hosts: all
tasks:
- name: "Informacje o hostach"
debug:
msg:
- "Host: {{ansible_hostname}}, Family: {{ansible_os_family}}"
- "IPv4: {{ansible_default_ipv4.address}}"
- name: "Dodatkowa informacje o {{ HOST }}"
shell: /usr/bin/uptime
register: uptime_result
when: ansible_default_ipv4.address == HOST
- debug:
var: uptime_result
when: ansible_default_ipv4.address == HOST or ansible_default_ipv4.address == '10.8.232.124'
...

[msleczek@vm0-net projekt_A]$


Tym razem, dla opcji "msg" modułu "debug" zdefiniowaliśmy listę. Warto porównać jej wynik z tym, jak wyglądał on w poprzednim artykule. Trochę niżej, wykorzystaliśmy także omówioną tam opcję "var" modułu "debug", a także parametr "register". Warto wrócić do tych informacji, jeżeli zastosowanie tych parametrów nie jest dla nas jasne.

W naszym playbook-u, jak i inwentarzu nie ma nigdzie zadeklarowanej zmiennej HOST.

[msleczek@vm0-net projekt_A]$ cat inventory 
10.8.232.121
10.8.232.122
10.8.232.123
10.8.232.124
[msleczek@vm0-net projekt_A]$


Zmienna HOST jest definiowana w trakcie wywoływania naszego playbooka, za pomocą opcji "--extra-vars".

[msleczek@vm0-net projekt_A]$ ansible-playbook playbook.yaml --extra-vars="HOST=10.8.232.122"

PLAY [all] ********************************************************************************************

TASK [Gathering Facts] ********************************************************************************
ok: [10.8.232.121]
ok: [10.8.232.124]
ok: [10.8.232.122]
ok: [10.8.232.123]

TASK [Informacje o hostach] ***************************************************************************
ok: [10.8.232.121] => {
"msg": [
"Host: vm1-net, Family: RedHat",
"IPv4: 10.8.232.121"
]
}
ok: [10.8.232.122] => {
"msg": [
"Host: vm2-net, Family: RedHat",
"IPv4: 10.8.232.122"
]
}
ok: [10.8.232.123] => {
"msg": [
"Host: ms3-net, Family: RedHat",
"IPv4: 10.8.232.123"
]
}
ok: [10.8.232.124] => {
"msg": [
"Host: vm4-net, Family: RedHat",
"IPv4: 10.8.232.124"
]
}

TASK [Dodatkowa informacje o 10.8.232.122] **********************************************************
skipping: [10.8.232.121]
skipping: [10.8.232.123]
skipping: [10.8.232.124]
changed: [10.8.232.122]

TASK [debug] ****************************************************************************************
skipping: [10.8.232.121]
ok: [10.8.232.122] => {
"uptime_result": {
"changed": true,
"cmd": "/usr/bin/uptime",
"delta": "0:00:00.006720",
"end": "2020-05-11 12:16:21.952928",
"failed": false,
"rc": 0,
"start": "2020-05-11 12:16:21.946208",
"stderr": "",
"stderr_lines": [],
"stdout": " 12:16:21 up 2 days, 3:17, 1 user, load average: 0.22, 0.05, 0.02",
"stdout_lines": [
" 12:16:21 up 2 days, 3:17, 1 user, load average: 0.22, 0.05, 0.02"
]
}
}
skipping: [10.8.232.123]
ok: [10.8.232.124] => {
"uptime_result": {
"changed": false,
"skip_reason": "Conditional result was False",
"skipped": true
}
}

PLAY RECAP ****************************************************************************************
10.8.232.121 : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
10.8.232.122 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.123 : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
10.8.232.124 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$


Wykonanie dwóch ostatnich zadań zostało w naszym playbook-u dodatkowo uwarunkowane za pomocą klauzuli "when". Przedostatnie zadanie wykonane zostało tylko dla "10.8.232.122", a ostatnie dla "10.8.232.122" i "10.8.232.124". Warto też zauważyć, że ostatnie zadanie daje różny rezultat dla tych dwóch hostów, jako iż zależny jest on od tego, co wydarzyło się w trakcie przedostatniego zadania.

Klauzula "when" jest przydatna przy tworzeniu uniwersalnych playbook-ów, warunkujących wykonywanie poszczególnych zadań od rodzaju czy stanu węzła na którym będą one pracować. Dla przykładu, jeżeli system operacyjny węzła będzie należał do rodziny "Debian" to do instalacji oprogramowania użyjemy modułu "apt", a jeżeli będzie nią "RedHat", to modułu "yum". W naszym przykładzie dodatkowo użyjemy warunku logicznego "AND", aby ograniczyć instalację tylko na jednym węźle o określonym adresie IP.

[msleczek@vm0-net projekt_A]$ cat condicion.yaml 
---
- name: START WEB SERVERS
hosts: all
tasks:
- name: Debian Family
apt:
name: apache2
state: present
become: true
when: ansible_os_family == 'Debian' and ansible_default_ipv4.address == '10.8.232.123'

- name: RedHat Family
yum:
name: httpd
state: present
become: true
when: ansible_os_family == 'RedHat' and ansible_default_ipv4.address == '10.8.232.123'

[msleczek@vm0-net projekt_A]$

 

Cały czas pracujemy na tym samym inwentarzu, który zawiera cztery hosty. Posłużymy się teraz opcją "--limit" polecenia "ansible-playbook", aby ograniczyć wykonanie playbook-a tylko dla dwóch z nich.

[msleczek@vm0-net projekt_A]$ ansible-playbook condicion.yaml --limit 10.8.232.122,10.8.232.123

PLAY [START WEB SERVERS] **************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [10.8.232.122]
ok: [10.8.232.123]

TASK [Debian Family] ******************************************************************************
skipping: [10.8.232.122]
skipping: [10.8.232.123]

TASK [RedHat Family] ******************************************************************************
skipping: [10.8.232.122]
changed: [10.8.232.123]

PLAY RECAP ****************************************************************************************
10.8.232.122 : ok=1 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
10.8.232.123 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$


W wyniku wykonania naszego playbook-a do instalacji oprogramowania doszło tylko na 1 hoście "10.8.232.123".

Więcej na temat wyrażeń warunkowych i logicznych można znaleźć w dokumentacji Ansible.


Z poprzedniego artykułu wiemy już, że w szablonach Jinja2 możemy odwoływać się do wartości zmiennych poprzez użycie podwójnych nawiasów klamrowych "{{ }}". W szablonach Jinja2 da się także wstawiać komentarze. Umieszcza się je wewnątrz nawiasów klamrowych, pomiędzy znakami hash-a "{# #}". Ponadto, w szablonach Jinja2 mogą być też stosowane wyrażenia warunkowe. Umieszcza się je wewnątrz nawiasów klamrowych i znaków procenta - "{% %}". Wyrażenie te mogą być dość rozbudowane i oprócz porównań mogą zawierać operatory logiczne AND i OR.

[msleczek@vm0-net projekt_A]$ cat index.html.j2 
Połączyłeś się do adresu IP: {{ ansible_default_ipv4.address }}.
{# To jest komentarz #}
{% if ansible_os_family == 'Debian' %}
Działa tu system z rodziny debianowatych.
{% elif ansible_os_family == 'RedHat' %}
Działa tu system z rodziny redhatowatych.
{% else %}
Działa tu system z rodziny nietypowej.
{% endif %}
Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt_A]$


Sekcja "{% elif %}" oraz "{% else %}" jest opcjonalna. Za to sekcje "{% if %}" i "{% endif %}" są obowiązkowe. W wyniku wykonania playbook-a, z naszego szablonu Jinja2 zostaną wstawione do pliku docelowego tylko te wiersze, które spełniają postawione warunki.

[msleczek@vm0-net projekt_A]$ cat jinja2-cond.yaml 
---
- name: FILE CUSTOMIZATION
hosts: all
tasks:
- name: CUSTOMIZE index.html FILE
template:
src: ./index.html.j2
dest: /var/www/html/index.html

msleczek@vm0-net projekt_A]$ ansible-playbook jinja2-cond.yaml --limit 10.8.232.121,10.8.232.122

PLAY [FILE CUSTOMIZATION] *************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [10.8.232.122]
ok: [10.8.232.121]

TASK [CUSTOMIZE index.html FILE] ******************************************************************
changed: [10.8.232.121]
changed: [10.8.232.122]

PLAY RECAP ****************************************************************************************
10.8.232.121 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.122 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$ curl http://10.8.232.122
Połączyłeś się do adresu IP: 10.8.232.122.

Działa tu system z rodziny redhatowatych.

Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt_A]$


Po nawiązaniu połączenia HTTP widać powstałą z naszego szablonu "index.html.j2" zawartość pliku "index.html".


Na poziomie języka YAML, jak i szablonów Jinja2 można także stosować pętle (ang. loops). Umożliwiają one cykliczne powtarzanie tych samych zadań na różnych elementach. Zwykle kończą swoje działanie po przejściu wszystkich elementów z listy lub na skutek zajścia jakiegoś zdarzenia czy spełnienia jakiegoś warunku.

Poniżej widać użycie pętli "for" wewnątrz szablonu Jinja2. Do tego celu stosowane jest wyrażenie "for .. in .. endfor" odpowiednio umieszczone wewnątrz nawiasów klamrowych i znaków procenta - "{% %}". 

[msleczek@vm0-net projekt_A]$ cat index.html.j2
Połączyłeś sie do adresu IP: {{ ansible_default_ipv4.address }}.

{% for item in ansible_dns.nameservers %}
Korzystam z serwera DNS: {{ item }}.
{% endfor %}

Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt_A]$


W trakcie zbierania faktów została zbudowana dla każdego z węzłów lista "ansible_dns.nameservers", zawierająca wszystkie skonfigurowane na nim serwery DNS. W szablonie Jinja2 wykorzystaliśmy wyrażenie "for" do wypisania wszystkich serwerów DNS z tej listy. O ile zmianie uległ szablon Jinja2, to nasz playbook wygląda dokładnie tak samo, jak w poprzednim przykładzie. Rezultat jego pracy można zobaczyć poniżej.

[msleczek@vm0-net projekt_A]$ cat jinja2-cond.yaml 
---
- name: FILE CUSTOMIZATION
hosts: all
tasks:
- name: CUSTOMIZE index.html FILE
template:
src: ./index.html.j2
dest: /var/www/html/index.html

[msleczek@vm0-net projekt_A]$ ansible-playbook jinja2-cond.yaml --limit 10.8.232.123,10.8.232.124

PLAY [FILE CUSTOMIZATION] *************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [10.8.232.123]
ok: [10.8.232.124]

TASK [CUSTOMIZE index.html FILE] ******************************************************************
changed: [10.8.232.123]
changed: [10.8.232.124]

PLAY RECAP ****************************************************************************************
10.8.232.123 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.124 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$ curl http://10.8.232.123
Połączyłeś sie do adresu IP: 10.8.232.123.

Korzystam z serwera DNS: 10.8.252.243.
Korzystam z serwera DNS: 10.8.252.244.
Korzystam z serwera DNS: 10.8.232.61.

Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt_A]$


Po nawiązaniu połączenia HTTP widać powstałą z naszego szablonu "index.html.j2" zawartość pliku "index.html".


W poprzednim artykule pokazaliśmy pierwsze wykorzystanie pętli wewnątrz playbook-a. Do obsługi takich pętli zalecane jest wyrażenie "loop" lub "until". Istnieje także wyrażenie "with_<lookup>", które powstało jako pierwsze i było niegdyś szerzej stosowane - obecnie powinniśmy go unikać.

Zastosowanie pętli wewnątrz playbook-ów jest dość szerokie. Korzystamy z nich, kiedy chcemy zainstalować więcej niż jeden pakiet, odblokować więcej niż jeden numer portu, założyć więcej niż jedno konto użytkownika, stworzyć więcej niż jeden VLAN czy dostosowanie uprawnień więcej niż jednego pliku. To co jest w wspólne dla tych wszystkich zadań, to kierunek "więcej niż jeden". Zatem, z pętli będziemy korzystać wtedy, kiedy będziemy chcieli wykonać jakieś zadanie więcej niż jeden raz, ale niekoniecznie z użyciem tych samych wartości zmiennych.

Z wyrażenia "until" korzystamy, kiedy pętla ma wykonywać się do momentu uzyskania jakiegoś określonego rezultatu, a z wyrażenia "loop", kiedy ma wykonać się dla każdego przedmiotu ze wskazanej listy. W praktyce, pętla "loop" jest o wiele częściej stosowana, dlatego przejdziemy przez kilka różnych przykładów jej zastosowania.

Zaczniemy od użycie pętli "loop" dla prostych list (ang. lists). Domyślnie, zmienna "{{ item }}" jest symbolem zastępczym, który przyjmuje kolejno wartości elementów listy, będącej parametrem pętli "loop".

[msleczek@vm0-net projekt_A]$ cat loop_list.yaml 
---
- name: "Play with loops"
hosts: localhost
gather_facts: false
vars:
devices:
- router
- switch
- hub
- firewall
tasks:
- name: "Loop through list"
debug:
msg: "{{ item }}"
loop: "{{ devices }}"

- name: "Loop through list"
debug:
msg: "{{ item }}"
loop:
- Linux
- Red Hat
- Cisco Systems
- networkers.pl
[msleczek@vm0-net projekt_A]$


Kiedy chcemy wskazać listę zdefiniowaną w sekcji "vars" lub w specjalnym pliku YAML ze zmiennymi, należy odwołać się do niej poprzez podwójny nawias klamrowy "{{ }}". Istnieje też możliwość zdefiniowania listy bezpośrednio w wyrażeniu "loop".


[msleczek@vm0-net projekt_A]$ ansible-playbook loop_list.yaml

PLAY [Play with loops] ***************************************************************************

TASK [Loop through list] *************************************************************************
ok: [localhost] => (item=router) => {
"msg": "router"
}
ok: [localhost] => (item=switch) => {
"msg": "switch"
}
ok: [localhost] => (item=hub) => {
"msg": "hub"
}
ok: [localhost] => (item=firewall) => {
"msg": "firewall"
}

TASK [Loop through list] ************************************************************************
ok: [localhost] => (item=Linux) => {
"msg": "Linux"
}
ok: [localhost] => (item=Red Hat) => {
"msg": "Red Hat"
}
ok: [localhost] => (item=Cisco Systems) => {
"msg": "Cisco Systems"
}
ok: [localhost] => (item=networkers.pl) => {
"msg": "networkers.pl"
}

PLAY RECAP *************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$

Powyżej widać efekt działania naszego playbook-a i wyświetlenie wszystkich elementów zdefiniowanych list. Dla każdej z nich, pętla "loop" wykonała się tyle razy ile widać komunikatów "msg".


Zdarza się, że chcemy wykonać pętlę "loop" na obiekcie będącym słownikiem (ang. dictionary). Słownik grupuje nam kilka zmiennych, nazywanych kluczami, do których przypisane są jakieś wartości. Każda z takich grup opisuje czy parametryzuje nam zwykle jakiś jeden obiekt czy element. W naszym przykładzie będzie nim VLAN. W związku z tym, że wyrażenie "loop" spodziewa się argumentu będącego listą, zastosujemy do manipulacji danymi filtr "dict2items". Skonwertuje on nasz słownik do postaci listy, z jaką poradzi sobie pętla "loop".

[msleczek@vm0-net projekt_A]$ cat loop_dict.yaml 
---
- name: "Play with loops"
hosts: localhost
gather_facts: false
vars:
vlans:
100:
name: "MGMT"
ip4: "10.8.100.1"
200:
name: "DATA"
ip4: "10.8.200.1"
208:
name: "VOICE"
ip4: "10.8.208.1"
tasks:
- name: "Loop through dictionary"
debug:
msg: >
VLAN={{vlan.key}}
NAME={{vlan.value.name}}
IPv4={{vlan.value.ip4}}
loop: "{{ vlans|dict2items }}"
loop_control:
loop_var: vlan

[msleczek@vm0-net projekt_A]$


Domyślnie, wewnątrz pętli kolejne wartości przypisywane są do zmiennej "item". W naszym przykładzie, zmieniliśmy tą nazwę na "vlan" za pomocą zmiennej "loop_var" sekcji "loop_control". Sekcja "loop_control" służy do manipulacji zachowaniem pętli, w tym ilością i rodzajem udostępnianych przez nią informacji. Więcej na temat "loop_control" znajduje się w dokumentacji Ansible.

W tym przykładzie nazwa "vlan" wydaje się bardziej intuicyjna. Natomiast w niektórych przypadkach możemy być zmuszeni do zmiany tej nazwy, ze względu na zazębianie się pętli i kolizje w nazwach. Przykładem może być użycie w pętli "loop" zadania, które samo korzysta z pętli. Wtedy ze zmiennej o tej samej nazwie - "item" - nie można korzystać w pętli zewnętrznej i wewnętrznej równocześnie.

[msleczek@vm0-net projekt_A]$ ansible-playbook loop_dict.yaml

PLAY [Play with loops] *************************************************************************

TASK [Loop through dictionary] *****************************************************************
ok: [localhost] => (item={'key': 100, 'value': {'name': 'MGMT', 'ip4': '10.8.100.1'}}) => {
"msg": "VLAN=100 NAME=MGMT IPv4=10.8.100.1\n"
}
ok: [localhost] => (item={'key': 200, 'value': {'name': 'DATA', 'ip4': '10.8.200.1'}}) => {
"msg": "VLAN=200 NAME=DATA IPv4=10.8.200.1\n"
}
ok: [localhost] => (item={'key': 208, 'value': {'name': 'VOICE', 'ip4': '10.8.208.1'}}) => {
"msg": "VLAN=208 NAME=VOICE IPv4=10.8.208.1\n"
}

PLAY RECAP *************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$

Powyżej widać efekt działania naszego playbook-a i wyświetlenie wszystkich elementów zdefiniowanego słownika z konfiguracją VLAN.


Innym zastosowaniem pętli "loop" może być wykonanie operacji na kolejnych plikach danego katalogu.

W naszym przykładzie użyjemy katalogu o zawartości widocznej poniżej:

[msleczek@vm0-net projekt_A]$ ls loop_*.yaml
loop_dict.yaml loop_dir.yaml loop_file.yaml loop_list.yaml
[msleczek@vm0-net projekt_A]$ pwd
/home/msleczek/projekt_A
[msleczek@vm0-net projekt_A]$


Aby dostarczyć listę interesujących nas plików na wejście pętli "loop", posłużymy się funkcją "lookup". Jej pierwszym argumentem będzie opcja "fileglob", która listuje pliki ze wskazanego katalogu, które pasują do wskazanego w drugim argumencie tej funkcji wzorca (tylko pliki, nie listuje katalogów). Wyszukiwanie odbywa się lokalnie na stacji zarządzającej, gdzie uruchomiony został playbook. Domyślnie, na wyjściu otrzymamy listę bezwzględnych ścieżek do plików w której separatorem będzie przecinek ",". Jeżeli chcemy na wyjściu otrzymać typową listę, należy ustawić wewnątrz tej funkcji zmienną "wantlist" na "true". Zostało to pokazane w niżej zamieszczonym przykładzie.

[msleczek@vm0-net projekt_A]$ cat loop_dir.yaml 
---
- name: "Play with loops"
hosts: localhost
gather_facts: false
tasks:
- name: "Loop through directory files"
debug:
msg: "{{ item }}"
loop: "{{ lookup('fileglob', 'loop_*.yaml', wantlist=true) }}"

[msleczek@vm0-net projekt_A]$ ansible-playbook loop_dir.yaml

PLAY [Play with loops] **************************************************************************

TASK [Loop through directory files] *************************************************************
ok: [localhost] => (item=/home/msleczek/projekt_A/loop_dir.yaml) => {
"msg": "/home/msleczek/projekt_A/loop_dir.yaml"
}
ok: [localhost] => (item=/home/msleczek/projekt_A/loop_list.yaml) => {
"msg": "/home/msleczek/projekt_A/loop_list.yaml"
}
ok: [localhost] => (item=/home/msleczek/projekt_A/loop_dict.yaml) => {
"msg": "/home/msleczek/projekt_A/loop_dict.yaml"
}
ok: [localhost] => (item=/home/msleczek/projekt_A/loop_file.yaml) => {
"msg": "/home/msleczek/projekt_A/loop_file.yaml"
}

PLAY RECAP *************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$


Da się także wykonywać operacje na plikach ulokowanych na zarządzanym węźle. Do tego celu należy korzystać z modułu "find". Więcej o nim znaleźć można w dokumentacji Ansible.


Pętla "loop" daje także możliwość wykonania określonych operacji dla każdego wiersza wskazanego pliku.

[msleczek@vm0-net projekt_A]$ cat ./inventory 
10.8.232.121
10.8.232.122
10.8.232.123
10.8.232.124
[msleczek@vm0-net projekt_A]$ cat loop_file.yaml
---
- name: "Play with loops"
hosts: localhost
gather_facts: false
tasks:
- name: "Loop through file content"
debug:
msg: "IP_ADDRESS={{ item }}"
loop: "{{ lookup('file', './inventory').splitlines() }}"

[msleczek@vm0-net projekt_A]$


Aby dostarczyć zawartość interesującego nas pliku na wejście pętli "loop", posłużymy się funkcją "lookup". Jej pierwszym argumentem będzie opcja 'file', która listuje zawartość wskazanego pliku. Jeżeli w trakcie przypisywania tej zawartości do zmiennej chcemy otrzymać listę, której oddzielnym elementem będzie każdy kolejny wiersz pliku, to należy skorzystać z metody języka Python "splitlines()".

[msleczek@vm0-net projekt_A]$ ansible-playbook loop_file.yaml

PLAY [Play with loops] **************************************************************************

TASK [Loop through file content] ****************************************************************
ok: [localhost] => (item=10.8.232.121) => {
"msg": "IP_ADDRESS=10.8.232.121"
}
ok: [localhost] => (item=10.8.232.122) => {
"msg": "IP_ADDRESS=10.8.232.122"
}
ok: [localhost] => (item=10.8.232.123) => {
"msg": "IP_ADDRESS=10.8.232.123"
}
ok: [localhost] => (item=10.8.232.124) => {
"msg": "IP_ADDRESS=10.8.232.124"
}

PLAY RECAP *************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$

Pętla "loop" wykonała się tyle razy ile widać komunikatów "msg", co odpowiada ilości wierszy pliku "./inventory".


Należy pamiętać, że Ansible został stworzony w języku Python, a w nim każda zmienna jest obiektem posiadającym pewne metody. Dzięki temu z metod tych możemy korzystać w Ansible. Poniżej zostało pokazane przykładowe użycie kilku metod na tym samym tekście.

[msleczek@vm0-net projekt_A]$ cat string_methods.yaml 
---
- name: "Play with methods"
hosts: localhost
gather_facts: false
vars:
TEKST: >
aAbB:cCdD:eEfF
aaaa:aaaa:aaaa
BBBB-BBBB-BBBB
AaBb CcDd EeFf
tasks:
- name: "String whitout method"
debug:
msg: "{{ TEKST }}"

- name: "String method swapcase()"
debug:
msg: "{{ TEKST.swapcase() }}"

- name: "String method lower()"
debug:
msg: "{{ TEKST.lower() }}"

- name: "String method capitalize()"
debug:
msg: "{{ TEKST.capitalize() }}"

- name: "String method split(':')"
debug:
msg: "{{ TEKST.split(':') }}"

- name: "String method count(BBBB)"
debug:
msg: "{{ TEKST.count('BBBB') }}"

[msleczek@vm0-net projekt_A]$


Lista dostępnych metod jest bardzo duża. My zastosowaliśmy kilka przydatnych do operacji na tekście:

  • "swapcase()" odwraca wielkość liter, czyli małe litery stają się dużymi, a duże małymi.
  • "lower()" sprawia, że wszystkie litery stają się małe.
  • "capitalize()" sprawia, że tylko pierwsza litera całego ciągu jest duża.
  • "split()" dzieli tekst na podstawie podanego separatora.
  • "count()" zlicza ilość wystąpień danego wzorca.

Poniżej, najpierw wyświetliliśmy oryginalny tekst, a potem kolejno jego wariacje z użyciem różnych metod. W związku z zastosowaniem operatora ">", jest to 1 wiersz ciągłego tekstu.

[msleczek@vm0-net projekt_A]$ ansible-playbook string_methods.yaml

PLAY [Play with methods] ************************************************************************

TASK [String whitout method] ********************************************************************
ok: [localhost] => {
"msg": "aAbB:cCdD:eEfF aaaa:aaaa:aaaa BBBB-BBBB-BBBB AaBb CcDd EeFf\n"
}

TASK [String method swapcase()] *****************************************************************
ok: [localhost] => {
"msg": "AaBb:CcDd:EeFf AAAA:AAAA:AAAA bbbb-bbbb-bbbb aAbB cCdD eEfF\n"
}

TASK [String method lower()] ********************************************************************
ok: [localhost] => {
"msg": "aabb:ccdd:eeff aaaa:aaaa:aaaa bbbb-bbbb-bbbb aabb ccdd eeff\n"
}

TASK [String method capitalize()] ***************************************************************
ok: [localhost] => {
"msg": "Aabb:ccdd:eeff aaaa:aaaa:aaaa bbbb-bbbb-bbbb aabb ccdd eeff\n"
}

TASK [String method split(':')] *****************************************************************
ok: [localhost] => {
"msg": [
"aAbB",
"cCdD",
"eEfF aaaa",
"aaaa",
"aaaa BBBB-BBBB-BBBB AaBb CcDd EeFf\n"
]
}

TASK [String method count(BBBB)] ***************************************************************
ok: [localhost] => {
"msg": "3"
}

PLAY RECAP *************************************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$


Istnieje o wiele więcej zastosowania pętli, dlatego odsyłamy po więcej do dokumentacji Ansible.


Uchwyty (ang. handlers) są typowymi zadaniami, których wykonanie jest wyzwalane poprzez powiadomienia (ang. notifications) o zmianie stanu z innych zadań. Użycie ich przekłada się na lepszą efektywność i wydajność playbook-a. Zadanie w uchwycie zostanie wykonane tylko raz, bez względu na to ile razy zostanie wywołane przez różne inne zadania. Dodatkowo, jeżeli dla danego węzła zadanie w uchwycie nie otrzyma żadnego powiadomienia, to nie będzie w ogóle uruchamiane.

[msleczek@vm0-net projekt_A]$ cat playbook.yaml 
---
- name: START WEB SERVERS
hosts: all
tasks:
- name: PACKAGE INSTALLATION
yum:
name: httpd
state: present
notify:
- START HTTPD SERVICE
- OPEN HTTP TRAFFIC

handlers:
- name: START HTTPD SERVICE
service:
name: httpd
state: started
enabled: true
- name: OPEN HTTP TRAFFIC
firewalld:
service: "{{ item }}"
permanent: true
immediate: true
state: enabled
loop:
- http
- https

[msleczek@vm0-net projekt_A]$


W naszym przykładzie, lista uchwytów została zdefiniowana przy zadaniu korzystającym z modułu "yum". Służy do tego celu sekcja "notify", gdzie wskazuje się nazwy uchwytów, które mają zostać powiadomione w przypadku wprowadzania zmiany. Zadania obsługujące uchwyty zdefiniowane są w specjalnej sekcji "handlers". 

[msleczek@vm0-net projekt_A]$ ansible-playbook playbook.yaml

PLAY [START WEB SERVERS] ********************************************************************

TASK [Gathering Facts] **********************************************************************
ok: [10.8.232.124]
ok: [10.8.232.123]
ok: [10.8.232.122]
ok: [10.8.232.121]

TASK [PACKAGE INSTALLATION] *****************************************************************
ok: [10.8.232.122]
ok: [10.8.232.121]
changed: [10.8.232.124]
changed: [10.8.232.123]

RUNNING HANDLER [START HTTPD SERVICE] *******************************************************
changed: [10.8.232.123]
changed: [10.8.232.124]

RUNNING HANDLER [OPEN HTTP TRAFFIC] *********************************************************
changed: [10.8.232.124] => (item=http)
changed: [10.8.232.123] => (item=http)
changed: [10.8.232.124] => (item=https)
changed: [10.8.232.123] => (item=https)

PLAY RECAP **********************************************************************************
10.8.232.121 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.122 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.123 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.124 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$

Wykorzystanie uchwytów (ang. handlers) w naszym playbook-u sprawia, że jeżeli na jakimś węźle znajduje się już zainstalowany pakiet "httpd", to nie będzie on próbował już nic więcej robić. W naszym przypadku, nie będzie próbował otwierać portów dla ruchu HTTP i HTTPS oraz uruchamiać usługi "httpd". Natomiast, gdy na węźle nie ma pakietu "httpd", to zostaną zainstalowane odpowiednie pakiety, co z kolei wyzwoli powiadomienie (ang. notify) i uruchomi uchwyty (ang. handlers), których zadaniem jest zadbanie o to, aby usługa "httpd" została uruchomiona i działała po restarcie, a systemowa zapora sieciowa wpuszczała do niej ruch HTTP i HTTPS.

W naszym przykładzie widać, że tylko 2 z 4 serwerów nie miały zainstalowanego pakietu "httpd" i tylko dla nich zostały uruchomione dodatkowe zadania z sekcji "handlers".


Narzędzie Ansible dostępne jest w systemie RHEL bez żadnych dodatkowych opłat. Niemniej, Red Hat nie udziela dla niego wsparcia i nie daje dostępu do innych komponentów platformy Red Hat Ansible Automation bez wykupienia stosownych subskrypcji. Zainteresowanych ich zakupem This email address is being protected from spambots. You need JavaScript enabled to view it..


Zapraszamy do kontaktu drogą mailową This email address is being protected from spambots. You need JavaScript enabled to view it. lub telefonicznie +48 797 004 932.