Systematyczne podejście do zmiennych w Ansible

ansible devops kodowanie

Cześć! Mam na imię Denis Kaljużny Pracuję jako inżynier w dziale automatyzacji procesów deweloperskich. Każdego dnia nowe wersje aplikacji są wdrażane na setkach serwerów firmy. W tym artykule dzielę się doświadczeniem w korzystaniu z Ansible w tym celu.

Ten przewodnik proponuje sposób organizacji zmiennych w procesie wdrożenia. Jest przeznaczony dla tych, którzy już używają ról w swoich playbookach i czytali BestPractices, ale napotykają na podobne problemy:

  • Znalezienie zmiennej w kodzie nie pozwala od razu zrozumieć, za co ona odpowiada;
  • Jest kilka ról, a zmienne trzeba połączyć jedną wartością, ale to nie działa;
  • Pojawiają się trudności w wyjaśnieniu innym, jak zorganizowana jest logika zmiennych w Twoich playbookach.

Z tymi problemami spotkaliśmy się w projektach w naszej firmie, co doprowadziło nas do ustalenia zasad formułowania zmiennych w naszych playbookach, które w pewnym stopniu rozwiązały te problemy.

Systematyczne podejście do zmiennych w Ansible

Zmienna w rolach

Rola to odrębny obiekt systemu wdrożeniowego. Jak każdy obiekt w systemie, powinna mieć interfejs komunikacji z innymi częściami systemu. Takim interfejsem są zmienne roli.

Weźmy na przykład rolę api, która instaluje aplikację Java na serwerze. Jakie zmienne mogą być w niej używane?

Systematyczne podejście do zmiennych w Ansible

Zmienne roli można podzielić na 2 rodzaje według typu:

1. Atrybuty
    a) niezależne od środowiska
    b) zależne od środowiska
2. Powiązania
    a) obserwatory
    b) zapytania wewnętrzne w systemie
    c) zapytania do środowiska

Atrybuty zmienne — to zmienne, które definiują zachowanie roli.

Zmienne zapytania — to zmienne, których wartość służy do określenia zasobów zewnętrznych w odniesieniu do roli.

Zmienne obserwatory — to zmienne, których wartość jest używana do tworzenia zmiennych zapytania.

Z drugiej strony, 1a, 2a, 2b — to zmienne, które nie zależą od środowiska (sprzęt, zasoby zewnętrzne itp.) i mogą być wypełnione wartościami domyślnymi w 'defaults' roli. Jednak zmienne typu 1b i 2c można wypełnić innymi wartościami niż 'example' tylko w przypadku, gdy zmieniają się one z instalacji na instalację w zależności od środowiska.

Styl kodu

  • Nazwa zmiennej musi zaczynać się od nazwy roli. Dzięki temu w przyszłości łatwo będzie zrozumieć, z jakiej roli pochodzi zmienna i za co ona odpowiada.
  • Kiedy używasz zmiennych w rolach, musisz ściśle przestrzegać zasady enkapsulacji i używać zmiennych zdefiniowanych albo w samej roli, albo w rolach, od których zależy obecna.
  • Staraj się nie używać słowników do zmiennych. Ansible nie pozwala na wygodne nadpisywanie pojedynczych wartości w słowniku.

    Przykład złej zmiennej:

    myrole_user:
        login: admin
        password: admin

    Tutaj login to zmienna niezależna, a password to zmienna zależna. Ale
    ponieważ są one złączone w słownik, musisz zawsze podawać ją w całości
    co jest bardzo niewygodne. Lepiej tak:

    myrole_user_login: admin
    myrole_user_password: admin

Zmienne w playbookach wdrożeniowych

Przy tworzeniu playbooka wdrożeniowego (dalej playbook) stosujemy zasadę, że powinien być on umieszczony w osobnym repozytorium. Podobnie jak role: każda w swoim repozytorium git. Umożliwia to zrozumienie, że role i playbook to różne niezależne obiekty systemu wdrożeniowego, a zmiany w jednym obiekcie nie powinny wpływać na działanie drugiego. Osiąga się to przez zmianę wartości domyślnych zmiennych.

Przy tworzeniu playbooka, mówiąc ogólnie, istnieje możliwość nadpisywania domyślnych wartości zmiennych roli w dwóch miejscach: w zmiennych playbooka i w zmiennych inwentarza.

mydeploy                        # Katalog wdrożenia
├── deploy.yml                  # Playbook wdrożeniowy
├── group_vars                  # Katalog zmiennych playbooka
│   ├── all.yml                 # Plik dla zmiennych dotyczących całego systemu
│   └── myapi.yml               # Plik zmiennych dla grupy myapi
└── inventories                 #
    └── prod                    # Katalog środowiska prod
        ├── prod.ini            # Plik inwentarza
        └── group_vars          # Katalog dla zmiennych inwentarza
            └── myapi           #
                ├── vars.yml    # Zmienne niezależne grupy myapi
                └── vault.yml   # Sekrety (zawsze niezależne)

* — Zmienne i Skarbczyki

Różnica polega na tym, że zmienne playbooka są używane zawsze podczas wywoływania playbooków, które znajdują się z nim na tym samym poziomie. Oznacza to, że te zmienne doskonale nadają się do zmiany domyślnych wartości zmiennych, które nie są zależne od środowiska. I odwrotnie, zmienne inwentarza będą używane tylko dla konkretnego środowiska, co jest idealne dla zmiennych zależnych od środowiska.

Ważne jest, aby zauważyć, że priorytety zmiennych nie pozwalają na nadpisanie zmiennych najpierw w zmiennych playbooka, a następnie osobno w jednym inwentarzu.

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

Na przykład w jednym projekcie zmienna odpowiedzialna za włączenie SSL przez długi czas była zależna od środowiska, ponieważ nie mogliśmy aktywować SSL z przyczyn niezależnych od nas na jednej z platform. Po usunięciu tego problemu stała się niezależna od środowiska i została przeniesiona do zmiennych playbooka.

Zmienne właściwości dla grup

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

Systematyczne podejście do zmiennych w Ansible

Przyjrzyjmy się, jak będzie wyglądał playbook w tym przypadku:

- hosts: myapi
  roles:
    - api

- hosts: bbauth
  roles:
    - auth

- hosts: ghauth
  roles:
    - auth

Mamy trzy grupy w playbooku, dlatego od razu zaleca się stworzenie tyle samo plików grupowych w group_vars zmiennych inwentarza oraz zmiennych playbooka. Jeden plik grupy w tym przypadku opisuje jeden komponent wyższej aplikacji w playbooku. Otwierając plik grupy w zmiennych playbooka, od razu widzisz wszystkie różnice w stosunku do domyślnego zachowania ról przypisanych do grupy. W zmiennych inwentarza: różnice w zachowaniu grupy od platformy do platformy.

Styl kodu

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

Zmienne typu relacja

Jednakże dotyczy to zmiennych właściwości, ale co z zmiennymi typu relacja?
Ich różnica polega na tym, że muszą mieć tę samą wartość w różnych grupach.

Na początku była idea użycia monstrualnej konstrukcji w stylu:
hostvars[groups['bbauth'][0]]['auth_bind_port'], ale od razu z niej zrezygnowano
ponieważ ma swoje wady. Po pierwsze, rozdmuchanie. Po drugie, zależność od konkretnego hosta w grupie. Po trzecie, należy przed rozpoczęciem wdrożenia zebrać fakty ze wszystkich hostów, jeśli nie chcemy napotkać błąd niezdefiniowanej zmiennej.

W rezultacie zdecydowano się na użycie zmiennych typu relacja.

Zmienne typu relacja — to są zmienne, które należą do playbooka i są potrzebne do połączenia obiektów systemu.

Zmienne powiązań są wypełniane w ogólnych zmiennych systemu. group_vars/all/vars i tworzone dzięki wydobyciu wszystkich zmiennych słuchaczy z każdej grupy, a następnie dodaniu na początku zmiennej nazwy grupy, z której słuchacz został wydobyty.

W ten sposób zapewnia się jednolitość i nieprzekraczalność nazw.

Spróbujmy połączyć zmienne z przykładu powyżej:

Systematyczne podejście do zmiennych w Ansible

Załóżmy, że mamy zmienne, które od siebie zależą:

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

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

Wydobądźmy do ogólnych zmiennych group_vars/all/vars wszystkich słuchaczy i dodajmy do nazwy nazwę grupy:

# 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ść połączenia, będziemy pewni, że zapytanie będzie kierować tam, gdzie znajduje się port.

Styl kodu

  • Ponieważ role i grupy to różne obiekty systemu, muszą mieć różne nazwy, wówczas zmienne powiązań będą dokładnie pokazywać, że należą do konkretnej grupy serwerów, a nie do roli w systemie.

Zależne od środowiska pliki

W rolach mogą być używane pliki, które różnią się w zależności od środowiska.

Przykładem takich plików mogą być certyfikaty SSL. Przechowywanie ich w postaci tekstowej
w zmiennej nie jest zbyt wygodne. Z kolei wygodne jest przechowywanie ścieżki do nich wewnątrz zmiennej.

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

Ponieważ oczywiste jest, że certyfikat klucza będzie się zmieniać w zależności od środowiska, to jest to zmienna zależna od środowiska, a zatem powinna znajdować się w pliku
group_vars/myapi/vars inwentarz zmiennych i zawierać wartość 'dla przykładu'.

Najwygodniej w tym przypadku umieścić plik klucza w repozytorium playbooka w ścieżce
files/prod/certs/myapi.key, wtedy wartość zmiennej będzie:
api_ssl_key_file: "prod/certs/myapi.key". Wygodność polega na tym, że osoby odpowiedzialne za wdrażanie systemu w konkretnym środowisku mają również swoje wydzielone miejsce w repozytorium na przechowywanie swoich plików. Jednocześnie pozostaje możliwość wskazania absolutnej ścieżki do certyfikatu na serwerze, na wypadek, gdyby certyfikaty były dostarczane przez inny system.

Kilka środowisk w jednym systemie

Często zachodzi potrzeba uruchomienia kilku praktycznie identycznych środowisk w tej samej przestrzeni z minimalnymi różnicami. W takim przypadku dzielimy zmienne zależne od środowiska na te, które nie zmieniają się w ramach tej przestrzeni, i te, które się zmieniają. Te ostatnie przenosimy bezpośrednio do plików inwentarza. Po tej operacji możliwe staje się stworzenie kolejnego inwentarza w katalogu środowiska.

Będzie on ponownie wykorzystywał zmienne group_vars inwentarza, a także będzie miał możliwość nadpisania niektórych zmiennych bezpośrednio dla siebie.

Ostateczna struktura katalogów dla projektu wdrożeniowego:

mydeploy                        # Katalog wdrożeniowy
├── deploy.yml                  # Playbook wdrożenia
├── files                       # Katalog dla plików wdrożeniowych
│   ├── prod                    # Katalog dla zmiennych zależnych od środowiska prod
│   │   └── certs               # 
│   │       └── myapi.key       #
│   └── test1                   # Katalog dla zmiennych zależnych od środowiska test1
├── group_vars                  # Katalog zmiennych playbooka
│   ├── all.yml                 # Plik dla zmiennych na poziomie całego systemu
│   ├── myapi.yml               # Plik zmiennych charakterystycznych dla grupy myapi
│   ├── bbauth.yml              # 
│   └── ghauth.yml              #
└── inventories                 #
    ├── prod                    # Katalog środowiska prod
    │   ├── group_vars          # Katalog dla zmiennych inwentarza
    │   │   ├── myapi           #
    │   │   │   ├── vars.yml    # Zmienne zależne od grupy myapi
    │   │   │   └── vault.yml   # Sekrety (zawsze zależne od środowiska)
    │   │   ├── bbauth          # 
    │   │   │   ├── vars.yml    #
    │   │   │   └── vault.yml   #
    │   │   └── ghauth          #
    │   │       ├── vars.yml    #
    │   │       └── vault.yml   #
    │   └── prod.ini            # Inwentarz dla środowiska prod
    └── test                    # Katalog środowiska test
        ├── group_vars          #
        │   ├── myapi           #
        │   │   ├── vars.yml    #
        │   │   └── vault.yml   #
        │   ├── bbauth          #
        │   │   ├── vars.yml    #
        │   │   └── vault.yml   #
        │   └── ghauth          #
        │       ├── vars.yml    #
        │       └── vault.yml   #
        ├── test1.ini           # Inwentarz dla środowiska test1 w przestrzeni test
        └── test2.ini           # Inwentarz dla środowiska test2 w przestrzeni test

Podsumowanie

Po uporządkowaniu zmiennych zgodnie z artykułem: każdy plik z zmiennymi odpowiada za określone zadanie. A ponieważ plik ma określone zadania, możliwe stało się wyznaczenie osoby odpowiedzialnej za poprawność każdego pliku. Na przykład, za prawidłowe wypełnienie zmiennych playbooka odpowiedzialny jest programista odpowiedzialny za wdrożenie systemu, podczas gdy za wypełnienie zmiennych inventory odpowiada bezpośrednio administrator, którego środowisko opisano w inventory.

Role stały się samodzielną jednostką rozwoju z własnym interfejsem, co umożliwiło programiście roli rozwijanie możliwości, a nie dostosowywanie roli do systemu. Szczególnie ten problem dotyczył wspólnych ról dla wszystkich systemów w firmie.

Administratorzy systemów nie muszą już zagłębiać się w kod wdrożenia. Wszystko, co od nich zależy do prawidłowego wdrożenia, to wypełnienie plików zmiennych zależnych od środowiska.

Literatura

  1. Dokumentacja

Autor

Deniś Kałużny

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster