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:
-
Wir wollen auf dem Control Host als unprivilegierter User mit Ansible arbeiten.
-
Unser gesamtes »Ansible-Projekt« soll sich innerhalb eines einzigen Ordners befinden. Vorteil: Diesen Ordner können Sie dann einfach sichern oder in ein Versionskontrollsystem einchecken.
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):
-
Inhalt der Umgebungsvariablen ANSIBLE_CONFIG
-
ansible.cfg im aktuellen Verzeichnis
-
~/.ansible.cfg
-
/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:
-
sich mit der Option --ask-become-pass bzw. -K bei jedem Aufruf eines Ansible-Kommandos prompten lassen
-
im Falle von sudo per Konfiguration auf den Target Hosts dann doch auf die Passworteingabe verzichten:
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) NOPASSWD: ALLListing 27.11 »/etc/sudoers«
-
in der Inventory-Datei die Variable ansible_become_pass auf das Klartext-Passwort setzen. In unserem Beispielszenario also etwa:
[ubuntu-servers:vars]
ansible_user=user1
ansible_become=yes
ansible_become_pass=supergeheim2018Listing 27.12 Ausschnitt aus der Datei »inventories/devel/inventory«
[ ! ] Das ist zu Testzwecken in Ordnung, aber ansonsten empfehlen wir dies ausdrücklich nicht!
-
Wenn Sie bald mehr über Ansible wissen, können Sie auch an anderer Stelle parametrisieren und diese Stellen ggf. auch verschlüsseln. Das wird in Abschnitt 27.4.4 und Abschnitt 27.4.12 noch ausführlicher beschrieben.
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:
-
Das Ping-Modul (wird gerne zum Testen genommen):
$ ansible all -m ping
-
Das Command-Modul:
$ ansible all -m command -a /bin/hostname
$ ansible all -m command -a "df -h /"
# '-m command' ist der Default, daher geht's auch kürzer:
$ ansible all -a /bin/hostname
$ ansible all -a "df -h /" -
Für Kommandos, die Shell-Mechanismen benötigen, muss das Shell-Modul genutzt werden:
$ ansible all -m shell -a "ls -l /usr | wc -l"
-
[+] Wenn Sie die Ausgabe von Ad-hoc-Kommandos – meist Command- oder Shell-Aufrufe – weiterverarbeiten wollen, bietet sich auch das einzeilige Output-Format (Option -o) an!
-
Das File-Modul um beispielsweise ein Verzeichnis anzulegen:
$ ansible all -m file -a "dest=/opt/schulung state=directory"
-
Das User-Modul um beispielsweise einen Benutzeraccount anzulegen:
$ ansible all -m user -a "name=john shell=/bin/bash"
-
Paket-Management-Module um beispielsweise Distributionspakete zu installieren:
# Debian/Ubuntu:
$ ansible server1 -m apt -a "name=tmux"
# SUSE:
$ ansible server2 -m zypper -a "name=tmux"
# CentOS:
$ ansible server3 -m yum -a "name=tmux" -
Es gibt seit Ansible 2.0 auch ein generisches package-Modul, aber erstens funktioniert es bei älteren Versionen nicht ohne weiteres im Ad-hoc-Kontext und zweitens sind die Paketnamen sowieso meist unterschiedlich.
-
Das Setup-Modul um Informationen über Target Hosts zu sammeln:
$ ansible server1 -m setup
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:
-
Eine Konfigurationsdatei enthält schon eine gewünschte Zeile – es ist nichts mehr zu tun.
-
Ein symbolischer Link ist schon vorhanden – es ist nichts mehr zu tun.
-
Ein Benutzer ist schon in einer Gruppe – es ist nichts mehr zu tun.
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«.
-
Installieren Sie zunächst das Paket git.
-
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 = matchingListing 27.17 Examplarische Git-Konfigurationsdatei »~/.gitconfig«
-
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>