Ansible-Tutorial: Setup von Docker, MySQL und WordPress mit Ansible

In den ersten zwei Teilen dieser Tutorial-Serie habe ich euch gezeigt, wie ich mit Ansible auf einem Root-Server von Hetzner das Betriebssystem-Image installiert habe und User-Accounts angelegt, die SSH-Konfiguration optimiert und die Firewall konfiguriert habe. In diesem dritten Teil des Ansible-Tutorials zeige ich euch, wie ich Docker und dann MySQL und WordPress als Docker-Images installiere. Da dies viel einfacher ist als gedacht, wird dieser Artikel auch entsprechend kürzer ausfallen als die zwei vorherigen.

Den Code zum Artikel findet ihr wie immer in meinem GitLab-Repository: https://gitlab.com/SvenWoltmann/happycoders-tutorial-server-setup

Und zusätzlich zum Artikel gibt es auch diese Mal wieder ein Video-Tutorial: Setup von Docker, MySQL und WordPress mit Ansible

Die Artikelserie ist in folgende vier Teile aufgeteilt:

Warum WordPress?

Zuerst einmal stellt ihr euch vielleicht die Frage, warum ich als Java-Entwickler ein in PHP entwickeltes Content Management System benutze. Die Antwort ist: Es hat den größten Marktanteil (knapp 60 %), erfüllt alle meine Anforderungen, ist schnell aufgesetzt, mit Tausenden Plugins und Themes erweiterbar und hat einen hervorragenden Community-Support. Hier gibt es einen ausführlichen Vergleich von WordPress mit Joomla und Drupal.

Im Java-Bereich gibt es auf der einen Seite Hobbyprojekte und auf der anderen Seite Schwergewichte wie Magnolia, OpenCMS oder Hippo, die eher auf die Bedürfnisse großer Unternehmen ausgerichtet sind. Diese sind deutlich aufwändiger aufzusetzen und aufgrund des geringen Marktanteils (unter 1 %) ist auch die Support-Community um ein Vielfaches kleiner.

Das Ganze möchte ich als Docker-Container installieren und natürlich wieder via Ansible.

Installation von Docker

Zunächst einmal muss ich Docker installieren. Dafür greife ich erneut auf eine Ansible-Rolle von Jeff Geerling zurück: Docker Ansible Role. Installiert wird die Rolle wie folgt (auf meiner Developer-Maschine – nicht dem Server):

ansible-galaxy install geerlingguy.docker
Installation von "geerlingguy.docker" aus der Ansible Galaxy
Installation von „geerlingguy.docker“ aus der Ansible Galaxy

Um die Rolle auf meinem Server auszuführen, füge ich in der Datei happy1.yml, die ich im vorangegangenen Artikel der Serie erstellt habe, unter dem Array roles den Eintrag gerlingguy.docker hinzu. Die Datei sieht nun wie folgt aus:

---
- hosts: happy1.happycoders.eu
vars:
ansible_port: "{{ ssh_port | default('22') }}"
remote_user: sven
become: yes
roles:
- hostnames
- users
- ssh
- bash_config
- tools
- geerlingguy.firewall
- geerlingguy.docker

Ich möchte gerne selbst festlegen, welche Docker- und Docker Compose-Versionen installiert werden. Dies kann ich über die Variablen docker_package und docker_compose_version tun. So finde ich die Versionsnummern heraus:

  • Die Docker-Version kann ich vorab nicht ohne weiteres herausfinden, da das Docker-Repository noch nicht installiert wurde. Ich kann die Rolle aber auch ohne Angabe einer Docker-Version ausführen, wodurch automatisch die neueste Versionen installiert wird. Die Versionsangabe kann ich nachträglich hinzufügen.
  • Docker Compose-Version: Diese erfahre ich auf der Docker Compose-Seite in GitLab. Zum jetzigen Zeitpunkt (September 2018) ist das die 1.22.0.

Die Docker-Versionsnummer, die ich hinter docker-ce schreiben würde, lasse ich also zunächst weg und die Docker Compose-Versionsnummer trage ich wie folgt in meine Host-Variablendatei host_vars/happy1.happycoders.eu ein:

docker_package: docker-ce
docker_compose_version: 1.22.0

Außerdem möchte ich auch als User sven Docker verwenden und benötige dafür noch folgenden Eintrag direkt darunter:

docker_users:
- sven

Nun führe ich das Playbook wie folgt aus:

ansible-playbook happy1.yml

Dieses Mal ist etwas Geduld gefragt – der Task „Install Docker“ kann bis zu zehn Minuten dauern.

Installation von Docker via Ansible
Installation von Docker via Ansible

Nun kann ich ganz einfach mit dpkg -s docker-ce | grep Version herausfinden, welche Docker-Version installiert wurde:

Installierte Docker-Version herausfinden
Installierte Docker-Version herausfinden

Diese Version hänge ich mit einem Gleichheitszeichen an die docker_package-Variable an, so dass meine Host-Variablendatei host_vars/happy1.happycoders.eu nun folgende Einträge bzgl. Docker enthält:

docker_package: docker-ce=18.06.1~ce~3-0~debian
docker_compose_version: 1.22.0
docker_users:
- sven

Ein erneutes Ausführen des Playbooks ist nicht nötig, da die neueste Version ja bereits installiert ist. Die Installation von Docker ist somit abgeschlossen.

Ansible-Firewall und Docker-Regeln

Es ist an dieser Stelle wichtig zu wissen, dass Docker einige iptables-Einträge hinzufügt:

Durch Docker hinzugefügte Firewall-Regeln
Durch Docker hinzugefügte Firewall-Regeln

Diese werden durch Ausführung der Ansible-Firewall-Rolle, die ich im Tutorial „Firewall mit Ansible“ konfiguriert habe, unglücklicherweise wieder gelöscht. Das Problem ist bekannt (https://github.com/geerlingguy/ansible-role-docker/issues/21), allerdings funktioniert der dort angegebene Workaround nicht: Docker wird im Widerspruch zur Erklärung des Entwicklers nicht neugestartet, nachdem die Firewall neugestartet wurde, sondern nur dann, wenn Docker aktualisiert wurde. Deshalb ist es momentan nötig Docker mittels service docker restart manuell neuzustarten, wenn die Firewall-Regeln geändert wurden.

Meine Meinung:

Ein Docker-Neustart ist nicht weiter wild, da die eigentlichen Container dabei nicht neu gestartet werden. Dennoch kommt es zu einer Unterbrechung des Dienstes für einige Sekunden, was für einen privaten Blog verschmerzbar ist. Für eine große Unternehmensseite wäre das nicht akzeptabel. Hier wäre die bessere Lösung, dass die Firewall-Rolle nicht die Firewall-Regeln löscht und komplett neu anlegt, sondern neue hinzufügt und nicht mehr benötigte löscht. Vielleicht werde ich das in einem zukünftigen Artikel aufgreifen.

Installation von MySQL und WordPress

Für die Installation von WordPress orientiere ich mich an diesem Artikel: Quickstart: Compose and WordPress. Dazu lege ich eine Ansible-Rolle wordpress_docker an und kopiere die docker-compose.yml aus dem o. g. Artikel in die Template roles/wordpress_docker/templates/docker-compose.yml.j2. Den Port ändere ich von 8000 auf 8001, da ich Port 8000 im nächsten Artikel für Certbot benötigen werde und ich keine Möglichkeit gefunden habe den Certbot-Port zu ändern.

Die MySQL-Version sowie die Passwörter möchte ich in Variablen extrahieren. Die Passwörter allerdings nicht im Klartext. Daher verschlüssele ich diese zunächst mit ansible-vault. Mit folgendem Befehl verschlüssele ich das Passwort 123456 und speichere es in der Variablen mysql_root_password:

ansible-vault encrypt_string '123456' --name 'mysql_root_password'

Ebenso verschlüssele ich das Passwort für den wordpress-User. Ihr solltet die Beispielpasswörter natürlich durch eigene ersetzen.

Passwörter verschlüsseln mit ansible-vault
Passwörter verschlüsseln mit ansible-vault

Die MySQL-Version aus der docker-compose.yml und die soeben verschlüsselten Passwörter kopiere ich in meine Host-Variablendatei. Auf die Tiefe der Einrückung unter den Variablennamen (also ab $ANSIBLE_VAULT) kommt es dabei nicht an; es reicht also, wenn ich vor den Variablennamen zwei Leerzeichen einfüge, wenn ich die Passwort-Variablen unter einer Eltern-Variable gruppieren möchte:

wordpress_docker:
mysql_version: 5.7
mysql_root_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
38616139646531363566313766663636356431366433356162353835653565363665646633306665
3738303831333335636532306565653939343862636336630a643135643935313737633235323464
38316464633238376636366263376462626438633139363465393736363462303864353864646638
6637396439303565340a376165306664353332363866356130313939366334363732653862663466
3838
mysql_wordpress_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
36356366653534343766383734343865616235636238333239396236623035656237623361653837
6432613033663832313837306539666235313837663431350a396231336131663235326466386135
65376633333963393663613562363039623030623334666161383362356237613234393162626135
6530323134636530300a626363613661663662353435396232363936343136636566333566393835
3738
wordpress_version: latest

Die fertige Template roles/wordpress_docker/templates/docker-compose.yml.j2 sieht wie folgt aus:

version: '3.3'

services:
db:
image: mysql:{{ wordpress_docker.mysql_version }}
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: {{ wordpress_docker.mysql_root_password }}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: {{ wordpress_docker.mysql_wordpress_password }}

wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8001:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: {{ wordpress_docker.mysql_wordpress_password }}
volumes:
db_data:

Tasks

Bisher habe ich noch keine Tasks für die wordpress_docker-Rolle angelegt. Das werde ich nun nachholen. Es gibt nicht viel zu tun: Verzeichnis anlegen, docker-compose.yml-Datei kopieren und docker-compose up aufrufen. Das mache ich mit folgenden drei Tasks in der Datei roles/wordpress_docker/tasks/main.yml:

Task 1 – Verzeichnis anlegen:

- name: Create docker/wordpress directory
file:
path: /opt/docker/wordpress
state: directory
owner: root
group: root
mode: 0755

Task 2 – docker-compose-Datei kopieren:

- name: Create docker-compose file
template:
src: docker-compose.yml.j2
dest: /opt/docker/wordpress/docker-compose.yml
owner: root
group: root
mode: 0644

Task 3 – docker-compose up ausführen:

- name: Run docker-compose up -d
shell: docker-compose up -d
args:
chdir: /opt/docker/wordpress/

Die verwendeten Ansible-Module file, template und shell habe ich bereits in den vorangegangenen Artikeln erläutert.

Firewall öffnen

Ich habe den WordPress-Docker-Container so konfiguriert, dass dieser auf Port 8001 erreichbar ist. Diesen muss ich noch vorübergehend in der Firewall öffnen, um die WordPress-Installation testen zu können. Vorübergehend deshalb, weil ich im nächsten Artikel HAProxy einrichten werde, um Port 80 auf 8001 weiterzuleiten und ich danach Port 8001 für den Zugriff von außen wieder schließen kann.

Zum Öffnen des Ports muss ich in der Host-Variablendatei das Array firewall_allowed_tcp_ports um den Wert 8001 erweitern, so dass der Eintrag nun wie folgt aussieht:

firewall_allowed_tcp_ports:
- 80
- 443
- 5930
- 8001

Ausführen des Playbooks

Wenn ich jetzt versuchen würde das Playbook auszuführen, würde dieses mit einer Fehlermeldung „Failed to program FILTER chain: iptables failed […]“ abbrechen. Den Grund habe ich weiter oben genannt: Durch die Änderung der Firewall-Regeln werden die Docker-Regeln gelöscht, so dass der Versuch docker-compose up aufzurufen fehlschlägt. Ich werde daher die Rolle wordpress_docker im Playbook happy1.yml vorübergehend auskommentieren, in dem ich ein # voranstelle:

#    - wordpress_docker

Nun kann ich das Playbook ausführen, um zunächst einmal die Firewall-Regeln zu ändern:

ansible-playbook happy1.yml

Einen Screenshot füge ich dieses Mal nicht ein. Folgende zwei Tasks melden den Status „changed“:

  • geerlingguy.firewall : Copy firewall script into place.
  • geerlingguy.firewall : restart firewall

Ich logge mich nun auf dem Server ein, um

  1. zu prüfen, ob Port 8001 geöffnet wurde,
  2. den Docker-Service neuzustarten und um
  3. danach zu prüfen, ob die Docker-Firewall-Regeln wieder aktiv sind:
ssh -p 5930 sven@happy1.happycoders.eu
sudo iptables -L
sudo service docker restart
sudo iptables -L
Überprüfung der Firewall-Regeln und Docker-Neustart
Überprüfung der Firewall-Regeln und Docker-Neustart

Es entspricht alles meinen Erwartungen: Port 8001 wurde geöffnet und die Docker-Einstellungen haben zunächst gefehlt. Nach einem Neustart von Docker sind diese wieder vorhanden.

Nun kann ich im Playbook happy1.yml die zuvor auskommentierte Rolle wordpress_docker wieder aktivieren und das Playbook erneut aufrufen. Dieses Mal muss noch der Parameter --ask-vault-pass hinzugefügt werden, so dass Ansible die verschlüsselten Passwörter entschlüsseln kann:

ansible-playbook --ask-vault-pass happy1.yml
Installation von MySQL und WordPress via Ansible
Installation von MySQL und WordPress via Ansible

Es dauert eine Weile, bis die Container laufen. Der Fortschritt lässt sich auf dem Server wie folgt beobachten:

cd /opt/docker/wordpress
docker-compose logs -f
Aufruf von "docker-compose logs -f"
Aufruf von „docker-compose logs -f“

Die Warnungen werde ich fürs Erste ignorieren. Nach etwa einer Minute erscheint die Meldung, dass die Datenbank bereit für Verbindungen ist. Die WordPress-Installation ist nun unter http://happy1.happycoders.eu:8001 erreichbar:

WordPress ist installiert und bereit für die Konfiguration
WordPress ist installiert und bereit für die Konfiguration

Persistente Daten der Docker-Container

Wo liegen eigentlich die Daten der Docker-Container? In der docker-compose-Datei wurde im db-Container das Verzeichnis /var/lib/mysql auf das Volume db_data gemounted. Doch wo genau liegt dieses Volume? Um das herauszufinden, ermittle ich zunächst die Container-ID mittels docker ps und führe anschließend docker inspect -f '{{ .Mounts }}' <Container-ID> aus, um den Mountpoint auszulesen:

Mountpoint auslesen mit "docker ps" und "docker inspect"
Mountpoint auslesen mit „docker ps“ und „docker inspect“

Die MySQL-Daten liegen also unter /var/lib/docker/volumes/wordpress_db_data/_data und würden damit ein Löschen und Neuanlegen des db-Containers überleben. Und die Daten des WordPress-Containers? Zwar ist hier in der docker-compose-Datei kein Volume gemounted, dennoch wird mir durch docker inspect ein Mountpoint angezeigt:

Mountpoint des Docker-Containers auslesen mit "docker inspect"
Mountpoint des Docker-Containers auslesen mit „docker inspect“

Wo wird dieser Mountpoint festgelegt? Ganz einfach: in der Dockerfile-Datei des WordPress-Images. Die WordPress-Dateien sind also auch persistent. Zusätzlich werde ich selbstverständlich ein gutes Backup-Plugin installieren, welches nicht nur die WordPress-Installation, sondern auch die MySQL-Datenbank sichern wird.

Zusammenfassung und Ausblick

In diesem dritten Artikel der Serie habe ich gezeigt, wie man mit Ansible Docker installiert und mit Hilfe einer docker-compose-Datei MySQL und WordPress als Docker-Images installiert und startet.

Auf die weitere Konfiguration von WordPress werde ich hier nicht weiter eingehen. Dazu findet ihr im Internet ausreichend Lektüre. Ich teile aber gerne mit euch das Theme und eine Liste der Plugins, die ich für diesen Blog verwende:

Damit endet dieser Artikel. Im vierten und letzten Teil werde ich HAProxy einrichten sowie ein kostenloses HTTPS-Zertifikat von Let’s Encrypt, so dass die Webseite über die Standard-HTTP/HTTPS-Ports erreichbar sein wird.

Ich freue mich wie immer, wenn ihr mich wissen lasst, wie euch der Artikel gefallen hat und was ich besser hätte machen können. Vielen Dank fürs Lesen!

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.