Posted in

IBM Power Ansible w praktyce: Dodawanie VLANu do SEA (Shared Ethernet Adapter)

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. 🚀

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *