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?!
Anlegen einer neuen Instanz
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:
Anpassen des zugewiesenen Speichers und erstellen von Snapshots
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"
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.