Ansible - Warum selber machen, wenn es sich auch automatisieren lässt?

titelbild-ansible

Könnt ihr euch noch an Linux-Install-Partys erinnern? Eine Gruppe (nicht selten Informatik-Studierende) trifft sich und installiert und konfiguriert den ganzen Abend lang Linux auf ihren privaten Rechnern. Ihr könnt euch ein schöneres Abendprogramm vorstellen? Dann zeige ich euch jetzt, wie ihr das Ganze auch in 30 Minuten erledigt, indem ihr die Konfiguration komplett automatisiert - mit Ansible!

Was ist Ansible?

Ansible ist ein Open-Source-Tool zur Orchestrierung, Konfiguration und Administration von Computern und erfreut sich seit dessen Erscheinungsjahr 2012 immer größerer Beliebtheit. Ansible benötigt für seine Aufgaben lediglich eine SSH-Verbindung zum Zielrechner und ist somit nicht auf einen dort bereits installierten Client angewiesen. Es selbst besteht aus einzelnen Kollektionen, die entweder schon vorinstalliert sind, nachinstalliert oder auch komplett selbst geschrieben werden können. Eine Kollektion wiederum besteht aus einzelnen Plugins und Modulen. Plugins erweitern die Basisfunktionalität von Ansible, wie zum Beispiel das Lesen und Schreiben von Dateiinhalten oder Umgebungsvariablen. Diese Funktionalitäten werden dann innerhalb von Modulen verwendet, welche alleinstehende und wiederverwendbare Skripte darstellen, die Ansible dann auf den Zielrechnern ausführen kann.

Ansibles Denkweise

Es ist wichtig, die Denkweise von Ansible zu verstehen: Wir sagen Ansible nicht, was zu tun ist, sondern nur, welchen Status Ansible herstellen soll. Dazu ein Beispiel: Wir teilen Ansible nicht mit, dass es ein gewisses Programm installieren soll, sondern nur, dass dieses Programm installiert sein soll. Ansible wird dann selbstständig erkennen, ob es hier etwas zu tun gibt, oder ob der gewünschte Status bereits erfüllt ist.

Ansible installieren

Ansible selbst liefert eine gute Dokumentation zur Installation auf Linux-Systemen. Sucht euch einfach die Anleitung für eure Distribution heraus und führt die entsprechenden Befehle aus. Wollt ihr Ansible unter Windows benutzen, so empfehle ich euch entweder auf WSL2 zurückzugreifen oder Ansible innerhalb eines Docker-Containers zu benutzen.

Unser erstes Playbook

Ansible-Playbooks sind YAML-Dateien, in denen wir Ansible mitteilen, WO wir WAS für einen Status mit WELCHEM USER erreichen möchten. Dazu ein kleines Beispiel:

# File: playbook.yml
---
- name: step 1
  hosts: 192.168.0.1,192.168.0.2
  remote_user: ordix
  tasks:
  - name install curl with apt
    apt:
      name: curl
      
- name: step 2
  hosts: 192.168.0.3
  remote_user: ordix
  tasks:
  - name install curl with apt
    apt:
      name: curl
  - name: install software-properties-common with apt
    apt:
      name: software-properties-common 

Das Playbook in diesem Beispiel besteht aus zwei Schritten (Plays), die ich mit step 1 und step 2 benannt habe. Diese Namen dienen nur unserer Übersicht und sollten bei euch natürlich aussagekräftig für den Inhalt sein. Über hosts legen wir nun das WO fest. Schritt 1 wird auf zwei Zielsystemen gleichzeitig ausgeführt (hier mit 192.168.0.1 und 192.168.0.2 angegeben) und Schritt 2 auf nur einem Zielsystem (192.168.0.3). Diese Zielsysteme werde ich im Folgenden als vm1, vm2 und vm3 bezeichnen. Die Frage des zu benutzenden USER beantworten wir per remote_user. Hier gehen wir davon aus, dass es auf den Zielsystemen ein User namens "ordix" existiert. Im Bereich tasks legen wir nun fest, WAS für einen Status Ansible auf den hosts herstellen soll.

Ähnlich zu einem Schritt im Playbook solltet ihr jeden Task passend über name benennen. Gute Namen helfen euch später, auch in großen Playbooks die Übersicht zu behalten! In diesen Tasks verwenden wir die bereits beschriebenen Module von Ansible. In diesem Beispiel wollen wir nur erreichen, dass auf allen Maschinen curl und auf dem dritten Host ebenfalls software-properties-common installiert ist. Passend dazu gibt es das Modul apt, welches die gewünschte Software über den gleichnamigen Paketmanager apt installieren kann. In der Dokumentation des Moduls sehen wir, dass wir unsere gewünschte Software einfach über das name-Attribut angeben müssen.

Führt ihr das Playbook mit dem Befehl ansible-playbook playbook.yml aus, so wird sich Ansible über SSH mit den Zielsystemen als User "ordix" verbinden und unsere Tasks ausführen. Sollte beispielsweise curl auf vm1 bereits installiert sein, so wird dies von Ansible erkannt und gibt den Status des Tasks mit "ok" aus. Hier war also nichts zu tun. Falls curl nicht installiert war, so wird der Status nach der Installation mit "changed" angegeben.

Tasks mit Ansible-Rollen wiederverwenden

Wie wir im Beispiel gesehen haben, taucht der Task zum Installieren von curl doppelt auf. Spätestens bei mehreren sich wiederholenden Tasks werdet ihr euch wünschen, dass ihr eure Tasks in irgendeiner Weise wiederverwenden könntet.

Die Lösung für dieses Problem sind Ansible-Rollen! In diesen könnt ihr beliebig viele Tasks bündeln und sie in mehreren Playbooks wiederverwenden. Dafür müsst ihr nur noch angeben, welcher Host welche Rolle haben soll, und Ansible führt automatisch alle Tasks der Rollen auf den zugehörigen Hosts aus.

Unsere erste Ansible-Rolle

Für benutzerdefinierte Rollen gibt Ansible eine feste Ordnerstruktur vor. Um unseren Task zum Installieren von curl wiederverwenden zu können, erstellt ihr euch eine neue Datei unter folgendem Pfad (ausgehend von der playbook.yml):
roles/common/tasks/main.yml
.
Hier ist common der Name der Rolle. Solltet ihr sie anders benennen wollen, so muss der Pfad bei euch natürlich anders aussehen. In die main.yml der Rolle schreibt ihr jetzt eure gewünschten Tasks. In unserem Fall ist das nur ein Task. Die main.yml sieht wie folgt aus:

# File: roles/common/tasks/main.yml
---
- name install curl with apt
    apt:
      name: curl={{curl_version}} 

Zu Demonstrationszwecken lasse ich Ansible curl in einer bestimmten Version installieren. Diese Version wird über eine Variable curl_version angegeben, welche anhand der doppelt geschweiften Klammern zu erkennen ist. Ansible wir nun bei der Ausführung der Rolle erwarten, dass diese Variable definiert wurde. Diese Definition findet dann innerhalb unseres Playbooks statt.

Der Vorteil wird natürlich schnell klar: Wir können somit Rollen schreiben, dessen Verhalten mithilfe dieser Variablen gesteuert werden können.

Nun können wir diese Rolle in unserem Playbook verwenden. Für diesen Fall gibt es das Attribut roles:

# File: playbook.yml
---
- name: step 1
  hosts: 192.168.0.1,192.168.0.2
  roles:
    - common
  vars:
    curl_version: 7.68.0-1ubuntu2.4
      
- name: step 2
  hosts: 192.168.0.3
  roles:
    - common
  vars:
    curl_version: 7.68.0-1ubuntu2.4
  tasks:
  - name: install software-properties-common with apt
    apt:
      name: software-properties-common 

Wie wir sehen, weisen wir nun allen Hosts die Rolle common zu, wobei wir auf dem Host vm3 noch ein weiteres Paket installieren.

Tipp: Rollen werden immer vor den Tasks ausgeführt.

Unsere Variable curl_version wird über vars innerhalb beider Schritte definiert und ist somit auch jeweils innerhalb der Rolle bekannt. Wollt ihr die Rolle common mit verschiedenen Belegungen der Variable curl_version innerhalb eines Schrittes verwenden, so verwendet ihr die hier vorgestellte Schreibweise.

Weitere Informationen über Ansible-Rollen findet ihr hier.

Wie verwalte ich meine Hosts?

Bisher haben wir unsere Zielsysteme in jedem Schritt des Playbooks direkt angegeben. Zum einen kann die Liste hosts in Schritt 1 natürlich beliebig lang werden und zum anderen müssten Änderungen der IPs an vielen verschiedenen Stellen vorgenommen werden. Zur besseren Verwaltung von Hosts bietet Ansible daher die Möglichkeit, diese in Inventories zu verwalten.

# File: inventory.yml
---
all:
  children:
    group1:
      hosts:
        vm1:
          ansible_user: ordix
          ansible_host: 192.168.0.1
        vm2:
          ansible_user: ordix
          ansible_host: 192.168.0.2
    group2:
      hosts:
        vm3:
          ansible_user: ordix
          ansible_host: 192.168.0.3 

Hier definieren wir unsere Hosts vm1, vm2 und vm3 mit zugehöriger IP-Adresse und geben den User an, den Ansible zum Ausführen seiner Tasks verwenden soll. Unsere Hosts sind außerdem in zwei Gruppen unterteilt, die hier nur group1 und group2 heißen. Diese Gruppen können das Zuweisen verschiedener Rollen in größeren Infrastrukturen vereinfachen. Möchtet ihr alle Hosts ansprechen, so könnt ihr das Schlüsselwort all verwenden.

Mithilfe dieses Inventories, welches direkt neben der playbook.yml liegt, können wir unser Playbook noch weiter vereinfachen:

# File: playbook.yml
---
- name: step 1
  hosts: all
  roles:
    - common
  vars:
    curl_version: 7.68.0-1ubuntu2.4
      
- name: step 2
  hosts: group2
  tasks:
  - name: install software-properties-common with apt
    apt:
      name: software-properties-common 

Wie ihr anhand der hosts Attribute erkennen könnt, weisen wir unsere Rollen und Tasks über Gruppen des Inventories zu. Im ersten Schritt wird die Rolle common auf alle im Inventory definierten Hosts angewendet (hosts: all). Im zweiten Schritt wenden wir den Task zur Installation von software-properties-common auf alle Hosts der Gruppe group2 an, wobei es sich hier nur um die vm3 handelt (hosts: group2).

Fazit

Mit Ansible könnt ihr schnell und einfach eine einzige Maschine oder auch eine ganze Infrastruktur in euren gewünschten Status versetzen. Kommt mal ein neues System dazu, so ist das Inventory schnell angepasst. Durch das Rollen-Konzept, zusammen mit der Benutzung von Variablen, spart ihr euch doppelte Arbeit und könnt eure Playbooks sehr übersichtlich gestalten.

Natürlich müssen die Rollen aber auch erstmal geschrieben werden. Vielleicht hört ihr dann bald von der ersten Ansible-Rollen-Schreib-Partys. Wer weiß das schon...

Ein abschließender Tipp: Wer noch mehr über Ansible lernen möchte, dem lege ich unser Seminar Konfigurationsmanagement mit Ansible nahe!

By accepting you will be accessing a service provided by a third-party external to https://blog.ordix.de/