Prozesse automatisieren mit Ansible

15.12.2021 | Thilo Mull in allgemein

Zu den populärsten dieser Systeme gehören Puppet, Saltstack, Chef und Ansible. Gemeinsam haben alle diese Systeme, dass ein Soll-Zustand (record of intent) beschrieben wird, der dann auf den Zielsystemen in einen Ist-Zustand (desired state) umgesetzt wird. Für diesen Zweck verwenden alle Konfigurationsmanagementsysteme eine bestimmte Syntax für diese Zustandsbeschreibung. Meistens besteht das System darüber hinaus aus mehreren Teilen, bei denen zwischen kontrollierenden und den kontrollierten Komponenten entschieden wird. Dem in Ruby programmierten und auf Master/Client-Komponenten basierenden Puppet wird nachgesagt, dass das Erlernen der DSL (Domain Specific Language) zum Verfassen des Codes mit einigem Aufwand verbunden sein soll. Das Gleiche gilt in ähnlicher Weise für das in Erlang (Server) und Ruby (Client) programmierte Chef, dessen Zustandsbeschreibungen in Ruby verfasst werden. Das Master/Client(Minion) System Saltstack sowie Ansible sind in Python geschrieben und nutzen beide YAML (Yet Another Markup Language) zur Beschreibung der gewünschten Systemzustände, jedoch ist die von Saltstack verwendete Syntax an einigen Stellen deutlich komplexer als die von Ansible, besonders dort wo die Templatingsprache Jinja2 zum Einsatz kommt.

Warum Ansible?

Ansible ist das jüngste der oben genannten Systeme und wurde 2012 von Michael DeHaan ins Leben gerufen. Seit 2015 befindet sich Ansible im Besitz von Red Hat und wird als Open Source Projekt weiterentwickelt. Es ist in Python geschrieben und nutzt, wie bereits erwähnt, YAML-Syntax für die Beschreibung der gewünschten Systemzustände. Ein großer Unterschied zu den anderen Konfigurationssystemen besteht darin, dass Ansible ohne feste Master-Client Struktur arbeitet und somit auf den Zielsystemen lediglich Python installiert sein muss. Dies hat den großen Vorteil, dass Ansible ohne großen Aufwand auf verschiedenen Systemen eingesetzt werden kann. (Im Folgenden wird daher anstatt von Master von einem Controller gesprochen, da es sich technisch nicht um einen fest definierten Master handelt und prinzipiell jeder Host, auf dem Ansible installiert ist, über eine SSH-Verbindung jeden anderen Host konfigurieren kann.) Da Ansible seine Aufgabe über SSH verrichtet, benötigt es im Gegensatz zu den anderen Kofigurationssystemen keine Anmeldung des Clients bei einem Master und auch der Controller ist nicht festgelegt auf eine bestimmte Maschine, sondern auf die Inhaber der SSH-Zugangsdaten. Auch MacOs- und Windowsclients können mit Ansible bespielt werden, hierfür wird bei Windows dann eine Verbindung über WinRM verwendet. Aber der Reihe nach.

Ohne Master/Client System

Damit Ansible die Zielsysteme finden kann, müssen diese zunächst in eine als “Inventory” bezeichnete Datei eingetragen werden. Die Einträge können mit Variablen versehen und in Gruppen aufgeteilt werden, so dass eine große Variabilität bei der Auswahl der Systeme für die jeweiligen Befehle stattfinden kann. Somit können im selben Aufruf beispielsweise bestimmte Programme auf allen Systemen installiert, jedoch nach Gruppen unterschiedlich konfiguriert werden usw. Damit Ansible die Zielsysteme erreicht, muss SSH-Zugriff bestehen. Es ist zwar möglich mit Passwörtern zu arbeiten, allerdings wird es erst wirklich entspannt, wenn SSH-Keys auf den Zielsystemen hinterlegt werden. Da Ansible allerdings in der Lage ist, für die Verbindungen die SSH-Config (~/.ssh/config) auszulesen und zu nutzen, ist das Einrichten der Verbindungen schnell und komfortabel erledigt. Hierfür werden im Inventory einfach die HostNames aus der SSH-Config eingetragen. Ansonsten können diese auch individuell erstellt und die Verbindungen beliebig konfiguriert werden. Statt einer globalen Inventory-Datei können auch mehrere verschiedene, auf den jeweiligen Kontext angepasste, Inventories verwendet werden. Die Abwesenheit einer permanenten Master-Client Verbindung, wie sie in den anderen Konfigurationsmanagementsystemen implementiert ist, kann auch als Sicherheitsgewinn betrachtet werden. In einer permanenten Verbindung ist es den beteiligten Komponenten theoretisch möglich, sich im laufenden Betrieb gegenseitig zu kompromittieren. Ein Beispiel für einen solchen Fall lieferte im letzten Jahr Saltstack (CVE-2020-11651 und CVE-2020-11652).

Gute Übersicht in YAML und Projektstrukturen

Als Sprachen für Code kann Ansible für den ausführbaren Code JSON oder dessen Dialekt YAML und für andere Dateien, z.B. den Inventories, das INI-Format verwenden. Da jedoch an allen Stellen YAML benutzt werden kann, kann die Strukturierung von Code sehr einheitlich und übersichtlich und damit gut lesbar organisiert werden. Wer noch nie mit YAML zu tun hatte, braucht sich keine Sorgen zu machen, denn die Struktur und Aufteilung der sogenannten Dictionaries und Listen ist eingängig und ohne große Hürden zu erlernen. Auf dem ausführenden Controller kann Ansible übersichtlich global konfiguriert werden, so dass auch die Grundeinstellungen leicht getroffen werden können. Der Code kann dann von jedem beliebigen Rechner auf Zielsystemen ausgeführt werden, solange ein Zugriff über SSH besteht. Dies macht ihn vielseitig und flexibel einsetzbar. Die Flexibilität wird zusätzlich durch die Art und Weise unterstützt, in der die Projekte in Ansible angelegt und verwaltet werden.

Code Struktur von Ansibleprojekten

Ansible verfügt grundsätzlich über zwei Funktionsmodi: Zum einen können durch Ausführen von einzelnen Befehlen auf der Kommandozeile sogenannte Ad-Hoc Befehle abgesetzt werden. Zum anderen stehen für komplexere Aufgaben die “Playbook” genannten Skriptstrukturen zur Verfügung, mit denen auch sehr komplexe Aufgaben automatisiert werden können.

Das eigentliche Ausführen der Befehle auf den Zielsystemen übernehmen in Python geschriebene Module (https://docs.ansible.com/ansible/2.8/modules/list_of_all_modules.html). In den Ad-Hoc Befehlen wird jeweils ein Modul mit bestimmten Parametern auf den aus dem Inventory gewählten Systemen ausgeführt. Somit lassen sich schnell einfache Dinge wie die Installation von Paketen, das Sammeln von Informationen oder kleine Konfigurationsänderungen erledigen. Um jedoch ein gesamtes System oder gar eine ganze Landschaft zu beschreiben, reichen die Ad-Hoc Befehle nicht aus. Hierfür setzt Ansible die “Playbooks” ein.

Playbooks

Als “Playbooks” werden in YAML verfasste Dateien bezeichnet, in denen als “Plays” benannte Listen von zu erfüllenden Aufgaben (“Tasks”) gesammelt werden. Zu Beginn eines “Plays” werden Parameter wie Zielsysteme, der auf dem Zielsystem ausführende Nutzer oder Variablen definiert, die dann für die einzelnen “Tasks” gelten. In den “Tasks” selber werden — analog zu den Ad-Hoc Befehlen — Module mit Parametern angegeben. Anders als bei den Ad-Hoc Befehlen können hier allerdings beliebig viele “Tasks” hintereinander aufgelistet werden, die sich auch aufeinander beziehen bzw. voneinander abhängig gemacht werden können. Hierdurch können in “Plays” komplexere Aufgaben wie z.B. die Installation von Paketen samt deren Einrichtung und Konfiguration sowie dem Einrichten eines Services erledigt werden. Die Module in Ansible führen ihre Aufgaben idempotent aus, d.h. es wird sichergestellt, dass der im “Task” beschriebene Zustand auf dem Zielsystem hergestellt wurde, egal wie oft der Task ausgeführt wird. Bevor Ansible die im “Task” (oder Ad-Hoc Befehl) beschriebene Aufgabe ausführt, prüft es zunächst, ob ein Unterschied zwischen Soll- und Ist-Zustand besteht. Besteht dieser nicht, weil etwa das Paket schon installiert ist oder die Konfigurationsdatei schon dem gewünschten Zustand entspricht, wird der “Task” übersprungen und ein “unchanged” zurück gegeben. Das idempotente Verhalten ist ein großer Vorteil gegenüber z.B. Bash-Skripten, die eine solche Prüfung oft nicht leisten. Darüber hinaus können die “Tasks” mit Kontrollstrukturen und Variablen für verschiedene Fälle optimiert werden. Ein simples Beispiel hierfür wäre, den richtigen Paketnamen für die jeweilige Distribution auszuwählen. Während auf einem Ubuntu das Paket “apache2” installiert werden muss, heißt es auf einem CentOS “httpd”. Damit das Playbook vielseitig einsetzbar bleibt, kann Ansible anhand von Variablen automatisch entscheiden, wann welches Paket installiert werden soll, so dass ein einziges Playbook für alle Systeme genügt.

Jinja2 in Ansible

Variablen und Kontrollstrukturen wie Schleifen und Konditionalprüfungen werden in Ansible mit Hilfe der Python Templating Sprache “Jinja2” deklariert bzw. formuliert. Dabei ist es möglich, neben den selbst deklarierten Variablen auch auf sogenannte “Ansible Facts” zu referieren. Bei den “Ansible Facts” handelt es sich um von Ansbile bei jedem Ausführen automatisch erhobene Informationen über die Zielsysteme. Somit können sehr leicht IP-Adressen, Nutzer, Hardwaredaten usw. in die Tasks oder in Kontrollstrukturen eingebunden werden. Die oben bereits erwähnte Filterung der Distribution für die automatische Auswahl des richtigen Webserverpakets kann z.B. einfach mit einer If-Abfrage in Jinja2 an dem entsprechenden “Ansible Fact” entschieden werden. Zudem können Werte, die von Tasks zurückgegeben werden, in Variablen registriert und in weiteren Tasks oder für Konditionalprüfungen genutzt werden. Selbst erstellte Variablen können auch als YAML-Listen deklariert werden, über die der Task dann iteriert, wodurch beispielsweise in einem einzigen Task mehrere Pakete installiert werden können. Der Ansible Befehl zum Ausführen von Playbooks verfügt über Parameter, mit denen sich Variablen direkt über die Kommandozeile überschreiben lassen, was für kurzfristige Änderungen oder für Fehleranalysen sehr hilfreich ist. Mit Jinja2 lassen sich neben Variablen und Kontrollstrukturen auch Filter und Lookups erstellen, mit denen Daten aus Ausgaben von “Tasks” sowie Variablen formatiert werden können, bevor sie dann im Playbook an anderer Stelle direkt weiterverwendet werden. Jinja2 wird von Ansible allerdings nicht nur für Variablen, Kontrollstrukturen und Filter verwendet, sondern auch für Templating. Mit Templating ist das Verwenden von Vorlagen oder Mustern gemeint. Durch die Verwendung dieser Vorlagen können beispielsweise häufig vorkommende Konfigurationsdateien, wie die für Virtuelle Hosts von Webservern, an die entsprechende Stelle im Zielsystem kopiert werden, wobei Jinja2 die Variablen in den Dateien mit den benötigten Werten — etwa Domainnamen oder Ip-Adressen — ausfüllt. Hierbei können auch solche Informationen verwendet werden, die erst während des Ausführens der im Playbook enthaltenden Tasks erzeugt werden. Mit der Verwendung von Jinja2 könnten mit Playbooks im Prinzip schon alle Aufgaben bewältigt werden. Allerdings drohen die Playbooks ab einer gewissen Länge zwangsläufig unübersichtlich zu werden, besonders wenn Kontrollelemente und Filter ins Spiel kommen. Daher ist es nicht verwunderlich, dass Ansible eine noch größere und weiter ausdifferenzierte Struktur nutzt: Rollen.

Rollen

Bei den Rollen handelt es sich im Prinzip um eine Verzeichnisstruktur, in der die einzelnen Bereiche eines Playbooks auf verschiedene Verzeichnisse verteilt werden. In diesen Bereichen befinden sich dann widerum YAML-Dateien, in die einzelne Aspekte aufgeteilt werden. So wird beispielsweise ein Verzeichnis für Variablen angelegt, in dem die einzelnen Variablen in eigenen Dateien deklariert werden. Analog dazu wird ein Verzeichnis angelegt, in dem die “Tasks” in jeweils eigenen Dateien ausgelagert werden. Weitere Verzeichnisse beinhalten unter anderem Jinja2 Vorlagen, Dateien, die auf Zielsysteme kopiert werden sollen oder Metadaten, die den Zweck der Rolle beschreiben. Durch die Verwendung von Rollen ist es möglich, Ansibleprojekte nach Teilaspekten — z.B. Webserver installieren und konfigurieren — gut strukturiert und autonom zu verwalten. Für das Ausrollen des Codes werden die Rollen in Playbooks integriert und wie gewohnt ausgeführt. Ansible ist in der Lage, die einzelnen Bereiche der Rolle anhand der Verzeichnisstruktur auszulesen und korrekt zuzuordnen. Das hat den großen Vorteil, dass ein Playbook mehrere Rollen einbinden kann, die wiederum jeweils einen Teilaspekt der Zielsysteme verwalten. Dadurch können einzelne Teilaspekte der Systemlandschaft von verschiedenen Teams entwickelt und gepflegt werden. Pro Rolle kann beispielsweise ein Git-Repo angelegt werden, in dem der Ansiblecode dann bearbeitet wird. Für den Einsatz werden alle Rollen in ein Playbook integriert und ausgeführt. Hierdurch bieten sich weitere Möglichkeiten für den Einsatz von Ansible, die über das bloße Konfigurationsmanagement hinausgehen.

Möglichkeiten mit Ansible

Da es viele Anwendungen gibt, die sehr häufig in verschiedenen Kontexten immer wieder zum Einsatz kommen und da die Ansible Rollen sehr flexibel einsetzbar sind, bietet es sich an, eine Art Sammlung von Rollen zu haben, die dann an die jeweilige Anforderung angepasst werden. Um genau so eine Sammlung handelt es sich bei der Ansible Galaxy. In der Galaxy befindet sich für nahezu jeden (Standard-)Fall die passende Ansible Rolle. Ansible bringt auch gleich das passende Kommandozeilenwerkzeug mit, mit dem die Rollen erstellt, geladen, geteilt und verwaltet werden können. Jeder kann dort Code beziehen oder zur Verfügung stellen.

Ansible hat aber auch über das reine Konfigurationsmanagement hinaus einiges zu bieten. Zum Beispiel bietet es sich an, eigene Anwendungen mit Ansible auszurollen (Application Deployment). Eine Art wäre mit bestimmten Modulen Code direkt aus Git-Repos, äquivalent zu einem “git pull”, zu laden und darüber hinaus mit anderen Modulen zu kompilieren und installieren. Somit kann Git über das Verwalten von Rollen hinaus in den Entwicklungsprozess mit Ansible integriert werden. Der Code kann damit von Ansible direkt aus Git geholt und genutzt werden, was neben dem Ausrollen auch für die Entwicklung von Software höchst interessante Möglichkeiten bietet. Ein hier noch nicht zur Sprache gekommener Mechanismus von Ansible sind Tags, mit denen Tasks markiert und anschließend isoliert ausgeführt werden können. Gemeinsam mit Tags, Gruppen im Inventory und als Schalter gesetzten Variablen lässt sich so zum Beispiel eine CI/CD (Continuous Integration/Continuous Delivery) Pipeline erzeugen, in der sich die verschiedenen Stationen der Codeentwicklung abbilden lassen. Hierdurch können bestimmte “Tasks” in den Playbooks und Rollen bestimmten Zielsystemen zugeordnet werden und durch die Variablen mit individuellen Modifikationen ausgeführt werden. Durch die Rollenstruktur bleibt der Projektcode selbst dabei höchst mobil und flexibel, so dass simultanes Arbeiten verschiedener Teams an demselben Code kein Problem darstellt. Durch das idempotente Verhalten von Ansible wird dabei stets sichergestellt, dass die Versionen auf den Zielsystemen stets den gewünschten Ständen entsprechen und es nicht zu ungewollten Fehlern kommt. Das idempotente Verhalten kann zudem auch automatisiert gewährleisten, dass die Sicherheitseinstellungen auf allen Systemen stets dem aktuellen Soll-Zustand entsprechen. Hierzu gehören dann z.B. die Installation von Sicherheitsprogrammen sowie deren Konfiguration, das Einrichten und Konfigurieren von Logging, Netzwerkkonfigurationen oder die Einstellung von Firewalls. Auch dieser Aspekt kann widerum von einem eigenen Sicherheitsteam entwickelt und zur Verfügung gestellt werden. Mit der von Ansible zur Verfügung gestellten Funktion “Ansible Vault” und Jinja2 Filtern für den Zugriff auf z.B. Keepass-Dateien können Zugangsdaten direkt in den Prozess integriert werden. Es ist möglich die Repositories mit dem Code öffentlich zu hosten, sensible Inhalte jedoch “Ansible Vault” innerhalb der YAML-Dateien zu verschlüsseln. Durch die Playbookstruktur können beliebig viele Rollen in einem Aufruf auf den Zielsystemen ausgeführt werden, so dass die an unterschiedlichen Enden entwickelten Komponenten hier beim Ausführen des Codes zusammenfließen.

Grafische Oberflächen für Ansible

Mit “Ansible Tower” oder dessen Open Source Upstream Variante “AWX” stehen zwei WEB basierte grafische Oberflächen für Ansible zur Verfügung. Mit diesen können per Klick Aufgaben automatisiert und Projekte an andere Nutzer delegiert werden, ohne dass diese selbst Zugriffsrechte auf den Zielsystemen erhalten müssen. “Ansible Tower”, bzw. “AWX” laden den Ansible Code direkt aus Quellen wie GitHub, verwalten Inventories sowie Zugangsdaten wie Benutzer, Passwörter und Keys. Innerhalb der Weboberflächen lassen sich Projekte vollständig verwalten, so dass es nicht mehr nötig ist, für das Ausführen des Codes auf der Kommandozeile zu arbeiten. Durch diese Eigenschaften – das automatische Laden des Codes, die Verwaltung der Zugriffsrechte sowie das Arbeiten in der grafischen Oberfläche – bietet sich die Arbeit mit “AWX”" bzw. “Ansible Tower” für die Arbeit in Teams an. Eine Entwicklungsabteilung kann den Ansible Code entwickeln und über das Web-Frontend sogar automatisiert testen. Administratoren und andere Anwender können den Code jedoch in dem ihnen zugewiesenen Umfang per Klick selber ausführen und damit unabhängig arbeiten. Genutzt werden kann entweder die kostenfreie Open Source Variante “AWX” oder, wenn es auf feste Release-Zyklen und ggf. Support von Seiten von Red Hat ankommt, die kostenpflichtige Enterprise Version “Ansible Tower.”

Integration in andere Systeme

Neben den Features und Funktionen, die Ansible selbst bereits mitbringt, kann Ansible durch vielfältige Integrationen auch in Kombination mit anderen Systemen genutzt werden und diese bereichern. Hierzu gehören etwa die Nutzung von APIs großer Cloud-Anbieter durch eigene Module. Als prominente Beispiele seien hier Amazons AWS oder Microsofts Azure erwähnt. Für AWS stehen fast 100 Module zur Verfügung, mit denen sich ganze AWS-Systemlandschaften, samt deren Einzelkomponenten, in einem Playbook bzw. einer Rolle beschreiben lassen. Ähnlich sieht es bei Azure aus, auch hier lassen sich mit den entsprechenden Modulen ganze Landschaften erstellen, einzelne Systeme provisionieren und Netzwerke einrichten. Durch das idempotente Verhalten können die so in den Clouds erstellten Ressourcen auch einfach durch kleine Änderungen an den Playbooks beliebig skaliert werden. Ansible setzt den gewünschten Soll-Zustand in den Clouds in einen Ist-Zustand um. Mit dem Projekt “Kubespray” wird eine Ansible-Rolle zur Verfügung gestellt, mit der sich einfach und komfortabel Kubernetes Cluster installieren lassen. Auch “Kubespray” ist für die Nutzung in Clouds optimiert, so dass sich Kubernetes damit automatisiert unter anderem in AWS, Azure oder Openstack installieren lässt. Auch Terraform lässt sich mit Ansible über ein spezielles Modul benutzen. Ansible ist dabei nicht nur in der Lage damit Ressourcen in der jeweiligen Cloud zu erzeugen, sondern kann darüber hinaus die in dem Prozess entstehenden Informationen auslesen und direkt weiterverwenden, um die Maschinen zu provisionieren und einzurichten. Zudem existieren eine Reihe an speziellen Modulen mit denen sich bestimmte Sonderfälle abdecken lassen. So kann z.B. Cisco Hardware mit Ansible eingerichtet und verwaltet werden. Wenn die Module bisher nicht existieren, so können benutzerdefinierte Module selbst geschrieben und verwendet werden.

Fazit

Mit Ansible lassen sich nahezu alle Prozesse abbilden und automatisieren. Anders als in den anderen Technologien für Konfigurationsmanagement benötigt Ansible keine Voraussetzungen außer einer Pythoninstallation auf den Zielsystemen. Clients können von jedem Rechner aus eingerichtet werden, von dem eine SSH-Verbindung besteht. Die dabei verwendete und relativ leicht zu erlernende YAML-Syntax in Verbindung mit den Playbook- und Rollenstrukturen gewährleisten in dem Prozess nicht nur eine sehr gute Übersichtlichkeit, sondern bilden eine sehr gute Grundlage, um Arbeitsabläufe insgesamt zu optimieren. Durch die Integration der Jinja2 Templating Sprache gestaltet sich das Deklarieren von Variablen und das Erstellen von Kontrollstrukturen einfach und übersichtlich. Durch die “Ansible Facts” stellt Ansible selber eine Reihe von Informationen bereit, die wie Variablen in der Erstellung von Aufgaben verwendet werden können. Das Jinja2 Templating erlaubt es, Vorlagen von Konfigurationsdateien automatisch mit individuellen Informationen aus den laufenden Prozessen auszufüllen und auf die Zielsysteme zu kopieren. Schon bestehende Konzepte wie CI/CD sowie die Entwicklung in verschiedenen Abteilungen und Teams lassen sich, nicht zuletzt durch die Nutzung von Versionierungswerkzeugen wie Git, ebenfalls sehr gut in den Arbeitsprozess mit Ansible integrieren. Mit den grafischen Web-Oberflächen AWX und “Ansible Tower” sind die Hürden für die Verwendung von Ansible nicht nur noch weiter gesenkt, sondern Prozesse können sicher automatisiert und delegiert werden. Das idempotente Verhalten in Verbindung mit den zahlreichen Modulen sorgen für ein sicheres und reibungsloses Ausrollen des Codes, mit dem ganze Landschaften nach der Beschreibung der Playbooks erzeugt und eingerichtet werden. Der Aufbau der Playbooks erlaubt zudem die nahtlose Integration von Sicherheitskonfigurationen und gewährleistet einfaches und unkompliziertes horizontales wie vertikales Skalieren im Bedarfsfall. Die Integration in alle wichtigen zeitgenössischen Technologien und Infrastrukturen sowie das einfache Erstellen von Modulen zum Erschließen neuer Schnittstellen und Technologien ermöglichen einen nahezu universellen Einsatz für fast alle denkbaren Szenarien. Ansible ist ein Allrounder, der durch seine vielen Stärken überzeugt.

Thilo Mull
Thilo Mull
Thilo Mull arbeitet seit 2019 als Technical Writer bei B1 Systems GmbH und verfasste seitdem unter anderem Schulungsunterlagen zu Themen wie Ansible, Salt und Kubernetes. Seit seiner Jugend ist er fasziniert von freier und Open-Source-Software und betreibt mit Begeisterung so viele Dienste wie möglich selber.

 


Haben Sie Anmerkungen oder Nachfragen? Melden Sie sich unter blog%b1-systems.de
Col 2