Oto praktyczny sposób na zarządzanie konfiguracją sieciową na serwerach IBM Power przy użyciu Ansible i kolekcji ibm.power_hmc
. Na przykładzie playbooka pokażę, jak dodać nowy VLAN i usunąć istniejący z konfiguracji SEA (Shared Ethernet Adapter) na serwerach VIOS (Virtual I/O Server). To podstawowy przykład automatyzacji, który można łatwo rozbudować o bardziej zaawansowane scenariusze.
Cel playbooka:
Playbook umożliwia dodanie nowego VLAN-u oraz usunięcie istniejącego z konfiguracji SEA na wszystkich serwerach VIOS w ramach zarządzanego systemu (managed system) w HMC (Hardware Management Console). Operacja ta jest wykonywana dynamicznie, bez konieczności restartowania VIOS czy adapterów.
Wykorzystane technologie:
- Ansible: Narzędzie do automatyzacji IT.
- Dynamiczne Inventory z kolekcji ibm.power_hmc: Zamiast statycznego pliku inventory, playbook korzysta z dynamicznego inventory dostarczanego przez kolekcję ibm.power_hmc. Pozwala to na automatyczne pobieranie informacji o zarządzanych systemach i ich konfiguracji bezpośrednio z HMC, eliminując potrzebę ręcznego utrzymywania pliku inventory. Jak skorzystać z dynamicznego inventory można przeczytać w artykule Michała Wiktorka https://osadmins.com/pl/devops-z-powervm-ansible-i-dynamiczne-inventory
- Kolekcja ibm.power_hmc: Zbiór modułów Ansible do zarządzania systemami IBM Power za pośrednictwem HMC.
- Moduł hmc_command: Kolekcja ibm.power_hmc nie posiada (w momencie pisania tego artykułu) dedykowanego modułu do zarządzania VLANami na SEA. Dlatego wykorzystujemy uniwersalny moduł hmc_command, który pozwala na wykonywanie dowolnych poleceń na HMC. Polecenia te są zgodne z dokumentacją IBM dotyczącą dynamicznego dodawania i usuwania VLANów na VIOS: https://www.ibm.com/docs/en/power9/9040-MR9?topic=networks-dynamically-adding-removing-vlans-virtual-io-server
Playbook (z komentarzami):
- name: Manage vlans on vios # Nazwa playbooka: Zarządzanie VLANami na VIOS
hosts: all # Playbook zostanie uruchomiony na wszystkich hostach zdefiniowanych w inventory.
connection: local # Użyj lokalnego połączenia (zakłada, że playbook jest uruchamiany z hosta zarządzającego HMC).
gather_facts: no # Nie zbieraj faktów o hostach (przyspiesza działanie, gdy fakty nie są potrzebne).
vars: # Definicja zmiennych używanych w playbooku.
curr_hmc_auth: # Dane uwierzytelniające do HMC.
username: "{{ ansible_user }}" # Nazwa użytkownika HMC, pobierana ze zmiennej Ansible 'ansible_user'. Jinja2 expression: pobiera wartość zmiennej globalnej.
password: "{{ ansible_password }}" # Hasło użytkownika HMC, pobierane ze zmiennej Ansible 'ansible_password'. Jinja2 expression: pobiera wartość zmiennej globalnej.
vlan_id: "432" # ID VLANu, który ma być zarządzany.
vea_id: "4" # ID wirtualnego adaptera Ethernet (VEA).
vlan_action: "remove_vlan" # Akcja do wykonania na VLANie: "add_vlan" (dodanie) lub "remove_vlan" (usunięcie).
tasks: # Sekcja zadań wykonywanych w playbooku.
- name: Get system capabilities # Pobierz możliwości systemu.
ibm.power_hmc.hmc_command: # Użyj modułu 'hmc_command' z kolekcji 'ibm.power_hmc' do wykonania polecenia na HMC.
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # Adres IP HMC, pobierany z faktów hosta (hostvars) dla bieżącego hosta (inventory_hostname) i klucza 'HMCIP'. Jinja2 expression: pobiera wartość zmiennej z inventory. Zmienna HMCIP jest dostarczna w dynamicznym inventory.
hmc_auth: "{{ curr_hmc_auth }}" # Dane uwierzytelniające do HMC (zdefiniowane w sekcji 'vars'). Jinja2 expression: pobiera wartość zmiennej zdefiniowanej wcześniej.
cmd: "lssyscfg -r sys -m {{ inventory_hostname }} -F capabilities" # Polecenie do wykonania na HMC: pobierz możliwości systemu zarządzanego (inventory_hostname). Jinja2 expression: Wstawia nazwę hosta z inventory.
register: hmc_output # Zapisz wynik polecenia do zmiennej 'hmc_output'.
- name: Check for virtual_eth_dlpar_capable # Sprawdź, czy system obsługuje dynamiczne partycjonowanie logiczne (DLPAR) dla wirtualnego Ethernetu.
set_fact: # Ustaw fakt (zmienną) na podstawie wyniku poprzedniego zadania.
is_virtual_eth_dlpar_capable: "{{ 'virtual_eth_dlpar_capable' in (hmc_output.command_output[0] | trim('\"')).split(',') }}" # Sprawdź, czy 'virtual_eth_dlpar_capable' znajduje się w liście możliwości. Jinja2 expression: Sprawdza czy element jest w tablicy.
# hmc_output.command_output[0]: Pierwszy element z listy wyników polecenia.
# | trim('\"'): Usuń cudzysłowy z początku i końca ciągu znaków.
# .split(','): Podziel ciąg znaków na listę, używając przecinka jako separatora.
# 'virtual_eth_dlpar_capable' in ...: Sprawdź, czy ciąg 'virtual_eth_dlpar_capable' znajduje się w powstałej liście.
- name: Fail if virtual_eth_dlpar_capable is not present # Przerwij playbook, jeśli DLPAR dla wirtualnego Ethernetu nie jest obsługiwany.
fail: # Moduł do przerwania playbooka z błędem.
msg: "virtual_eth_dlpar_capable is NOT present!" # Komunikat błędu.
when: not is_virtual_eth_dlpar_capable # Warunek przerwania: jeśli fakt 'is_virtual_eth_dlpar_capable' jest fałszywy.
- name: Get VIOS IDs # Pobierz ID partycji VIOS.
ibm.power_hmc.hmc_command: # Użyj modułu 'hmc_command'.
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # Adres IP HMC. Jinja2 expression: pobiera wartość zmiennej z inventory.
hmc_auth: "{{ curr_hmc_auth }}" # Dane uwierzytelniające do HMC. Jinja2 expression: pobiera wartość zmiennej zdefiniowanej wcześniej.
cmd: "lssyscfg -r lpar -m {{ inventory_hostname }} -F lpar_id,lpar_env" # Polecenie: pobierz ID i typ środowiska partycji. Jinja2 expression: Wstawia nazwę hosta z inventory.
register: lpar_output # Zapisz wynik do zmiennej 'lpar_output'.
- name: Extract VIOS IDs # Wyodrębnij ID partycji VIOS z wyniku.
set_fact: # Ustaw fact
vios_ids: "{{ lpar_output.command_output | select('search', 'vioserver') | map('split', ',') | map(attribute=0) | map('int') | list | sort }}" # Wyodrębnij ID VIOS. Jinja2 expression: przetwarza wynik polecenia.
# lpar_output.command_output: Wynik polecenia z poprzedniego kroku.
# | select('search', 'vioserver'): Wybierz tylko te elementy, które zawierają ciąg 'vioserver'.
# | map('split', ','): Podziel każdy element na listę, używając przecinka jako separatora.
# | map(attribute=0): Pobierz pierwszy element z każdej podlisty (czyli ID partycji).
# | map('int'): Przekonwertuj każdy element na liczbę całkowitą.
# | list: Zamień wynik na listę.
# | sort: Posortuj listę.
- name: Fail if no VIOS servers found # Przerwij, jeśli nie znaleziono żadnych partycji VIOS.
fail:
msg: "No VIOS servers found. Check HMC configuration."
when: vios_ids | length == 0 # Warunek: jeśli lista 'vios_ids' jest pusta. Jinja2 expression: Sprawdza długość listy
- name: Get VLAN configurations from VIOS # Pobierz konfigurację VLAN z każdego VIOSa
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # Adres IP HMC.
hmc_auth: "{{ curr_hmc_auth }}" # Dane uwierzytelniające.
cmd: "lshwres -m {{ inventory_hostname }} -r virtualio --rsubtype eth --level lpar -F addl_vlan_ids --filter lpar_ids={{ item }}" # Polecenie pobrania VLANów dla konkretnej partycji (item). Jinja2 expression: Wstawia nazwę hosta z inventory.
loop: "{{ vios_ids }}" # Wykonaj pętlę dla każdego ID VIOS z listy 'vios_ids'. Jinja2 expression: Iteruje po liście vios_ids
register: vlan_output # Zapisz wyniki do zmiennej 'vlan_output'.
- name: Extract and clean VLANs # Wyodrębnij i oczyść listę VLANów.
set_fact:
vlan_lists: "{{ vlan_output.results | map(attribute='command_output') | join(',') | replace('[', '') | replace(']', '') | replace('\"', '') | replace(\"'\", '') | split(',') | map('trim') | list }}" # Przetwórz wyniki polecenia
# vlan_output.results: Lista wyników z pętli 'loop'.
# | map(attribute='command_output'): Pobierz wartość klucza 'command_output' z każdego wyniku.
# | join(','): Połącz wszystkie wyniki w jeden ciąg znaków, oddzielając je przecinkami.
# | replace('[', ''): Usuń znaki '['.
# | replace(']', ''): Usuń znaki ']'.
# | replace('\"', ''): Usuń znaki '"'.
# | replace(\"'\", ''): Usuń znaki " ' ".
# | split(','): Podziel ciąg na listę, używając przecinka jako separatora.
# | map('trim'): Usuń białe znaki z początku i końca każdego elementu listy.
# | list: Zamienia wynik na listę.
- name: Check VLAN presence # Sprawdź, czy VLAN o podanym ID jest obecny.
set_fact:
vlan_missing: "{{ vlan_id not in vlan_lists }}" #Sprawdza czy element NIE JEST w liście. Jinja2 expression: Sprawdza przynależność elementu do listy.
- name: Debug VLAN list # Wyświetl informacje debugowania.
debug: # Moduł do wyświetlania informacji.
msg:
- "VLAN LIST -> {{ vlan_lists }}" # Wyświetl listę VLANów.
- "VLAN MISSING -> {{ vlan_missing }}" # Wyświetl informację, czy VLAN jest nieobecny.
- name: Handle VLAN action failures # Obsłuż błędy związane z akcją na VLANie
fail:
msg: "VLAN {{ vlan_id }} cannot be {{ 'removed' if vlan_action == 'remove_vlan' else 'added' }}!" # Sprawdza jaka akcja miała być wykonana na vlanie. Jinja2 expression: Warunkowe wstawianie ciągu znaków.
when: # Kiedy ma wystąpić błąd.
- (vlan_action == "remove_vlan" and vlan_missing) or # Jeśli akcja to usunięcie, a VLAN nie istnieje.
(vlan_action == "add_vlan" and not vlan_missing) # Jeśli akcja to dodanie, a VLAN już istnieje.
- name: Perform VLAN action - Add VLAN # Wykonaj akcję dodania VLANu.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # Adres IP HMC.
hmc_auth: "{{ curr_hmc_auth }}" # Dane uwierzytelniające.
cmd: "chhwres -r virtualio --rsubtype eth -m {{ inventory_hostname }} -o s --id {{ item }} -s {{ vea_id }} -a 'addl_vlan_ids+={{ vlan_id }},ieee_virtual_eth=1'" # Polecenie dodania VLANu. Jinja2 expressions: wstawianie danych.
loop: "{{ vios_ids }}" # Pętla dla każdego ID VIOS. Jinja2 expression: Iteruje po liście vios_ids
when: vlan_action == "add_vlan" and vlan_missing # Warunek wykonania: jeśli akcja to dodanie, a VLAN nie istnieje.
loop_control:
pause: 3 # Doda 3 -sekundową przerwę po każdej iteracji
- name: Perform VLAN action - Remove VLAN # Wykonaj akcję usunięcia VLANu.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # Adres IP HMC.
hmc_auth: "{{ curr_hmc_auth }}" # Dane uwierzytelniające.
cmd: "chhwres -r virtualio --rsubtype eth -m {{ inventory_hostname }} -o s --id {{ item }} -s {{ vea_id }} -a 'addl_vlan_ids-={{ vlan_id }},ieee_virtual_eth=1'" # Polecenie usunięcia VLANu. Jinja2 expressions: wstawianie danych.
loop: "{{ vios_ids }}" # Pętla dla każdego ID VIOS. Jinja2 expression: Iteruje po liście vios_ids
when: vlan_action == "remove_vlan" and not vlan_missing # Warunek wykonania: jeśli akcja to usunięcie, a VLAN istnieje.
loop_control:
pause: 3 # Doda 3 -sekundową przerwę po każdej iteracji
- name: Recheck VLAN configurations from VIOS # Ponownie pobierz konfigurację VLAN po wykonaniu akcji.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # Adres IP HMC.
hmc_auth: "{{ curr_hmc_auth }}" # Dane uwierzytelniające.
cmd: "lshwres -m {{ inventory_hostname }} -r virtualio --rsubtype eth --level lpar -F addl_vlan_ids --filter lpar_ids={{ item }}" # To samo polecenie co wcześniej.
loop: "{{ vios_ids }}" # Pętla dla każdego ID VIOS. Jinja2 expression: Iteruje po liście vios_ids
register: vlan_output_after # Zapisz wyniki do zmiennej 'vlan_output_after'.
- name: Extract VLANs after action # Wyodrębnij listę VLANów po wykonaniu akcji.
set_fact:
vlan_lists_after: "{{ vlan_output_after.results | map(attribute='command_output') | join(',') | replace('[', '') | replace(']', '') | replace('\"', '') | replace(\"'\", '') | split(',') | map('trim') | list }}" #identyczna operacja jak linia 104.
- name: Check VLAN presence after action # Sprawdź, czy VLAN jest obecny/nieobecny po wykonaniu akcji.
set_fact:
vlan_missing_after_action: "{{ vlan_id not in vlan_lists_after }}" # Sprawdza czy vlan NIE JEST na liście. Jinja2 expression: Sprawdza przynależność elementu do listy.
- name: Debug vlan_lists_after # Debug
debug:
msg:
- "VLAN LIST AFTER -> {{ vlan_lists_after }}" # Wyświetl listę VLANów po akcji.
- "VLAN MISSING AFTER ACTION -> {{ vlan_missing_after_action }}" #Wyswietl informację, czy VLAN jest nieobecny po wykonaniu akcji.
- name: Handle post-action VLAN failures # Obsłuż błędy po wykonaniu akcji na VLANie.
fail:
msg: "VLAN {{ vlan_id }} was not {{ 'removed' if vlan_action == 'remove_vlan' else 'added' }} successfully!" # Wyświetla czy vlan miał być usunięty czy dodany. Jinja2 expression: Warunkowe wstawianie ciągu znaków.
when: # warunki kiedy ma wystąpić fail
- (vlan_action == "remove_vlan" and vlan_id in vlan_lists_after) or # Jeśli akcja to usunięcie, a VLAN nadal istnieje.
(vlan_action == "add_vlan" and vlan_id not in vlan_lists_after) # Jeśli akcja to dodanie, a VLAN nie został dodany.
Ten diagram przedstawia logikę playbooka, dzieląc ją na główne kroki:
+---------------------+
| START Playbooka |
+---------------------+
|
V
+---------------------------------------+
| Pobierz Możliwości Systemu (lssyscfg) | --> Sprawdź "virtual_eth_dlpar_capable"
+---------------------------------------+
| (Przerwij, jeśli brak możliwości)
V
+---------------------------------+
| Pobierz ID LPAR VIOS (lssyscfg) | --> Wyodrębnij ID VIOS (liczby całkowite)
+---------------------------------+
| (Przerwij, jeśli brak VIOSów)
V
+-------------------------------------------+
| Pobierz VLANy z VIOSów (lshwres, PĘTLA) | --> Zbierz istniejące ID VLANów
+-------------------------------------------+
|
V
+---------------------------------+
| Sprawdź, czy Docelowy VLAN Istnieje | --> vlan_missing (wartość logiczna)
+---------------------------------+
| (Przerwij, jeśli akcja jest nieprawidłowa)
V
+-------------------------------------------------------------------+
| (Warunkowo) |
| JEŚLI dodawanie VLANu i go brakuje: |
| - Dodaj VLAN do każdego VIOS (chhwres, PĘTLA, addl_vlan_ids+=) |
| W PRZECIWNYM WYPADKU, JEŚLI usuwanie VLANu i on istnieje: |
| - Usuń VLAN z każdego VIOS (chhwres, PĘTLA, addl_vlan_ids-=) |
+-------------------------------------------------------------------+
|
V
+-------------------------------------------------+
| Ponownie sprawdź VLANy z VIOSów (lshwres, PĘTLA) | --> Pobierz zaktualizowane ID VLANów
+-------------------------------------------------+
|
V
+---------------------------------------------------+
| Sprawdź, czy Akcja na VLANie się Powiodła |
+---------------------------------------------------+
| (Przerwij, jeśli akcja się nie powiodła)
V
+---------------------+
| KONIEC Playbooka |
+---------------------+
Klucz (Legenda):
* lssyscfg: Polecenie HMC do pobrania konfiguracji systemu.
* lshwres: Polecenie HMC do pobrania zasobów sprzętowych (w tym VLANów).
* chhwres: Polecenie HMC do zmiany zasobów sprzętowych (dodawanie/usuwanie VLANów).
* PĘTLA (LOOP): Wskazuje, że akcja jest wykonywana w pętli dla każdego VIOS.
* addl_vlan_ids+= : Część polecenia chhwres do DODANIA VLANu.
* addl_vlan_ids-= : Część polecenia chhwres do USUNIĘCIA VLANu.
* (Warunkowo): Wskazuje blok warunkowy oparty na `vlan_action` i `vlan_missing`.
* --> : Wskazuje przepływ danych lub wynik kroku.
Uwagi i możliwe ulepszenia:
- Automatyczne wykrywanie SEA: Playbook zakłada, że wiemy, który adapter VEA (Virtual Ethernet Adapter) chcemy modyfikować (vea_id). W bardziej zaawansowanym scenariuszu można by dodać logikę do automatycznego wykrywania dostępnych SEA i adapterów VEA.
- Wybór najmniej obciążonego adaptera VEA: Zamiast sztywnego przypisania vea_id można by zaimplementować mechanizm wybierający adapter VEA w ramach SEA, który ma najmniej przypisanych VLANów, aby równomiernie rozłożyć obciążenie.
- Obsługa usuwania VLANów: Obecnie playbook zawiera sekcję umożliwiającą usunięcie VLAN-u, jednak wymaga ręcznego podania numeru adaptera, z którego ma zostać usunięty. Można to usprawnić, automatycznie wykrywając odpowiedni adapter, co uprości proces i zminimalizuje ryzyko błędów.
- Wykrywanie Identyfikatorów SEA i VEA: playbook opiera się na zdefiniowanych statycznie wartościach vea_id. W środowisku produkcyjnym, te identyfikatory powinny być wykrywane dynamicznie.
- Struktura playbooka i przejrzystość: Przy okazji dodawania tych modyfikacji można zbudować prostą rolę, która rozbije playbooka na osobne zadania (taski), co zwiększy przejrzystość i ułatwi zarządzanie automatyzacją w bardziej złożonych środowiskach.
- Struktura playbooka i przejrzystość: Przy okazji dodawania tych modyfikacji można zbudować prostą rolę, która rozbije playbooka na osobne zadania (taski), co zwiększy przejrzystość i ułatwi zarządzanie automatyzacją w bardziej złożonych środowiskach.
Dodanie tych ulepszeń znacznie zwiększyłoby złożoność playbooka, co nie jest celem tego artykułu. Chciałem pokazać prosty, ale działający przykład wykorzystania Ansible i kolekcji ibm.power_hmc do zarządzania konfiguracją sieciową na serwerach IBM Power.
Uczę się Ansible i ten tekst jest formą nauki oraz utrwalania wiedzy. Jeśli zauważyłeś jakieś błędy, daj znać – zostaną poprawione. 🚀