Willkommen zur neuen Rubrik „kuerbis.org weekly“. Um diesem Blog ein wenig mehr Leben einzuhauchen, will ich von Zeit zu Zeit ein paar Sätze über das schreiben, was mir in den letzten Tagen aufgefallen ist, woran ich gearbeitet habe usw.. Ob es wirklich jede Woche stattfindet, sei mal dahin gestellt, aber in loser Folge werde ich sicherlich dazu kommen.
Ansible
Für den Anfang habe ich mir zwei Themen ausgesucht, die mir letzte Woche einmal mehr über den Weg gelaufen sind. Ansible ist grob gesagt ein Tool zur Automatisierung von Administrationstätigkeiten. Vor einiger Zeit habe ich mich bereits damit beschäftigt, als es darum ging, das Deployment einer PHP-Anwendung zu vereinfachen bzw. neu zu erstellen. Neben Ansible existieren klassisch die beiden Tools Puppet und Chef. Ein Vorteil von Ansible ist, dass auf der Server-Seite außer einem SSH-Zugang keine weiteren Voraussetzungen bestehen. Ansible setzt auf Python auf, was zum Standardumfang der meisten Distributionen gehört.
Warum Ansible und wie wird es eingesetzt? An meiner Arbeitsstelle wurde die Server-Infrastruktur vor kurzem neu gestaltet. Mittlerweile gibt es ein paar Dutzend virtuelle Server, die auf einer Handvoll Hosts verteilt sind. Dabei lassen sich Kategorien von Servern zusammen fassen, etwa Web-Server, Datenbank-Nodes, Cache-Server usw.. Zwar betreibe ich privat auch ein paar virtuelle Server bei verschiedenen Providern, und die Anzahl der virtuellen Server im Keller nimmt gefühlt auch ständig zu, aber hier stellen sich andere Anforderungen, da diese Umgebung sehr heterogen ist. Hier gleicht kein Server dem anderen, muss es auch nicht, da ich viele der internen Server zu Testzwecken verwende, und auch die externen Server sind von den Aufgaben her eher vielfältig. Insofern unterscheidet sich auch die eingesetzte Software auf den einzelnen Servern. Ansible kann seine Vorteile jedoch erst richtig ausspielen, wenn mehrere Server, die zu Gruppen zusammen gefasst werden können, administriert werden müssen. Wenn Gruppen nicht ausreichen, lassen sich auch Hierarchien bilden, diesbezüglich verweise ich jedoch auf die offizielle Dokumentation.
Zur Vorbereitung muss es einen User auf dem Server geben, der sich mit SSH einloggen darf. Ich habe auf allen Servern daher einen speziellen User eingerichtet, der sudo-Rechte besitzt. Aus praktischen Gründen sind die Rechte (mit visudo) so angepasst, dass bei diesem User für die sudo-Rolle keine weitere Passworteingabe mehr notwenig ist. Der SSH-Key hingegen ist mittels Passwort geschützt.
Ansible lässt sich grundsätzlich in zwei Modi betreiben. Zum einen gibt es die Ad-hoc-Commands, zum anderen die Ansible-Playbooks.
Ansible-Ad-hoc-Commands
Die Ad-hoc-Commands sind genau das, was ihr Name nahe legt: Dabei lassen sich einmalig Tätigkeiten ausführen, natürlich auf beliebigen einzelnen Hosts oder Gruppierungen.
Ein Beispiel:
ansible __server/group__ -m replace -a "dest=/etc/postfix/main.cf regexp='^relayhost.*$' replace='relayhost=mailout.example.com' backup=yes" --sudo
Im betreffenden Fall ging es darum, die Konfiguration von Postfix auf einigen Servern zu korrigieren. Das Kommando nutzt das Ansible-Modul ‚replace‘ und ersetzt in der Datei /etc/postfix/main.cf mittels regulärem Ausdruck den leeren Eintrag ‚relayhost=‘ durch das hier genannte Beispiel. Dabei wird ein Backup der ursprünglichen Datei angelegt. Anstatt einen speziellen Server oder eine Gruppe zu verwenden, kann auch das Schlüsselwort ‚all‘ benutzt werden, damit wird Ansible dazu aufgefordert, das Kommando auf allen konfigurierten Hosts auszuführen. Die Hosts bzw. Gruppen werden per Default in /etc/ansible/hosts eingetragen.
Ein weiteres Beispiel:
ansible __server/group__ -m service -a "name=postfix state=restarted" --sudo
Noch einfacher – der Postfix-Service wird neu gestartet. Anstatt sich also auf jedem Server einzuloggen und das Kommando zum Restart auszuführen, genügt hier ein einziger Befehl. In beiden Beispielen wurden Ansible-Module verwendet, von denen eine stattliche Anzahl bereit steht. Falls das einmal nicht ausreichen sollte, lassen sich auch beliebige Shell-Kommandos ausführen, eben mit dem shell-Modul.
Ansible-Playbooks
Kommen wir zur zweiten Möglichkeit, dem Einsatz von so genannten Ansible-Playbooks. Dabei handelt es sich um gescriptete Konfigurationsanweisungen im YAML-Format, man könnte es auch Konfigurations-Skript nennen. Ein Playbook kann dabei aus einer Vielzahl von Tasks und Handlers bestehen. Zur Wiederverwendung und einfachen Orchestrierung ganzer Server-Landschaften lassen sich zudem Roles einrichten, so dass z.B. eine ‚webserver‘-Role auf eine Anzahl von Servern angewendet werden kann, womit die Standard-Konfiguration von Web-Servern erledigt wird. Für Details verweise ich hier wieder auf die ausführliche, offiizielle Dokumentation.
Noch ein Wort zur Strukturierung: Bei der Verwendung von Playbooks sollte eine gewisse Struktur der einzelnen Dateien vorliegen. Wenn Roles hinzu kommen, ist diese letztlich unerlässlich. Für viele Standard-Software finden sich vollständige Playbooks und Beispiele im Netz, u.a. auf Github. Die Struktur ist jedoch nicht immer einheitlich – Ansible erlaubt hier einige Freiheiten. Die Beispiele und fertigen Playbooks, die sich finden lassen, haben mir daher eher als Orientierung gedient. Darüber hinaus gibt es Ansible Best Practices als Empfehlung, die einen Blick wert sind.
Praxisbeispiel: Nginx, php5-fpm
Als konkrete Aufgabe habe ich auf einem Ubunu-Server Nginx mit php5-fpm einrichten wollen. Da ich mir sicher war, dass sich dieser Task wiederholen wird, wollte ich die Möglichkeit eines Playbooks nutzen. Die Wahl bestand nun darin, ein fertiges Nginx-Playbook zu verwenden, oder dies kurzerhand selbst zu schreiben.
Daher habe ich mir eines der vorhandenen Playbooks angesehen. Der Funktionsumfang war mächtig, vermutlich hätte sich alles damit erledigen lassen. Jedoch war es nicht speziell für die eingesetzte Ubuntu-Distribution geschrieben, sondern allgemein gehalten, so dass es auf vielen weiteren Distributionen lauffähig war. Das führte dann auch dazu, dass es sich nicht an den Bordmitteln von Ubuntu orientierte, sondern eine eigene Struktur von Nginx-Konfigurationsdateien mit sich brachte. Daran ist an sich nichts falsch, doch versuche ich im Allgemeinen, mich an der von der Distribution nahe gelegten Struktur zu orientieren. Beispielsweise liegen die Nginx-vhost-Konfigurationsdateien in /etc/nginx/sites-available/ und werden von /etc/nginx/sites-enabled/… per Symlink eingebunden. Eine komplett von diesem Standard abweichende Struktur empfinde ich daher eher als verwirrend denn als hilfreich. Daher habe ich mich dazu entschlossen, das entsprechende Playbook selbst zu schreiben.
Bevor ich zum Nginx-Beispiel komme, ein Beispiel eines sehr einfachen Playbooks. Damit soll git auf dem Server installiert werden.
--- # Base setup for webserver, install git - hosts: webservergroup pre_tasks: - name: Update apt cache if needed. apt: update_cache=yes cache_valid_time=3600 tasks: - name: "Install git" sudo: true apt: name=git state=installed
Dieses Playbook besteht aus einem einzigen Task – genannt „Install git“. Damit wird das apt-Modul verwendet, um die Installation von git auszuführen bzw. genau genommen sicher zu stellen, dass git installiert ist. Falls git bereits existiert, wird keine Neuinstallation versucht, Ansible verhält sich insofern idempotent. Vorab wird mittels eines weiteren Tasks ggf. „apt-get update“ ausgeführt.
Die Hosts, auf die dieses Playbooks angewendet wird, sind hier in der Gruppe „webservergroup“ enthalten. Im einfachsten Fall wird eine so genannte Inventory-Datei, die vergleichbar ist mit der hosts-Datei unter /etc/ansible/ bei Ausführung des Kommandos als Parameter übergeben.
Nun kann Ansible-Playbook gestartet werden:
ansible-playbook -i ../inventories/hosts git/git.yml
Natürlich wäre es für einen einzigen Server auf den ersten Blick einfacher, sich einzuloggen und dort ‚apt-get install git‘ auszuführen. Bei zwei oder mehr Servern wird es dann schon mühselig, vor allem weil dies das Übel der Wiederholung mit sich bringt. Und nach dem Motto DRY – Don’t repeat yourself – führt die Ansible-Variante einerseits zur Zeitersparnis, andererseits werden Fehler vermieden, insbesondere wenn es nicht bei der einfachen Installation ohne Konfiguration bleibt.
Dazu nun das Beispiel von Nginx mit php5-fpm und der Einrichtung des Default-vhosts mit einer Index-Datei, die den echten Namen des Webserver preis geben soll:
--- # Base setup for webserver - hosts: webservergroup tasks: - name: "Install php5-fpm with dependencies" sudo: true apt: name={{ item }} state=installed with_items: - php5-fpm - php5-mysqlnd - php5-cli - php5-curl - name: "Install Nginx" sudo: true apt: pkg=nginx state=present register: nginxinstalled notify: - start Nginx - name: "Ensure php5-fpm autostart" sudo: true service: "name=php5-fpm state=started enabled=yes" - name: "Ensure nginx autostart" sudo: true service: "name=nginx state=started enabled=yes" - name: "Create default www folder" sudo: true when: nginxinstalled|success register: webrootcreated file: path=/vol/www/{{ ansible_fqdn }}/ state=directory group=www-data owner=www-data recurse=yes - name: "Create default www folder subdirectories" sudo: true when: webrootcreated|success register: wwwfoldercreated file: path=/vol/www/{{ ansible_fqdn }}/{{ item.path }} state=directory group=www-data owner=www-data recurse=yes with_items: - { path: 'logs' } - { path: 'htdocs' } - name: "Create default web root" sudo: true when: webrootcreated|success template: src=templates/index.html.j2 dest=/vol/www/{{ ansible_fqdn }}/htdocs/index.html owner=www-data group=www-data mode=644 notify: - restart Nginx - name: Create default server config sudo: true template: src=templates/default.conf.j2 dest=/etc/nginx/sites-available/default.conf register: defaultconfigcreated - name: Remove old default server config sudo: true when: defaultconfigcreated|success file: path=/etc/nginx/{{ item.path }} state=absent with_items: - { path: 'sites-available/default' } - { path: 'sites-enabled/default' } register: oldconfigremoved - name: Enable new default config sudo: true when: oldconfigremoved file: src=/etc/nginx/sites-available/default.conf dest=/etc/nginx/sites-enabled/default.conf state=link notify: - restart Nginx handlers: - name: start Nginx service: name=nginx state=started - name: restart Nginx service: name=nginx state=restarted - name: reload Nginx service: name=nginx state=reloaded
Dazu gehören noch zwei Template-Dateien, und zwar:
Welcome to {{ ansible_fqdn }}!
Sowie die Nginx-Konfigurationsdatei namens „default“, die hier in default.conf umbenannt und als Template gestaltet wurde.
server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /vol/www/{{ ansible_fqdn }}/htdocs; index index.php index.html index.htm; # Make site accessible from http://localhost/ server_name localhost {{ ansible_fqdn }}; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; # Uncomment to enable naxsi on this location # include /etc/nginx/naxsi.rules } # Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests #location /RequestDenied { # proxy_pass http://127.0.0.1:8080; #} #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # #error_page 500 502 503 504 /50x.html; #location = /50x.html { # root /usr/share/nginx/html; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini # # With php5-cgi alone: # fastcgi_pass 127.0.0.1:9000; # With php5-fpm: fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} error_log /data/www/{{ ansible_fqdn }}/logs/error.log; access_log /data/www/{{ ansible_fqdn }}/logs/access.log; }
Beide Templates liegen im Verzeichnis „templates“, so dass sich folgende Struktur ergibt:
ansible-playbooks/ git/ git.yml nginx/ nginx.yml templates/ default.conf.j2 index.html.j2 inventories/ hosts
Insgesamt ist diese Struktur sicherlich noch verbesserungswürdig. So könnten innerhalb des templates-Verzeichnisses die echten Verzeichnishierarchien angelegt werden. Falls später einmal Roles benutzt werden, wären ebenfalls Änderungen notwendig. Als erstes Experimentierfeld hat mir jedoch diese Struktur zunächst ausgereicht.
Die Ausführung erfolgt auch hier mittels:
ansible-playbook -i ../inventories/hosts nginx/nginx.yml
Die wichtigsten Schritte:
- Die Pakete php5-fpm, php5-mysqlnd, php5-cli und php5-curl werden installiert. Dabei kommt die Direktive „with_items“ zum Einsatz, mit der eine Schleife über eine Liste von Items erzeugt wird.
- Nginx wird installiert, dazu wird sicher gestellt, dass die Services php5-fpm und nginx beim Booten automatisch gestartet werden.
- Ein Verzeichnis /vol/www/{{ ansible_fqdn }} wird erzeugt, in dem sich später die HTML-Dateien für den Webserver befinden sollen. Darüber hinaus werden die Verzeichnisse logs und htdocs erzeugt. Bei mehreren Webservern halte ich es für geschickt, wenn der jeweilige Server, etwa web01.example.com unter diesem Namen erreichbar ist und seinen Namen ausgibt. Genau das passiert ahd. der Template-Datei index.html.j2, wobei daraus die index.html erzeugt wird. Der Platzhalter {{ ansible_fqdn }} wird durch den echten Hostnamen ersetzt, der durch die so genannten Facts ermittelt wird. Dabei ist eine Vielzahl von Variablen verfügbar, die mittels Kommando „
ansible hostname -m setup
“ angezeigt werden können. Die Templates sind mittels Jinja2 realisiert – wer Templates aus der Web-Programmierung kennt, wird sich vermutlich sofort heimisch fühlen. - Die Default-Konfiguration, bestehend aus der Datei /etc/nginx/sites-available/default und dem dazu gehörigen Symlink, werden entfernt. Daraufhin wird die Datei default.conf aus dem Template default.conf.j2 in das Verzeichnis sites-available kopiert. Dabei handelt es sich letztlich um eine Kopie der default-Konfigurationsdatei, erweitert durch die Aktivierung von PHP mittels FastCGI und das Setzen der hier eingerichteten Verzeichnisstruktur.
- Die Handler am Ende der Datei dienen zum Restart des Webservers und werden durch die entsprechenden „notify“-Events aufgerufen.
Sollte nun beispielsweise ein weiteres PHP-Modul notwendig werden, genügt es, dies in der Liste der Items einzutragen und das Playbook erneut aufzurufen. Ansible führt daraufhin die notwendigen Schritte zur Installation des Moduls aus, lässt den Rest hingegen unangetastet.
Fazit
Meines Erachtens bestechen Playbooks durch ihre einfache Struktur und Übersichtlichkeit. Eine weiter gehende Dokumentation ist kaum notwendig, die Dateien sind nahezu selbsterklärend. Ansible-Module sind für nahezu jede Aufgabe verfügbar. Als nächstes könnte etwa die Einrichtung von vhosts mittels eines Playbooks erstellt werden. Genau genommen ist es mir schleierhaft, wie man dazu noch im letzten Jahr neue, aber dennoch unübersichtliche und fehlerträchtige Shell-Skripte schreiben konnte, aber das ist eine ganz andere Geschichte…
Neben der offiziellen Dokumentation ist mittlerweile auch ein E-Book Ansible for Devops verfügbar, was eine sehr gute Einführung bietet. Ansible selbst ist Open Source unter der GNU GPL zur Verfügung. Mit Ansible Tower wird eine (kostenpflichte) UI angeboten. Laut README beinhaltet Ansible Beiträge von über 900 Nutzern, weshalb mir diese kleine Anmerkung hoffentlich gestattet wird, denn letztes Jahr habe ich im Composer-Modul die bis dato fehlende update-Option hinzu gefügt, ein paar Zeilen Python genügten.
Graylog2
Eigentlich wollte ich an dieser Stelle noch einiges über Graylog2 schreiben, dessen Version 1.0 letzte Woche erschienen ist. Graylog2 ist eine Plattform für Log-Management und steht unter einer Open-Source-Lizenz bereit. Graylog2 nutzt die Datenbank MongoDB und die Search-Engine Elasticsearch und bietet eine vollumfassende Lösung zur Sammlung, Konsolidierung und Auswertung von Log-Informationen. Ich habe bereits einige Versionen vor der jetzt frei gegebenen Version 1.0 getestet und war begeistert von den vielfältigen Möglichkeiten – von der Integration von Syslogs bis hin zum Application-Monitoring. Aus dem kommerziellen Bereich ist vielleicht Splunk bekannt, was ich vor einiger Zeit im Einsatz gesehen habe, und davon ebenfalls recht beeindruckt war.
Die Installation von Graylog2 war jedoch selbst auf einem Server etwas komplexer, da zunächst die benötigten Abhängigkeiten, noch dazu in bestimmten Versionen, installiert werden mussten. Inzwischen werden neben Appliances für OpenStack, VMware oder Amazon EC2 auch Docker-Container bereit gestellt, so dass die Installation letztlich mit zwei, drei Kommandos erledigt sein sollte. Das wollte ich mir näher ansehen, insbesondere, da ich Docker bisher zwar faszinierend fand, aber aufgrund einiger Merkmale und fehlender Praxis noch nicht ganz warm geworden bin. Aus Gründen bin ich in der letzten Woche jedoch nicht dazu gekommen, somit wird dies ein Thema für einen der nächsten kuerbis.org-Weekly-Beiträge sein.