TYPO3 mit SOLR und NGINX mittels docker-compose auf EC2

Heute geht es mal darum, einen Blick auf TYPO3 in Verbindung mit Docker auf einer Amazon AWS EC2 Instanz zu betreiben. Es geht also weg vom klassischen Hosting, hin zu einer sehr weit skalierbaren Cloud Lösung. Zusätzlich wird die Performance des Projektes erhöht, indem anstelle eines Apache Webserver hier ein NGINX Webserver zum Einsatz kommt. Als zusätzlicher Dienst wird ein SOLR Server mit einem Core für das Projekt betrieben. Um das ganze automatisiert auf der EC2 Instanz aktualisieren zu können, wird über die Gitlab CI ein kleines, rudimentäres Deployment aufgesetzt. Das Deployment ist wirklich nur rudimentär, weil es weder ein Code Quality Gate, noch einen Frontend-Build besitzt. Auch das Anlegen von Releases oder Rollback gehört nicht zu diesem Thema.


Einleitung

Weshalb weg vom klassischen Hoster, weshalb weg von Apache? Diese Fragen sind einfach beantwortet. Weil man sehr viel flexibler ist und mehr Performance im Gegensatz zu klassischem Hosting herausholen kann. Wer also sein Projekt plant, sollte auf jeden Fall direkt das Thema Performance mit einbeziehen. In diesem Projekt kann nginx übrigens gleich mehrfach punkten: Es ist performanter, es nimmt  weniger Speicher in Anspruch und statische Seiten sind noch eine weitere Stärke des Webservers. Statische Seiten? Hä? Auf jeden Fall empfehle ich StaticFileCache gleich zu Beginn eines Projektes mit einzuplanen. Die Performance der Seite wird dadurch unschlagbar! Einfach mal in diesem Artikel nachlesen. Der erste Punkt, weniger Speicher, ist auch bei diesem Projekt entscheidend, denn man sollte sich natürlich vorab überlegen, welche der vielen EC2 Instanzen man für sein Projekt wählt. Weniger Speicherverbrauch bedeutet weniger Kosten. Und gerade bei Amazon AWS EC2 können die Kosten sehr schnell aus dem Ruder laufen, denn für den interessierten Entwickler finden sich sehr viele tolle Instanzen und viele weitere Services. Dazu gleich mehr.

Welche Instanz, welche Kosten, welches was?!

Für den Anfang kann das sehr verwirrend sein. Amazon bietet unzählige Dienste an, die sich miteinander verbinden lassen. Die meisten Dienste werden dann nach CPU Zeit, Speicher, Bandbreite oder anderen Faktoren berechnet. Daraus ergibt sich eine schier unendliche Anzahl an Verwendungsmöglichkeiten, und eine ebenso hohe Anzahl Faktoren, wie die Rechnung am Ende des Tages aussehen wird. Ich werde während dem Verfassen des Artikels eine Instanz anlegen und ein Projekt lauffähig machen. Und das wirklich Gute daran ist: Ich erstelle zwar eine neue Instanz, die auch ans Netz geht, aber letztlich wird die Instanz so gut wie keine Kosten verursachen, da ich nur sehr wenig Uptime habe und die Instanz dann stoppe.

Auf der Seite "Amazon EC2-Instanz-Typen" ist eine Auflistung aller möglichen Instanzen zu finden. Die Instanzen unterteilen sich in mehrere Kategorien, als da wären (linkes Sub-Menü)

  • Allgemeine Zwecke
  • Für Datenverarbeitung optimiert
  • und viele mehr...

Für jede Kategorie gibt es viele weitere Unterkategorien. Diese teilen sich auf verschiedene Merkmale auf, zum Beispiel die Art des Prozessors, die Menge des Arbeitsspeichers, die Größe der Festplatte und noch vieles mehr. Für dieses Projekt entscheide ich mich für die Instanz "t3.medium", die zum Zeitpunkt des Artikels über 2 vCPU's (eine vCPU ist ein Thread eines Intel Xeon oder EPYC Kerns) sowie über 4 GB Arbeitsspeicher verfügt. Die Netzwerkanbindung ist bis zu 5 Gbit/s schnell, was wirklich ordentlich ist. Die Größe des Festplattenspeichers wird später bestimmt.

Was kostet das? Das ist eine Gute Frage. Dafür hat Amazon einen "AWS Pricing Calculator" entwickelt. Damit lassen sich die Kosten in etwa berechnen. Für dieses Projekt kommt also ein "t3.medium" infrage. Da es sich um einen Webserver handelt, kann man bei der Projektplanung davon ausgehen, dass die Auslastung der Instanz bei 100 % im Monat liegt. Bedeutet: Das Ding läuft einfach immer. Das wird für einen Webserver auch das Beste sein, denn Besuchern die Webseite nur von 8.00 bis 17.00 anzubieten macht (hoffentlich) niemand. Nachdem also die Instanz gewählt wurde, wäre die Preisstrategie hier "EC2 Instance Savings Plans". Als Hinweis: Ergibt die Projektplanung, dass dieses System nur 20 % im Monat verfügbar sein wird, wäre die Preisstrategie "On-Demand-Instances", da hier nach CPU Zeit abgerechnet wird.

Für den gewählten Instanz Typen "t3.medium" sowie der gewählten Preisstrategie "EC2 Instance Savings Plans" fallen die im Bild gezeigten kosten pro Monat an. Wie die Kosten exakt berechnet werden, wird an einem Rechenbeispiel direkt gezeigt. Für diese Instanz werden zum Zeitpunkt des Artikels rund 22 USD fällig. bei dieser Instanz ist aber noch kein Festplattenspeicher enthalten. Der Festplattenspeicher kann beliebig zugewiesen werden. Hier noch ein Hinweis: Im Verlauf werde ich ein Ubuntu 20.04 als Host Betriebssystem für das Projekt nutzen. Wird zu Beginn 10 GB Speicher zugewiesen, und die Instanz gestartet, kann man nach Belieben mehr Speicher zuweisen. Es reicht dann auch aus, die Instanz neu zu starten. Der Speicher wird automatisch zugewiesen.

Das Amazon Elastic Block Storage (EBS) wird zu den monatlichen Gebühren hinzugerechnet. Hier ein Preisbeispiel für 30 GB:

30 GB x 0,119 USD x 1 instances = 3,57 USD (EBS Storage Cost)
EBS-Speicherkosten: 3,57 USD
Preise für Amazon Elastic Block Storage (EBS) (monthly): 3.57 USD

Und ja, für 300 GB werden entsprechend 35 USD fällig. In der Konstellation kostet die Instanz also 25.62 USD.

 

 

Anlegen einer neuen Instanz

Voraussetzung ist ein natürlich ein AWS Konto. Das normale Amazon Konto funktioniert nicht. Über diesen Link kann die Registrierung gestartet werden. Nach dem Login kann links unter "Services" der Service EC2 ausgewählt werden. WICHTIG: Oben rechts ist eine Auswahl für die Region. Hier sollte direkt "Frankfurt" (eu-central-1) ausgewählt werden. Ein Server in Ohio ist zwar auch nett, bringt aber in unserer Region nicht soviel.

Die Instanz wird links über Instanz-Typen ausgewählt. Über das Filter-Feld wird nach "t3.medium" gesucht. Die gewünschte Instanz wird aktiviert und rechts oben über das "Aktionen" DropDown mittels "Launch Instance" gestartet.

Im nächsten Schritt wird das System ausgewählt. Hier gibt es massig viele Sachen, einfach mal durchstöbern. Für dieses Projekt wähle ich als Docker Host das aktuelle Ubuntu aus. Über das Filter-Input kann gesucht werden. Vor dem Auswählen sollte das System auf 64bit (x86) gestellt sein. Root-Gerätetyp "EBS" besagt, das wir den Amazon Elastic Block Service nutzen können. Quasi die Festplatte für den Server.

Weiter geht es mit der Bestätigung der ausgewählten Instanz. Hier kann nochmal geprüft werden, ob das passt. Entweder kann die Instanz sofort lanciert werden, oder im Assistent geht es weiter mit den Details. Für dieses Projekt werden die Details gleich angepasst. Es geht weiter mit: "Konfigurieren von Instance-Details"

Im nächsten Schritt können sehr viele Optionen eingestellt und umgestellt werden. Für jede Option ist eine Hilfestellung vorhanden. Die für den Start wichtigsten Optionen sind "Beendigungsverhalten", welche auf "Stopp" stehen muss, sowie "Aktivieren des Beendigungsschutzes", welches aktiviert werden sollte. Was ist der Unterschied zwischen "Stopp" und "Beenden"? Wird die Instanz gestoppt, gleicht das in etwa einem ausschalten des Rechners. Er lässt sich jederzeit wieder einschalten. Wird die Instanz beendet, ist dass auch das Ende der Instanz, im wahrsten Sinne des Wortes. Der Schutz zum Beenden sieht eine nochmalige, zusätzliche Abfrage vor. Weiter geht es mit dem Speicher.

Festplatte ist nicht gleich Festplatte. Sogar für die Historiker unter den Seitenbetreibern stehen wirklich noch HDDs zur Verfügung. Echte Scheiben mit echten magnetischen Leseköpfen. Für dieses Projekt reicht eine Allzweck SSD mit gp2 aus. Wer große Dateien hat und viel Streams vom Speicher anbieten muss, kann auch die gp3 Variante nehmen, und den Durchsatz des Speichers konfigurieren. Hier kann vorab bereits die Speichergröße angegeben werden, ebenso können weitere Volumes erstellt werden. Der nächste Schritt wird übersprungen und damit geht es zum letzten: die Sicherheitsgruppe.

Eine Sicherheitsgruppe beschreibt ein Set von Regeln, wie der Datenverkehr geöffnet, bzw. eingeschränkt werden soll. Bei den meisten Projekten wird SSH für den Zugriff benötigt, dieser ist aber auf die IP des Firmennetzwerks beschränkt (Ja, die IP ist natürlich ungültig ;-)), HTTP sowie HTTPS ist offen, und der Port für den SOLR Server, beziehungsweise der Admin-Oberfläche ist wieder eingeschränkt. Sind die Einstellungen erledigt, geht es zur Überprüfung und Lancierung.

Auf dieser Seite wird nochmal alles in der Übersicht angezeigt. Beim Klick auf "Starten" erscheint dieses Pop-up. Der Zugriff auf die Instanz ist entweder als Verbindung zur seriellen Konsole via Managementkonsole möglich, oder durch einen SSH Client mittels Schlüsselpaar. Hier sollte auf jeden Fall ein neues Schlüsselpaar erzeugt werden. Der öffentliche Schlüssel verbleibt bei Amazon, der private Schlüssel kann dann einfach nach ~/.ssh/ kopiert werden. Für weitere Instanzen lassen sich neue Paare erstellen, oder ein vorhandenes auswählen. Im Anschluss wird die Instanz gestartet.

Einfacher Zugriff: Einrichten einer festen IP-Adresse

Eine neue Instanz erhält automatisch eine IP-Adresse aus dem IP-Pool von Amazon AWS. Für einen Webserver ist das natürlich denkbar ungeeignet, da man ja auch eine Domain nutzen möchte, und wenn man die Instanz neu startet, wird Ihr eine neue IP-Adresse zugewiesen. Dieses Verhalten lässt sich ändern. Amazon bietet hierzu einen Service an, der sich "Elastic IP" nennt. Hier gibt es zwei Möglichkeiten: Man kann einen eigenen Pool an IP-Adressen mitbringen, oder man nutzt eine IP-Adresse von Elastic IP. Diese wird wie folgt eingerichtet:

Über das Navigationsmenü rechts geht es zu "Netzwerk & Sicherheit" mit dem Punkt "Elastic IPs". Hier werden alle eingerichteten festen IP-Adressen angezeigt. Gestartet wird der Vorgang rechts oben mit "Elastic IP-Adresse zuweisen".

Für diesen Account steht nur die Auswahl des öffentlichen IPv4-Adress-Pool von Amazon zur Verfügung. Im nächsten Schritt wird wieder die Übersicht angezeigt. Von dort aus geht es weiter mit der Zuordnung der IP-Adresse zur EC2 Instanz.

In dieser Übersicht werden nun alle Elastic IPs angezeigt, die im Konto erstellt wurden. Durch Aktivierung der gewünschten IP-Adresse und das Aktionsmenü rechts oben lässt sich eine Zuweisung zwischen Elastic IP und EC2 Instanz erstellen. Ebenso lässt sich hier die Verbindung zwischen EC2 Instanz und Elastic IP auch wieder lösen. Die Elastic IP-Adresse kann dann an eine andere EC2 Instanz gebunden werden.

In diesem Schritt wird die Elastic IP der Instanz zugeordnet. Dafür wird die EC2 Instanz sowie die private IP-Adresse der EC2 Instanz ausgewählt. Anschließend ist die EC2 Instanz unter dieser IP-Adresse erreichbar. Bevor es an den ersten Zugriff geht, prüfe ich nochmal den Speicher des EBS, und zeige auch hier das Interface zum Einstellen der Parameter.

Anpassen des zugewiesenen Speichers und erstellen von Snapshots

Das gezeigte Bild und die Funktionen sind selbsterklärend. Jedes Volume lässt sich beliebig in der Art und Größe ändern. Volumes können auch von einer Instanz getrennt und einer neuen EC2 Instanz zugeordnet werden. Das ist besonders dann sehr praktisch, wenn sich die Anforderungen ändern und die EC2 Instanz anders skaliert werden soll. Zusätzlich können Snapshots erstellt und verwaltet werden. Alles in allem eine sehr runde Sache und dem klassischen Hosting weit voraus.

Erster Zugriff auf das System und Setup

Im ersten Schritt wird der private Schlüssel nach ~/.ssh/ kopiert und die Rechte der Datei angepasst:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [8:19:56] C:1
$ sudo cp /Users/manfredrutschmann/Downloads/rbiz_aws_ec2.pem ~/.ssh/

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [8:20:03]
$ sudo chmod 0600 ~/.ssh/rbiz_aws_ec2.pem

 

Danach erfolgt das erste Verbinden auf die Instanz. Der Benutzername bei diesem Image ist "ubuntu", kann aber mit dem Aktionsbutton rechts oben in der EC2 Instanzübersicht über den  "Verbinden" Eintrag geändert werden.

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [8:20:31]
$ ssh -A ubuntu@ec2-18-193-204-133.eu-central-1.compute.amazonaws.com

 

Im Anschluss muss die Verbindung explizit bestätigt werden (known hosts), und die Verbindung steht:

 

The authenticity of host 'ec2-18-193-204-133.eu-central-1.compute.amazonaws.com (18.193.204.133)' can't be established.
ECDSA key fingerprint is SHA256:mfT/Kv/BQ1XNhAAOWl2uR86p+oBEgNEdpR7HjVNuPek.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ec2-18-193-204-133.eu-central-1.compute.amazonaws.com,18.193.204.133' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-1045-aws x86_64)

 * Documentation:  help.ubuntu.com
 * Management:     landscape.canonical.com
 * Support:        ubuntu.com/advantage

  System information as of Thu Jun  3 06:41:19 UTC 2021

  System load:  0.0               Processes:             104
  Usage of /:   7.4% of 19.32GB   Users logged in:       0
  Memory usage: 6%                IPv4 address for ens5: 172.31.4.38
  Swap usage:   0%

 * Super-optimized for small spaces - read how we shrank the memory
   footprint of MicroK8s to make it the smallest full K8s around.

   ubuntu.com/blog/microk8s-memory-optimisation

1 update can be applied immediately.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ip-172-31-4-38:~$

 

Was wirklich auf absolut keinen Fall fehlen darf, ist zu testen, wie es um die Netzwerkgeschwindigkeit steht:

 

ubuntu@ip-172-31-4-38:~$ curl ftp://speedtest.tele2.net/10GB.zip -o /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10.0G  100 10.0G    0     0   428M      0  0:00:23  0:00:23 --:--:--  383M
ubuntu@ip-172-31-4-38:~$

 

Nachdem das ehrfürchtige grinsen sich beruhigt hat, geht es weiter mit ein paar Installationen. Nicht alles wird benötigt, ist bei mir aber Routine.

 

ubuntu@ip-172-31-4-38:~$ sudo apt update

 

ubuntu@ip-172-31-4-38:~$ sudo apt upgrade

 

Setzen der Zeitzone:

 

ubuntu@ip-172-31-4-38:~$ sudo timedatectl set-timezone Europe/Berlin

 

ZSH installieren und in der passwd beim Benutzer die Shell auf zsh ändern:

 

ubuntu@ip-172-31-4-38:~$ sudo apt-get install -y git zsh

 

ubuntu@ip-172-31-4-38:~$ sudo vi /etc/passwd

ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash > ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/zsh

 

Installieren von ohMyZSH:

 

ubuntu@ip-172-31-4-38:~$ sh -c "$(curl -fsSL raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

 

Danach neu verbinden und die Shell ist aktualisiert.

Jetzt wird Docker installiert:

 

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

 

$ curl -fsSL download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

 

$ echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

 

$ sudo apt-get update

 

$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose
$ sudo groupadd docker
$ sudo usermod -aG docker $USER

 

Danach einmal ausloggen und wieder einloggen.

Prüfen, ob alles passt:

 

$ sudo systemctl status docker

● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2021-06-03 10:07:05 CEST; 6min ago
TriggeredBy: ● docker.socket
       Docs: docs.docker.com
   Main PID: 19323 (dockerd)
      Tasks: 10
     Memory: 41.4M
     CGroup: /system.slice/docker.service
             └─19323 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

 

Hello World testen:

 

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:5122f6204b6a3596e048758cabba3c46b1c937a46b5be6225b835d091b90e46c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!

 

Top. Läuft. Im Prinzip sollte es das für den Host gewesen sein. Ich werde nachher nochmal etwas einstellen, aber im nächsten Schritt sollte eine Domain per A-Record auf den Server geleitet werden. Nachdem das erledigt ist, geht es an das Setup für den Betrieb von TYPO3 auf dieser Instanz.

Lokales Setup und Projektstruktur

Lokal werde ich dieses Projekt mit DDEV laufen lassen. Für das Produktivsystem wird dann eine eigene Docker Konfiguration via docker-compose eingerichtet. Ich nutze lokal DDEV aus dem Grunde, da ich viele Projekte eingerichtet habe, und diese öfters auch parallel aktiv sind. Das Starten und Beenden, sowie das Handling multipler Projekte zur gleichen Zeit auf dem lokalen Host geht mit DDEV um ein vielfaches einfacher. Eventuell werde ich dazu später nochmal einen eigenen Artikel verfassen.

Zu dem Projekt gehören nun die folgenden Komponenten:

  • Lokales DDEV (ohne tiefgreifende Erklärung)
  • TYPO3 Basissetup mit composer
  • docker-compose Konfiguration
  • Kleines Deployment über Gitlab CI

Das Projekt erstelle ich für diesen Artikel von Scratch, die Daten stelle ich über GitLab dann öffentlich zur Verfügung.

 

DDEV Konfiguration

Die DDEV-Konfiguration wird im Projektordner unter .ddev/config.yaml abgelegt. Aussehen wird das Config File dann so:

 

---

name: ec2-18-193-204-133.aws
type: typo3
docroot: web
php_version: "7.4"
webserver_type: nginx-fpm
router_http_port: "80"
router_https_port: "443"
provider: default
use_dns_when_possible: true
webimage_extra_packages: [zsh, cron, graphicsmagick]
nfs_mount_enabled: true
mysql_version: "5.7"
timezone: "Europe/Berlin"
composer_version: "2"

 

 

Mit ddev start kann ich das Projekt lokal schon starten. Im Prinzip läuft jetzt hierfür lokal bereits ein nginx, mysql, ein SSH Router und ein Nameservice für die lokale Auflösung der URL des Projektes intern.

TYPO3 Installation via Composer

Nun wird TYPO3 via Composer im Projekt installiert. Da ich hier DDEV nutze, lasse ich auch Composer im Docker Container des Projekts laufen. Ich installiere ausgewählte Pakete und den TYPO3 Core in der TYPO3 Version 10 LTS, anstelle eines fertigen Distributionspaketes:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [10:33:00]
$ ddev composer req "typo3/cms-about:^10.4" "typo3/cms-backend:^10.4" \
     "typo3/cms-belog:^10.4" "typo3/cms-beuser:^10.4" "typo3/cms-core:^10.4" \
     "typo3/cms-dashboard:^10.4" "typo3/cms-extbase:^10.4" "typo3/cms-filelist:^10.4" \
     "typo3/cms-filemetadata:^10.4" "typo3/cms-fluid:^10.4" \
     "typo3/cms-fluid-styled-content:^10.4" "typo3/cms-frontend:^10.4" \
     "typo3/cms-info:^10.4" "typo3/cms-install:^10.4" "typo3/cms-lowlevel:^10.4" \
     "typo3/cms-recordlist:^10.4" "typo3/cms-recycler:^10.4" "typo3/cms-redirects:^10.4" \
     "typo3/cms-reports:^10.4" "typo3/cms-rte-ckeditor:^10.4" "typo3/cms-scheduler:^10.4" \
     "typo3/cms-seo:^10.4" "typo3/cms-setup:^10.4" "typo3/cms-tstemplate:^10.4"

 

Ich installiere später noch ein kleines Sitepackage, aus diesem Grund gebe ich in der composer.json noch den Speicherort meiner zusätzlichen Extensions an:

 

    "repositories": [
        { "type": "path", "url": "packages/*" }
    ],

 

Zusätzlich mache ich noch ein paar weitere Konfigurationen, wie zum Beispiel die Packagestates generieren, DB Update und Cache leeren, die auch über Composer laufen. Weiterführend kommen noch die Einstellungen zum Webroot und spezifizieren der PHP Version hinzu:

 

    "scripts": {
        "ext-setup": [
            "@php vendor/bin/typo3cms install:generatepackagestates",
            "@php vendor/bin/typo3cms database:updateschema",
            "@php vendor/bin/typo3cms cache:flush --files-only",
            "@php vendor/bin/typo3cms extension:setupactive"
        ]
    },
    "extra": {
        "typo3/cms": {
            "cms-package-dir": "{$vendor-dir}/typo3/cms",
            "web-dir": "web"
        },
        "helhum/typo3-console": {
            "install-extension-dummy": false
        },
        "helhum/dotenv-connector": {
            "cache-dir": "tmp"
        }
    },
    "config": {
        "platform": {
            "php": "7.4"
        }
    }

 

Nachdem dieser Teil erledigt ist, lege ich eine .env Datei an, um die Umgebungsvariablen zu definieren. Diese Datei wird nicht versioniert, da hier je nach System (Lokal, Staging, Testing und das Produktivsystem) andere Werte für Datenbank und weiteres eingetragen werden. Ich lege eine .env.dist bei, die umbenannt und verwendet werden kann:

 

TYPO3_CONTEXT='Development'
DB_CONNECTION_DEFAULT_DBNAME='db'
DB_CONNECTION_DEFAULT_HOST='db'
DB_CONNECTION_DEFAULT_USER='db'
DB_CONNECTION_DEFAULT_PASSWORD='db'

BE_TYPO3_INSTALL_TOOL_PASSWORD='$argon2i$v=19$m=65536,t=16,p=1$Q2pGU2dCRHVaUkcvTy9WWQ$reUG4W3s9wpgxVK6bSkx9f6ohrP+zzn0iFG3+PGzHHY'

TYPO3_TRUSTED_HOST_PATTERN='ec2-18-193-204-133.aws.ddev.site'

GFX_PROCESSOR_PATH='/usr/bin/'
GFX_PROCESSOR_PATH_LZW='/usr/bin/'
GFX_PROCESSOR='GraphicsMagick'

SOLR_HOST_READ='solr'
SOLR_PATH_READ='/solr/'
SOLR_PORT_READ='8983'
SOLR_SCHEME_READ='http'


MAIL_defaultMailFromAddress='noreply@example.com'
MAIL_defaultMailFromName='Example Industries'
MAIL_transport='smtp'
MAIL_transport_sendmail_command='/usr/sbin/sendmail -t -i'
MAIL_transport_smtp_encrypt='false'
MAIL_transport_smtp_password=''
MAIL_transport_smtp_server='localhost:1025'
MAIL_transport_smtp_username=''

SOLR_DE_CORE='core_de'

DDEV_SYS=1

BASE_DOMAIN='https://ec2-18-193-204-133.aws.ddev.site'
NGINX_SERVER_NAME='ec2-18-193-204-133.aws.ddev.site'

 

Damit die Werte auch im TYPO3 verwendet werden, erzeuge ich die folgende AdditionalConfiguration.php im Verzeichnis web/typo3conf:

 

<?php
call_user_func(function () {

    /**
     * DATABASE
     */
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['initCommands'] = 'SET @@session.sql_mode = ""';
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] = getenv('DB_CONNECTION_DEFAULT_DBNAME');
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] = getenv('DB_CONNECTION_DEFAULT_HOST');
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] = getenv('DB_CONNECTION_DEFAULT_USER');
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] = getenv('DB_CONNECTION_DEFAULT_PASSWORD');

    /**
     * BACKEND
     */
    $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'] = getenv('TYPO3_INSTALL_TOOL_PASSWORD');
    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL'] = true;

    /**
     * FRONTEND
     */
    $GLOBALS['TYPO3_CONF_VARS']['FE']['hidePagesIfNotTranslatedByDefault'] = 1;
    $GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] = false;

    /**
     * GRAPHICS
     */
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path'] = getenv( 'GFX_PROCESSOR_PATH');
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path_lzw'] = getenv('GFX_PROCESSOR_PATH_LZW');
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] = 1;
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor'] = getenv('GFX_PROCESSOR');
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_effects'] = 1;
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowTemporaryMasksAsPng'] = '';
    $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_colorspace'] = 'RGB';
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog'] = '0';
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting'] = '0';

    /**
     * SYSTEM
     */
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = false;
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[PRODUCTION] Project';
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'] = 0;
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'] = 28674;
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = getenv('TYPO3_TRUSTED_HOST_PATTERN');
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog'] = '0';
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting'] = '0';

    /**
     * MAIL
     */

    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'] = getenv('MAIL_defaultMailFromAddress');
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'] = getenv('MAIL_defaultMailFromName');
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = getenv('MAIL_transport');
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_sendmail_command'] = '';
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_encrypt'] = getenv('MAIL_smtp_encrypt');
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_password'] = getenv('MAIL_transport_smtp_password');
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = getenv('MAIL_transport_smtp_server');
    $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_username'] = getenv('MAIL_transport_smtp_username');;

    if(getenv('DDEV_SYS')){
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_core']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_hash']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_pages']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_pagesection']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_phpcode']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_runtime']['backend'] = \TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_rootline']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_imagesizes']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['l10n']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_object']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_reflection']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_datamapfactory_datamap']['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
        $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'smtp';
        $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = 'localhost:1025';
    }
    /**
     * STAGE OVERWRITES
     */
    if (\TYPO3\CMS\Core\Core\Environment::getContext()->isDevelopment()) {
        $GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] = true;
        $GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] = true;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = true;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[DEV] Project';
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLogLevel'] = 0;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'] = 28674;
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog'] = '0';
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting'] = '0';

    } elseif ('Production/Staging' === (string)\TYPO3\CMS\Core\Core\Environment::getContext()) {
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[STAGING] Project';
        $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] = 5;
        $GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel'] = 5;
    } elseif ('Production/Live' === (string)\TYPO3\CMS\Core\Core\Environment::getContext()) {
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[PRODUCTION] Project';
        $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] = 5;
        $GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel'] = 5;
    }

});

 

Sind diese Vorbereitungen getroffen, werden noch eben zusätzliche Pakete für den ersten Start installiert:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [11:21:54] C:1
$ ddev composer req helhum/dotenv-connector:^2.3 helhum/typo3-console:^6.4

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [11:22:01]
$ ddev composer update --with-all-dependencies

 

Und danach das ganze Setup einmal durchlaufen lassen, damit alles für den Betrieb von TYPO3 erzeugt wird. Die Setup-Eingaben sind ebenfalls zu sehen:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [11:28:03]
$ ddev exec php vendor/bin/typo3cms install:setup

Welcome to the TYPO3 Console installer!

➤ Prepare installation
✔ Ok
✔ Check environment and create folders
✔ Skipped Set up database connection
✔ Skipped Select database
➤ Set up database
Username of to be created administrative user account (required): admin
Password of to be created administrative user account (required):
Name of the TYPO3 site (default: "New TYPO3 Console site"): Example
✔ Ok
➤ Set up configuration
Specify the site setup type (default: "no"):
  [no  ] Do nothing
  [site] Create root page
 > site
Specify the site base url (default: "/"): /
✔ Ok
➤ Set up web server configuration
Specify the web server you want to write configuration for (default: "none"):
  [none  ] Do not write any configuration files
  [apache] Create Apache .htaccess file
  [iis   ] Create Microsoft-IIS web.config file
 > none
✔ Ok
✔ Set up extensions

Successfully installed TYPO3 CMS!

 

Zum Abschluss noch einmal die TYPO3 Scripts via Composer laufen lassen

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [11:30:05]
$ ddev composer ext-setup

 

und fertig ist die lokale Installation. TYPO3 kann jetzt über den Browser geöffnet werden. Ich lege dem Paket noch eine Basetemplate Extension bei, wo noch weitere Zusatz-Extensions für das Projekt geladen werden.

Docker Compose: Setup für web, php, db und solr

Web Server

Das Setup für docker-compose um TYPO3 10 LTS nachher auf dem externen Host laufen zu lassen, teilt sich in die 4 Bereiche web (nginx Webserver), php, db und solr auf. Alle Daten für docker-compose liegen in ./Build/Docker/. Ich fange mit dem ersten Service an, nginx:

 

services:
  web:
    build: ./Build/Docker/web/.
    environment:
      NGINX_SERVER_NAME: ${NGINX_SERVER_NAME}
      NGINX_SERVER_SSL: ${NGINX_SERVER_SSL}
    depends_on:
      - php
      - db
      - solr
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./:/var/www/html
      - ./Build/Docker/web/includes:/etc/nginx/includes
      - /etc/letsencrypt/live/:/etc/letsencrypt/live
      - /etc/letsencrypt/archive:/etc/letsencrypt/archive
      - /etc/ssl/:/etc/ssl
      - ./Build/Docker/web/templates:/etc/nginx/templates:rw,cached

 

Das Dockerfile für den nginx ist relativ überschaubar:

 

FROM nginx:1.21

RUN apt-get update
RUN apt-get install -y certbot python3-certbot-nginx ssl-cert
RUN make-ssl-cert generate-default-snakeoil --force-overwrite

 

Die eigentliche Konfigurationsdatei für nginx liegt in ./Build/Docker/web/templates/default.conf.template:

 

map $http_accept $webp_suffix {
    default   "";
    "~*webp"  ".webp";
}

include '/etc/nginx/includes/server-80.conf';
include '/etc/nginx/includes/server-443.conf';

 

Die Konfiguration für die Extension "webP" ist bereits hinterlegt. Eingebunden werden hierfür der Server auf Port 80 und der Server auf Port 443. Diese sehen wie folgt aus:

server-80.conf

 

server {
    listen 80;
    server_name ${NGINX_SERVER_NAME};
    root /var/www/html/web;
    location /.well-known/acme-challenge/ {
        try_files $uri =404;
    }
    return 301 www.$host$request_uri;
}

server {
    listen 80;
    server_name www.${NGINX_SERVER_NAME};
    root /var/www/html/web;
    index index.php index.html;
    location /.well-known/acme-challenge/ {
        try_files $uri =404;
    }
    return 301 $host$request_uri;
}

 

server-443.conf

 

server {
    listen 443 ssl;
    server_name         ${NGINX_SERVER_NAME};
    ssl_certificate /etc/letsencrypt/live/${NGINX_SERVER_NAME}/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/${NGINX_SERVER_NAME}/privkey.pem; # managed by Certbot
    return      301     www.${NGINX_SERVER_NAME}$request_uri;
}

server {
    listen 443 ssl http2;
    server_name www.${NGINX_SERVER_NAME};

    ssl_certificate /etc/letsencrypt/live/${NGINX_SERVER_NAME}/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/${NGINX_SERVER_NAME}/privkey.pem; # managed by Certbot

    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/web;

    include '/etc/nginx/includes/locations.conf';
    include '/etc/nginx/includes/cache-header.conf';
    include '/etc/nginx/includes/compression.conf';
}

 

Für Port und Port 443 sind jeweils zwei Server vorhanden. Bei Port 80 wird example.com sowie www.example.com abgefragt und lediglich auf https mit www weitergeleitet. Die vorhandene Location Condition wird nachher für Lets Encrypt benötigt. Wichtig hier ist, beim ersten Rollout NICHT den Server auf Port 443 auszuspielen,  da die Zertifikate noch fehlen. Die Vorgehensweise ist relative einfach: Ausrollen der Konfiguration auf die EC2 Instanz OHNE Server 443, über die CLI die Zertifikate auf der EC2 Instanz holen, danach lokal Server 443 aktivieren und nochmal ausrollen.  

Zusätzlich ist noch eine Konfiguration  für StaticFileCache, die allgemeinen Rewrites für TYPO3, CacheHeaders und Compression enthalten.

 

PHP Build

Auch für PHP ist in der docker-compose etwas definiert:

 

  php:
    build: ./Build/Docker/php/.
    user: "1000:1000"
    restart: unless-stopped
    volumes:
      - ./:/var/www/html
      - ./Build/Docker/php/php.ini:/usr/local/etc/php/php.ini
      - ./Build/Docker/php/log.conf:/usr/local/etc/php-fpm.d/zz-log.conf

 

Das Dockerfile ist schon etwas umfangreicher. Hier kann je nach Projekt und belieben zusätzliche Pakete und Module installiert werden. Das gezeigte ist ein Standardsetup für php74. Zusätzlich liegt noch eine php.ini bei, um php für das Projekt anpassen zu können:

 

FROM php:7.4-fpm
USER root

RUN apt-get update
RUN apt-get install -y \
        unzip \
        iputils-ping \
        imagemagick \
        ghostscript

RUN docker-php-ext-enable opcache
RUN docker-php-ext-install calendar
RUN docker-php-ext-install bcmath
RUN docker-php-ext-install tokenizer
RUN docker-php-ext-install json
RUN docker-php-ext-install mysqli pdo pdo_mysql \
    && docker-php-ext-enable pdo_mysql

RUN apt-get install -y \
        libonig-dev \
    && docker-php-ext-install iconv mbstring

RUN apt-get install -y \
        libcurl4-openssl-dev \
    && docker-php-ext-install curl

RUN apt-get install -y \
        libssl-dev \
    && docker-php-ext-install ftp phar

RUN apt-get install -y \
        libicu-dev \
    && docker-php-ext-install intl

RUN apt-get install -y \
        libmcrypt-dev \
    && docker-php-ext-install session

RUN apt-get install -y \
        libxml2-dev \
    && docker-php-ext-install simplexml xml xmlrpc

RUN apt-get install -y \
        libzip-dev \
        zlib1g-dev \
    && docker-php-ext-install zip

RUN apt-get install -y \
        libgmp-dev \
    && docker-php-ext-install gmp

RUN apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd

RUN apt-get install -y libmagickwand-dev
RUN pecl install imagick && docker-php-ext-enable imagick

ENV COMPOSER_BINARY=/usr/local/bin/composer \
    COMPOSER_HOME=/usr/local/composer
ENV PATH $PATH:$COMPOSER_HOME

RUN curl -sS getcomposer.org/installer | php && \
    mv composer.phar $COMPOSER_BINARY && \
    chmod +x $COMPOSER_BINARY

COPY php.ini /etc/php7/fpm/php.ini
COPY php.ini /etc/php7/cli/php.ini

RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /var/www/html

EXPOSE 9000

CMD ["php-fpm", "-F", "-c", "/etc/php7/fpm"]

 

Und hier die php.ini:

 

upload_max_filesize=24M
post_max_size=24M
always_populate_raw_post_data=1
max_execution_time=240
max_input_vars=4500
memory_limit=256M
extension=gd.so

 

 

MySQL Datenbank

In der docker-compose wird der Datenbankserver konfiguriert. Ich nutze hier die Environment Variablen aus der .env Datei weiter. Zusätzlich lege ich eine SQL Init Datei ab, die beim ersten Erstellen des Containers die Datenbank, den Benutzer und die Rechte entsprechend vorbereitet:

 

  db:
    build: ./Build/Docker/mysql/.
    ports:
      - 3306:3306
    command: mysqld --default-authentication-plugin=mysql_native_password
    restart: unless-stopped
    volumes:
      - db-data:/var/lib/mysql
      - ./Build/Docker/mysql/mysql.cnf:/etc/mysql/conf.d/custom.cnf
      - ./Build/Docker/mysql/:/docker-entrypoint-initdb.d
    environment:
      MYSQL_USER: ${DB_CONNECTION_DEFAULT_USER}
      MYSQL_PASSWORD: ${DB_CONNECTION_DEFAULT_PASSWORD}
      MYSQL_DATABASE: ${DB_CONNECTION_DEFAULT_DBNAME}
      MYSQL_ROOT_PASSWORD: ${DB_CONNECTION_DEFAULT_PASSWORD}

 

Vorbereitung der Datenbank, Benutzer und Rechte:

 

CREATE DATABASE db;
CREATE USER 'db'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}';
GRANT ALL PRIVILEGES ON * . * TO 'db'@'%';
FLUSH PRIVILEGES;

 

Und außerdem gleich die richtige Einstellung für den mysqld Service:

 

[mysqld]
character-set-server        = utf8mb4
collation-server            = utf8mb4_unicode_ci
skip-character-set-client-handshake

bind-address           = 0.0.0.0

 

 

SOLR Server

Bleibt zum Schluss nur noch der SOLR Server. Im Dockerverzeichnis liegt bereits das "data" Verzeichnis aus der TYPO3 SOLR Extension. Im Verzeichnis "cores" habe ich bereits alle Cores gelöscht, und nur einen übrig gelassen. Beim ersten Starten des Containers wird gleich dieser Core erzeugt, sodass ich mich darum später ebenfalls nicht kümmern muss. Der Block in der docker-compose schaut entsprechend so aus:

 

  solr:
    build: ./Build/Docker/solr/.
    ports:
      - "8983:8983"
    restart: unless-stopped
    volumes:
      - ./Build/Docker/solr/data:/var/solr/data

 

Das Dockerfile dazu ist ebenfalls sehr übersichtlich (genau genommen ist es das Dockerfile aus der TYPO3 Extension):

 

FROM solr:8.5
MAINTAINER Timo Hund <timo.hund@dkd.de>
ENV TERM linux

USER root
RUN rm -fR /opt/solr/server/solr/*
USER solr

COPY --chown=solr:solr data/ /var/solr/data
RUN mkdir -p /var/solr/data/data

 

 

Überprüfen, ob alles funktioniert geht recht einfach:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [20:24:03]
$ docker-compose build

 

Und zum Starten entsprechend:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [20:24:03]
$ docker-compose up

 

 

Deployment: Ausrollen von Änderungen über Gitlab CI

Dieser Bereich des Artikels soll das Thema nur leicht anschneiden. In erster Linie geht es darum, Änderungen die lokal gemacht wurden, über eine Pipeline via Gitalb auf das Zielsystem zu bringen. Normalerweise sind die Pipelines deutlich aufwendiger, da unter anderem Code Qualität und Review Stages zum Einsatz kommen, außerdem in der Regel ein Frontend-Build zum Projekt dazugehört, und zu guter Letzt das Deployment mit TYPO3 Surf via Docker in der Pipeline durchgeführt wird, da hier auf dem Zielsystem dann einfacher Releases angelegt werden können, und auch Rollbacks möglich werden.

Ziel von diesem Deployment ist, Änderungen durch mergen in den Master Branch zu erkennen, und den aktuellen Stand an das Zielsystem zu übertragen. Das Zielsystem wird aktualisiert (TYPO3 Code Änderungen sowie auch Änderungen der Docker Konfiguration) und docker-compose lädt die ganzen Services neu.

Für die CI wird ein Gitlab Runner benötigt. Da ich für diese Projektgruppe  sowieso einen Runner anlegen muss, dokumentiere ich hier noch eben das Prozedere mit.

Auf dem Gitlab werden die CI/CD Einstellungen der Projektgruppe geöffnet und der Bereich "Runners" aufgeklappt. Dort wird schon der Runner Registration-Token eingeblendet. Diesen nehmen wir mit und führen auf der Bash des GitLab Servers die Registrierung durch: 

 

# user @ gitlab in ~ [20:37:13]
$ sudo gitlab-runner register --url gitlab.example.com --registration-token xx_CCAASSSDDDD

 

Runtime platform                                    arch=amd64 os=linux pid=22895 revision=7f7a4bb0 version=13.11.0
Running in system-mode.

Enter the GitLab instance URL (for example, gitlab.com/):
[https://gitlab.example.com/]:
Enter the registration token:
[xx_CCAASSSDDDDDD]:
Enter a description for the runner:
[gitlab]: opensource-runner
Enter tags for the runner (comma-separated):
docker,opensource
Registering runner... succeeded                     runner=xx_CCAAA
Enter an executor: custom, parallels, docker+machine, docker-ssh+machine, virtualbox, kubernetes, docker, docker-ssh, shell, ssh:
docker
Enter the default Docker image (for example, ruby:2.6):
ubuntu:latest
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

# user @ gitlab in ~ [20:44:17]

 

Es werden einige Dinge abgefragt, das wichtigste ist jedoch der Executor: In diesem Fall Docker, da ich ein Docker Image lade, einen SSH Key rein platziere und dort einige Befehle ausführe, um das Zielsystem zu aktualisieren. Es wäre als Executor auch Shell oder SSH möglich, aber mit dem Docker Image bin ich vom Host unabhängiger. 

Für den Zugriff auf die EC2 Instanz vom Gitlab Runner, also aus dem Docker Container heraus, erzeuge ich ein Schlüssel, und lege den privaten Schlüssel als Variable im Gitlab an, den öffentlichen Schlüssel speichere ich auf der EC 2 Instanz. Das Schlüsselpaar erzeuge ich lokal, allerdings OHNE Passphrase:

 

# manfredrutschmann @ MacPro in /Volumes/aws/ec2-18-193-204-133 on git:master x [18:57:03] C:255
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/manfredrutschmann/.ssh/id_rsa): ec2-18-193-204-133_CI
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ec2-18-193-204-133_CI.
Your public key has been saved in ec2-18-193-204-133_CI.pub.

 

Den Inhalt der .pub Datei speichere ich in ~/.ssh/authorized_keys.

Der private Schlüssel wird in der Gruppe des Gitlab > Einstellungen > CI/DC im Bereich "Variables" als neue Variable angelegt. Der Variablennamen lautet: SSH_PRIVATE_KEY:

 

Als Nächstes geht es auf der EC2 Instanz weiter. Ich lege das Verzeichnis /var/www/html an, und passe auch gleich die Rechte an:

 

# ubuntu @ ip-172-31-4-38 in ~ [17:04:45] C:1
$ sudo mkdir /var/www/

# ubuntu @ ip-172-31-4-38 in ~ [17:04:49]
$ sudo mkdir /var/www/html

# ubuntu @ ip-172-31-4-38 in ~ [17:05:01]
$ sudo chown 1000:1000 /var/www/html

# ubuntu @ ip-172-31-4-38 in ~ [17:05:16]

 

Anschließend checke ich im html Verzeichnis das Repository aus:

 

# ubuntu @ ip-172-31-4-38 in ~ [17:05:16]
$ cd /var/www/html

# ubuntu @ ip-172-31-4-38 in /var/www/html [17:05:43]
$ git clone git@gitlab.rutschmann.biz:open-source/typo3-docker-ec2-template.git .

 

Dieser Schritt ist wichtig: Die .env.dist wird nach .env kopiert und angepasst:

 

# ubuntu @ ip-172-31-4-38 in /var/www/html on git:master o [17:19:08] C:1
$ cp .env.dist .env && nano .env

 

Für SOLR müssen noch die Rechte angepasst werden:

 

$ sudo chown 8983:8983 -R Build/Docker/solr/data

 

Ist das erledigt, lasse ich den Build das erste Mal auf der EC2 Instanz laufen:

 

$ docker-compose build

 

Ist auch das erledigt, wird das Ganze im Hintergrund gestartet:

 

$ docker-compose up -d

 

und anschließend die Zertifikate geholt:

 

$ docker-compose exec web sh -c 'certbot certonly --quiet  --agree-tos --email test@example.com  --nginx -d example.com -d www.example.com'

 

Als vorletzter Schritt muss der Host noch auf den GitLab Server zugreifen können, damit Git Pull aus dem Deployment heraus auf dem EC2 Host funktioniert. Auf dem EC2 Host führe ich ein ssh-keygen durch, und speichere den Public Key auf dem Gitlab Server als Deployment Key:

 

# ubuntu @ ip-172-31-4-38 in /var/www/html on git:master o [19:19:57]
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ubuntu/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ubuntu/.ssh/id_rsa
Your public key has been saved in /home/ubuntu/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:7dWuWdq6Gn/UCApVT/56EFfljAbwqlNkQFmO77Z6ONE ubuntu@ip-172-31-4-38
The key's randomart image is:
+---[RSA 3072]----+
|       .ooooo . +|
|        .+.. = +.|
|        ..+ . * +|
|        .= ..o + |
|        So=...oo.|
|        .=E. .oo.|
|        oo=  .+ .|
|        oo.+ *.. |
|        .+o.B+.  |
+----[SHA256]-----+

# ubuntu @ ip-172-31-4-38 in /var/www/html on git:master o [19:21:19]
$ cat ~/.ssh/id_rsa.pub

 

Bevor das Deployment das erste Mal läuft, wird noch der TYPO3 Datenbank Dump eingespielt:

 

# ubuntu @ ip-172-31-4-38 in /var/www/html on git:master o [20:00:16] C:1
$ docker-compose exec -T db  mysql -uroot  db < Build/DbDump/import.sql

 

Weiter geht es mit dem Deployment. Das Deployment ist wie Eingangs schon beschrieben, sehr rudimentär. Hier ist das zugehörige gitlab.ci.yaml:

 

stages:
  - deploy

release production:
  stage: deploy
  image: ubuntu:latest
  before_script:
    - apt-get update
    - 'command -v ssh-agent >/dev/null || ( apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  script:
    - ssh ubuntu@ec2-18-193-204-133.eu-central-1.compute.amazonaws.com "cd /var/www/html/ &&
      git config pull.rebase false &&
      git checkout master &&
      git pull origin master &&
      docker-compose exec -T php composer install &&
      docker-compose exec -T php composer du &&
      docker-compose exec -T php composer ext-setup &&
      docker-compose build &&
      docker-compose down &&
      docker-compose up -d"
  tags:
    - docker
  only:
    - master

 

Vom Ablauf her wird bei einem Push auf Master ein Gitlab Runner mit dem Tag "docker" gestartet, den Runner habe ich weiter oben schon eingerichtet. Der Runner startet einen Ubuntu Container, installiert einen SSH Client, und bekommt den privaten Schlüssel aus der Variable SSH_PRIVATE_KEY vom GitLab. Damit kann er nun per SSH auf den EC2 Host verbinden. Dort wird das Projektverzeichnis aktualisiert, ein Rebuild für docker-compose ausgeführt und alles neu gestartet. Beim ersten Deployment wird dann der deaktivierte nginx Server auf Port 443 aktiviert. Nach Ablauf der Pipeline ist der Host dann über HTTPS erreichbar und das TYPO3 läuft.

Das Projekt ist auf dem Gitlab hier zu finden.