Ansible – Metadane

Ostatnio zostałem zaproszony jako prelegent na spotkanie białostockiej grupy użytkowników Pythona – PyStok#22. Jakość dźwięku w nagraniu, niestety, nie zachwyca, stąd linkuję również do samej prezentacji. Napisałem także artykuł w którym omówiłem dobre praktyki korzystania z Ansible. Ostatnie przemyślenia, jednak, budzą we mnie obawę pozostawienia Czytelnika z niewiedzą i pewnymi niedopowiedzeniami. Zdecydowałem zatem, że warto unikać niedomówienia, choćby w imię swojego dobrego imienia. Chciałbym, zatem, naprawić błędy przeszłości i rozpocząć serię artykułów o Ansible.   Postaram się poruszać trzy aspekty:

  • dobre praktyki,
  • problemy wraz z ich rozwiązaniami,
  • przykłady użycia.

Zapraszam do pierwszego z serii artykułów o  Ansible; dzisiaj będzie mowa o wykorzystaniu metadanych.

Wprowadzenie

Zasadniczo, zaglądając do dokumentacji Ansible znajdujemy podstawy jeśli chodzi o strukturę katalogów [4] dla pojedynczej roli.

production                # inventory file for production servers
staging                   # inventory file for staging environment

group_vars/
   group1                 # here we assign variables to particular groups
   group2                 # ""
host_vars/
   hostname1              # if systems need specific variables, put them here
   hostname2              # ""

library/                  # if any custom modules, put them here (optional)
filter_plugins/           # if any custom filter plugins, put them here (optional)

site.yml                  # master playbook
webservers.yml            # playbook for webserver tier
dbservers.yml             # playbook for dbserver tier

roles/
    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  <-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  <-- handlers file
        templates/        #  <-- files for use with the template resource
            ntp.conf.j2   #  <------- templates end in .j2
        files/            #
            bar.txt       #  <-- files for use with the copy resource
            foo.sh        #  <-- script files for use with the script resource
        vars/             #
            main.yml      #  <-- variables associated with this role
        defaults/         #
            main.yml      #  <-- default lower priority variables for this role
        meta/             #
            main.yml      #  <-- role dependencies
        library/          # roles can also include custom modules
        lookup_plugins/   # or other types of plugins, like lookup in this case

    webtier/              # same kind of structure as "common" was above, done for the webtier role
    monitoring/           # ""

W dzisiejszym artykule skupimy się na dobrych praktykach i przypadkach użycia jednego pliku: roles/common/meta/main.yml.

Treść

Korzystając z narzędzia: ansible-galaxy spróbujemy wygenerować przykładową rolę i zaczerpniemy z niej metadane.

$ ansible-galaxy init role

Odczytując teraz plik: role/meta/main.yml, otrzymujemy taki oto szkic.

galaxy_info:
  author: your name
  description: your description
  company: your company (optional)

  # If the issue tracker for your role is not on github, uncomment the
  # next line and provide a value
  # issue_tracker_url: http://example.com/issue/tracker

  # Some suggested licenses:
  # - BSD (default)
  # - MIT
  # - GPLv2
  # - GPLv3
  # - Apache
  # - CC-BY
  license: license (GPLv2, CC-BY, etc)

  min_ansible_version: 1.2

  # Optionally specify the branch Galaxy will use when accessing the GitHub
  # repo for this role. During role install, if no tags are available,
  # Galaxy will use this branch. During import Galaxy will access files on
  # this branch. If travis integration is cofigured, only notification for this
  # branch will be accepted. Otherwise, in all cases, the repo's default branch
  # (usually master) will be used.
  #github_branch:

  #
  # Below are all platforms currently available. Just uncomment
  # the ones that apply to your role. If you don't see your
  # platform on this list, let us know and we'll get it added!
  #
  #platforms:
  #- name: OpenBSD
  #  versions:
  #  - all
  #  - 5.6
  #  - 5.7
  #  - 5.8
  #  - 5.9
  #  - 6.0
  #- name: Fedora
  #  versions:
  #  - all
  #  - 16
  #  - 17
  #  - 18
  #  - 19
  #  - 20
  #  - 21
  #  - 22
  #  - 23
  #  - 24
  #  - 25
  #- name: DellOS
  #  versions:
  #  - all
  #  - 10
  #  - 6
  #  - 9
  #- name: MacOSX
  #  versions:
  #  - all
  #  - 10.10
  #  - 10.11
  #  - 10.12
  #  - 10.7
  #  - 10.8
  #  - 10.9
  #- name: Synology
  #  versions:
  #  - all
  #  - any
  #- name: Junos
  #  versions:
  #  - all
  #  - any
  #- name: GenericBSD
  #  versions:
  #  - all
  #  - any
  #- name: Void Linux
  #  versions:
  #  - all
  #  - any
  #- name: GenericLinux
  #  versions:
  #  - all
  #  - any
  #- name: NXOS
  #  versions:
  #  - all
  #  - any
  #- name: IOS
  #  versions:
  #  - all
  #  - any
  #- name: Amazon
  #  versions:
  #  - all
  #  - 2013.03
  #  - 2013.09
  #  - 2016.03
  #  - 2016.09
  #- name: ArchLinux
  #  versions:
  #  - all
  #  - any
  #- name: FreeBSD
  #  versions:
  #  - all
  #  - 10.0
  #  - 10.1
  #  - 10.2
  #  - 10.3
  #  - 11.0
  #  - 8.0
  #  - 8.1
  #  - 8.2
  #  - 8.3
  #  - 8.4
  #  - 9.0
  #  - 9.1
  #  - 9.1
  #  - 9.2
  #  - 9.3
  #- name: Ubuntu
  #  versions:
  #  - all
  #  - lucid
  #  - maverick
  #  - natty
  #  - oneiric
  #  - precise
  #  - quantal
  #  - raring
  #  - saucy
  #  - trusty
  #  - utopic
  #  - vivid
  #  - wily
  #  - xenial
  #  - yakkety
  #- name: Debian
  #  versions:
  #  - all
  #  - etch
  #  - jessie
  #  - lenny
  #  - sid
  #  - squeeze
  #  - stretch
  #  - wheezy
  #- name: Alpine
  #  versions:
  #  - all
  #  - any
  #- name: EL
  #  versions:
  #  - all
  #  - 5
  #  - 6
  #  - 7
  #- name: Windows
  #  versions:
  #  - all
  #  - 2012R2
  #- name: SmartOS
  #  versions:
  #  - all
  #  - any
  #- name: opensuse
  #  versions:
  #  - all
  #  - 12.1
  #  - 12.2
  #  - 12.3
  #  - 13.1
  #  - 13.2
  #- name: SLES
  #  versions:
  #  - all
  #  - 10SP3
  #  - 10SP4
  #  - 11
  #  - 11SP1
  #  - 11SP2
  #  - 11SP3
  #  - 11SP4
  #  - 12
  #  - 12SP1
  #- name: GenericUNIX
  #  versions:
  #  - all
  #  - any
  #- name: Solaris
  #  versions:
  #  - all
  #  - 10
  #  - 11.0
  #  - 11.1
  #  - 11.2
  #  - 11.3
  #- name: eos
  #  versions:
  #  - all
  #  - Any

  galaxy_tags: []
    # List tags for your role here, one per line. A tag is
    # a keyword that describes and categorizes the role.
    # Users find roles by searching for tags. Be sure to
    # remove the '[]' above if you add tags to this list.
    #
    # NOTE: A tag is limited to a single word comprised of
    # alphanumeric characters. Maximum 20 tags per role.

dependencies: []
  # List your role dependencies here, one per line.
  # Be sure to remove the '[]' above if you add dependencies
  # to this list.%

 

Widzimy, w powyższym kodzie źródłowym, że w ramach roli jesteśmy w stanie przekazać praktycznie wszystkie niezbędne informacje wydawnicze, takie jak:

  • minimalną wersja oprogramowania Ansible,
  • licencję, w ramach której rola jest udostępniona,
  • autora, opis i firmę obsługująca rolę,
  • platformy, na które została stworzona rola.

Możemy również przekazać zależności oraz tagi, o których poniżej. Oto przykład uzupełnionego pliku z metadanymi:

---
galaxy_info:
  author: 'Kamil Boratyński'
  description: 'This role configures and starts sshd service.'
  company: '@linsec'
  license: 'GPLv3'

  min_ansible_version: '2.2'
  platforms:
    - name: 'EL'
      versions:
        - 7

  galaxy_tags:
    - 'sshd'
    - 'centos'
    - 'rhel'

dependencies: []

Tak jak widzimy, autorem jest moja skromna osoba; rola opisuje konfigurację serwera SSH, została wydana na licencji GPL w wersji trzeciej. Jedyną platformą, z którą jest kompatybilna to Linuksy z klasy serwer, to jest: RedHat oraz CentOS w wersji siódmej. Dodałem również 3 tagi, które pomogą potomnym skorzystać z publiczne dostępnej roli. Pełną rolę można zobaczyć na profilu GitHub [5].

Kilka autorskich rad, dlaczego uważam, że warto uzupełniać metadane dla ról; nawet tych wykorzystywanych prywatnie, wewnątrz organizacji.

  • Dobrze udokumentowana rola to podstawa.
  • Znajomość autora, mimo, że jest prawdopodobnie znany po dzienniku narzędzia do zarządzania kodem źródłowym, rownież jest pomocna.
  • Dla publicznych ról – nieoceniona jest wiadomość pod jakie systemy była pisana. Role generyczne to z zasady tak mnoga ilość zmiennych, że ich czytelność spada do zera.
  • Przeczytanie informacji w polu description jest dużo mniej czasochłonne niż przeczytanie zestawu zadań.

Oczywiście, cytując Clean-code [6], sama nazwa roli powinna być na tyle czytelna, żeby nieobeznany Czytelnik mógł się bez problemu poznać jej przeznaczenie

Uwaga. Ominięcie metadanych jest, rzeczywiście, do zaakceptowania dla pracy w małych zespołach, gdzie każdy członek doskonale wie nad czym pracuje i jest w stanie bez problemu przyporządkować rolę do jej zastosowania. Dla publicznych ról – potrzeba tym większa, im bardziej ogólny kod.

Zależności

Jedną z rzadziej wykorzystywanych możliwości są zależności między poszczególnymi rolami. Biorąc kod źródłowy widzimy tam następującą linijkę:

dependencies: []

Przykład wykorzystania takiej dyrektywy podaję niżej:

---
galaxy_info:
  author: 'Kamil Boratyński'
  description: 'Installs and configures docker-engine-cs.'
  company: '@linsec'
  license: 'GPLv3'

  min_ansible_version: '2.2'
  platforms:
    - name: 'EL'
      versions:
        - 7

  galaxy_tags:
    - 'docker'
    - 'docker-engine'
    - 'centos'
    - 'rhel'

dependencies:
  - 'centsible.repository-docker-engine-cs'

Powyższe metadane to plik wzięty z autorskiej roli, centsible.docker-engine-cs, służącej do instalacji silnika Docker.

Przed procesem instalacji, w katalogu z rolami poszukiwana rola centsible.repository-docker-engine-cs, która odpowiada za weryfikację dostępności repozytorium pakietów na maszynie, na której ma zostać zainstalowany silnik Docker. Obie role, choć wymagają aktualizacji wersji silnika, dostępne są na GitHub [7][8].

Role abstrakcyjne

Jednym z ciekawszych rozwiązań dla zależności są role abstrakcyjne. Powiedzmy, że mamy w infrastrukturze kilkadziesiąt aplikacji tego samego typu, ten sam stos narzędziowy, te same pakiety, te same referencje, ta sama struktura katalogów, z wyłączeniem takich szczegółów jak nazwa aplikacji. Dobrym pomysłem jest wtedy zastosowanie roli abstrakcyjnej.

roles
├── app-java-sampleapp
│   └── meta
│       └── main.yml
└── templates
    └── template-java-app
        ├── README.md
        ├── defaults
        │   └── main.yml
        ├── handlers
        │   └── main.yml
        ├── meta
        │   └── main.yml
        ├── tasks
        │   └── main.yml
        └── vars
            └── main.yml

Wtedy metadane dla aplikacji app-java-sampleapp mogłyby wyglądać następująco:

---
dependencies:
  - role: 'template-java-app'
    appname: 'sampleapp'
    appdir: '/srv/sampleapp'

Zauważmy, że cała rola aplikacyjna app-java-sampleapp zawarta jest tylko w metadanych. Żadnych dodatkowych zadań. Czysty, prosty kod. Tutaj, jednak, ważna uwaga.

Uwaga. Każda modyfikacja roli: template-java-app spowoduje modyfikację wszystkich z naszych aplikacji. Takie zastosowanie jest użyteczne tylko dla aplikacji opartych o ten sam schemat.

Zakończenie

W artykule podałem kilka przypadków użycia ról oraz pewne autorskie zadania na temat metadanych w Ansible.

Każdy wybór, każde rozwiązanie powinno być uwarunkowane rozsądnym podejściem do problemu.
Jedni powiedzą bowiem, że lepiej zmienne wpisać w playbook podczas uruchamiania roli; dla innych czytelność kodu będzie tym większa, im krótsze będą zapisy w pliku playbook.yml.

Każdy problem trzeba jasno zdefiniować. Odpowiedzieć sobie na przyszłościowe pytania dotyczące rozwoju. Automatyzacja to nie tylko proces pisania kodu i narzędzi ; to przede wszystkim proces rozumowego podejścia do rozwiązania problemu przy znajomości istniejącej i przyszłej infrastruktury.

Postscriptum:

Mam też kilka pomysłów na artykuły związane z  Ansible; jeśli Czytelnik jest zainteresowany jakimś tematem, bardzo proszę o informację w komentarzu lub e-mailem.

  • Ansible: automatyczne testowanie ról
  • Ansible: pisanie własnego modułu
  • Ansible: pisanie własnego filtra
  • Ansible: dlaczego nie jest idealne?
Postscriptum 2:

Artykuł został udostępniony również na łamach:

Referencje

[1] „YouTube”, #22PyStok: Kamil Boratyński – Anisible: Best practices Use cases. [Online]. Available: https://www.youtube.com/watch?v=-IoQc624wIg. [Udostępniono: 20-mar-2017] [Source]
[2] K. Boratyński, „SlideShare”, PyStok#22. [Online]. Dostępny: https://www.slideshare.net/KamilBoratyski/pystok22?qid=4e4264b7-b512-4113-9f67-ca1583d2a707&v=&b=&from_search=3. [Udostępniono: 20-mar-2017] [Source]
[3] K. Boratyński, „inleo.pl”, Ansible – Dobre praktyki. [Online]. Dostępny: https://lelusz.comblog/ansible-dobre-praktyki/. [Udostępniono: 20-mar-2017]
[4] I. Ansible, „Best Practices”, Directory Layout. [Online]. Dostępny: http://docs.ansible.com/ansible/playbooks_best_practices.html#directory-layout. [Udostępniono: 20-mar-2017]
[5] K. Boratyński, „centsible/role-sshd”, centsible/role-sshd. [Online]. Dostępny: https://github.com/centsible/role-sshd. [Udostępniono: 20-mar-2017]
[6] R. Martin C., Clean Code. Pearson Education, 2009.
[7] K. Boratyński, „centsible/role-repository-docker-engine-cs”, centsible/role-repository-docker-engine-cs. [Online]. Dostępny: https://github.com/centsible/role-repository-docker-engine-cs. [Udostępniono: 20-mar-2017]
[8] K. Boratyński, „centsible/role-docker-engine-cs”, centsible/role-docker-engine-cs. [Online]. Dostępny: https://github.com/centsible/role-docker-engine-cs. [Udostępniono: 20-mar-2017]