Systemowe podejście do zmiennych w Ansible

styl kodu ansible devops

Hej! Nazywam się Denis Kalużny Pracuję jako inżynier w dziale automatyzacji procesów deweloperskich. Codziennie na setkach serwerów kampanii pojawiają się nowe wersje aplikacji. W tym artykule dzielę się moimi doświadczeniami z używania Ansible do tych celów.

W tym przewodniku opisano sposób organizowania zmiennych podczas wdrażania. Ten przewodnik jest przeznaczony dla tych, którzy już korzystają z ról w swoich podręcznikach i przeczytali Najlepsze praktyki, ale napotyka podobne problemy:

  • Po znalezieniu zmiennej w kodzie nie można od razu zrozumieć, za co ona odpowiada;
  • Jest kilka ról i zmienne muszą być powiązane z jedną wartością, ale to po prostu nie działa;
  • Masz trudności z wyjaśnieniem innym, jak działa logika zmiennych w twoich podręcznikach

Natrafiliśmy na te problemy na projektach w naszej firmie, w wyniku czego doszliśmy do zasad projektowania zmiennych w naszych playbookach, które w pewnym stopniu rozwiązały te problemy.

Systemowe podejście do zmiennych w Ansible

Zmienne w rolach

Rola jest odrębnym obiektem systemu wdrażania. Jak każdy obiekt systemowy, musi posiadać interfejs umożliwiający interakcję z resztą systemu. Takim interfejsem są zmienne roli.

Weźmy na przykład rolę api, który instaluje aplikację Java na serwerze. Jakie zmienne może mieć?

Systemowe podejście do zmiennych w Ansible

Role zmienne można podzielić na 2 typy w zależności od typu:

1. Свойства
    a) независимые от среды
    б) зависимые от среды
2. Связи
    a) слушатели 
    б) запросы внутри системы
    в) запросы в среду

Zmienne właściwości to zmienne określające zachowanie roli.

Zmienne zapytania - są to zmienne, których wartość służy do wyznaczenia zasobów zewnętrznych w stosunku do roli.

Różni słuchacze - są to zmienne, których wartość służy do tworzenia zmiennych żądania.

Natomiast 1a, 2a, 2b to zmienne niezależne od środowiska (sprzęt, zasoby zewnętrzne itp.) i w roli defaults można je wypełnić wartościami domyślnymi. Niemożliwe jest jednak wypełnienie zmiennych typu 1.b i 2.c wartościami innymi niż „przykład”, ponieważ będą one zmieniać się ze stanowiska na stanowisko w zależności od środowiska.

Styl kodu

  • Nazwa zmiennej musi zaczynać się od nazwy roli. Ułatwi to w przyszłości zorientowanie się, jaką rolę pełni dana zmienna i za co odpowiada.
  • Używając zmiennych w rolach należy pamiętać o zachowaniu zasady enkapsulacji i wykorzystywać zmienne zdefiniowane albo w samej roli, albo w rolach, od których aktualna jest zależna.
  • Unikaj używania słowników dla zmiennych. Ansible nie pozwala na wygodne nadpisywanie poszczególnych wartości w słowniku.

    Przykład złej zmiennej:

    myrole_user:
        login: admin
        password: admin

    Tutaj login jest zmienną niezależną, a hasło jest zmienną zależną. Ale
    ponieważ są one połączone w słownik, będziesz musiał go całkowicie określić
    Zawsze. Co jest bardzo niewygodne. Lepiej tą drogą:

    myrole_user_login: admin
    myrole_user_password: admin

Zmienne w podręcznikach wdrażania

Kompilując playbook wdrożeniowy (zwany dalej playbookiem) kierujemy się zasadą, że należy go umieścić w osobnym repozytorium. To samo co role: każda w swoim własnym repozytorium git. Pozwala to zrozumieć, że role i podręcznik są różnymi, niezależnymi obiektami systemu wdrażania, a zmiany w jednym obiekcie nie powinny wpływać na działanie drugiego. Osiąga się to poprzez zmianę domyślnych wartości zmiennych.

Podsumowując, podczas kompilowania playbooka możliwe jest nadpisanie domyślnych wartości zmiennych roli w dwóch miejscach: w zmiennych playbooka i w zmiennych inwentarza.

mydeploy                        # Каталог деплоя
├── deploy.yml                  # Плейбук деплоя
├── group_vars                  # Каталог переменных плейбука
│   ├── all.yml                 # Файл для переменных связи всей системы
│   └── myapi.yml               # Файл переменных свойств группы myapi
└── inventories                 #
    └── prod                    # Каталог окружения prod
        ├── prod.ini            # Инвентори файл
        └── group_vars          # Каталог для переменных инвентори
            └── myapi           #
                ├── vars.yml    # Средозависимые переменные группы myapi
                └── vault.yml   # Секреты (всегда средозависимы) *

* - Zmienne i skarbce

Różnica polega na tym, że zmienne podręcznika są zawsze używane podczas wywoływania podręczników znajdujących się na tym samym poziomie. Oznacza to, że zmienne te świetnie nadają się do zmiany domyślnych wartości zmiennych niezależnych od środowiska. I odwrotnie, zmienne asortymentowe będą używane tylko dla określonego środowiska, co jest idealne w przypadku zmiennych specyficznych dla środowiska.

Należy pamiętać, że priorytet zmiennej nie pozwoli na nadpisanie zmiennych najpierw w zmiennych podręcznika, a następnie oddzielnie w jednym zasobach.

Oznacza to, że już na tym etapie należy zdecydować, czy zmienna jest zależna od środowiska, czy nie i umieścić ją w odpowiednim miejscu.

Przykładowo w jednym projekcie zmienna odpowiedzialna za włączenie SSL była przez długi czas zależna od środowiska, gdyż na jednym ze stoisk z przyczyn od nas niezależnych nie mogliśmy włączyć SSL. Po naprawieniu tego problemu stał się on niezależny od środowiska i został przeniesiony do zmiennych podręcznika.

Zmienne właściwości dla grup

Rozbudujmy nasz model na rysunku 1, dodając 2 grupy serwerów z inną aplikacją Java, ale z różnymi ustawieniami.

Systemowe podejście do zmiennych w Ansible

Wyobraźmy sobie, jak będzie wyglądał podręcznik w tym przypadku:

- hosts: myapi
  roles:
    - api

- hosts: bbauth
  roles:
    - auth

- hosts: ghauth
  roles:
    - auth

W podręczniku mamy trzy grupy, dlatego natychmiast zaleca się utworzenie tej samej liczby plików grup w zmiennych inwentarzowych group_vars i zmiennych podręcznika. Jeden plik grupowy w tym przypadku jest opisem jednego komponentu powyższej aplikacji w playbooku. Kiedy otworzysz plik grupy w zmiennych podręcznika, natychmiast zobaczysz wszystkie różnice w stosunku do domyślnego zachowania ról zainstalowanych w grupie. W zmiennych inwentarzowych: różnice w zachowaniu grupy w zależności od stanowiska.

Styl kodu

  • Staraj się w ogóle nie używać zmiennych host_vars, ponieważ nie opisują one systemu, a jedynie szczególny przypadek, co w przyszłości będzie prowadzić do pytań: „Dlaczego ten host różni się od pozostałych?”, na które odpowiedź nie jest zawsze łatwo znaleźć.

Zmienne komunikacyjne

Jednakże o to właśnie chodzi w zmiennych właściwości, ale co ze zmiennymi komunikacyjnymi?
Różnica polega na tym, że powinny mieć to samo znaczenie w różnych grupach.

Na początku tak było pomysł użyj potwornej konstrukcji, takiej jak:
hostvars[groups['bbauth'][0]]['auth_bind_port'], ale natychmiast je odrzucili
bo ma wady. Po pierwsze, objętość. Po drugie, zależność od konkretnego gospodarza w grupie. Po trzecie, przed rozpoczęciem wdrożenia konieczne jest zebranie faktów ze wszystkich hostów, jeśli nie chcemy, aby wystąpił błąd niezdefiniowanej zmiennej.

W rezultacie zdecydowano się na wykorzystanie zmiennych komunikacyjnych.

Zmienne komunikacyjne - są to zmienne należące do playbooka i potrzebne do połączenia obiektów systemowych.

Zmienne komunikacyjne są wypełniane w ogólnych zmiennych systemowych group_vars/all/vars i powstają poprzez usunięcie wszystkich zmiennych słuchacza z każdej grupy i dodanie na początku zmiennej nazwy grupy, z której słuchacz został usunięty.

Zapewnia to jednolitość i brak nakładania się nazw.

Spróbujmy powiązać zmienne z powyższego przykładu:

Systemowe podejście do zmiennych w Ansible

Wyobraźmy sobie, że mamy zmienne zależne od siebie:

# roles/api/defaults:
# Переменная запроса
api_auth1_address: "http://example.com:80"
api_auth2_address: "http://example2.com:80"

# roles/auth/defaults:
# Переменная слушатель
auth_bind_port: "20000"

Umieśćmy to we wspólnych zmiennych group_vars/all/vars wszystkich słuchaczy i dodaj nazwę grupy do tytułu:

# group_vars/all/vars
bbauth_auth_bind_port: "20000"
ghauth_auth_bind_port: "30000"

# group_vars/bbauth/vars
auth_bind_port: "{{ bbauth_auth_bind_port }}"

# group_vars/ghauth/vars
auth_bind_port: "{{ ghauth_auth_bind_port }}"

# group_vars/myapi/vars
api_auth1_address: "http://{{ bbauth_auth_service_name }}:{{ bbauth_auth_bind_port }}"
api_auth2_address: "http://{{ ghauth_auth_service_name }}:{{ ghauth_auth_bind_port }}"

Teraz zmieniając wartość konektora będziemy mieli pewność, że żądanie trafi w to samo miejsce, w którym znajduje się port.

Styl kodu

  • Ponieważ role i grupy są różnymi obiektami systemowymi, muszą mieć różne nazwy, wówczas zmienne łączące będą dokładnie wskazywać, że należą one do określonej grupy serwerów, a nie do roli w systemie.

Pliki zależne od środowiska

Role mogą używać plików różniących się w zależności od środowiska.

Przykładem takich plików są certyfikaty SSL. Przechowuj je w formie tekstowej
w zmiennej nie jest zbyt wygodne. Ale wygodnie jest przechowywać ścieżkę do nich w zmiennej.

Na przykład używamy zmiennej api_ssl_key_file: "/path/to/file".

Ponieważ jest oczywiste, że certyfikat klucza będzie się zmieniać w zależności od środowiska, jest to zmienna zależna od środowiska, co oznacza, że ​​powinna znajdować się w pliku
group_vars/myapi/vars spis zmiennych i zawierać wartość „na przykład”.

Najwygodniejszym sposobem w tym przypadku jest umieszczenie pliku klucza w repozytorium playbooków wzdłuż ścieżki
files/prod/certs/myapi.key, wówczas wartość zmiennej będzie wynosić:
api_ssl_key_file: "prod/certs/myapi.key". Wygoda polega na tym, że osoby odpowiedzialne za wdrożenie systemu na konkretnym stanowisku mają także własną dedykowaną przestrzeń w repozytorium do przechowywania swoich plików. Jednocześnie pozostaje możliwość podania bezwzględnej ścieżki do certyfikatu na serwerze, w przypadku, gdy certyfikaty są dostarczane przez inny system.

Wiele stoisk w jednym środowisku

Często istnieje potrzeba wdrożenia kilku niemal identycznych stanowisk w tym samym środowisku z minimalnymi różnicami. W tym przypadku zmienne zależne od środowiska dzielimy na te, które nie zmieniają się w tym środowisku i te, które się zmieniają. A te ostatnie przenosimy bezpośrednio do samych plików inwentarzowych. Po tej manipulacji możliwe staje się utworzenie kolejnego inwentarza bezpośrednio w katalogu środowiska.

Ponownie użyje zasobów group_vars, a także będzie w stanie przedefiniować niektóre zmienne bezpośrednio dla siebie.

Ostateczna struktura katalogów dla projektu wdrożenia:

mydeploy                        # Каталог деплоя
├── deploy.yml                  # Плейбук деплоя
├── files                       # Каталог для файлов деплоя
│   ├── prod                    # Католог для средозависимых файлов стенда prod
│   │   └── certs               # 
│   │       └── myapi.key       #
│   └── test1                   # Каталог для средозависимых файлов стенда test1
├── group_vars                  # Каталог переменных плейбука
│   ├── all.yml                 # Файл для переменных связи всей системы
│   ├── myapi.yml               # Файл переменных свойств группы myapi
│   ├── bbauth.yml              # 
│   └── ghauth.yml              #
└── inventories                 #
    ├── prod                    # Каталог окружения prod
    │   ├── group_vars          # Каталог для переменных инвентори
    │   │   ├── myapi           #
    │   │   │   ├── vars.yml    # Средозависимые переменные группы myapi
    │   │   │   └── vault.yml   # Секреты (всегда средозависимы)
    │   │   ├── bbauth          # 
    │   │   │   ├── vars.yml    #
    │   │   │   └── vault.yml   #
    │   │   └── ghauth          #
    │   │       ├── vars.yml    #
    │   │       └── vault.yml   #
    │   └── prod.ini            # Инвентори стенда prod
    └── test                    # Каталог окружения test
        ├── group_vars          #
        │   ├── myapi           #
        │   │   ├── vars.yml    #
        │   │   └── vault.yml   #
        │   ├── bbauth          #
        │   │   ├── vars.yml    #
        │   │   └── vault.yml   #
        │   └── ghauth          #
        │       ├── vars.yml    #
        │       └── vault.yml   #
        ├── test1.ini           # Инвентори стенда test1 в среде test
        └── test2.ini           # Инвентори стенда test2 в среде test

Podsumowując

Po uporządkowaniu zmiennych zgodnie z artykułem: każdy plik zmiennych odpowiada za określone zadanie. A ponieważ plik ma określone zadania, możliwe stało się przypisanie osoby odpowiedzialnej za poprawność każdego pliku. Przykładowo za prawidłowe uzupełnienie zmiennych playbooka staje się twórca wdrożenia systemu, natomiast za wypełnienie inwentarza zmiennych bezpośrednio odpowiedzialny jest administrator, którego stanowisko jest opisane w inwentarzu.

Role stały się własną jednostką programistyczną z własnym interfejsem, umożliwiając programiście roli rozwijanie możliwości zamiast dostosowywania roli do systemu. Problem ten szczególnie dotyczył wspólnych ról dla wszystkich systemów w kampanii.

Administratorzy systemu nie muszą już rozumieć kodu wdrożenia. Wszystko, co jest od nich wymagane do pomyślnego wdrożenia, to wypełnienie plików zmiennych zależnych od środowiska.

literatura

  1. Dokumentacja

autor

Kaliużny Denis Aleksandrowicz

Źródło: www.habr.com

Dodaj komentarz