27.2    Basiseinrichtung und Ad-hoc-Kommandos

27.2.1    Verzeichnisstruktur einrichten

Wir machen zunächst einen Vorschlag, wie Sie Ihre Arbeit mit Ansible auf Verzeichnisebene strukturieren können. Ansible selbst macht uns hier im Prinzip kaum feste Vorgaben. Unser Vorschlag ist im Wesentlichen Best Practice – wenn Sie es später einmal anders machen möchten, können Sie natürlich jederzeit davon abweichen. Abgesehen von konkreten Verzeichnisstrukturen möchten wir noch zwei Vorgaben machen:

Und so soll die Verzeichnisstruktur aussehen:

ansible
|-- inventories
| `-- devel
| |-- group_vars
| `-- host_vars
|-- playbooks
`-- roles

Mit dieser Befehlsfolge könnten Sie diese Struktur in Ihrem Heimatverzeichnis anlegen:

$ mkdir ~/ansible && cd $_
$ mkdir -p inventories/devel/{host,group}_vars playbooks roles

27.2.2    Grundkonfiguration (ansible.cfg)

Da wir Ansible in der Folge mit einigen vom Default abweichenden Einstellungen betreiben wollen, benötigen wir eine Konfigurationsdatei.

Diese Datei wird an folgenden Stellen gesucht (der Reihe nach, die erste gewinnt):

  1. Inhalt der Umgebungsvariablen ANSIBLE_CONFIG

  2. ansible.cfg im aktuellen Verzeichnis

  3. ~/.ansible.cfg

  4. /etc/ansible.cfg

Es folgt eine erste Version, die wir nach und nach bei Bedarf erweitern werden. Legen Sie sie in Ihren ansible/-Basisordner (d. h. wir arbeiten hier mit Möglichkeit Nr. 2):

[defaults]
inventory = inventories/devel/inventory
remote_user = root

Listing 27.5    Erste Version einer Konfigurationsdatei »ansible.cfg«

Diese zwei Einstellungen legen den Namen einer (noch zu erstellenden) Inventar-Datei fest sowie die Tatsache, dass der administrative User auf den Target Hosts in unserem Szenario per Default »root« heißt. (Alle Konfigurationseinstellungen sind übrigens unter http://docs.ansible.com/ansible/intro_configuration.html beschrieben.)

[+]  Ab Ansible 2.4 werden alle relativen Pfadangaben konsequent stets relativ zur benutzten Konfigurationsdatei interpretiert!

Stellen Sie sicher, dass Sie sich im Basisordner befinden, und testen Sie, ob die Konfiguration gefunden wird (die Ausgaben sind teilweise gekürzt):

$ ansible --version
ansible 2.6.1
config file = /home/user1/ansible/ansible.cfg
[…]

Damit Ansible stets seine Konfigurationsdatei findet, müssten Sie nun immer alle Aufrufe im Basisordner machen, was aber nicht immer bequem ist. Wir empfehlen daher für unser Szenario, zusätzlich die Umgebungsvariable ANSIBLE_CONFIG zu setzen:

export ANSIBLE_CONFIG=~/ansible/ansible.cfg

Listing 27.6    Ausschnitt aus der persönlichen »~/.bashrc«

Die Konfigurationsdirektive host_key_checking

Ansible verwendet per Default OpenSSH – und damit auch das typische strikte HostKeyChecking, das Sie sicher vom normalen SSH-Betrieb kennen:

$ ssh root@server1
The authenticity of host 'server1 (192.168.150.11)' can't be established.
ECDSA key fingerprint is 8a:a3:a5:43:c6:e4:6c:11:3a:c9:2b:97:94:23:40:c9.
Are you sure you want to continue connecting (yes/no)?

Dies ist unter Sicherheitsaspekten natürlich völlig in Ordnung, kann aber zuweilen auch lästig werden (z. B. bei häufigen Neuinstallationen von Testmaschinen).

Wenn Sie sich über die Implikationen im Klaren sind, können Sie dies in der Ansible-Konfiguration generell deaktivieren:

# [defaults]
host_key_checking = false

Listing 27.7    Ausschnitt aus der »ansible.cfg«

Wenn Ihnen das ein wenig zu viel des Guten ist, können Sie alternativ auch mit OpenSSH-Bordmitteln selektiv arbeiten; z. B. so:

Host server1 server2
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null

Listing 27.8    Exemplarische »~/.ssh/config«: Selektives Deaktivieren des Host-Key-Checkings

Die Konfigurationsdirektive log_path

Falls gewünscht, können Sie Ansible mit der Direktive log_path dazu veranlassen, alle Aktionen mitzuprotokollieren. Die Logdatei ist frei wählbar, nur das Verzeichnis, das die Logdatei enthalten soll, muss existieren. Wenn also z. B. das Verzeichnis ~/logs/ existiert, könnten Sie folgende Einstellung vornehmen:

# [defaults]
log_path = ~/logs/ansible.log

Listing 27.9    Ausschnitt aus der »ansible.cfg«

27.2.3    Erstellen und Verwalten eines Inventorys

Jetzt muss Ansible natürlich noch wissen, um welche Hosts es sich in Zukunft kümmern soll. Dies wird im einfachsten Fall mit einer Inventory-Datei bestimmt.

In unserer ansible.cfg haben wir ja schon eine solche Datei festgelegt. Hier folgt ein examplarischer Inhalt:

[test-servers]
server1 ansible_host=192.168.150.10

[more-servers]
server2 ansible_host=192.168.150.20
server3 ansible_host=192.168.150.30

[ubuntu-servers]
server4 ansible_host=192.168.150.40

[ubuntu-servers:vars]
ansible_user=user1
ansible_become=yes

Listing 27.10    »inventories/devel/inventory«

[+]  Bei fehlender Namensauflösung »ansible_host« setzten

Die »ansible_host=<IP_ADRESSE>«-Zusätze brauchen Sie dabei nur, wenn die IP-Adresse nicht durch eine Namensauflösung bestimmt werden kann!

Mit den Namen in eckigen Klammern definieren Sie Gruppen (z. B. test-servers). Es wäre auch kein Problem, wenn derselbe Host mehrfach in unterschiedlichen Gruppen auftauchen würde. Der letzte Abschnitt definiert eine besondere Parametrisierung für die Gruppe mit Ubuntu-Systemen. ansible_become=yes bedeutet: Es muss jeweils eine Rechteerhöhung durchgeführt werden (per Default via sudo).

Unter http://docs.ansible.com/ansible/intro_inventory.html sind alle Möglichkeiten einer Inventory-Datei dokumentiert.

Ein erster Test

Ob alles so weit stimmig ist, testen Sie am einfachsten mit:

$ ansible all -m ping

Wenn Sie Target Hosts ohne direkte Root-Zugriffsmöglichkeit im Inventory haben, wird das Ping bei diesen Hosts fehlschlagen. Wir werden dies nun besprechen und Lösungsmöglichkeiten aufzeigen.

Target Hosts ohne direktes root-SSH

Welche Möglichkeiten haben wir mit Ansible, wenn der Root-Account nicht direkt von außen erreichbar ist (wie z. B. per Default unter Ubuntu)?

In der exemplarischen Inventory-Datei haben wir diesbezüglich am Ende schon eine Sonderkonfiguration eingefügt; im Falle der su-Methode würden Sie die zusätzliche Zeile

ansible_become_method=su

benötigen (per Default ist die Methode sudo). An irgendeiner Stelle muss aber nun die Privilege Escalation passieren, also brauchen Sie irgendwo ein sudo- bzw. su-Passwort. Sie können in der Praxis nun zwischen mehreren Alternativen wählen:

Alternative Inventory-Files und localhost

Natürlich können Sie auch weitere Inventory-Files vorhalten. Allen Ansible-Kommandos können Sie mit dem Schalter -i auch recht einfach eine andere Inventory-Datei mitgeben, etwa:

$ ansible all -i inventories/production/inventory -m ping

Eine Sonderrolle haben Targets wie localhost oder 127.0.0.1 – sie können verwendet werden, ohne dass sie explizit im Inventory auftauchen:

$ ansible localhost -m ping

In diesem Fall arbeitet Ansible aber implizit mit der Einstellung ansible_connection=local, was SSH umgeht und direkt eine Shell anspricht.

Wenn Sie also als unprivilegierter User arbeiten, können Sie hier demzufolge bislang nichts tun, was höhere Privilegien erfordert:

$ ansible localhost -a "touch /xyz"
localhost | FAILED | rc=1 >>
touch: "/xyz" kann nicht berührt werden: Keine Berechtigung

Nun lohnt es meist nicht, für localhost eine eigene Inventory-Datei anzulegen (man kann es natürlich aber machen). Ein relativ sparsam dokumentiertes Feature ist aber, dass Sie dem Schalter -i auch eine kommaseparierte Liste von Hosts mitgeben können (quasi ein Ad-hoc-Inventory). Und damit würde Ansible wieder SSH nutzen:

$ ansible localhost -i localhost, -a "touch /xyz"
localhost | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: Permission denied [...]",
"unreachable": true
}

(Beachten Sie das Komma hinter localhost!)

Wenn Sie nun Ihren SSH-Key auch beim lokalen root-Account deponieren, sollte alles wie geschmiert laufen.

Dynamisches Inventory

Statt einer simplen Datei kann auch der Pfad zu einem ausführbaren Programm konfiguriert werden. Dieses muss dann die Option --list verstehen und daraufhin Informationen in einem definierten JSON-Format nach STDOUT liefern (siehe bei Bedarf:
http://docs.ansible.com/ansible/developing_inventory.html).

Weiterhin muss es noch die Option --host <HOSTNAME> verarbeiten können und daraufhin assoziierte Host-Variablen liefern.

Wenn Sie Ihre Hosts mit einem »prominenten« Produkt wie z. B. AWS EC2 oder OpenStack verwalten, können Sie in der Regel auf bereits fertige Lösungen zurückgreifen. Lesen Sie daher zunächst bitte http://docs.ansible.com/ansible/intro_dynamic_inventory.html.

Das Kommando ansible-inventory

Mit diesem Kommando können Sie Informationen über Ihr Inventory gewinnen und die Parametrisierung von Hosts darstellen (was natürlich erst nützlich wird, wenn Sie eine relevante Parametrisierung haben – dazu später mehr). Versuchen Sie einmal Aufrufe wie:

$ ansible-inventory --graph
$ ansible-inventory --graph --vars

$ ansible-inventory --list
$ ansible-inventory --list --yaml

$ ansible-inventory --host <HOSTNAME>
$ ansible-inventory --host <HOSTNAME> --yaml

Listing 27.13    Exemplarische Aufrufe des Kommandos »ansible-inventory«

27.2.4    Ad-hoc-Kommandos

Das spontane, interaktive Verwenden einer Ansible-Funktionalität über das Kommando ansible bezeichnet man als Ad-hoc-Kommando oder Ad-hoc-Aufruf. (In der späteren Praxis wird dies eher unbedeutend, da wir dann fast immer mit Playbooks arbeiten werden.) Hier sind einige Beispiele:

27.2.5    Patterns zum Adressieren von Hosts

Wie Sie in den letzten Beispielen schon gesehen haben, können Sie Gruppen oder einzelne Hosts aus dem Inventory ansprechen:

$ ansible test-servers -m ping
$ ansible server3 -m ping

Mittels gewisser Sonderzeichen stehen auch andere Auswahlmöglichkeiten zur Verfügung. Beispielsweise steht der übliche Joker »*« zur Verfügung, den Sie wegen der Shell aber quotieren müssen:

$ ansible '*' -m ping

Der Doppelpunkt ist eine Oder-Verknüpfung, und das Ausrufezeichen sorgt für einen Ausschluss:

# Alle au"ser server4:
$ ansible 'all:!server4' -m ping

Sämtliche Möglichkeiten (es gibt tatsächlich noch einige mehr) sind unter
http://docs.ansible.com/ansible/intro_patterns.html beschrieben.

27.2.6    Die Ansible-Konsole

Wenn Sie für längere Zeit interaktiv mit Target Hosts arbeiten wollen, können Sie das Kommando ansible-console nutzen, das quasi eine intergrierte Shell-Umgebung zur Verfügung stellt.

Das erste Wort auf jeder Kommandozeile wird als Ansible-Modul interpretiert. Existiert kein solches Modul, so wird die ganze Zeile stattdessen mit dem Shell-Modul als Linux-Kommando ausgeführt:

$ ansible-console

root@all (4)[f:5]$ $ ping
[…]

root@all (4)[f:5]$ $ df -h /
[…]

Listing 27.14    Exemplarische Aufrufe innerhalb von »ansible-console«

[+] In den seltenen Fällen, in denen Sie ein Kommando ausführen wollen, das genauso heißt wie ein Modul (z. B. hostname), schreiben Sie am einfachsten command oder shell davor!

Mit dem eingebauten cd-Kommando können Sie in einzelne Target-Hosts oder -Gruppen »hineinwechseln«. Ein cd ohne Argument weitet die Adressierung wieder auf alle Hosts aus.

27.2.7    Idempotenz

Eine Eigenschaft, auf die bei Ansible sehr großer Wert gelegt wird, ist die Idempotenz. Dieser Begriff kommt ursprünglich aus der Mathematik und bezeichnet dort eine Eigenschaft von Funktionen:

f * f = f

Etwas salopp bedeutet dies, dass es egal ist, ob man eine Funktion einmal oder zweimal ausführt – wiederholtes Ausführen ändert nichts (mehr) am Ergebnis. Beispiele in der Mathematik sind etwa Projektionen oder Betragsfunktionen.

Für ein Konfigurationsmanagementsystem bedeutet dies: Wenn ein Konfigurationszustand erreicht ist, dann sind alle weiteren Ausführungen der entsprechenden Anweisung ohne Effekt.

Ein Beispiel dazu: Ein Softwarepaket soll installiert werden. Der erste Aufruf erledigt das; alle weiteren Aufrufe stellen nur noch fest, dass nichts mehr zu tun ist. Das klingt banal, trifft aber auf nahezu alle Ansible-Funktionen zu:

Dieses sehr angenehme Verhalten würde man mit einer »selbst gescripteten« Lösung niemals in dieser Konsequenz erreichen. Idempotenz ist also eine der vielen Annehmlichkeiten von Ansible!

27.2.8    Parallele Ausführung

Ansible greift grundsätzlich parallel auf seine Hosts zu. Die Anzahl der Forks (also der gleichzeitigen Verbindungen) ist mit dem Defaultwert von 5 allerdings sehr konservativ bemessen.

Sie können den gewünschten Wert entweder dynamisch mit der Option -f auf der Kommandozeile angeben:

$ ansible all -f 10 -m ping

Oder ihn dauerhaft in der Konfiguration setzen:

# [defaults]
forks = 10

Listing 27.15    Ausschnitt aus der Datei »ansible.cfg«

In der Praxis hat man schon Werte wie 50 oder 500 gesehen. Hauptsache, Ihr Control Host und Ihr Netzwerk halten das aus.

27.2.9    »Hängende« Verbindungen

Ab und an gibt es das Problem einer hängenden Verbindung: Ansible bekommt von einem bestimmten Zielhost einfach keine Antwort, obwohl netzwerktechnisch (normales Ping, SSH) alles in Ordnung ist.

Dies liegt dann in der Regel daran, dass die persistenten (Open-)SSH-Verbindungen, die Ansible standardmäßig nutzt, »tot« sind. Abhilfe schafft in dem Moment der Wechsel auf eine Paramiko-basierte Verbindung. Das ist die hauseigene Python-SSH-Bibliothek:

$ ansible […] -c paramiko

Etwas aufwendiger ist die Methode, mittels eines Tools wie lsof die hängenden SSH-Prozesse ausfindig zu machen und zu beenden:

$ lsof -i :22
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
[…]
ssh 12314 user1 3u IPv4 1249038 0t0 TCP […]-><zielhost>:ssh
[…]

$ kill -HUP 12314

Listing 27.16    Identifizieren und killen hängender SSH-Verbindungen

27.2.10    Exkurs: Versionskontrolle mit Git

An dieser Stelle ist es zwar nicht unbedingt nötig, aber dennoch empfehlenswert: Stellen Sie Ihr Projekt unter Versionskontrolle. Die Software Git ist dazu heutzutage eine typische Wahl. Wenn Sie noch keine Erfahrung mit Git haben, zeigen wir Ihnen hier kurz, wie Sie vorgehen können. Werfen Sie zudem einen Blick in Kapitel 25, »Versionskontrolle«.

  1. Installieren Sie zunächst das Paket git.

  2. Speichern Sie diese Mini-Config unter dem Namen ~/.gitconfig ins Heimatverzeichnis Ihres Arbeits-Users. Name und Mailadresse sind natürlich anzupassen:

    [user]
    name = First User
    email = user1@ansible.localdomain
    [color]
    ui = auto
    [core]
    editor = vim
    pager = less -R
    [push]
    default = matching

    Listing 27.17    Examplarische Git-Konfigurationsdatei »~/.gitconfig«

  3. Machen Sie schließlich aus Ihrem Ansible-Projekt-Ordner ein Git-Repository. Sie können damit nichts kaputtmachen; es werden nur Zusatzinformationen im Unterordner .git/ gespeichert:

    $ cd ~/ansible

    $ git init
    $ git add .
    $ git commit -m "Initial commit"
Nützliche Git-Kommandos (Kurzübersicht)
# Änderungen verfolgen:
git status
git log [--stat] [<DATEIEN...>]
git diff [<DATEIEN...>]


# Änderungen bzw. neue Dateien "einchecken":
git add [<DATEIEN...>]
git commit -m «KOMMENTAR>"
Klonen eines Git-Repositorys

Auch das geht »out of the box«. Auf einem anderen Host, der unseren »Git-Host« per SSH erreichen kann, starten Sie einen Klonvorgang mittels

$ git clone <USER@HOST:PFAD>

# oder
$ git clone ssh://<USER@HOST/ABSOLUTER/PFAD>

Lokales Klonen auf derselben Maschine ist sowieso problemlos. Sie wählen einfach ein Arbeitsverzeichnis für die neue Arbeitskopie und geben dort Folgendes ein:

$ git clone file:///<ABSOLUTER/PFAD>