Nachdem wir uns bereits eingehend mit den Grundlagen, mit der Installation von Ansible und auch schon mit der Grundkonfiguration beschäftigt sowie erste Erfahrungen mit Playbooks gesammelt haben, wollen wir uns nun mit der tiefer gehenden Konfiguration von Ansible beschäftigen. Ein wesentlicher Punkt, den wir bei unseren Überlegungen im Kapitel Ansible - Erweiterte Konfigurationsbeispiele angestellt hatten, war unter anderem Woher beziehen wir die Hostdefinitionen und deren Eigenschaften?.
Klar ist natürlich dass sich da eine Hobby-mässige Installation mit einer Hand voll Geräten sich wohl erheblich von einer professionellen Umgebung mit hunderten oder gar mehr Hosts unterscheiden wird. Bei ersteren wird man vermutlich alle Information bestmöglich versuchen z.B. in einer Datei oder in einem Verzeichnis vor zuhalten. Bei grösseren Installationen wird man hingegen eine CMDB1) zurück greifen und dort die Informationen pflegen, wie z.B.:
Egal was wir als Basis verwenden, Ziel sollte immer sein aus den aktuell gepflegten Daten alle Informationen so für Ansible aufzubereiten, dass alle relevanten Daten, die zum Abarbeiten eines Playbooks benötigt werden, auch zur Verfügung stehen.
Zur Verwaltung/Inventarisierung von mehreren Knoten oder Hosts in unserer Infrastrukturumgebung verwendet Ansible Listen oder eine Gruppe von Listen, die man als Inventory bezeichnet. Im einfachsten Fall beschreibt solch eine Inventory-Datei lediglich Hosts und/oder deren Hostnamen bzw. IP-Adressen. Auf der anderen Seite kann man aber auch komplexe Landschaften abbilden und Eigenschaften einzelnen Hosts und /oder Gruppe zuordnen.
Im Kapitel (Grund-)Konfiguration - /etc/ansible/hosts hatten wir uns schon einmal kurz mit dem Thema Inventory beschäftigt. In den nachfolgenden Beispielen wollen wir nun intensiver auf Details dazu eingehen.
Im Kapitel How to build your inventory der Ansible-Onlinedoku finden sich dazu viele tiefergehende Informationen.
In der Ansible-Konfigurationsdatei /etc/ansible/ansible.cfg
zeigt zu Beginn der default-Eintrag auf die vorgenannte Datei /etc/ansible/hosts
.
$ less /etc/ansible/ansible.cfg
... [defaults] # some basic default values... #inventory = /etc/ansible/hosts ...
Unsere eigene Konfigurationsdatei für die Hosts wollen wir aber künftig unserer Ansible-Administationsumgebung beim jeweiligen Admin-Users vorhalten. Im Beispiel unseres Adminusers django wäre dies entsprechend der Pfad /home/django/ansible/inventories/production
. Das entsprechende Ansible: Directory Layout haben wir ja mit Hilfe unsere Ansible-Playbooks zur initialen Einrichtung bereits erfolgreich angelegt.
In unserer benutzerspezifischen Ansible-Konfigurationsdatei wird auch bereits auf das entsprechende Ziel verwiesen.
$ less ~/.ansible.cfg
some basic default values... # Generated by Ansible on 2022-09-22, do not edit manually! # default: #inventory = /etc/ansible/hosts inventory = /home/django/ansible/inventories/production
Dort legen wir uns unsere erweiterte Host-Datei im YAML-Format an.
$ vim ~/ansible/inventories/production/hosts.yml>
--- #YAML start syntax (optional) all: hosts: # nicht zugeordnete Hosts n3r0.intra.nausch.org: g33k.intra.nausch.org: children: # Untermenge/-gruppe aller Hosts centos8: # Gruppe der definierten CentOS 8 Hosts vars: # Variablen, die für die ganze Gruppe gelten ansible_ssh_port: 22 ansible_ssh_user: ansible ansible_ssh_private_key_file: /home/django/.ssh/id_ed25519_ansible hosts: # Liste aller Hosts die dieser gruppe zugehören www8.dmz.nausch.org: ansible_ssh_host: 10.0.0.90 # Hostspezifische Ansible-Systemvariable centos7: # Gruppe der definierten CentOS 7 Hosts vars: # Variablen, die für die ganze Gruppe gelten ansible_ssh_port: 22 ansible_ssh_user: ansible ansible_ssh_private_key_file: /home/django/.ssh/id_rsa_ansible hosts: # Liste aller Hosts die dieser gruppe zugehören www7.dmz.nausch.org: ansible_ssh_host: 10.0.0.97 # Hostspezifische Ansible-Systemvariable # optische Abtrennung zu nachfolgenden Definitionen ffmucgluon: # Definition der Gruppe aaler Freifunk Knoten/Hosts vars: # Variablen, die für die ganze Gruppe gelten ansible_ssh_port: 22 ansible_ssh_user: root ansible_ssh_private_key_file: /home/django/.ssh/id_rsa4096_freifunk_2014 contact_info: 'Django [BOfH] | django@nausch.org | chat -> @django' hosts: # Liste aller Hosts die dieser gruppe zugehören ff_pliening_gbw_ug: # Host hostname: ff_pliening_gbw_ug # Hostspezifische Informationen / Variablen latitude: -48.19861319429455 longitude: -168.2017571420684 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: TP-Link TL-WDR4300 v1 ansible_ssh_host: 2001:608:a01:102:32b5:c2ff:fe56:62b1 ff_pliening_gbw_egod: # Host hostname: ffplieninggbwegod # Hostspezifische Informationen / Variablen pretty_hostname: ff_pliening_gbw_egod latitude: 48.198652080 longitude: 11.797969940 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: Ubiquiti UniFi-AC-MESH ansible_ssh_host: 2001:608:a01:102:1ae8:29ff:fea9:22ed ff_pliening_gbw_ogod: # Host hostname: ffplieninggbwogod # Hostspezifische Informationen / Variablen pretty_hostname: ff_pliening_gbw_ogod latitude: 48.198699460 longitude: 11.798053090 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: Ubiquiti UniFi-AC-MESH ansible_ssh_host: 2001:608:a01:102:1ae8:29ff:fec0:aaae ff_pliening_gbw_dgod: # Host hostname: ffplieninggbwdgod # Hostspezifische Informationen / Variablen prettyhostname: ff_pliening_gbw_dgod latitude: 48.198671230 longitude: 11.798122820 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: Ubiquiti UniFi-AC-MESH ansible_ssh_host: 2001:608:a01:102:1ae8:29ff:fec6:c8eb ff_pliening_gbw_cpod: # Host hostname: ffplieninggbwcpod # Hostspezifische Informationen / Variablen pretty_hostname: ff_pliening_gbw_cpod latitude: 48.198726280 longitude: 11.798159030 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: Ubiquiti UniFi-AC-MESH ansible_ssh_host: 2001:608:a01:102:1ae8:29ff:fec6:c8dd ff_pliening_gbw_kvm_ol: # Host hostname: ffplieninggbwkvmol # Hostspezifische Informationen / Variablen pretty_hostname: ff_pliening_gbw_kvm_ol latitude: 48.198646720 longitude: 11.798047720 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: Red Hat KVM ansible_ssh_host: 2001:608:a01:102:5054:ff:fe9e:b358 ... #YAML end syntax
Die YAML-Konfigurationsdatei enthält entsprechende Bemerkungen, die die einzelnen Blöcke und Funktionen ausreichend beschreiben. Wie können so individuelle und Anwendungsspezifische Lösungen abbilden.
Natürlich gilt zu bedenken, dass das gezeigte Beispiel mit nur 8 Hosts und überschaubaren Variablen doch schon recht umfangreich geworden ist, wenn dies alles in eine Inventory-Datei gepackt wird. Bei Dutzenden oder Hunderten von Maschinen wird dies dann daraus schwer zu handeln - von verschachtelten Gruppen in Gruppen, oder Hosts die Mitglied in mehreren Gruppen sein sollen, sprechen wir dann besser gar nicht.
Wollen wir später auch noch vertrauliche Informationen in einzelnen vault
-Dateien vorhalten werden wir mit einer derartigen Inventarisierung Grenzen stossen!
Hier werden wir später auf eine andere/aufgeteilte Lösung einschwenken müssen.
Zum Testen, ob die definierten Hosts in unserem inventory-File auch erreichbar ist, können wir nun z.B. mit dem Befehl ansible -m ping all
überprüfen.
www7.dmz.nausch.org | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python2.7" }, "changed": false, "ping": "pong" } ... www8.dmz.nausch.org | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.0.0.90 port 22: No route to host", "unreachable": true } ...
Wie schon im vorhergehenden Beispiel angemerkt, wird es bei einer grösseren Anzahl von Hostdefinitionen mit umfangreichen oder verschachtelten (Gruppe in Gruppe) Gruppenzugehörigkeiten mit verschiedensten zugeordneten Variablen sehr schnell „schmutzig“. All das in einer ein zigsten Konfiguration abbilden zu wollen, wird man vermutlich in die Ecke „Hang zu masochistischen Tendenzen“ oder Selbstgeisselung eines Admins einordnen - kurzum hier muss ein anderer Lösungsansatz her!
In dem nachfolgenden Konfigurationsbeispiel sehen wir uns eine kleinere Installation an, die zwar nicht genau dem Kriterien „gross“, aber an Hand dieses Beispiels lässt sich das grundsätzliche Struktur und der Umgang mit verschachtelten Gruppen anschaulich erklären. Wir gehen bei diesem Beispiel von einer mittleren Freifunk-Installation mit folgenden unterschiedlichen Systemkomponenten und -eigenschaften aus. aus:
In unserem Konfigurationsbeispiel haben wir folgende Komponenten und (Betriebs-)Systeme im Einsatz, woraus sich unterschiedliche Gruppenkonstellationen ergeben, die im Betrieb zum Tragen kommen bzw. verwendet werden.
ffmuc
:olall
:gluonall
aller Gluon-Systemeolfutro
der Offloader mit Futro-Hardware:ololkvm
der KVM virtualisierten Offloader:olgluon
aller Offloader mit Gluonoldeb
aller Offloader mit Debianall
aller Nodes:Nachfolgendes Schaubild visualisiert die einzelnen Gruppen und die entsprechenden Überlappungen.
Die Inventory-Datei inventory.yml
unseres Beispiels legen wir im Arbeitsverzeichnis unseres Ansible-Administrationshost ab.
$ vim ~/ansible/inventories/production/inventory.yml
--- #Inventory - YAML syntax all: children: ffmuc: hosts: ff_pliening_gbw_cpod: ff_pliening_gbw_dgod: ff_pliening_gbw_egod: ff_pliening_gbw_ogod: ff_pliening_gbw_ug: ff_roding_as_nausch: oldeb: hosts: ff_pliening_gbw_client: ff-django-raspi: olfutro: hosts: ff_pliening_gbw_futro_mesh: olkvm: hosts: ff_pliening_gbw_kvm_ol: olgluon: children: olkvm: olfutro: olall: children: oldeb: olgluon: gluonall: children: ffmuc: olgluon: ffmucall: children: ffmuc: olall: ... #YAML end syntax
Die Datei ist relativ übersichtlich und doch recht einfach zu verstehen. Würden wir nun aber die Host- und Gruppenspezifischen Variablen mit in die Datei aufnehmen, wäre dies jedoch gänzlich anders. Dabei haben wir hier nur 10 Hosts und noch keine 100 oder noch mehr.
Ansible bietet daher einen skalierbareren Ansatz, um den Überblick über Host- und Gruppenvariablen zu behalten. Man kann für jeden Host und jede Gruppe die Variablen in einer jeweils zugehörigen Datei auslagern. Ansible wir beim Aufruf und Abarbeiten eines Playbooks diese hostspezifischen Dateien im Verzeichnis host_vars
und die gruppenspezifischen Variablen im Verzeichnis group_vars
. Diese Verzeichnisse werden von Ansible entweder im dem Verzeichnis erwartet, in dem das aufgerufene Playbook gespeichert wurde oder alternativ dazu im Verzeichnis in dem das die Inventory-Datei gespeichert wurde.
Diese beiden Verzeichnisse wurden bereits bei der initialen Konfiguration unserer Ansible-Umgebung mit Hilfe des Ansible-Playbooks ~/ansible/playbooks/ansible_grundconfig_v2.yml
angelegt.
$ tree inventories/production/ -d
inventories/production/ ├── group_vars │ └── all └── host_vars
Je Gruppe speichern wir also eine individuelle Datei im Verzeichnis ~/ansible/inventories/production/group_vars
ab.
$ vim ~/ansible/inventories/production/group_vars/ffmuc.yml
ffmuc: ansible_ssh_port: 22 ansible_ssh_user: root ansible_ssh_private_key_file: /home/django/.ssh/id_rsa4096_freifunk_2014.pub contact_info: 'Django [BOfH] | django@nausch.org | chat -> @django'
Für die anderen Gruppe(n) verfahren wie ebenso.
Je Host speichern wir dann jeweils eine individuelle Datei im Verzeichnis ~/ansible/inventory/hosts_vars
ab.
$ vim ~/ansible/inventories/production/host_vars/ff_pliening_gbw_egod.yml
ff_pliening_gbw_egod: hostname: ffplieninggbwegod pretty_hostname: ff_pliening_gbw_egod latitude: 48.198652080 longitude: 11.797969940 branch: stable domain: ffmuc_muc_ost director: ffmuc_muc_ost modell: Ubiquiti UniFi-AC-MESH ansible_ssh_host: 2001:608:a01:102:1ae8:29ff:fea9:22ed
Auch für die anderen Hosts legen wir entsprechende Dateien mit den Variablen an.
Nun haben alle unsere eigenen definierten Freifunk Knoten haben nun auf der Freifunk München Karte die aktualisierten GeoDaten. Zum anderen ist der Administrative Aufwand entsprechend überschaubar, so könnte man erst einmal den Stand der „Ermittlungen“ zusammenfassen.
Zum Thema administrativer Aufwand wollen wir uns kurz noch ein kleines Rechenbeispiel ansehen. Nehmen wir mal an, wie hätten eine etwas grössere Installation mit folgenden Eckdaten:
Bei durchschnittlich 15 Variablen pro Host und 5 Variablen pro Gruppe ergäbe das eine Inventory-Datei mit
ergäbe dies eine Inventory-Datei mit 1.350 Zeilen.
Wir sehen, dass sich die Pflege hier doch sehr schnell zu einer doch erheblichen Herausforderung auswachsen wird! Ein weiteres Thema ist, dass YML-Files bedingt durch Ihre Struktur zwar für den Menschen recht gut zu lesen und auch zu bearbeiten sind.
Aber wenn man solch eine Datei aus einer CMDB automatisiert erzeugen möchte, klappt dies schon nicht mehr so einfach, wie anfänglich angenommen. Wir werden uns daher im nächsten Beispiel eine doch geeignetere Variante ansehen.
Wie schon im vorhergehenden Beispiel angemerkt, wird es zum einen bei professionellen Umgebungen, nicht mehr praktikabel pflegbar sein, alle Informationen in einer YML-Datei mit verschiedenen Host- oder Gruppen basierenden Variablen-Definitionen versuchen vor zuhalten. Der Export und das automatische Generieren von Inventory-Daten in strukturierter bash
-Notation eignet sich hier wesentlich besser!
Bei der Grundkonfiguration unserer Ansible-Umgebung mit Hilfe des Ansible-Playbooks ~/ansible/playbooks/ansible_grundconfig_v2.yml
wurde bereits das entsprechend benötigte Ansible: Directory Layout automatisch angelegt.
inventories/production/ ├── group_vars │ └── all └── host_vars
Wir können nun entweder eine Inventory-Datei hosts
im betreffenden Verzeichnis manuell anlegen oder eben aus unserer CMDB scriptiert erstellen lassen. Nachfolgend sehen wir, wie solch eine Inventory Datei aufgebaut und strukturiert sein könnte. Das exemplarische Beispiel ist hier entsprechend gekürzt und die Stellen mit Hilfe von ...
markiert wiedergegeben„
$ less ~/ansible/inventories/production/hosts
# Generiert mit Hilfe von Ansible am 2022-09-20 - diese Datei nicht manuell bearbeiten! # Inventory Datei für die System-Umgebung bei nausch.org # # Hinweise: # Kommentare beginnen mit einem '#'-Zeichen # leere Zeilen werden ignoriert # Host- und Gruppendefinitionen werden mit [] abgegrenzt # Hosts können über ihren Hostnamen, FQN oder ihrer IP-Adresse definiert # übergeordnete Gruppen werden durch [:children] abgegrenzt # # Host-Definitionen # Hosts ohne Gruppenzuordnung localhost [intranet] pml010002 pml010003 pml010004 ... ... pml010124 pml010125 pml010126 [IDMZ] vml030010 vml030020 vml030030 vml030040 ... ... vml030230 vml030240 vml030250 [EDMZ] vml050010 vml050020 vml050030 vml050040 vml050250 [TKDMZ] vml070010 vml070020 vml070030 [external] customer_no_001 customer_no_002 ... ... customer_no_042 [gluon] ff_pliening_gbw__ug_ ff_pliening_gbw_egod ff_pliening_gbw_ogod ff_pliening_gbw_dgod ff_pliening_gbw_cpod ff_roding_fwg_nausch [raspbian] ff_pliening_rpb4_ol_v6 # Host-Gruppen-Definitionen # (zu welcher Gruppe gehören Untergruppen bzw. Hosts) [freifunk:children] gluon raspbian [linux:children] intranet IDMZ EDMZ TKDMZ external
Die betreffenden Host- bzw. Gruppenspezifischen Variablen halten wir hier in entsprechenden Dateien bzw. Unterverzeichnissen vor, wie z.B. die automatisch erzeugte Datei ansible_environment.ym
mit den Ansible Konfiguration zur Rechteerweiterung.
$ less inventories/production/group_vars/all/ansible_environment
# Generated by Ansible on 2022-09-22, do not edit manually! ansible_become: True ansible_become_method: sudo ansible_become_user: root ansible_become_ask_pass: False
Oder z.B. die Definitionen für eine bestimmte Gruppe.
$ less inventories/production/group_vars/IDMZ/ssh_environment
# Generated by Ansible on 2022-09-22, do not edit manually! ssh_port: 22 ssh_user: django ssh_protocol: 2 ssh_keyfile: ~/.ssh/id_idmz
Das Beispiel hier zeigt die Host-spezifischen Variablen eines Hosts im Intranet.
$ less inventories/production/host_vars/pml111002
# Generated by Ansible on 2022-09-20, do not edit manually! host_alias: printer host_mac: "84:3a:de:ad:be:ef" host_ipv4: "10.111.0.2" host_ipv6: "::1" host_sshjump: "vml070010"
Unser Inventory hat nun in etwa nachfolgenden strukturellen Aufbau, bei dem auch wieder die entsprechend gekürzt und die Stellen mit Hilfe von ...
markiert wurden.
inventories/production/ ├── group_vars │ ├─── all │ | ├── ansible_environment │ | ├── ssh_environment │ | └── vault | ├─── IDMZ | | └── ssh_environment | ├─── EDMZ | | └── ssh_environment | ├─── TKDMZ | | └── ssh_environment | └─── external │ ├── ssh_environment | └── vault ├── hosts └── host_vars ├── ff_pliening_gbw_cpod ├── ff_pliening_gbw_dgod ├── ff_pliening_gbw_egod ├── ff_pliening_gbw_ogod ├── ff_pliening_gbw__ug_ ├── ff_pliening_rpb4_ol_v6 ├── ff_roding_fwg_nausch ├── pml111002 ├── pml111003 ├── pml111004 ... ... ├── pml010124 ├── pml010125 ├── pml010126 ├── vml030010 ├── vml030020 │ ├── dhcpd │ ├── named │ └── hostconfig ├── vml030030 ├── vml030040 ... ... ├── vml030230 ├── vml030240 ├── vml030250 ├── vml050010 ├── vml050020 │ ├── vhosts │ └── hostconfig ├── vml000030 │ ├── exporter │ ├── github │ └── hostconfig ├── vml050030 ├── vml050040 │ ├── prometheus │ ├── grafana │ ├── apache │ └── hostconfig ├── vml050250 ├── vml070010 ├── vml070020 ├── vml070030 ├── customer_no_001 ├── customer_no_002 ... ... └── customer_no_002
Wir haben nun eine standardisiertes Inventory. Die hilft uns zum einen bei der ggf. manuellen Pflege, da Informationen strukturiert immer an definierten Stellen stehen. Dies verlangt natürlich den Admins einiges an Disziplin ab, aber so können sich alle darauf verlassen, dass benötigte Informationen und Konfigurationsoptionen an den gleichen Stellen stehen (sollten).
Pflegt man die Inventory-Daten hingegen über eine (WEB)UI einer CMBD-Anwendung und generiert dann automatisiert die entsprechenden Inventory-Daten, ist dann auch sicher gestellt, dass alle Daten wirklich gleich strukturiert und an den vorgesehenen Stellen stehen. Wie das von Statten geht, werden wir uns später noch eingehender ansehen.