Ansible für automatisierte Roll-Outs – das erste Playbook

This entry is part [part not set] of 2 in the series Ansible from scratch

Nachdem wir uns im ersten Teil der Serie häuslich eingerichtet haben, werden wir jetzt wirklich ans Eingemachte gehen und das Basisplaybook, sowie die ersten Aktionen bauen.

Begrifflichkeiten

Ansible nennt den kompletten Ablauf, den man startet, ein „Playbook“. Ein Playbook wiederum enthält sogenannte Plays, die dann wiederum die Tasks enthalten. Diese ganzen Dinge werden in Files festgehalten, die im YAML Format sind. YAML ist ein einfaches Format, um strukturierte Informationen in Textdateien zu speichern.

Fangen wir mal hinten an: Tasks sind konkrete Aufgaben, die auf einem Host durchgeführt werden sollen, wie zum Beispiel das Anlegen eines Benutzers, das Erstellen eines Config Files mit einem bestimmten Inhalt oder das Starten eines Dienstes. Hier ist ein Beispiel für einen Task, der eine Config Datei aus einem Template erzeugt und diese auf dem Zielhost unter /etc/ntp.conf ablegt:

Die Einrückung definiert in YAML die Blöcke (wie in Python).

Ein Play fasst nun mehrere Tasks zusammen und kombiniert diese mit einer Gruppe von Hosts, auf denen diese ausgeführt werden. Durch diese Strukturierung ist es möglich Multi-Host Deployments zu bauen, bei denen auf verschiedenen Hosts auch verschiedene Aufgaben ausgeführt werden müssen (LAMP Plattform: Apache Server, Datenbank Server, evtl. Loadbalancer, …).

Dieses Play installiert das Paket apache2 auf allen Hosts aus der Gruppe webservers, schreibt eine Config in das Config Verzeichnis und stellt sicher, dass Apache beim Systemstart automatisch hochgefahren wird und auch gerade läuft. Der Handler ist eine weitere Spezialität, der man oft begegnet: falls eine neue Config Datei geschrieben wurde (und nur dann), wird der Apache Daemon von diesem Handler neu gestartet.

Hier sehen wir auch gleich eine Eigenschaft von Ansible: Tasks werden nur ausgeführt, wenn diese auch eine Änderung bewirken. Falls also in dem zweiten Task keine Änderung am Config File stattfindet, wird auch der Apache nicht neu gestartet. Diese Eigenschaft wird als „Idempotenz“ bezeichnet. Man kann also (im Allgemeinen) ein Playbook auch mehrfach ausführen und das Endergebnis ist immer dasselbe.

Ein Playbook fasst jetzt einfach mehrere Plays zusammen, sodass zum Beispiel auf der Gruppe webservers Apache eingerichtet wird und auf der Gruppe dbservers MariaDB.

Oft wird man die einzelnen Plays in externe Dateien auslagern, um die Lesbarkeit zu verbessern und nicht alles in einer großen Datei zu haben. Hierzu existiert ein entsprechender include Task:

Also los…

Für den Anfang halten wir unser Playbook einfach, verzichten auf Gruppen und machen mit dem einfachen inventory File aus dem letzten Teil weiter:

Unsere erste Aufgabe soll sein, dass alle Hosts eine Login Meldung ausgeben (aus /etc/motd) und ein richtiger Admin User angelegt wird, der sich per sudo zu root machen kann. Zur besseren Erweiterbarkeit werden wir von vornherein die Tasks auf mehrere Files aufteilen. Hierzu legen wir in unserem Playbook Verzeichnis erstmal ein weiteres Unterverzeichnis namens includes an: cd MeinPlaybook; mkdir includes.

Jetzt bearbeiten wir unser site.yml Playbook und fügen dort die entsprechenden Tasks hinzu. Hierzu müssen wir unter hosts: eine Gruppe angeben. Da wir keine Gruppen definiert haben, verwenden wir hier einfach die immer verfügbare Gruppe all, welche alle im Inventory vorkommenden Hosts umfasst. (Anmerkung: jedes YAML File beginnt mit --- – einfach mit reinschreiben)

motd.yml

Um die Login Nachricht auf meinen Systemen etwas zurechtzustutzen (ich verwende Ubuntu 16.04LTS), müssen einige Dateien aus /etc/update-motd.d entfernt werden und das File /etc/motd muss mit entsprechendem Inhalt versehen werden.

Zuerst räumen wir mal die Einzelfiles aus /etc/update-motd.d weg. Dazu benutzen wir das Ansible file Modul, mit dem man das Vorhandensein und das Fehlen von Dateien und Verzeichnissen steuern kann.

Ubuntu gibt standardmäßig beim Login eine Reihe von Links aus, die auf die Hilfe verweisen und bei Cloud-Instanzen (AWS, OpenStack usw.) wird auch noch ein Hinweis auf cloud-init ausgegeben. Beides benötige ich nicht bei jedem Login und möchte es daher entfernen. Die Meldungen kommen aus /etc/update-motd.d/10-help-text bzw. 51-cloudguest. Diese beiden Dateien müssen also gelöscht werden, was wir mit folgendem Task erledigen (den wir einfach in includes/motd.yml schreiben):

In der ersten Zeile geben wir einen Namen für den Task an. Dieser wird beim Ausführen des Playbooks angezeigt und erleichtert das verfolgen der durchgeführten Tasks und das Troubleshooting, wenn mal was nicht funktioniert. Die nächste Zeile gibt das zu verwendende Ansible Modul an, in diesem Fall file. Im Anschluss folgen die Parameter für das File Modul:

  • path spezifiziert den (absoluten!) Pfad zu der Datei, um die es geht
  • state spezifiziert den Zielzustand, in unserem Fall also absent

Bei der Angabe des Pfades sehen wir auch zum ersten Mal eine Variable in Jinja2 Template Notation: {{ item }}. Diese Variable entsteht aus dem nächsten Block: with_items spezifiziert eine Liste, über die dann der Task in einer Schleife laufen soll, damit wir nicht zwei fast identische Tasks für zwei Dateien benötigen. Die Variable {{ item }} wird bei jedem Durchlauf nacheinander mit den Werte aus der Liste belegt.

admin.yml

Als zweiten Job wollen wir einen Admin User anlegen, der sich mittels eines SSH Keys einloggen kann und der sudo ohne Passwort ausführen kann. Dazu legen wir einen normalen User an, installieren den entsprechenden SSH Key in der Datei ~/.ssh/authorized_keys. Ferner fügen wir den Benutzer der Gruppe sudonopwhinzu (diese legen wir vorher an) und erlauben dieser Gruppe dann den Befehl sudo ohne Passwort zu verwenden.

Diesen Code fügen wir in die Datei admin.yml ein. Zuerst stellen wir sicher, dass die Gruppe sudonopw existiert:

Dieser Task legt bei Bedarf die Gruppe an (state: present) und markiert diese als Systemgruppe. Dies hat unter Linux im Allgemeinen keine besondere Bedeutung, aber je nach Distribution werden Gruppen in einer bestimmten GID Range als Systemgruppen bezeichnet. Der entsprechende Parameter sorgt dafür, dass diese Regelungen eingehalten werden.

Als nächstes legen wir einen Benutzer an, und stellen sicher, dass dieser in der gewünschten Gruppe ist, wozu wir das userModul verwenden:

Die name und state Parameter funktionieren wie gehabt und comment hinterlegt einfach nur einen Kommentar zu dem User in /etc/passwd. Der append Parameter gibt an, ob die die unter groups spezifizierten Gruppen zu schon bestehenden hinzugefügt werden sollen (append: True) oder ob diese die schon bestehenden komplett ersetzen sollen (append: False). Mittels password geben wir einen Passwort Hash an. Diesen muss man bei Bedarf selbst erstellen – wir setzen hier den Hash auf den (unmöglichen) Wert „!!“, was dazu führt, dass dieser User sich nicht mit einem Passwort anmelden kann, sondern nur mittels eines SSH Keys, den wir im nächsten Task installieren:

Das authorized_key Modul von Ansible ermöglicht die Verwaltung von SSH Keys für Benutzer. Mittels user geben wir den Benutzer an, dessen Keys wir verwalten wollen, manage_dir teilt dem Modul mit, dass es das ~/.ssh Verzeichnis anlegen soll, falls es noch nicht existiert und exclusive sorgt dafür, dass nur genau der eine unter key angegebene Key für diesen User vorhanden ist (eventuelle andere Keys, die bereits vorhanden sind, werden entfernt!).

Beim keyParameter sehen wir auch gleich eine weitere Funktion: da SSH Keys inline im Playbook sehr unhandlich sein können, da diese unter Umständen sehr lang sind, lesen wir den Key mittels der lookup Funktion aus einer Datei ein, welche wir unter files/admin_ssh_key.pub im Playbookverzeichnis ablegen.

Zuletzt müssen wir noch die Konfiguration für passwortloses sudo erledigen. Hierzu verwenden wir das template Modul, mit dem man eine Datei aus einer Vorlage erstellen kann. An dieser Stelle sind zwar noch keine Variablen erforderlich, so dass wir auch einfach die fertige Datei auf das Zielsystem kopieren könnten, aber im weiteren Verlauf werden wir die Funktionalität noch erweitern, so dass es Sinn macht, das Modul hier schon zu verwenden.

owner, group und mode geben die UNIX Berechtigungen der Zieldatei an, dest zeigt an, wo die Datei auf dem Zielsystem liegen soll und src spezifiziert das Templatefile, das als Quelle genutzt werden soll. Das template Modul sucht Templates per Default unter templates/ im Playbook Verzeichnis (unter anderem) und genau dort legen wir die entsprechende Datei jetzt ab. Die Endung „.j2“ soll anzeigen, dass es sich bei der Datei um ein Jinja2 Template handelt und nicht etwa um eine direkt verwendbare Datei. Der Inhalt der Datei ist im Folgenden angegeben und entspricht der Standard sudoers Syntax.:

Die einzige Besonderheit ist die erste Zeile. Genau wie in den Playbooks können in einem Template Variablen referenziert werden. Das hier verwendete Makro ansible_managed gibt einfach einen Text aus, der anzeigt, dass die Config Datei durch Ansible erzeugt und verwaltet wird und der Inhalt sich daher automatisch ändern kann.

Und ab dafür…

Unser erstes (noch recht einfaches) Playbook ist fertig und wir müssen es nur noch laufen lassen.

Das geschieht mit dem Befehl ansible-playbook:

Mit -i geben wir das Inventoryfile an und site.yml ist der Name des Playbookfiles selbst. Wenn alles in Ordnung war, sollten jetzt einige „changed“ Tasks vorliegen und die Änderungen an den Zielsystemen wurden durchgeführt.

Ausblick

Im nächsten Teil schauen wir uns dann an, wie man wiederkehrende Aufgaben in eigene Module (die so genannten „Rollen“) auslagert und wie man diese mit Parametern versehen kann.

Series Navigation

Patrick Dreker

Patrick arbeitet seit 2006 bei der Proact Deutschland GmbH und bearbeitet dort die Themenfelder OpenStack, Cloud, Linux, Automatisierung und DevOps. Sein erster Linux Kernel war 1.2.13 und was Netscape und NCSA Mosaic waren, weiß er auch noch.

 
Kommentare

Noch keine Kommentare vorhanden.

Hinterlassen Sie einen Kommentar