Hibernate Envers mit Identity Strategy

Hibernate Envers bietet die Möglichkeit zur Versionierung von Hibernate Entitäten über separate Audit-Tabellen. Das funktioniert auch als JPA-Provider. Wo man im klassischen Ansatz INSERT/UPDATE/DELETE Trigger und Stored-Procedures einsetzt, um Auditierung auf Tabellen-Ebene durchzuführen, ist Envers im Java-Spring Umfeld eine sehr interessante Alternative.

Hierzu stellt Envers eine Reihe von Annotationen zur Verfügung, mit denen die Auditierung deklarativ vorgenommen werden kann. Das funktioniert auf Klassenebe , auf Feldebene und sogar für Reletionsbeziehungen. Die Dokumentation ist gut verständlich und übersichtlich. Eine Entität Approval könnte beispielsweise wie folgt mit einer Audit-Annotation deklariert werden:

@Entity @Table(name = "approvals") @Data 
@Audited
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Approval {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @EqualsAndHashCode.Include
    @Column(unique = true, updatable = false, nullable = false)
    private String approvalNumber;

    @Enumerated(EnumType.STRING) @NotNull
    private ApprovalStatusType status;

    private String title;
    private String description;

    @OneToOne(cascade = CascadeType.ALL)
    private Person cancelInitiator;
}

Das ist schon alles! Jetzt ist nur noch dafür zu sorgen, dass die Audit-Tabelle approval_aud mit den erforderlichen Spalten vorhanden ist, und Hibernate protokolliert dort dann automatisch alle Änderungen.

FEHLER: Relation »hibernate_sequence« existiert nicht

In dem ganz konkreten Fall funktioniert das mit H2 als Datenbank ganz wunderbar und ohne Probleme. Mit dem Ausrollen auf unsere Stage-Umgebung sehen wir dann allerdings die Exception:

org.postgresql.util.PSQLException: FEHLER: Relation »hibernate_sequence« existiert nicht
  Position: 17
	at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2532)
	at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2267)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:312)
	at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:448)
	at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:369)
	at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:153)
	at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:103)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)

Wie kommt das? Nun, Envers erzeugt pro Vorgang zwei Dinge:

  • Eine eindeutige Revisionsnummer in der übergeordneten Tabelle REVINFO.
  • Einen Eintrag in der Entity-spezifischen Audit-Tabelle bei Hinterlegung der vorgenannten Revisionsnummer.

Für REVINFO benutzt Envers intern eine eigene Entity:

@MappedSuperclass
public class DefaultRevisionEntity implements Serializable {
    private static final long serialVersionUID = 8530213963961662300L;
    @Id
    @GeneratedValue
    @RevisionNumber
    private int id;
    @RevisionTimestamp
    private long timestamp;
...

Die MappedSupereclass definiert keine explizite Strategie, so dass der Default greift. Auf unserer Stage-Umgebung ist eine Postgres-Datenbank installiert, und Hibernate wendet in diesem Fall die GenerationType.SEQUENCE Strategie an. Ist die hierfür erforderliche Sequenz in der Datenbank nicht vorhanden, dann kommt es zu o.g. Exception. Wird die Datenbank – wie auf dem Dev-Laptops üblich – automatisch über Hibernate generiert, so fällt auch das nicht unmittelbar auf. Ab Stage rollen wir Schema-Änderungen jedoch explizit über Flyway aus, so dass in diesem Fall die Sequenz tatsächlich nicht vorhanden ist.

Eigene Revision Enity

Als Lösung wird die DefaultRevisionEntity durch eine CustomRevisionEntity ersetzt. Hierbei ist wichtig, dass die Entity die @RevisionEntity Annotation bekommt und die beiden Revisionfelder die Annotationen @RevisionNumber bzw. @RevisionTimestamp. Das Ergebnis sieht dann so aus:

@Entity
@Table(name = "REVINFO")
@RevisionEntity
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class CustomRevisionEntity implements Serializable {
    ...
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @RevisionNumber
    @EqualsAndHashCode.Include
    @Column(name = "REV")
    private Long id;

    @RevisionTimestamp
    @EqualsAndHashCode.Include
    @Column(name = "REVTSTMP")
    private long timestamp;
}

Mit dieser Version läuft die Applikation auch unter Postgres.

Revision Entity erweitern

In einer Spring-Security Web-Application kann die Entity jetzt zusätzlich noch um Infos des initiierenden Benutzers ergänzt werden. Hierfür stellt Hibernate Envers einen Listener-Ansatz zur Verfügung, der ebenfalls über die Annotation registriert werden kann.

@RevisionEntity(CustomRevisionEntityListener.class)

Der Listener implementiert das RevisionListener Interface mit einer Methode, die mit jeder neuen Revisionen aufgerufen wird. Hat man dort einen SecurityContext zur Hand, so kann der angemeldete User sehr einfach mit in die Revionstabelle vermerkt werden:

@Slf4j
@Component
public class CustomRevisionEntityListener implements RevisionListener {

    @Override
    public void newRevision(final Object revisionEntity) {
        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Optional.ofNullable(authentication)
                .map(Authentication::getPrincipal)
                .filter(Objects::nonNull)
                .filter(principle -> principle instanceof MyUserDetails)
                .map(MyUserDetails.class::cast)
                .ifPresent(userDetails -> ((CustomRevisionEntity)revisionEntity).setUsername(userDetails.getUsername()));
    }
}

WordPress Tutorial – Teil 6

Die WordPress Instanz ist jetzt grundsätzlich einsatzfähig: Sie ist über das Internet verfügbar gemacht, ist an die Corporate Identity angepasst und wir schreiben auch schon munter Artikel – aber Achtung: Zwar sind die Basiskonfiguration, sowie die Plugin- und Theme-Folder im Git versioniert und daher gesichert, für die Datenbank gilt dies aber noch nicht. Das müssen wir jetzt dringend nachholen!

Backup-Script erstellen

Unser Backup-Konzept sieht wie folgt aus: Wir implementieren ein Script, das im MYSQL-Container laufen soll und die lokale Datenbank per mysqldump sichert. Es wird in den Container gemounted, ist so von der Versionsverwaltung erfasst und schreibt Backups nach /var/backups:

#!/bin/bash

datetime=`date +"%Y%m%d%H%M%S"`
dir=/var/backups
target=$dir/$MYSQL_DATABASE-$datetime.sql.gz

mkdir -p "$dir"

mysqldump \
    --user=$MYSQL_USER \
    --password=$MYSQL_PASSWORD \
    --max-allowed-packet=1G \
    --triggers \
    --routines \
    --quick \
    --add-drop-database \
    --default-character-set=utf8 \
    --host=localhost \
    --databases "$MYSQL_DATABASE" | gzip -9 > $target

Datensicherung auf NFS Share

Wir haben einen NFS Server, auf dem die Backups abgelegt werden sollen. Der vom Server exportierte Folder wiederum wird über Dropbox in die Cloud synchronisiert. Daher müssen wir das /var/backups Volume nur auf den NFS Share mounten: Dazu wird im Docker-Compose das Mapping in der Servicedefinition für db erweitert und in der Volume-Konfiguration der NFS-Zugriff konfiguriert:

...
services:
  db:
    ...
    volumes:
      - ./volumes/mysql/mysqlbackup.sh:/usr/local/bin/mysqlbackup.sh:ro
      - nas1_backup:/var/backups

...
volumes:
  db_data: {}
  nas1_backup:
    driver: local
    driver_opts:
      type: "nfs"
      o: "addr=ip-des-nfs-servers,rsize=65536,wsize=65536,timeo=14,tcp,rw,noatime"
      device: ":/export-und-zielpfad-zum-backup-folder"

Dazu muss der NFS-Client auf der VM installiert sein. Unter Ubuntu geht das beispielhaft über das folgende Kommando:

sudo apt install nfs-common

Nach dem Neustart des Containers kann die Sicherung nun von außen über das folgende Kommando angestoßen werden:

docker exec -it docker_wordpress_db_1 /bin/bash -c "chmod +x /usr/local/bin/mysqlbackup.sh; /usr/local/bin/mysqlbackup.sh"

Sicherung einrichten

Das ginge z.B. über einen Cron-Job auf der VM. Allerdings wollen wir Konfigurationen auf der VM vermeiden, um etwa bei einem Ausfall schnell auf eine andere beliebige VM mit dem Container umziehen zu können. Daher benutzen wir Jenkins und fügen dort einen täglich laufenden Freestyle-Job ein, der das folgende Script ausführt:

ssh -tt -l jenkins 192.168.1.24 -o StrictHostKeyChecking=no "\
    docker exec -it docker_wordpress_db_1 \
    /bin/bash -c \"chmod +x /usr/local/bin/mysqlbackup.sh; /usr/local/bin/mysqlbackup.sh\""

Desaster Recovery proben

TBD

WordPress Tutorial – Teil 5

Im letzten Teil haben wir Vorbereitungen getroffen, um das Hemingway-Theme maßgeschneidert auf unsere Corporate Identity anpassen zu können – und genau das soll nun auch erfolgen.

Visual Studio Code

Die VM, auf der unser Docker-Container läuft, hat Ubuntu installiert, und das Editieren von Dateien über die Konsole ist recht mühselig. Um die Änderungen an dem Theme zu vereinfachen installieren wir uns lokal VS Code auf dem Laptop.

  • Nach Installation und Start ist links unten ein grünes Symbol zu sehen: Dieses anklicken und dann Remote SSH: Connect to Host… | 192.168.1.24 | Linux und dann ggf. den Fingerprint mit Continue bestätigen.
  • Nun File | Open Folder… und dann in das Verzeichnis von WordPress navigieren, so dass die Folder build und volumes sowie das docker-compose.yml im linken Explorer erscheinen.

VS Code erkennt das Git Repository auf der VM, so dass Files nunmehr über die IDE verwaltet werden können. Über Terminal | New Terminal kann ein Remote Console mit Bash geöffnet werden.

Copyright ändern

Im Footer der Seite fügt Hemingway den Namen des Blogs ein, dies entspricht jedoch i.d.R. nicht dem Seitenbesitzer. Das korrigieren wir nun exemplarisch: Im VS Code navigieren wir im Explorer nach volumes / themes / hemingway und öffnen die Datei footer.php und scrollen zur Zeile 56: Der Block wird nun wie folgt ersetzt:

<p class="credits-left">
    &copy; <?php echo date( 'Y' ); ?> 
    <a href="https://www.tdm-consult.com>">
        TDM Consult GmbH
    </a>
</p>

Ein Page-Refresh zeigt sofort die Änderung. Zudem zeigt VS Code die Änderung links im Git-Panel an: Die Datei kann von dort direkt in die Versionsverwaltung eingecheckt werden. Nach dem Commit nicht die Synchronisation vergessen, damit die Änderungen in die Origin gepushed werden. Dies ist dann auch sogleich die Sicherung für unsere Änderung.

Apropos Sicherung: Dies soll Gegenstand des nächsten Teils werden.

WordPress Tutorial – Teil 4

Im letzten Teil haben wir ein Theme installiert und erste Anpassungen vorgenommen. Nun geht es darum, den Blog an das Corporate Design der Homepage anzupassen. Auch hier gibt es verschiedene Ansätze, die auch davon abhängen, wir umfangreich unsere Änderungen am Design werden sollen.

Zunächst soll nur die Gestaltung des Menübandes geändert werden, in dem wir im letzten Teil ja die einzelnen Einträge hinzugefügt haben. Um dieses Ziel zu erreichen müssen wir eigene CSS Styles in die Seite einbringen. Hierfür gibt es verschiedenste Möglichkeiten:

  • Installation eines für CSS spezialisiertes Plugin.
  • Über den Theme Editor, der unter Appearance | Themes | Theme Editor erreichbar ist.
  • Über Appearance | Themes | Customize | Additional CSS

Der Theme Editor ist sehr nützlich, um herauszufinden, welche Styles es eigentlich gibt und welche demzufolge überschrieben werden können. Die Syles für das Menüband sind dort ab Zeile 650 unter 5. Navigation zu finden. Erweiterungen fügen wir als Additional CSS hinzu.

.bg-dark {
	background-color: #000033!important;
}

.navigation.section {
  border-top: 2px solid white;	
  border-bottom: 10px solid #6699cc;	
}

.blog-menu > li + li:before {
	content: "";
}

.blog-menu a {
	padding: 10px 15px;
	font-weight: bold;
	color: #fff;
	font-size: 13px;
	font-family: "Helvetica Neue",sans-serif!important;
}

.blog-menu li:hover > a { 
	color: #6699cc; 
}

Schön an der Lösung ist, dass alle Änderungen sofort online im Browser angezeigt werden, so dass man den Effekt sofort erkennen kann. Zudem werden diese Styles in der Datenbank gespeichert, so dass sie auch mit einen Datenbanksicherung erhalten bleiben, auch dann, wenn das Theme neu installiert und aktualisiert wird.

Eigenes Theme aufbauen

Allerdings sind die Änderungen auf Styles beschränkt. Möchte man größere Änderungen durchführen, etwa das Menüband über das Teaser-Image verschieben, dann geht das – ohne recht aufwändige CSS-Tricks – nicht. Um mittelfristig mehr Möglichkeiten zu haben, soll hier deshalb noch ein anderer Weg beschritten werden, nämlich das Erzeugen eines eigenen Themes.

Dazu gehen wir schrittweise vor: Zunächst soll das Hemingway-Theme nicht mehr von remote installiert, sondern über Docker-Compose als Mount eingebunden werden. Weil damit auch diese Dateien nach Git übernommen werden, haben damit auch zugleich alle Vorteile der Versionsverwaltung. Nun können wir sukzessive Änderungen an dem Theme vornehmen und daraus sukzessive unser eigenes Theme erzeugen.

Mit Docker vorbereiten

Docker-Compose wird nun zunächst so erweitert, dass das Theme-Verzeichnis „von außen“ als Mount zur Verfügung gestellt wird. Für Plugins machen wir dasselbe, auch wenn wir das jetzt noch nicht benötigen. Der Vorteil ist aber, dass so auch alle installierten Plugin in die Versionsverwaltung mit eingecheckt werden können.

  wordpress:
    depends_on:
      - db
    build:
      context: ./build
    image: docker.tdm-consult.com:443/docker/com.tdmconsult/wordpress:latest
    ports:
      - "80:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
      HTTP_X_FORWARDED_PROTO: https
    volumes:
      - ./volumes/plugins:/var/www/html/wp-content/plugins
      - ./volumes/themes:/var/www/html/wp-content/themes

Das volumes Verzeichnis erzeugen wir auf dem Server und kopieren dort den Hemingway Theme-Folder hinein. Außerdem übernehmen wir die Dateien wp-config.php und docker-entrypoint.sh in den Files-Subfolder. Die Struktur sieht dann so aus:

+- build
|  +- files
|  |  +- docker-entrypoint.sh
|  |  +- wp-config.php
|  +- Dockerfile
+- volumes
|  +- plugins
|  |  +- index.php
|  +- themes
|     +- index.php
|     +- hemingway
+- docker-compose.yml

Auch das Dockerfile wird angepasst, um unnötige Plugins und Themes vor der Installation zu entfernen:

FROM wordpress:latest

# to make live more easy
RUN apt-get update \
 && apt-get install -y vim sudo

# install wp-cli to be able to install themes and plugins
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
 && chmod +x wp-cli.phar \
 && mv wp-cli.phar /usr/local/bin/wp

# fix some base configurtaion
COPY files/wp-config.php /var/www/html
COPY files/docker-entrypoint.sh /usr/local/bin
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# remove unwanted plugins and themes to avoid installation
RUN rm -rf /usr/src/wordpress/wp-content/plugins/akismet \
 && rm -f /usr/src/wordpress/wp-content/plugins/hello.php \
 && rm -rf /usr/src/wordpress/wp-content/themes/twentynineteen \
 && rm -rf /usr/src/wordpress/wp-content/themes/twentytwenty \
 && rm -rf /usr/src/wordpress/wp-content/themes/twentytwentyone

Nun das Image neu bauen und starten:

docker-compose build
docker-compose up

Der Blog sollte wieder verfügbar sein und alle bisherigen Customizings intakt.

Mit Git versionieren

Sofern noch nicht gemacht, wird nun ein Git Repository angelegt und alle Files in die Versionsverwaltung eingecheckt:

$ git init
$ git add --all
$ git commit -m "Initial Commit"
$ git remote add origin ssh://git@your-origin/doc/docker_wordpress.git
$ git push -u origin master

Im Bitbucket wurde zuvor im Projekt DOC das Repository docker_wordpress angelegt. Zudem Git für SSH konfiguriert sein.

WordPress Tutorial – Teil 3

In den Teilen 1 und 2 haben wir unsere WordPress Instanz veröffentlicht und über ein Reverse Proxy NGINX mit einem Let’s Encrypt Zertifikat abgesichert. Nun wollen wir ein Theme installieren und haben uns für Hemingway entschieden.

Die Installation ist an sich sehr einfach: Navigation nach Appearance | Themes | Add New | Search themes… und dann Install und Activate. Das Problem ist nur: Wird WordPress danach wieder über Docker-Compose neu installiert, so sind damit auch alle Themes und Plugins weg, denn sie werden direkt in das Filesystem installiert. Man kann sie zwar danach wieder manuell nachinstallieren, das ist aber nicht das, was wir wollen. Folglich suchen wir nun nach einer Lösung, Themes und Plugins automatisch über Docker zu installieren. Hierfür gibt es verschiedene Vorgehensweisen.

WP-CLI im Dockerfile

WP-CLI ist ein Tool zur Automatisierung von Aufgaben die sonst vom Benutzer manuell in der Administrationsoberfläche von WordPress durchgeführt werden. Das Hemingway-Theme kann so etwa wie folgt installiert und aktiviert werden:

wp theme install --activate https://downloads.wordpress.org/theme/hemingway.2.1.0.zip

Der naive Ansatz besteht also darin, das WP-CLI Tool im Dockerfile zu installieren und dort dann das o.g. Kommando aufzurufen. Achtung Spoiler: Das funktioniert so leider nicht, wie wir bald sehen werden.

Zunächst müssen wir uns jedoch von dem Basis-Image lösen und ein eigenes Dockerfile erzeugen. Um die Build-Prozess zu vereinfachen, integrieren wir die Schritte in Docker-Compose, das wie folgt erweitert wird:

  wordpress:
    ...
    build:
      context: ./build
    image: docker.tdm-consult.com:443/docker/com.tdmconsult/wordpress:latest
    ...

Zudem legen wir den Folder build an, in dem wir eine Datei Dockerfile anlegen, zunächst mit einer Zeile: FROM wordpress:latest. Unser Custom-Image können wir nun über docker-compose build bauen.

Nun wird das Dockerfile erweitert: Und zwar installieren wir zunächst WP-CLI und rufen dann weiter unten dass Tool auf, um the Hemingway-Theme direkt herunterzuladen und zu aktivieren:

FROM wordpress:latest

# install wp-cli to be able to install themes and plugins
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
 && chmod +x wp-cli.phar \
 && mv wp-cli.phar /usr/local/bin/wp

# install default theme
wp theme install --allow-root --force --activate https://downloads.wordpress.org/theme/hemingway.2.1.0.zip

Mit diesem Ansatz bekommen wir aber leider eine Fehlermeldung, die aussagt, dass WP-CLI keine gültige WordPress Installation vorfinden würde. Wie kann das sein? Nun das Problem besteht darin, dass das Basis-Image über FROM wordpress:latest nur vorbereitet wird, die eigentliche Installation – etwa das Anlegen der Tabellen für die Datenbank und die Basiskonfiguration – aber erst durch das Script docker-entrypoint.sh erfolgt, das erst mit dem Start des Containers ausgeführt wird, also bei docker-compose up.

Anpassen von docker-entrypoint.sh

Das Aufrufen von wp install theme muss also in die Startphase des Containers verlagert werden. Hierzu gibt es verschiedene Möglichkeiten, etwa auch die, das komplette Script über den COPY Befehl zu ersetzen. Wir entscheiden uns hier aber dafür, das Script selbst zu erweitern. Das wiederum können wir über Deteimanipulation im Dockerfile machen.

Wir wissen, dass der letzte Befehl im Script immer eine Zeile der Form exec "$@" sein wird: Hiermit wird das im Dockerfile unter CMD registrierte Kommando (hier: CMD [„apache2-foreground“]) ausgeführt, also letztlich das Starten der Apache Instanz. Demzufolge fügen wir in das Script vor dieser letzten Zeile Code ein, um die Theme-Installation durchzuführen. Das Dockerfile sieht dann so aus:

FROM wordpress:latest

# to make live more easy
RUN apt-get update \
 && apt-get install -y vim sudo

# install wp-cli to be able to install themes and plugins
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
 && chmod +x wp-cli.phar \
 && mv wp-cli.phar /usr/local/bin/wp

# install required themes on startup
RUN sed -i '/^exec/d' /usr/local/bin/docker-entrypoint.sh \
 && echo "if [ ! -e wp-content/themes/hemingway ]; then" >> /usr/local/bin/docker-entrypoint.sh \
 && echo "wp theme install --allow-root --force --activate https://downloads.wordpress.org/theme/hemingway.2.1.0.zip" >> /usr/local/bin/docker-entrypoint.sh \
 && echo "chown -R www-data:www-data /var/www/html/wp-content" >> /usr/local/bin/docker-entrypoint.sh \
 && echo "fi" >> /usr/local/bin/docker-entrypoint.sh \
 && echo "exec \"\$@\"" >> /usr/local/bin/docker-entrypoint.sh \
 && cat /usr/local/bin/docker-entrypoint.sh

Mit jedem Start von WordPress wird also geprüft, ob das Hemingway-Theme schon installiert ist und wenn nicht, so wird über wp theme install zur Verfügung gestellt. Und genau dies kann man beim ersten docker-compose up auch beobachten.

Die Apache Instanz läuft im Container mit dem User www-data. Der Befehl chown -R www-data:www-data /var/www/html/wp-content sorgt dafür, dass nach der Installation alle Dateien diesem User zugeordnet sind. Dies ist notwenddig, weil docker-entrypoint.sh mit Root-Rechten ausgeführt wird,

Theme konfigurieren

Nachdem das Theme nun installiert ist, kann das weitere Customizing durchgeführt werden. Insbesondere soll das Menüband zwischen Teaser-Bild und dem Hauptbereich eingestellt werden. Zudem werden unter Appearance | Customizing | Colors die Hintergrund- und Accentfarbe angepasst.

Das Menüband zeigt die Überschriften aller verfügbaren Seiten – die haben wir aber aktuell in dem Sinne gar nicht, weil wir nur mit Blogbeiträgen arbeiten wollen. Statt dessen soll das Menüband mit Einträge genauso wie auf unsere Homepage aufgebaut sein. Hierzu definieren wir ein neues Hauptmenü: Appearance | Menus. Unter Menu structure geben wir den Namen „Main“ ein und fügen dann pro Menüeintrag einen Custom Link hinzu. Schließlich wird unter Menu Settings noch die Option Display location: Primary Menu angehakt.

Nun erscheint die Menüleiste wir auf unserer Homepage, allerdings passen die Farbe und einige andere Details noch nicht. Im nächsten Teil soll das Theme weiter an die Corporate Identity angepasst werden.

WordPress Tutorial – Teil 2

Im ersten Teil haben wir mit docker-compose eine erste WordPress Instanz gestartet, diese wollen wir nun aus dem Internet erreichbar machen.

Wir haben nur eine öffentliche IP Adresse, die wir unter unseren verschiedenen Applikationen routen müssen. Für diesen Zweck haben wir einen NGINX, der als Reverse Proxy fungiert. HTTP(S) Traffic aus dem Internet wird über den Router an den NGINX weitergeleitet. Pro Zielapplikation wird von dort dann abhängig vom Hostname im HTTP Header an die richtige VM im lokalen Netz 192.168.1.0/24 weitergeleitet. Zudem ist der NGINX für die SSL Terminierung über Let’s Encrypt Zertifikate zuständig. Intern betreiben wir alle Applikation mit HTTP unverschlüsselt.

Nameserver konfigurieren

Wir haben dyndns.com als als Domain Name Server. Dort navigieren wir in den Zone Level Service für tdm.consult.com und legen einen neuen CNAME Record wie folgt an:

blog.tdm-consult.com 600 CNAME hage.tdm-consult.com.

Dabei „zeigt“ der CNAME Record auf den A-Record hage.tdm-consult.com, hinter dem sich die eigentliche IP Adreesse verbirgt. 600 ist der TTL Wert, also die Gültigkeitsspanne des Eintrags.

Reverse Proxy konfigurieren

Auf unserem Router müssen wir nichts machen: Der leitet bisher schon jeden Traffic auf Port 80 (HTTP) und 443 (HTTPS) an den Internen Reverse Proxy. Auch der NGINX läuft natürlich als Docker-Container. Für die Konfiguration haben wir ein Volume für die Konfiguration ./conf:/etc/nginx/conf.d:ro gemappt. In diesem Verzeichnis wird nun eine weitere Datei für unseren neuen Service angelegt. Per internen Konvention benennen wir sie nach der Domain mit .conf Endung, also blog.tdm-consult.com.conf:

server {
  listen 80;
  listen [::]:80;

  server_name blog.tdm-consult.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  listen [::]:443 ssl;

  ssl_certificate /etc/nginx/letsencrypt/live/blog.tdm-consult.com/fullchain.pem;
  ssl_certificate_key /etc/nginx/letsencrypt/live/blog.tdm-consult.com/privkey.pem;

  server_name blog.tdm-consult.com;

  location /.well-known/ {
      root /etc/nginx/acme-challenge;
  }

  location / {
      proxy_pass http://192.168.1.24/;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Forwarded-Port $server_port;
  }
}

Wir leiten als Traffic auf Port 80 HTTP direkt auf Port 443 HTTPS via Redirection (HTTP Code 301) um und akzeptieren nur verschlüsselte Verbindungen.

Zertifikat anfordern

Die Zertifikate gibt es ja noch nicht – sie werden unter /etc/nginx/letsencrypt erwartet. Hierzu haben wir ein separates Docker-Compose File, dass das Image certbot/certbot geeignet parametrisiert und den Ziel-Folder ebenfalls als Volume mounted:

version: '2'
services:
  certbot:
    image: certbot/certbot
    entrypoint:
      - certbot
      - certonly
      - -m info@tdm-consult.com
      - --agree-tos
      - --webroot
      - --webroot-path=/data/acme-challenge
      - -d ${FQDN}
    volumes:
      - ./acme-challenge:/data/acme-challenge
      - ./letsencrypt:/etc/letsencrypt

Der Server, für den das Zertifikat angefragt werden soll, wird als FQDN Environment in den Container gegeben. Das neue Zertifikat kann nun wie folgt abgerufen werden:

export FQDN=blog.tdm-consult.com
docker-compose -f docker-compose-certbot.yml up

Der Zertifikat ist drei Monate gültig, danach kann es über denselben Mechanismus aktualisiert werden. Im letzten Schritt wird der NGINX zum Neuladen der Konfiguration veranlasst:

# reload NGINX configuration
docker exec -it docker_nginx_nginx_1 nginx -s reload

Zur Sicherung aller Daten, also der NGINX Konfiguration, der Docker-Files sowie der Zertifikate selbst, liegen diese alle im Git: Sie werden nun eingecheckt. Dabei ist zu beachten, dass der cerbot-Container als root läuft, so dass auch die Dateien entsprechend erzeugt wurden: Dies müssen wir vor dem Commit noch ändern. Das Script sieht dann so aus:

sudo chown tdm:users -R letsencrypt
git add --all
git commit -m "New Certificate for blog Domain"
git push origin

Das ganze verpacken wir noch in ein Script, damit später die Erneuerung aller Zertifikate automatisch und ggf. über Cron gesteuert funktioniert:

#!/bin/bash

# request for renewal
export FQDN=www.tdm-consult.com
docker-compose -f docker-compose-certbot.yml up
...
export FQDN=blog.tdm-consult.com
docker-compose -f docker-compose-certbot.yml up

# reload NGINX configuration
docker exec -it docker_nginx_nginx_1 nginx -s reload

# checkin new certificates with GIT
sudo chown tdm:users -R letsencrypt
git add --all

today=$(date +%Y-%m-%d)
git commit -m "Certificate re-newal as of $today"
git push origin

WordPress konfigurieren

Jetzt fehlen noch zwei Dinge, die wir in WordPress konfigurieren muss: Da ist zum einen die Basis-URL, die wie umstellen müssen. Dazu wird unter Settings | General für WordPress Address (URL) und Site Address (URL) die öffentliche Adresse eingetragen, also z.B. https://blog.tdm-consult.com.

Zudem müssen wir WordPress mitteilen, dass wir hinter einem Reverse Proxy betrieben werden. Unter Administration Over SSL sind die Hintergründe beschrieben. Für uns bedeutet dies ganz konkret, dass beim Start von WordPress über Docker-Compose die Umgebungsvariable HTTP_X_FORWARDED_PROTO gesetzt werden muss. Der wordpress-Block im Docker-Compose sieht dann wie folgt aus:

    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
      HTTP_X_FORWARDED_PROTO: https

Anschließend ist die Seite unter blog.tdm-consult.com zu erreichen.

Im nächsten Teil wollen wir ein Theme installieren und dem Blog so etwas mehr Eleganz verleihen – und das natürlich so, dass das Theme auch bei zukünftigen Installation über Docker automatisch heruntergeladen und aktiviert wird.

WordPress Tutorial – Teil 1

Heute ist der 31.12.2020 und ich habe mich entschlossen, in 2021 einen Technologie-Blog zu starten.

Mein Name ist Thorsten Marsen und ich bin CEO eines kleinen Software- und Beratungshauses im schönen Hamburg. Wir entwickeln komplexe Unternehmensanwendungen für unsere Kunden, zumeist in Java-Technologie.

Im Rahmen unserer Tätigkeit kommen wir ständig mit neuen Technologien in Berührung: Über Virtualisierungslösungen wie WMware und Citrix XenServer, über Containerisierung mit Docker und Kubernetes, dem Aufbau von CI/CD Pipelines im Entwicklungsprozess mit BitBucket, GitLab, Jenkins, Artifactory und Nexus, den diversen Frameworks in der Entwicklung für Frontend- und Backend-Lösungen, bei Microservices unter Verwendung von Middleware-Technologien wie ActiveMQ, RabbitMQ oder Kafka und Datenbanken wie Mysql, Postgres oder auch Redis, bis hin zum operativen Betrieb dieser Anwendungen unter Einsatz von ELK, Prometheus und Grafana. Fast täglich kommen neue Technologien hinzu, die wir einsetzen oder für unsere Kunden evaluieren. Bisher habe wir alles in unsere internes Wiki dokumentiert. Für 2021 haben wir uns vorgenommen, Erfahrungen in diesem Blog zu teile und gerne auch mit anderen zu diskutieren. Los geht’s!

Technologieauswahl

Für den Blog kamen verschiedene Plattformen in Betracht. Derzeit benutzen wir für unser internes Wiki und auch für den Internetauftritt unter www.tdm-consult.com das Produkt Atlassian Confluence, das auch ein kleines Blog-Modul beinhaltet. Wenngleich wir mit dem Produkt als Wiki sehr zufrieden sind, möchten wir für diesen Blog eine neue Technologie ausprobieren. Dabei lag der Focus auf Open Source Projekten. Folgende Kandidaten kamen in die engere Auswahl:

  • WordPress.org
  • Drupal
  • Ghost
  • Joomla

Im Internet gibt es diverse Artikel, die sich mit der Bewertung und Gegenüberstellung der Lösungen befassen – das wollen wir hier nicht machen. Stattdessen ist es unser Ziel, alle Lösungen einmal installiert und ausprobiert zu haben. Den Anfang soll WordPress machen. Alle Installationen sollen In-House betrieben werden.

Betrieb als Container

Seit geraumer Zeit betreiben wir alle unsere Installationen als Docker-Container entweder in einem Kubernetes Cluster oder einfach über docker-compose auf einer dedizierten VM. Als Virtualisierungslösung kommt bei uns Citrx XenServer zum Einsatz. Wenn möglich nehmen wir offizielle Images aus Docker Hub. Sofern notwendig führen wir ein Customizing durch, indem wir eigene Images über Dockerfiles erzeugen und in eine interne Docker Registry speichern: Hierfür nehmen wir JFrog. Alle Artefakte (Dockerfile, docker-compose.yml, etc. ) werden über ein Git Repository verwaltet. Bewegungsdaten werden auf Volumes gehalten und täglich gesichert.

en und ausprobieren und anfangen wollen wir mit WordPress. Wir werden die Lösung in-haus betreiben, und zwar zunächst als Docker-Container, später ziehen wir möglicherweise in ein Kubernetes-Cluster um. Das aber erst, nachdem wir uns für eines der o.g. Lösungen entschieden haben. Bis dahin genügt der Betrieb über eine simple docker-compose Lösung.

WordPress mit Docker

Um ein erstes Ergebnis zu erzielen erzeugen wir eine neue VM über XenCenter, die wir mit einer CPU und 2G RAM bestücken. Als Basisimage kommt bei uns aktuelle die Ubuntu Distribution in Version 18.04 zum Einsatz. Unser vordefiniertes Template, aus dem wir Clonen, bringt uns Docker, Git und einen SSH-Zugang über Zertifikate mit, so dass wir unmittelbar beginnen können. Unser lokales Netz ist das 192.168.1.0/24. Wir tragen in unseren DHCP die MAC Adresse der neuen VM ein und weisen dort die IP 192.168.1.24 zu. Nach einem Restart der VM können wir uns mit SSH auf die Maschine einloggen und können beginnen.

Unter Ubuntu haben wir leider noch nicht herausgefunden, wir der vom DHCP gelieferte Hostname (option host-name) automatisch übernommen werden kann, vermutlich geht das in der Version von Netplan nicht, die mit Ubuntu 18.04 kommt. Daher müssen wir in /etc/cloud/cload.cfg den Wert von preserve_hostname auf true stellen und dann den Hostnamen manuell festlegen: sudo hostnamectl set-hostname wordpress.

Jetzt erzeugen wir ein neues Verzeichnis mkdir -p docker/docker_wordpress && cd docker/docker_wordpress und legen dort ein neues File docker-compose.yml an. Interessanterweise bezieht sich die Dokumentation von docker-compose auf genau diesen Anwendungsfall, so dass wir den ersten Wurf von dort übernehmen können: https://docs.docker.com/compose/wordpress. Weil wir auf einer eigenen VM laufen, muss bei uns der Port nicht gemappt werden, so dass wir den Port 80:80 direkt durchleiten. Das sieht dann etwa so aus:

version: '2'

services:
  db:
    image: mysql:5.5.47
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: xxx
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: yyy

  wordpress:
    depends_on:
      - db
    ports:
      - "80:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: yyy
      WORDPRESS_DB_NAME: wordpress

volumes:
  db_data: {}

Nach docker-compose up startet die Mysql Datenbank und der Apache Webserver mit WordPress hoch und der Blog ist unter http://192.168.1.24 erreichbar und die initiale Konfiguration kann durchgeführt werden.

Im Teil 2 des Tutorials werden wir den Blog öffentlich zugängig zu machen und dabei gleich die SSL Terminierung über unseren NGINX Reverse Proxy mit einem Zertifikat von Let’s Encrypt konfigurieren.

© 2024 TDM Consult GmbH | Impressum | Datenschutz

Friday 3. May 2024 - 18:51:48Hoch ↑