112 lines
4.4 KiB
Markdown
112 lines
4.4 KiB
Markdown
# Watermaps auf Hetzner Cloud
|
|
|
|
Diese OpenTofu-Konfiguration erstellt einen einzelnen Ubuntu-24.04-Server mit
|
|
fester IPv4-Adresse, vorgeschalteter Hetzner-Firewall und einem persistenten
|
|
ext4-Volume für Routingdaten, Geofabrik-Rohdaten und die PostGIS-Datenbank mit
|
|
Häfen, Schleusen, Brücken und Kontaktdaten.
|
|
Kartenkacheln, DNS-Einträge und konkrete Anwendungsversionen sind bewusst nicht
|
|
Teil dieses Infrastrukturmoduls. Die Anwendung wird anschließend als
|
|
commitgenaues Container-Image aus der Gitea Registry deployt.
|
|
|
|
## Konfiguration
|
|
|
|
Die lokale Datei `terraform.tfvars` wird aus der versionierten Vorlage angelegt
|
|
und durch die lokale `.gitignore` ausgeschlossen:
|
|
|
|
```sh
|
|
cp terraform.tfvars.example terraform.tfvars
|
|
```
|
|
|
|
Vor dem ersten Plan müssen dort mindestens folgende Werte ersetzt werden:
|
|
|
|
- `hcloud_token`: Read/Write-API-Token des richtigen Hetzner-Cloud-Projekts
|
|
- `ssh_public_key`: vollständiger Inhalt des öffentlichen SSH-Schlüssels
|
|
- `admin_cidrs`: öffentliche Administrator-IP mit `/32` beziehungsweise ein
|
|
bewusst gewähltes Netz
|
|
|
|
`terraform.tfvars.example` bleibt als geheimnisfreie Vorlage versioniert.
|
|
Da `terraform.tfvars` den Token im Klartext enthält, sollte sie nur für den
|
|
lokalen Benutzer lesbar sein:
|
|
|
|
```sh
|
|
chmod 600 terraform.tfvars
|
|
```
|
|
|
|
Auch `terraform.tfstate` bleibt lokal und wird nicht nach Git oder auf den
|
|
Anwendungsserver übertragen. Nach jedem Import oder Apply muss die State-Datei
|
|
mit Dateimodus `0600` verschlüsselt gesichert werden, weil OpenTofu sie für
|
|
spätere Änderungen an denselben Ressourcen benötigt. Ist ein älterer State
|
|
verloren, werden vorhandene Ressourcen vor jedem Apply anhand ihrer
|
|
Hetzner-IDs importiert; ein paralleles Neuerzeugen gleichnamiger Ressourcen ist
|
|
nicht zulässig.
|
|
|
|
## Infrastruktur erzeugen
|
|
|
|
```sh
|
|
cd infra/opentofu
|
|
tofu init
|
|
tofu fmt -check
|
|
tofu validate
|
|
umask 077
|
|
tofu plan -out=watermaps.tfplan
|
|
tofu apply watermaps.tfplan
|
|
```
|
|
|
|
Gespeicherte Plan-Dateien enthalten trotz als sensibel markierter Variablen
|
|
unter Umständen den Hetzner-Token. Sie müssen deshalb wie `terraform.tfvars`
|
|
und der State immer Modus `0600` haben und dürfen nicht weitergegeben werden.
|
|
|
|
Die feste IPv4-Adresse und den erforderlichen manuellen DNS-Eintrag zeigt
|
|
OpenTofu anschließend an:
|
|
|
|
```sh
|
|
tofu output server_ipv4
|
|
tofu output dns_a_record
|
|
tofu output ssh_command
|
|
```
|
|
|
|
Der A-Record `watermaps.incoso.eu` kann direkt nach dem Apply auf
|
|
`server_ipv4` gesetzt werden. Das eigentliche SSL-Livegehen erfolgt erst durch
|
|
das separate Deployment-Skript, nachdem DNS propagiert ist und die Routingdaten
|
|
bereitstehen.
|
|
|
|
## Auf dem Server
|
|
|
|
Cloud-init installiert Docker einschließlich Compose v2, `curl`, `jq` und
|
|
`rsync`. Der Benutzer `deploy` erhält Zugriff per SSH und auf Docker. Es wird
|
|
kein Git-Repository auf den Server geklont. Das
|
|
persistente Volume wird nach dem Anhängen unter `/srv/watermaps-data` gemountet
|
|
und enthält die vom Produktions-Stack verwendeten Verzeichnisse `geofabrik`,
|
|
`local` und `certbot`. Ein systemd-Drop-in lässt Docker bei jedem Start auf den
|
|
erfolgreichen Volume-Mount warten, bevor bestehende Container neu gestartet
|
|
werden.
|
|
|
|
Der Status des ersten Starts lässt sich so prüfen:
|
|
|
|
```sh
|
|
ssh deploy@"$(tofu output -raw server_ipv4)" \
|
|
"cloud-init status --wait && systemctl status watermaps-volume-setup --no-pager"
|
|
```
|
|
|
|
Cloud-init bindet ausschließlich das von OpenTofu erzeugte Volume über den
|
|
stabilen Hetzner-Gerätepfad
|
|
`/dev/disk/by-id/scsi-0HC_Volume_<volume-id>` ein. Es wird nicht heuristisch
|
|
das erste Blockgerät mit dem Modellnamen `Volume` ausgewählt. Nach dem Mount
|
|
wird zusätzlich die UUID des tatsächlich eingehängten Geräts geprüft. Ein
|
|
Volume ohne lesbares Dateisystem wird standardmäßig nicht formatiert; nur bei
|
|
einem garantiert neuen, leeren Volume darf
|
|
`initialize_empty_routing_volume = true` für die einmalige Initialisierung
|
|
gesetzt werden. Bei Importen und Serverwechseln bleibt der Wert `false`.
|
|
|
|
Der Löschschutz ist getrennt konfiguriert:
|
|
|
|
- `enable_server_protection` für den austauschbaren Host,
|
|
- `enable_primary_ip_protection` für die feste öffentliche Adresse,
|
|
- `enable_volume_protection` für Routing-, Zertifikats- und PostGIS-Daten.
|
|
|
|
Bei einem kontrollierten Servertausch bleiben IP- und Volume-Schutz immer
|
|
aktiv. Ein Plan, der `hcloud_primary_ip.main` oder
|
|
`hcloud_volume.routing_data` ersetzen beziehungsweise löschen will, darf nicht
|
|
angewendet werden. Zusätzlich verhindert OpenTofu für beide Ressourcen mit
|
|
`prevent_destroy` eine versehentliche Löschung durch den IaC-Plan.
|