Ansible - Erweiterte Konfigurationsbeispiele

Bild: Ansible Logo

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 tiefergehenden Konfiguration von Ansible beschäftigen.

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 /oider Gruppe zuordnen.

Im Kapitel erweiterte Konfiguration - Anpassungen "Inventory hosts.yml" 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 vile tiefergehende Informationen.

In der Konfigurationsdatei /etc/ansible/hosts werden alle Hosts und Hostgruppen definiert, über die Ansible bei der Orchestrierung mit Hilfe der SSH zentral administriert werden sollen.

In dieser Konfigurationsdatei gelten folgende Rahmenparameter:

  • Kommentare beginnen mit dem Zeichen '#'.
  • Leere Zeilen werden ignoriert
  • Gruppen von Hosts werden durch [header]-Elemente abgegrenzt
  • Es können Hostnamen oder IP-Adressen verwendet werden
  • Ein Hostname/IP-Adresse kann Mitglied mehrerer Gruppen sein
 # vim /etc/ansible/hosts
/etc/ansible/hosts
#
# It should live in /etc/ansible/hosts
#
#   - Comments begin with the '#' character
#   - Blank lines are ignored
#   - Groups of hosts are delimited by [header] elements
#   - You can enter hostnames or ip addresses
#   - A hostname/ip can be a member of multiple groups
 
# Ex 1: Ungrouped hosts, specify before any group headers.
 
## green.example.com
## blue.example.com
## 192.168.100.1
## 192.168.100.10
 
# Ex 2: A collection of hosts belonging to the 'webservers' group
 
## [webservers]
## alpha.example.org
## beta.example.org
## 192.168.1.100
## 192.168.1.110
 
# If you have multiple hosts following a pattern you can specify
# them like this:
 
## www[001:006].example.com
 
# Ex 3: A collection of database servers in the 'dbservers' group
 
## [dbservers]
## 
## db01.intranet.mydomain.net
## db02.intranet.mydomain.net
## 10.25.1.56
## 10.25.1.57
 
# Here's another example of host ranges, this time there are no
# leading 0s:
 
## db-[99:101]-node.example.com

Für unsere nachfolgenden ersten Test ergänzen wir die Konfigurationsdatei um folgende Zeilen.

# Django : 2019-12-30
# alle CentOS 7 Hosts
[centos_7]
bh7.dmz.nausch.org
 
# alle CentOS 8 Hosts
[centos_8]
10.0.0.190
 
# alle CentOS-Hosts unabhängig von der Releaseversion
[centos_all]
10.0.0.190
bh7.dmz.nausch.org

Wir definieren damit folgende Hostgruppen:

  • centos_7 mit dem Host, definiert über dessen Hostname bh7.dmz.nausch.org
  • centos_8 mit dem Host, definiert über dessen IP-Adresse 10.0.0.190
  • centos_all, einer Hostgruppe, die beide zuvor definierten Hosts beinhaltet.

Mit dem Befehl ansible und der Option –list-hosts können wir nun abfragen welche Hoste in den entsprechendne Hostgruppen enthalten sind.

 # ansible --list-hosts centos_7
  hosts (1):
    bh7.dmz.nausch.org
 # ansible --list-hosts centos_8
  hosts (1):
    10.0.0.190
 # ansible --list-hosts centos_all
  hosts (2):
    10.0.0.190
    bh7.dmz.nausch.org

Im Kapitel erweiterte Konfiguration - Anpassungen "Inventory hosts.yml" hatten wir uns schon einmal kurz mit dem Thema Inventory beschäftigt.

In der Ansible-Konfigurationsdatei /etc/ansible/ansible.cfg zeigt zu Beginn der default-Eintrag auf die vorgenannte Datei /etc/ansible/hosts.

 ...

[defaults]

# some basic default values...

#inventory      = /etc/ansible/hosts
...

Unsere erweiterte YAML-Konfigurationsdatei für die Hosts wolen wir aber künftig in der Ansible-Administationsumgebung unseres Admin-Users vorhalten und pflegen. Im Beispiel unseres Adminusers django wäre dies entsprechend der Pfad /home/django/ansible/hosts.yml

Wir hinterlegen also dort, dass zukünftig die Inventory-Datei hosts.yml eingelesen und ausgewertet werden soll.

 # vim /etc/ansible/ansible.cfg
[defaults]

# some basic default values...

# Django : 2020-01-01 
# default: #inventory      = /etc/ansible/hosts
inventory      = /home/django/ansible/hosts.yml

inventory - Beispiel

Dort legen wir uns unsere erweiterte Host-Datei im YAML-Format an.

 $ vim /home/django/ansible/hosts.yml>
/home/django/ansible/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 Host die Mitglied in mehreren Gruppen sein sollen, sprechen wir dann besser gar nicht. 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 mit dem Befehl ansible -m ping all überprüfen.

playbook

Unsere gerade angelegte Inventory-Datei wollen wir nun verwenden um die Geolocation-Daten unserer Freifunk-Knoten zu organisieren. Die Geodaten sind in der obigen Inventory-Datei in den beiden Variablen latitude und longitude gespeichert - diese beiden Variablen nutzen wir nun in unserem Playbook.

 $ ~/ansible/08_set_location.yml
~/ansible/08_set_location.yml
---
- hosts: ffmucgluon
  gather_facts: False
 
  tasks:
    - name: "Set GEO location of our own ffmuc-nodes"
      #https://docs.ansible.com/ansible/latest/modules/raw_module.html
      raw: uci set gluon-node-info.@location[0]="location"; uci set gluon-node-info.@location[0].share_location="1"; uci set gluon-node-info.@location[0].latitude="{{ latitude }}" ; uci set gluon-node-info.@location[0].longitude="{{ longitude }}"; uci commit gluon-node-info

Script ausführen

Nun wollen wir unser ersten Playbook ausführen, um die Geodaten aus der Inventory-Datei auf unseren Freifunk-Knoten abzugleichen; hierzu rufen wir unser Script wie folgt auf:

 $ ansible-playbook -v 08_change_contact.yml

Using /etc/ansible/ansible.cfg as config file
BECOME password: 

PLAY [ffmucgluon] **********************************************************************************************************************

TASK [Update new contact-address on own ffmuc-nodes] ***********************************************************************************
changed: [ff_pliening_gbw_egod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fea9:22ed closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fea9:22ed closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_kvm_ol] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:5054:ff:fe9e:b358 closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:5054:ff:fe9e:b358 closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_ogod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec0:aaae closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec0:aaae closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_ug] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:32b5:c2ff:fe56:62b1 closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:32b5:c2ff:fe56:62b1 closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_dgod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8eb closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8eb closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_cpod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8dd closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8dd closed."], "stdout": "", "stdout_lines": []}
PLAY RECAP ***************************************************************************************************************************** ff_pliening_gbw_cpod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_dgod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_egod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_kvm_ol : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_ogod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_ug : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ergebnis

Alle unsere eigenen definierten Freifunk Knoten haben nun auf der Freifunk München Karte die aktualisierten GeoDaten.

Wie schon im vorhergehenden Beispiel angemerkt, wird es bei einer größeren Anzahl von Hostdefinitionen mit umfangreichen oder verschachtelten (Gruppe in Gruppe) Gruppenzugehörigkeiten mit verschiedensten zugeordneten Variablen sehr schnell „schmutzig“. All das in einer einzigsten Konfiguration abbilden zu wollen, wird man vermutlich in die Ecke „Hand 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 „groß“, 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:

  • Standard-Nodes: Dies sind einfach ausgedrückt Standard-WLAN-Access-Points1), die für Endanwender einen Zugang zum Internet bereitstellen. In einfachen Installationen spannen diese einen Tunnel in Richtung des zentralen Gateways auf. Auf diesen Nodes läuft in aller Regel eine Firmware, die auf dem Gluon-Framework sowie OpenWrt basiert.
  • Offloader: Bei entsprechenden Verkehrsmengen (Traffic und Nutzer) nutzt man meist in kleinen vermaschten Netzen sog. Offloader, die leistungsfähigere Hardware verbaut haben um einen Tunnel zum zentralen Gateway der Freifunk Community aufzuspannen. Als Offloader kommen meist x86-Hardware in Form von futro-thinclients, virtualisierte Systeme KVM oder auch Raspberry 4 Auf letzterem läuft als Betriebssystem ein spezielles Debian, auf den beiden anderen i.d.R. Gluon.

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.

  1. Gruppe aller WiFi-AccessPoints mit Gluon ffmuc:
    • ff_pliening_gbw_cpod
    • ff_pliening_gbw_dod
    • ff_pliening_gbw_egod
    • ff_pliening_gbw_ogod
    • ff_pliening_gbw_ug
    • ff_roding_as_nausch
  2. Gruppe aller Offloader olall:
    • ff_pliening_gbw_client
    • ff-django-raspi
    • ff_pliening_gbw_futro_mesh
    • ff_pliening_gbw_kvm_ol
  3. Gruppe gluonall aller Gluon-Systeme
    • ff_pliening_gbw_cpod
    • ff_pliening_gbw_dod
    • ff_pliening_gbw_egod
    • ff_pliening_gbw_ogod
    • ff_pliening_gbw_ug
    • ff_roding_as_nausch
    • ff_pliening_gbw_futro_mesh
    • ff_pliening_gbw_kvm_ol
  4. Gruppe olfutro der Offloader mit Futro-Hardware:
    • ff_pliening_gbw_futro_mesh
  5. Gruppe ololkvm der KVM virtualisierten Offloader:
    • ff_pliening_gbw_kvm_ol
  6. Gruppe olgluon aller Offloader mit Gluon
    • ff_pliening_gbw_futro_mesh
    • ff_pliening_gbw_kvm_ol
  7. Gruppe oldeb aller Offloader mit Debian
    • ff_pliening_gbw_futro_mesh
    • ff_pliening_gbw_kvm_ol
  8. Gruppe all aller Nodes:
    • ff_pliening_gbw_cpod
    • ff_pliening_gbw_dod
    • ff_pliening_gbw_egod
    • ff_pliening_gbw_ogod
    • ff_pliening_gbw_ug
    • ff_roding_as_nausch
    • ff_pliening_gbw_client
    • ff-django-raspi
    • ff_pliening_gbw_futro_mesh
    • ff_pliening_gbw_kvm_ol

Nachfolgendes Schaubild visualisiert die einzelnen Gruppen und die entsprechenden Überlappungen.

Bild: Übersicht einer möglichen (Infra)Struktur

inventory - Beispiel

Die Inventory-Datei inventory.yml unseres Beispiels legen wir im Arbeitsverzeichnis unseres Ansible-Administrationshost ab.

 $ vim ~/ansible/inventory.yml
~/ansible/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.

Wir legen also die beiden Verzeichnisse an.

 $ mkdir ~/ansible/inventory/host_vars
 $ mkdir ~/ansible/inventory/group_vars

Somit haben wir dann aktuell folgende Verzeichnisstruktur auf unserem Admin-/Ansible-Host.

.
├── authkeys
├── files
│   ├── CentOS7
│   └── CentOS8
├── includes
├── inventory
│   ├── group_vars
│   └── host_vars
├── playbooks
└── templates
    └── chrony-client

Je Gruppe speichern wir also eine individuelle Datei im Verzeichnis ~/ansible/inventory/group_vars ab.

 $ vim ~/inventory/group_vars/ffmuc.yml
~/inventory/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/inventory/host_vars/ff_pliening_gbw_egod.yml
~/ansible/inventory/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.

playbook

Die zuvor angelegte Inventory- und auch Host-/Group-Variablendateien wollen wir nun verwenden um die Geolocation-Daten unserer Freifunk-Knoten zu organisieren. Die Geodaten sind in den jeweiligen Host-Dateien in den beiden Variablen latitude und longitude gespeichert - diese beiden Variablen nutzen wir nun in unserem Playbook.

 $ ~/ansible/playbooks/09_set_location.yml
~/ansible/playbooks/09_set_location.yml
---
- hosts: ffmuc
  gather_facts: False
 
  tasks:
    - name: "Set GEO location of our own ffmuc-nodes"
      #https://docs.ansible.com/ansible/latest/modules/raw_module.html
      raw: uci set gluon-node-info.@location[0]="location"; uci set gluon-node-info.@location[0].share_location="1"; uci set gluon-node-info.@location[0].latitude="{{ latitude }}" ; uci set gluon-node-info.@location[0].longitude="{{ longitude }}"; uci commit gluon-node-info

Script antesten

Bevor wir nun das Playbook ausführen wollen wir erst einmal shen, ob auch die richtigen Hosts verwendet werden würden. Hierzu hat der Befehl ansible-playbook eine entsprechende Option –list-hosts.

 $ ansible-playbook -v ~/ansible/playbooks/09_set_location.yml --list-hosts

Using /etc/ansible/ansible.cfg as config file

playbook: /home/django/ansible/playbooks/09_set_location.yml

  play #1 (ffmuc): ffmuc	TAGS: []
    pattern: ['ffmuc']
    hosts (6):
      ff_pliening_gbw_egod
      ff_roding_as_nausch
      ff_pliening_gbw_cpod
      ff_pliening_gbw_dgod
      ff_pliening_gbw_ogod
      ff_pliening_gbw_ug

Script ausführen

Nun wollen wir unser ersten Playbook ausführen, um die Geodaten aus der Inventory-Datei auf unseren Freifunk-Knoten abzugleichen; hierzu rufen wir unser Script wie folgt auf:

 $ ansible-playbook -v ~/ansible/playbooks/09_set_location.yml

Using /etc/ansible/ansible.cfg as config file
BECOME password: 

PLAY [ffmuc] ***************************************************************************************************************************

TASK [Update new contact-address on own ffmuc-nodes] ***********************************************************************************
changed: [ff_pliening_gbw_egod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fea9:22ed closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fea9:22ed closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_kvm_ol] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:5054:ff:fe9e:b358 closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:5054:ff:fe9e:b358 closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_ogod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec0:aaae closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec0:aaae closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_ug] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:32b5:c2ff:fe56:62b1 closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:32b5:c2ff:fe56:62b1 closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_dgod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8eb closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8eb closed."], "stdout": "", "stdout_lines": []}
changed: [ff_pliening_gbw_cpod] => {"changed": true, "rc": 0, "stderr": "Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8dd closed.\r\n", "stderr_lines": ["Shared connection to 2001:608:a01:102:1ae8:29ff:fec6:c8dd closed."], "stdout": "", "stdout_lines": []}
PLAY RECAP ***************************************************************************************************************************** ff_pliening_gbw_cpod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_dgod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_egod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_kvm_ol : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_ogod : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ff_pliening_gbw_ug : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ergebnis

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.

Zum Thema administrativer Aufwand wollen wir uns kurz noch ein kleines Rechenbeispiel ansehen. Nehmen wir mal an, wie hätten eine etwas größere Installation mit folgenden Eckdaten:

  • 145 Zeilen (ohne Variablen):
    • mit 76 Hosts
    • und 13 Unter-/Gruppen

Bei durchschnittlich 15 Variablen pro Host und 5 Variablen pro Gruppe ergäbe das eine Inventory-Datei mit

  • 145 Zeilen (ohne Variablen): 145
    • mit 76 Hosts ~15 Variablen pro Host: 1.140
    • und 13 Unter-/Gruppen ~5 Variablen je Gruppe: 65

ergäbe dies eine Inventory-Datei mit 1.350 Zeilen. 8-o

Bevor wir nun in die Welt der roles bei Ansible eintauchen, werfen wir kurz noch einen Blick aufzwei gängige Szenarien:

  • Bsp. 1:
    Hat man nur eine Aufgabe, wie z.B. den automatisierten Bau eines Freifunk-Offloaders auf Basis eines Raspberry 4B zu bewältigen, liegt es natürlich nahe, ein Playbook anzulegen, in dem alle Schritte nacheinander aufgeführt werden, die zum Erledigen der Aufgabe nötig sind. Ehe man sich's vbersieht hat man am Ende ein Playbook vor Augen welches 390 Zeilen umfasst. Sowas kann natürlich alsbald doch sehr umfangreich und vor allem pflegeintensiv werden.
  • Bsp. 2:
    In ambitionierteren Umgebungen hat man es meist mit einer Vielzahl unterschiedlicher Systeme, wie z.B. Web-, Datenbank-, Mail-, Infrastrukturserver und/oder Loadbalancer-Systeme zu tun. In Ihren Aufgabestellungen unterscheiden sich diese doch erheblich, haben aber jedoch in aller Regel eine grundlegende Basisinstallation und Konfiguration. Die sind z.B. Definitionen zu Admin-Acounts, NTP-Client-Definitionen oder z.B. beim MTA-Relayhost. Nun könnte man natürlich auch hier pragmatisch vorgehend und ein Playbook mit diesen grundlegenden Einzelaufgaben wie z.B. Userkontenanlage oder NTP-Clientinstallation und -konfiguration als Vorlage erstellen wollen. So eine Vorlage könnte man dann entsprechend vervielfältigen und mit den Hostspezifischen Installations- und Konfigurationsanweisung anreichern.

    Aus folgenden Gründen ist so ein Vorgehen aber nicht zu empfehlen:
    1. Bei Änderungen an grundlegenden gemeinsamen Punkte, wie z.B. neuer Mitarbeiter, müsste man später jedes einzelne Playbook anfassen und anpassen.
    2. Je nach Freunde am Umgang mit Ansible, kann im Arbeitsverzeichnis des Ansible-Nutzers es durch die vielen playbooks und/oder includierten Konfigurationsdateien sehr schnell unübersichtlich werden.
    3. Daten wie Aufgaben (tasks), Variablen (vars) oder „Spezielle Aufgaben“ (handlers) sind so mehrfach definiert und vorhanden, was eine fortlaufende Pflege mehr als erschwert.
    4. Da es keine Trennung zwischen variablen Daten und den Aufgaben (tasks und handlers) gibt, ist es sehr schwierig unser spezielles Ansible-Playbook oder auch Teile daraus wiederzuverwenden oder mit anderen zu teilen bzw. weiterzugeben. In aller Regel sind nämlich die Daten eines Playbook organisations- und firmenspezifisch, der dazu verwendete Code natürlich allgemeingültig und allgemein an- und verwendbar. Sind die Daten und der Code nun in einer großen Datei (Playbook) zusammengefasst, ist es damit nicht mehr möglich Inhalte zu teilen oder wiederzuverwenden!

Wenn wir das Thema nun nüchtern und wertfrei betrachten werden wir feststellen, dass wir so eine Vielzahl von Systemen nicht auf dauer ohne große Aufwände mit Ansible verwalten können. Um nun diese Herausforderungen elegant zu lösen bringt uns Ansible die Funktion der Rollen (roles) mit.

Rollen sind im Grunde nichts anderes als Verzeichnisse, die auf eine bestimmte Art und Weise angelegt sind. Rollen folgen vordefinierten Verzeichnis-Layout-Konventionen und erwarten, dass sich debei die zugehörigen Komponenten in dem für sie vorgesehenen Verzeichnispfad befindet. Bei der Grundkonfiguration unseres Ansible-Hosts hatte wir bereits diese Verzeichnisstruktur beim Anlegen des Ansible: Directory Layout angelegt.

/home/ansible/ansible/roles/
└── roles                          # Verzeichnis für die einzelnen (unterschiedlichen) Rollen
    └── common                     # Verzeichnis "role" common mit seinen entsprechenden Definitionen - Vorlage zum Kopieren
        ├── defaults               # Verzeichnis "defaults"
        │   └── main.yml           # Standardvariablen mit niedrigerer Priorität für diese Rolle
        ├── files                  # Verzeichnis "files"
        │   └── main.yml           # (Skript-)Dateien zur Verwendung als Kopier- bzw. Script-Rressource
        ├── handlers               # Verzeichnis "handlers"
        │   └── main.yml           # Datei mit den Definitionen zu den rollenspezifischen "handlers"
        ├── library                # Verzeichnis für benutzerdefinierte Module einer der Rolle (role) "common"
        ├── lookup_plugin          # Verzeichnis für weitere Arten von Plugins, wie in diesem Fall "lookup"
        ├── meta                   # Verzeichnis "meta" für Rollenspezifische Definitionen/Abhängigkeiten
        │   └── main.yml           # Datei mit Rollenspezifischen Definitionen
        ├── module_utils           # Verzeichnis "module_utils", die benutzerdefinierte module_utils der Rollen enthalten könnte
        ├── tasks                  # Verzeichnis "tasks" für kleinere Aufgabendateien
        │   └── main.yml           # Datei für kleinere Aufgaben, falls diese benötigt werden würden 
        ├── templates              # Verzeichnis mit den Templates
        │   └── main.j2            # Template-Datei mit dem Dateisuffix/-Ende .j2
        └── vars                   # Verzeichnis "vars", mit den zu dieser Rolle zugeordneten Variablen
            └── main.yml           # Datei mit den Rollenspezifischen Variablen

Diese Kopiervorlage common brauchen wir nur noch für jede entsprechende Rolle dann kopieren, so z.B. für die Rolle postfix

 $ cd ~/ansible/roles
 $ cp -avr common/ postfix/

In folgendem Konfigurationsbeispiel wollen wir uns zu folgendem Szenario eine handelbare Lösung genauer betrachten. Wir gehen dabei von folgendem Einsatzszenario aus:

Aufgabenstellung

Es ist ein Webserver mit Hilfe von Ansible zu erstellen. Am Host soll sich der verantwortliche WEB-Admin ruben anmelden können und der Host soll sich des NTP-Servers im der eigenen Zone bedienen. Dies sind Standardkonfigurationsaufgaben, die auf jedem Server in der betreffenden Sicherheitszonezutreffen. Nach der Installation des Webserver-Daemon apache soll dieser konfiguriert, die initiale Homepage Hello World installiert unde zum Schluß der Webserver gestartet werden. </WRAP>

Lösung

Als erstes zerlegen wir die Aufgabenstellung in einzelne Arbeitspakete und unterteilen diese Pakete in einzelne Aufgaben, als da wären:

  • Basiskonfiguration
    • Gruppe für den Webentwickler ruben anlegen (unter CentOS ist dabei die Gruppe gleich dem Benutzernamen).
    • User(konto) für den Webentwickler ruben anlegen.
    • Initiales Passwort für den Webentwickler ruben setzen.
    • NTP-Client
      • Installation des NTP-clients chrony
      • Sichern der originalen chrony-Konfigurationsdatei
      • Konfiguration von chrony
      • Starten des NTP-Clients/-Daemon und Sicherstellen dass dieser auch bei einem Systemneustart gestartet wird.
  • Host-/Aufgabenspezifische Installation: Apache-Webserver
    • Installation des http-Daemon
    • Sichern der originalen Apache-Konfigurationsdatei
    • Konfigurieren des Apache Webservers
    • Kopieren der initialen Webseite nach DOCUMENT_ROOT des Apache-Webservers
    • Starten des Webserver/-Daemon und sicherstellen dass dieser auch bei einem Systemneustart gestartet wird.

Um möglichst für die Zukunft flexibel zu sein, werden wir unser Ansible Playbook oder bessere gesagt den darunter liogenden Code, möglichst flexibel halten. Das bedeutet, dass wir sowohl für die Grundkonfiguration (User/Gruppe anlegen, wie installation des Chrony-Daemon) wie auch für die spätere Host-/Aufgabenspezifische Installation des Webservers (Apache) eigene Rollen defionieren werden. So können wir später die Rollen mühelos erweitern, wenn wir z.B. weitere Dienste wie z.B. einen PHP-Interpreter oder die zum Apachen zugehörige TLS-Konfiguration vornehmen werden.

Folgende Struktur soll definiert werden:

  • site.yml
    • beinhaltet alle Site-spezifischen Playbooks, wie z.B.
      • Datenbank db.yml (spätere erweiterte Konfiguration)
      • Mailserver postfix.yml (spätere erweiterte Konfiguration)
      • Webserver web.yml
        • sein zugehöriges Playbook web.yml bedient sich der Rollen:
          • base über die die Benutzeranlage erfolgt
          • chrony mit Hilfe deren die NTP-Client-Konfiguration erlediggt wird
          • apache Installation und Konfiguration des Apache Webservers mit Hilfe von tasks, files und handlers

Systemweite Host/Playbook-Definition : site.yml

Zunächst befüllen/editieren wir die Konfigurationsdatei site.yml, die wir automatisiert bei der Grundkonfiguration von Ansibles Directory Layout erzeugt hatten. Diese Datei wird später alle Definitionen unserer Systemumgebung enthalten, also nicht nur die für den Apache-Webserver, sondern auch für den Datenbank-, Mail- und sonstige Server. Die spezisfischen Playbooks werden dann hier nur noch includiert bzw. importiert.

 $ vim ~/ansible/site.yml 
~/ansible/site.yml
--- # Start des systemweiten Playbooks site.yml
- import_playbook: web.yml # Playbook zum Konfigurieren unseres Webservers einbinden
 
... # Ende unseres systemweiten Playbooks

Webserver Host/Playbook-Definition : web.yml

Für die Konfiguration des Webservers an sich verwenden wir dann die Konfigurationsdatei web.yml; die wir in der zuvor angelegten systemweiten Konfigurationsdatei site.yml includiert hatten. Da die Konfiguration auf allen WEB-Hosts erfolgen soll, geben wir beim Parametewr hosts den Namen der Hostgruppe aus unserer Inventory-Definition an. Bei der Definition der Rollen geben wir die drei roles an, die wir in unseren zuvor angestellten Überlegungen gewählt hatten.

 $ vim ~/ansible/web.yml 
web.yml
--- # Start des Playbooks für den Web-Server
- hosts: www
 
  roles:
    - base   # Basiskonfiguration (User anlegen)
    - chrony # Installation und Konfiguration NTP-Client
    - www    # Installation und Konfiguration Apache Webserver
 
... # Start des Playbooks für den Web-Server

Unser Kopiervorlage common für die Rollen kopieren wir nun für die gewählten Arbeitspakete (roles), als erstes also für die Rolle base, bei der wir den verantwortlichen Web-Admin anlegen werden.

 $ cd ~/ansible/roles
 $ cp -avr common/ base/

Unser Playbook-Beispiel 01 passen wir nun an und speichern dieses im Verzeichnis ~/ansible/roles/base/tasks/ unter den Namen main.yml

 $ vim ~/ansible/roles/base/tasks/main.yml
~/ansible/roles/base/tasks/main.yml
--- # Grundlegende Konfiguration für alle Hosts
  - name: "***base*** : Gruppe für (WEB-Entwickler) '{{ createuser }}' erstellen"
    group: # https://docs.ansible.com/ansible/latest/modules/group_module.html
      name: '{{ createuser }}'
      gid: '{{ createguid }}'
      state: present
 
  - name: "***base*** : WEB-Admin Nutzerkonto für den User '{{ createuser }}' mit frn zugehörigen UID '{{ createguid }}' anlegen un der Gruppe '{{ createuser }}' zuordnen."
    user: # https://docs.ansible.com/ansible/latest/modules/user_module.html
      name: '{{ createuser }}'
      comment: '{{ createusername }}'
      uid: '{{ createguid }}'
      group: '{{ createuser }}'
      state: present
 
  - name: "***base*** : Initiales Passwort für den WEB-Admin '{{ createuser }}' hinterlegen"
    shell: # https://docs.ansible.com/ansible/latest/modules/shell_module.html
      cmd: usermod -p $(echo '{{ createpassword }}' | openssl passwd -1 -stdin) {{ createuser }}

Wie wir sehen, beinhaltet diese Datei nur noch die Definition der tasks aber keine Sitespezifischen Variablen, da wir diese in eine separate Date auslagern werden. Wir könnten als jederzeit diese Date auf andere Installationsumgebungen portieren oder an interessierte Adminkollegen weitergeben!

Natürlich benötigen wir nun die Definition der Rolenspezifischen Variablen, die je Rolle hier ~/ansible/roles/base/vars/main.yml abzulegen sind.

 $ vim ~/ansible/roles/base/vars/main.yml
~/ansible/roles/base/vars/main.yml
--- # Definition der rollenspezifische Variablen
createguid    : '1010'                        # GID/UID des Benutzers00
createuser    : 'ruben'                       # Username
createusername: 'Ruben Nausch'                # Vollständiger Name
createpassword: 'M31nP4p4157d3r4113r83573!'   # Initialpasswort (ungecrypted!)

Die nächste Rolle, die wir konfigurieren müssen, ist die für den NTP-Daemon chrony. Auch hier kopieren wir zunächst das Default-role-template common.

 $ cd ~/ansible/roles
 $ cp -avr common/ chrony/

Auch hier greifen wir wieder auf das bereits bekannte Playbook-Beispiel 05 zurück, passen dieses an und speichern es im Verzeichnis ~/ansible/roles/chrony/tasks/ unter den Namen main.yml. Bei diesem Konfigurationsbeispiel besteht das playbook aber lediglich aus vier tasks, die wir als eigenständige Dateien auslagern und hier nur includieren werden.

 $ vim ~/ansible/roles/chrony/tasks/main.yml
~/ansible/roles/chrony/tasks/main.yml
--- # Hauptinstallations-/Konfigurationsdatei für den Dienst chrony
- include: install.yml       # Installation
- include: config-backup.yml # original-Konfig sichern
- include: configure.yml     # Konfiguration
- include: service.yml       # Service starten

Die vier tasks hinterlegen wire dann anschließend in der zugehörigen YML-Datei.

 $ vim ~/ansible/roles/chrony/tasks/install.yml
~/ansible/roles/chrony/tasks/install.yml
---
  - name: "***chrony*** : Installation des Deamon '{{ daemon_name }}' (in der aktuellsten Version)"
    dnf: 
    #https://docs.ansible.com/ansible/latest/modules/dnf_module.html
      name: '{{ daemon_name }}'
      state: latest
 $ vim ~/ansible/roles/chrony/tasks/config-backup.yml
~/ansible/roles/chrony/tasks/config-backup.yml
---
  - name: "***chrony*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '{{ config_file }}' bereits existiert"
    stat: # https://docs.ansible.com/ansible/latest/modules/stat_module.html 
      path: /etc/chrony.conf.orig
    register: stat_result
 
  - name: "***chrony*** : 2) Von der bestehenden originalen Konfigurationsdatei '{{ config_file }}' ein Backup '{{ config_file }}'.orig erstellen"
    copy: # https://docs.ansible.com/ansible/latest/modules/copy_module.html
      remote_src: yes
      src: '{{ config_file }}'
      dest: /etc/chrony.conf.orig
    when: stat_result.stat.exists == False
 $ vim ~/ansible/roles/chrony/tasks/configure.yml
~/ansible/roles/chrony/tasks/configure.yml
---
  - name: "***chrony*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen"
    template: # https://docs.ansible.com/ansible/latest/modules/template_module.html 
      src: templates/chrony-client.conf.j2 
      dest: "{{ config_file }}"

Das zugehörige Template für die Konfigurationsdatei legen wir im zugehörigen Verzeichnis ~/ansible/roles/chrony/templates ab.

 $ vim ~/ansible/roles/chrony/templates/chrony-client.conf.j2
~/ansible/roles/chrony/templates/chrony-client.conf.j2
# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
{{ chrony_pool }}
 
# Ignore stratum in source selection
{{ chrony_stratumweight }}
 
# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift
 
# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3
 
# Enable kernel synchronization of the real-time clock (RTC).
rtcsync
 
# In first three updates step the system clock instead of slew
# if the adjustment is larger than 10 seconds.
{{ chrony_makestep }}
 
# Enable hardware timestamping on all interfaces that support it.
#hwtimestamp *
 
# Increase the minimum number of selectable sources required to adjust
# the system clock.
#minsources 2
 
# Allow NTP client access from local network.
#allow 192.168.0.0/16
 
# Serve time even if not synchronized to a time source.
#local stratum 10
 
# Specify file containing keys for NTP authentication.
keyfile /etc/chrony.keys
 
# Get TAI-UTC offset and leap seconds from the system tz database.
leapsectz right/UTC
 
# Specify directory for log files.
logdir /var/log/chrony
 
# Select which information is logged.
#log measurements statistics tracking

Der task zum Starten des Daemon legen wir im Verzeichnis task ab.

 $ vim ~/ansible/roles/chrony/tasks/service.yml
~/ansible/roles/chrony/tasks/service.yml
---
  - name: "***chrony*** : Sicherstellen dass der  Daemon '{{ daemon_name }}' (beim Systemstart) gestartet wird und läuft"
    service: # https://docs.ansible.com/ansible/latest/modules/service_module.html 
      name: chronyd 
      state: started 
      enabled: yes

Zu guter letzt benötigen wir auch hier eine zur Rolle gehörige Parameter-Datei mit den Definitionen der Variablen.

 $ vim ~/ansible/roles/chrony/vars/main.yml
~/ansible/roles/chrony/vars/main.yml
--- # Definition der rollenspezifische Variablen zum Dienst chrony
daemon_name         : chrony
config_file         : /etc/chrony.conf
# chronyd client config-options
chrony_pool         : "server time.dmz.nausch.org iburst"
chrony_stratumweight: "stratumweight 0"
chrony_makestep     : "makestep 10 3"

Alle bisher getroffenen Konfigurationseinstellungen gelten für alle Applikations-Hosts und wir müssen lediglich die beiden Rollen base und chrony bei den entsprechenden Playbook-Dateien includieren!

Nun folgt der Webserverspezifische Teil unseres Ansible-Musterkonfiguration eines Webserversmit der Definition der Role www. Zunächst benötigen wir natürlich auch für diese Rolle die altbekannte Verzeichnisstruktur.

 $ cd ~/ansible/roles
 $ cp -avr common/ www/

Angelehnt an die Rolle chrony definieren wir auch hier als erstes das playbook mit fünf einzelnen tasks, die wir als eigenständige Dateien auslagern und hier nur includieren werden. In dieses Falle sindes fünf tasks, also einer mehr wie bei der Rolle chrony, da wir ja hier unsere initialen Webseite nach DOCUMENT_ROOT des Apache-Webservers noch kopieren wollen.

 $ vim ~/ansible/roles/www/tasks/main.yml
~/ansible/roles/www/tasks/main.yml
--- # Hauptinstallations-/Konfigurationsdatei für den Apache-Webserver
- include: install.yml       # Installation
- include: config-backup.yml # original-Konfig sichern
- include: configure.yml     # Konfiguration
- include: content.yml       # Initiale Webseite befüllen
- include: service.yml       # Service starten

Da wir das Rad ja bekanntlicher Weise nicht 2x erfinden müssen, greifen wir bei der Konfiguration der einzelnen tasks der Rolle www auf altbewährtes, nämlich der Konfigurationsvorlage der zuvor erstellten Rolle chrony zurück. Wir kopieren uns die Datei und verändern diese entsprechend.

 $ cp ~/ansible/roles/chrony/tasks/install.yml ~/ansible/roles/www/tasks/install.yml
 $ vim ~/ansible/roles/www/tasks/install.yml
~/ansible/roles/www/tasks/install.yml
---
  - name: "***www*** : Installation des Deamon '{{ daemon_name }}' (in der aktuellsten Version)"
    dnf:
    #https://docs.ansible.com/ansible/latest/modules/dnf_module.html
      name: '{{ daemon_name }}'
      state: latest

Ähnlich verfahren wir mit dem task zum Sichern der originalen Konfigurationsdatei config-backup.yml sowie zum Konfigurieren des Daemon configure.yml.

 $ cp ~/ansible/roles/chrony/tasks/config-backup.yml ~/ansible/roles/www/tasks/config-backup.yml
 $ vim ~/ansible/roles/www/tasks/config-backup.yml
~/ansible/roles/www/tasks/config-backup.yml
---
  - name: "***www*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '{{ config_file }}' bereits existiert"
    stat: # https://docs.ansible.com/ansible/latest/modules/stat_module.html 
      path: '{{ backup_file }}'
    register: stat_result
 
  - name: "***www*** : 2) Von der bestehenden originalen Konfigurationsdatei '{{ config_file }}' ein Backup '{{ backup_file }}' erstellen"
    copy: # https://docs.ansible.com/ansible/latest/modules/copy_module.html
      remote_src: yes
      src: '{{ config_file }}'
      dest: '{{ backup_file }}'
    when: stat_result.stat.exists == False
 $ cp ~/ansible/roles/chrony/tasks/configure.yml ~/ansible/roles/www/tasks/configure.yml
 $ vim ~/ansible/roles/www/tasks/configure.yml
~/ansible/roles/www/tasks/configure.yml
---
  - name: "***www*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen"
    template: # https://docs.ansible.com/ansible/latest/modules/template_module.html 
      src: templates/httpd-server.conf.j2
      dest: "{{ config_file }}"

Auch hier benötigen wir eine Datei, in der die Variablen für den Task www vorgehalten werden.

 $ vim ~/ansible/roles/www/vars/main.yml
~/ansible/roles/www/vars/main.yml
--- # Definition der rollenspezifische Variablen zum Apache-Webserver httpd
daemon_name                : httpd
config_file                : /etc/httpd/conf/httpd.conf
backup_file                : /etc/httpd/conf/httpd.conf.orig
# httpd-Server config-options
httpd_server_admin         : "ServerAdmin webmaster@nausch.org"
httpd_server_http_header   : "ServerTokens Prod\nServerSignature Off\nHeader always unset \"X-Powered-By\"\nHeader unset \"X-Powered-By\""
httpd_server_extendedstatus: "ExtendedStatus On"
httpd_server_traceenable   : "TraceEnable off"

Zu guter letzt müssen wir natürlich auch noch Sorge Tragen dass der HTTP-Daemon läuft und beim Systemstart auch geladen wird. Auch hier kopieren wir uns der Einfachheit halber das passende Gegenstück aus unserem Erfahrungsschatz.

 $ cp ~/ansible/roles/chrony/tasks/service.yml ~/ansible/roles/www/tasks/service.yml
 $ vim ~/ansible/roles/www/tasks/service.yml
~/ansible/roles/www/tasks/service.yml
---
  - name: "***www*** : Sicherstellen dass der  Daemon '{{ daemon_name }}' (beim Systemstart) gestartet wird und läuft"
    service: # https://docs.ansible.com/ansible/latest/modules/service_module.html 
      name: httpd
      state: started
      enabled: yes
 
  - name: "***www*** : Paketfilter anpassen und Port 80 (HTTP) öffnen"
    firewalld: # https://docs.ansible.com/ansible/latest/modules/firewalld_module.html
      service: http
      permanent: yes
      state: enabled

Wir haben also ein modular aufgebautes Installationsplaybook für unseren Webserver, mit den drei Rollen base, chrony und www und jeweils abgetrennte lokale individuelle Konfigurationsparameter, sowie das Playbook web.yml und der übergeordneten Site-Konfiguration site.yml.

/home/ansible/ansible/
├── filter_plugins
├── inventories
│   ├── production
│   │   ├── group_vars
│   │   ├── hosts.yml
│   │   └── host_vars
│   └── staging
│       ├── group_vars
│       ├── hosts.yml
│       └── host_vars
├── library
├── module_utils
├── roles
│   ├── base
│   │   ├── defaults
│   │   │   └── main.yml
│   │   ├── files
│   │   │   └── main.yml
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── library
│   │   ├── lookup_plugin
│   │   ├── meta
│   │   │   └── main.yml
│   │   ├── module_utils
│   │   ├── tasks
│   │   │   └── main.yml
│   │   ├── templates
│   │   │   └── main.yml
│   │   └── vars
│   │       └── main.yml
│   ├── chrony
│   │   ├── defaults
│   │   │   └── main.yml
│   │   ├── files
│   │   │   └── main.yml
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── library
│   │   ├── lookup_plugin
│   │   ├── meta
│   │   │   └── main.yml
│   │   ├── module_utils
│   │   ├── tasks
│   │   │   ├── config-backup.yml
│   │   │   ├── configure.yml
│   │   │   ├── install.yml
│   │   │   ├── main.yml
│   │   │   └── service.yml
│   │   ├── templates
│   │   │   ├── chrony-client.conf.j2
│   │   │   └── main.yml
│   │   └── vars
│   │       └── main.yml
│   ├── common
│   │   ├── defaults
│   │   │   └── main.yml
│   │   ├── files
│   │   │   └── main.yml
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── library
│   │   ├── lookup_plugin
│   │   ├── meta
│   │   │   └── main.yml
│   │   ├── module_utils
│   │   ├── tasks
│   │   │   └── main.yml
│   │   ├── templates
│   │   │   └── main.yml
│   │   └── vars
│   │       └── main.yml
│   └── www
│       ├── defaults
│       │   └── main.yml
│       ├── files
│       │   └── main.yml
│       ├── handlers
│       │   └── main.yml
│       ├── library
│       ├── lookup_plugin
│       ├── meta
│       │   └── main.yml
│       ├── module_utils
│       ├── tasks
│       │   ├── config-backup.yml
│       │   ├── configure.yml
│       │   ├── content.yml
│       │   ├── install.yml
│       │   ├── main.yml
│       │   └── service.yml
│       ├── templates
│       │   ├── homepage.conf.j2
│       │   ├── httpd-server.conf.j2
│       │   └── main.yml
│       └── vars
│           └── main.yml
├── site.yml
└── web.yml

Abschließend kann man sich nun berechtigter Weise die Frage stzellen, warum man nun mehrere Dateien erstellt haben,um den Code und die Variablen dazu, der die Pakete installiert und die Dienste verwaltet, separat zu speichern? Wir sind so sehr leicht in der Lage zum, Beispiel Dienste in mehreren Phasen bereitstellen. In einer ersten Phase können wir so Anwendungen lediglich installieren und konfigurieren und erst in der zweiten Phase dann die Dienste dann starten. Natürlich haben es wir so auch leichter Teiel des Codes wiederzu verwenden, als wenn man eine große Datei vernweden würde in der alle Einzelschritte selektiv aufgeführt sind!


Script ausführen

Zum Schluss rufen wir unser erstes role-based Playbook auf uns installieren und konfigurieren unseren Webserver.

 $ ansible-playbook site.yml 

BECOME password: 

PLAY [demo] *************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************
ok: [demo]
TASK [***base*** : Gruppe für (WEB-Entwickler) 'ruben' erstellen] ******************************************************************* changed: [demo]
TASK [***base*** : WEB-Admin Nutzerkonto für den User 'ruben' mit frn zugehörigen UID '1010' anlegen un der Gruppe 'ruben' zuordnen.] changed: [demo]
TASK [***base*** : Initiales Passwort für den WEB-Admin 'ruben' hinterlegen] ******************************************************** changed: [demo]
TASK [***chrony*** : Installation des Deamon 'chrony' (in der aktuellsten Version)] ************************************************* ok: [demo]
TASK [***chrony*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '/etc/chrony.conf' bereits existiert] ********************** ok: [demo]
TASK [***chrony*** : 2) Von der bestehenden originalen Konfigurationsdatei '/etc/chrony.conf' ein Backup '/etc/chrony.conf'.orig erstellen] ***************************************************************************************************************************** changed: [demo]
TASK [***chrony*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen] ********************************** changed: [demo]
TASK [***chrony*** : Sicherstellen dass der Daemon 'chrony' (beim Systemstart) gestartet wird und läuft] *************************** ok: [demo]
TASK [***www*** : Installation des Deamon 'httpd' (in der aktuellsten Version)] ***************************************************** changed: [demo]
TASK [***www*** : 1) Überprüfen ob das Backup der Konfigurationsdatei '/etc/httpd/conf/httpd.conf' bereits existiert] *************** ok: [demo]
TASK [***www*** : 2) Von der bestehenden originalen Konfigurationsdatei '/etc/httpd/conf/httpd.conf' ein Backup '/etc/httpd/conf/httpd.conf.orig' erstellen] ************************************************************************************************************* changed: [demo]
TASK [***www*** : Template Konfigurationsdatei an Ort und Stelle kopieren und Variablen setzen] ************************************* changed: [demo]
TASK [***www*** : default-Homepage an Ort und Stelle kopieren] ********************************************************************** changed: [demo]
TASK [***www*** : Sicherstellen dass der Daemon 'httpd' (beim Systemstart) gestartet wird und läuft] ******************************* changed: [demo]
TASK [***www*** : Paketfilter anpassen und Port 80 (HTTP) öffnen] ******************************************************************* changed: [demo]
PLAY RECAP ************************************************************************************************************************************* demo : ok=16 changed=11 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

  • rsync-Server unter CentOS 8.x einrichten
  • tftp-Server unter CentOS 8.x einrichten

Links


1)
Access Point
Cookies helfen bei der Bereitstellung von Inhalten. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Rechner gespeichert werden. Weitere Information
  • centos/ansible/detail.txt
  • Zuletzt geändert: 05.07.2020 18:42.
  • von django