Das Provisionierungstool Ansible eignet sich nicht nur zum Konfigurations- und Releasemanagement mittelgroßer Serverfarmen, sondern auch für den Hausgebrauch zum Restaurieren von Anpassungen auf dem Linux-Desktop daheim.
Etwa alle fünf Jahre ist es in den Perlmeister-Labs an der Zeit, eine neue Linux-Distro zu installieren. Nur alle fünf Jahre deshalb, weil es meiner Erfahrung nach mit einem Wahnsinnsaufwand verbunden ist, nach dem "do-release-upgrade" all die kleinen Cronjobs und Webapplikationen wieder zum Laufen zu bringen, die ein solcher Upgrade wegen neuer Versionen von Perl, Apache und diverser Libs anscheinend zwangsläufig mit sich bringt. Viele Open-Source-Entwickler, sowohl in der Distro-Szene (hallo, Ubuntu!) als auch im Tool-Bereich (hallo, Openssl!) scheren sich einen feuchten Kehricht um Backwards-Compatibility und ich darf mir jedes Mal ein paar Tage Urlaub nehmen, um fluchend an meinem Linux-Desktop zu sitzen und Konfigurationen zu ändern oder Skripts anzupassen. Und oft erinnere ich mich dann nur noch vage an all die vielen kleinen von Hand eingefügten Patches, die notwendig waren, um das ein oder andere Tool endlich zur Zufriedenheit zum Laufen zu bringen. Auch Wochen nach dem Upgrade ist es nicht ungewöhnlich, etwas zu finden, das nicht ordnungsgemäß funktioniert und ich zermartere mir oft das Hirn, um herauszufinden, wie ich das vor Jahren nochmal gelöst habe.
Server von Hand einzurichten löst ja im Profibereich schon seit vielen Jahren Lachkrämpfe aus, und Werkzeuge wie Puppet oder Chef erledigen dort Konfiguration und neue Releases automatisch und zuverlässig. Wer seine Änderungen in die versionierten Konfigurationsdateien dieser Tools einträgt hat zudem den Vorteil, dass alles jederzeit fehlerfrei reproduzierbar ist. Leider kommt jedes dieser Tools mit einer kultartigen Religion daher, deren Jünger sich anscheinend lebenslang verpflichten müssen, kostenpflichtige Trainings zu absolvieren, Zertifizierungen abzulegen, und ja nicht von der verbotenen Frucht zu naschen, nach deren Verzehr der Abtrünnige sich dann vielleicht fragen würde, ob es tatsächlich sinnvoll ist, sich vom kreativen Skripthacker zum willfährigen Anpasser von Konfigurationsdateien nach einem vorgegebenen Schema umschulen zu lassen.
Deswegen freute es mich während der Lektüre des relativ neuen und
sehr empfehlenswerten
Ansible-Buches [2], zu erfahren,
dass dieses freie Provisionierungswerkzeug (https://github.com/ansible/ansible)
nicht nur keinen Agenten auf dem
Zielsystem erfordert, weil
es sich einfach mittels ssh
dorthin verbindet,
sondern auch nach unten skaliert, also
auch für die rechenzentrumslose Oma Meume
ohne viel Brimborium einen einzigen Desktop verwalten kann.
Der Name Ansible kommt übrigens aus der Science-Fiction-Literatur und bezeichnet ein Gerät, mit dem man mit Überlichtgeschwindigkeit über Galaxieentfernungen hinweg völlig verzögerungsfrei kommunizieren kann. Wikipedia kommentiert jedoch lapidar, dass unsere Wissenschaftler zur Zeit noch nicht wissen, wie ein solches Gerät zu bauen wäre. Außerdem kann man zwar der Ansible-Religion beitreten und seine Anweisungen deklarativ im Yaml-Format schreiben, aber auch genauso gut handgestrickte Perl-Skripts ablaufen lassen.
Modernen Linux-Distros liegt das Tool als Paket bei. Unter Ubuntu installiert es sich zum Beispiel mit
sudo apt-get install ansible
und mit dem Ping-Modul und dem heimischen Desktop localhost
als
Ziel aufgerufen bestätigt es, dass alles in Ordnung ist und nichts
verändert wurde:
$ ansible localhost -m ping localhost | success >> { "changed": false, "ping": "pong" }
Nun zur Praxis: Eine einfache, aber ständig wiederkehrende Aufgabe nach der
Neuinstallation meines Systems zuhause ist es zum Beispiel, eine
maßgeschneiderte .vimrc
-Datei ins Home-Verzeichnis zu platzieren.
Glücklicherweise liegt diese öffentlich zugänglich in einem meiner Repositories
auf Github (Abbildung 1), aber Ansible kann jetzt das Herunterladen
automatisieren und gibt der Datei nach der Installation auch gleich noch die
voreingestellten Nutzerrechte. Und da die die Datei, die den Wunschzustand des
Systems beschreibt, zusammen mit einer Vielzahl anderer Ansible-Skripts in
einem Verzeichnis liegt, ist die Wiederherstellung der Vim-Konfiguration nun
fester Bestandteil der Systemrestauration, den garantiert kein schussliger
Admin vergisst.
Abbildung 1: Auf Github steht die maßgeschneiderte .vimrc-Datei des Autors. |
Seine Instruktionen bezieht Ansible aus sogenannten Playbooks, die im
Yaml-Format beschreiben, welche Kommandos auszuführen sind und welche
Dateien diese modifizieren, sodass es später feststellen kann, ob
eine Anweisung schon vorher erfolgt ist und keiner neuen
Anstrengung mehr bedarf. Ruft man Ansible also zweimal hintereinander
auf, sollte der zweite Aufruf ganz schnell ohne Änderungen zurückkehren.
Diese Idempotenz ist sehr hilfreich. Um zum Beipiel die besagte Datei
.vimrc
zu installieren, nutzt das Playbook in Listing 1 das standardmäßig
mit ausgelieferte Ansible-Modul get_url
,
das laut Dokumentation mindestens
die Parameter url
(Download-URL einer Datei),
dest
(der Name der lokal installierten Datei) und
mode
(die Unix-Nutzerrechte) erwartet. Dieses Playbook läuft auf
allen Linux-Distros und Plattformen, die Ansible unterstützt,
hilft also auch auch unterwegs auf meinen Macbook.
Das Modul get_url
wird übrigens bei einem zweiten Lauf in der Standardeinstellung
keinen erneuten Download einleiten, falls die lokale Datei bereits existiert.
Wer stetig auffrischen möchte, wenn neue Versionen vorliegen, weist Ansible
mit dem zusätzlichen Parameter force=yes
an,
die Datei bei jedem Aufruf des Plays vom Internet zu laden und die lokale Version
nur dann zu
ersetzen, falls sie von der frisch eingetroffenen abweicht.
01 --- 02 - name: Dev Tools Setup 03 hosts: localhost 04 tasks: 05 - name: Vimrc from Github dotfiles 06 get_url: > 07 url=https://raw.githubusercontent.com/mschilli/dotfiles/master/.vimrc 08 mode=0644 09 validate_certs=no 10 dest=~/.vimrc
Weiter steht in Listing 1 mit localhost
der Zielhost, und die Yaml-Datei
hat noch viel Platz für mehrere solcher "Plays", die jeweils aus verschiedenen
"Tasks" bestehen, denen jeweils mit name
ein Name zugeordnet ist, der bei der Ausführung mit
ansible-playbook vimrc.yml
wie in Abbildung 2 gezeigt darüber Auskunft
gibt, was gerade passiert und im Falle eventuell auftretender Fehler bei der
Ursachensuche hilft. Die Ausgabe zeigt an, dass .vimrc
ohne Probleme
heruntergeladen und installiert wurde, und dass sich deswegen der
Status des Linux-Desktopcomputers verändert hat. Die längliche Github-URL
zum direkten Laden der neuesten Version der dort eingecheckten Datei
fand ich übrigens im Browser auf github.com unter dem "Raw"-Button.
Abbildung 2: Ansible lädt eine .vimrc-Datei aus einem Github-Repository und installiert sie lokal. |
Als nächstes steht beim Anpassen einer Linux-Distribution typischerweise
ein Lauf meines Skripts binlinks
an, das seinerseits in einem lokal
geklonten Git-Repository liegt
und eine Vielzahl weiterer praktischer Perlskripts, die ihrerseits in
Unterverzeichnissen des gleichen Repos liegen, mit Einträgen im
bin
-Verzeichnis unter meinem Home-Directory verlinkt. Mein
Perlskript-Template-Generator tmpl
mit dem ich beinahe jedes Projekt starte,
ist so zum Beispiel
einfach von der Kommandozeile als tmpl
verfügbar, da mein
Kommandopfad $PATH
das Verzeichnis bin
in meinem
Homeverzeichnis mit einschließt.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use File::Basename; 04 use Sysadm::Install qw(mkd); 05 06 my($home) = glob "~"; 07 my $home_bin = "$home/bin"; 08 09 while(<DATA>) { 10 chomp; 11 12 my($linkbase, $src) = split ' ', $_; 13 14 $src = "$home/$src"; 15 my $binpath = "$home_bin/$linkbase"; 16 17 if(-l $binpath) { 18 warn "$binpath already exists"; 19 next; 20 } elsif (-e $binpath) { 21 warn "$binpath already exists, " . 22 "but not a link!"; 23 next; 24 } 25 26 if(! -d dirname($binpath)) { 27 mkd dirname($binpath); 28 } 29 30 symlink $src, $binpath or 31 die "Cannot link $binpath->$src ($!)"; 32 } 33 34 __DATA__ 35 binlinks git/myrepo/binlinks/binlinks 36 tmpl git/myrepo/tmpl/tmpl
1 --- 2 - name: Tools setup part 2 3 hosts: localhost 4 tasks: 5 - name: binlinks script 6 command: ./binlinks
Wie Listing 2 zeigt, erzeugt das Skript einfach für jedes unterhalb des
__DATA__-Bereichs am unteren Ende aufgelistete Skript einen Symlink, der
auf die Position des Originals im Repository zeigt. Der Aufruf
ansible-playbook binlinks.yml
lässt das Playbook in
Listing 3 mit dem Ansible beiliegenden command
-Modul ablaufen,
das wiederum das Skript binlinks
aufruft
und mit einer Erfolgsmeldung zurückkehrt, falls das Skript
seinerseits mit dem Returncode 0 abschließt.
Mit einer Kombination aus dem von Ansible unterstützten Template-Mechanismus zum Modifizieren von Dateien und weiteren Skripts kann ich nun auch kompliziertere Setups wiederholen, wie zum Beispiel die bislang bei jedem Upgrade zerstörte Nagios-Konfiguration. Auch wenn selbst Ansible nicht hellsehen kann und nur die anno dazumals ausgeführten Schritte wiederholt, hilft es doch ungemein, wenigstens zu wissen, welche Eingriffe zum Installieren notwendig waren, damit man sie später wenn auch leicht angepasst mit Ansible wiederholen kann.
Eine weitere Konfigurationsorgie steht an, wenn man zum Beispiel
ausprobieren möchte,
ob ein gerade in der Entwicklungsumgebung fertiggestelltes Perlmodul
auch auf einem Produktionssystem mit Apache2-Server und
mod_perl2-Beschleuniger fehlerfrei läuft. Am besten eignet sich dazu
ein Reinraum, der ausgehend von einer blanken Linux-Distro in einer
Vagrant-VM (vagrantup.com und [3]) nur die
absolut notwendigen Pakete installiert, den Server entsprechend konfiguriert
und das Perl-Modul ebenfalls hineinpflanzt. Damit sich der Entwickler
bei diesem Verfahren nicht die Finger wund tippt, liegen die Instruktionen
hierfür maschinenlesbar im Source-Repository und Ansible führt sie bei
jeder neu erzeugten VM genau gleich aus. Das Vagrantfile in Listing 4
setzt auf einer aktuellen Ubuntu-Distro auf, die sich vagrant
aus
dem Internet pumpt, falls sie noch nicht in der lokalen
VirtualBox-Installation bereitliegt. Weiter sorgt die Direktive
forwarded_port
dafür, dass der innerhalb der VM auf Port 80 lauschende
Apache-Server auf dem Hostsystem unter Port 8080 lauert.
1 VAGRANTFILE_API_VERSION = "2" 2 3 Vagrant::configure(VAGRANTFILE_API_VERSION) do |config| 4 5 # 32-bit Ubuntu Box 6 config.vm.box = "ubuntu/trusty64" 7 config.vm.network "forwarded_port", guest: 80, host: 8080 8 end
Zunächst muss Ansible allerdings erst einmal wissen, unter welchem
SSH-Port und mit welchem privaten Schlüssel es sich mit der frisch
mittels vagrant up
hochgefahrenen VM verbinden kann.
Der Aufruf von vagrant ssh-config
offenbart, dass die Ubuntu-VM
über ssh-Port 2222 auf dem lokalen Host erreichbar ist und unter welchem
Pfad der
private SSH-Schlüssel zur Identifizierung auf der Festplatte liegt.
Die Ansible-Konfigurationsdatei
in Listing 5 setzt diese Werte für Ansible und stellt damit sicher, dass
das Tool sich in der neu erzeugten VM einloggen und dort die
vordefinierten Arbeiten
verrichten kann. Wird ansible-playbook
aus dem gleichen Verzeichnis
aufgerufen, stellt es zum Verbindungsaufbau die in der
Konfigurationsdatei ansible.cfg
in Listing 5 festgelegten Werte
ein und fährt damit gut.
1 [defaults] 2 hostfile = hosts 3 remote_user = vagrant 4 private_key_file = /home/mschilli/.vagrant.d/insecure_private_key 5 host_key_checking = False
Bei neu geschriebenen oder aufgrund von Bugfixes leicht
modifizierten Softwareprojekten stellt sich immer die Frage, ob das System
nach dem Fix auch noch funktioniert. Eine Unit-Testsuite räumt schon mal
besonders peinliche Fehler aus dem Weg, aber letztendliche Klarheit
verschafft nur der Test in einer Produktionsumgebung. Ob der mod_perl-Handler
in Listing 6 zum Beispiel funktioniert, ist mit Sicherheit unter einem
Apache-Server mit installiertem mod_perl-Modul zu sagen. Der Entwickler müsste
dazu nicht nur die entsprechenden Distro-Pakete wie apache2
und mod-perl2
installieren, sondern auch noch den Webserver unter dem Pfad /modperltest
mittels
einer Konfigurationsdatei wie in Listing 7 auf mod_perl einnorden.
01 package ModPerlTest; 02 use strict; 03 use warnings; 04 05 use Apache2::RequestRec (); 06 use Apache2::RequestIO (); 07 use Apache2::Const -compile => qw( OK ); 08 09 sub handler { 10 my( $r ) = @_; 11 12 $r->content_type( 'text/html' ); 13 print "<H1>Ansible did it!</H1>\n"; 14 15 return Apache2::Const::OK; 16 } 17 18 1;
1 <Location /modperltest> 2 SetHandler perl-script 3 PerlResponseHandler ModPerlTest 4 </Location>
Ansible hilft auch hier.
Listing 8 zeigt das Playbook für einen Apache2-Server mit mod_perl2-Modul und
der Testapplikation (Listing 6) und die
das Playbook beim Aufruf von ansible-playbook mod-perl.yml
in der
vorher mittels vagrant up
hochgefahrenen VM installiert.
Der Parameter apt
gibt mit den Schlüsseln name
die Namen der
Ubuntu-Pakete an, die es zu installieren gilt. Der Parameter
update_cache=yes
veranlasst Ubuntu vorher dazu, die neuesten Indexdateien
der Repositories einzuholen, was zwar enorm Zeit kostet, aber bei einer
frischen Installation unumgänglich ist, da der Cache einer frischen Ubuntu-VM
immer veraltete Informationen birgt, die dazu führen, dass ein nachfolgendes
apt-get install
oft die benötigten Pakete nicht findet.
Da Ansible normalerweise nicht als root
läuft, das Kommando apt-get install
aber Root-Rechte in der VM benötigt, aktiviert Zeile 7 in Listing 8 den Sudo-Modus.
So kann jede Task ihre Rechte feinjustieren und nichts läuft unnötigerweise
mit gefährlich viel Feuerpower. Außerdem kopiert das Playbook in Listing 8
die Apache-Konfigurationsdatei aus Listing 7 nach /etc/apache2/conf, damit
der Aufruf von http://localhost:80/modperltest innerhalb der VM oder
dank des Port-Mappings von Listing 4 auf Port 8080 auf dem Hostrechner
die Mod-Perl-Applikation anspringt, die die Ausgabe in Abbildung 3 im
Browser erzeugt. Weiter sorgt Ansible dafür, dass der Apache-Server neu startet,
falls sich seine Konfiguration oder der Source-Code der Applikation
geändert hat. Die Direktive notify: restart apache
aus Zeile 11 löst immer dann den ab Zeile 18 definierten Restart-Mechanismus
via apachectl
aus, wenn der Entwickler eine neue Version der Applikation
einspielt.
01 --- 02 - name: mod_perl Test Application 03 hosts: testserver 04 tasks: 05 - name: Apache and mod_perl 06 apt: name=apache2,libapache2-mod-perl2 update_cache=yes 07 sudo: True 08 - name: Test script 09 copy: src=ModPerlTest.pm dest=/usr/lib/perl5/ModPerlTest.pm 10 sudo: True 11 notify: restart apache 12 - name: mod_perl conf 13 copy: > 14 src=mod-perl-test.conf 15 dest=/etc/apache2/sites-enabled/mod-perl-test.conf 16 sudo: True 17 handlers: 18 - name: restart apache 19 command: sudo apachectl restart
Abbildung 3: In der Vagrant-VM läuft dank Ansible mod_perl2 auf apache2 mit dem Testskript aus Listing 4. |
Mit diesen Skripts wird der Upgrade auf Ubuntu 21.04 im Jahre 2020 zweifelsohne wie am Schnürchen klappen. Da die Ansible-Instruktionen in einem Git-Repository liegen, können sie über die kommenden Jahre ständig gepflegt und erweitert werden, und beim nächsten Upgrade wird auch garantiert nichts versehentlich ausgelassen. Voraussetzung ist natürlich, dass Ansible in dem 2020 vermutlich aktuellen Release 7.42 noch die Syntax der Version 1.5.3 von anno 2015 beherrscht, was ja auch nicht selbstverständlich ist!
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2015/10/Perl
"Ansible: Up and Running", Lorin Hochstein, O'Reily 2015
"Entspannt nach draußen", Michael Schilli, Linux-Magazin 05/2013, http://www.linux-magazin.de/Ausgaben/2013/05/Perl-Snapshot