Deploy: Terraform-Code-Rollout auf App-Nodes (ohne Recreate)
terraform_data.app_deploy führt per remote-exec auf jedem App-Node ein Update
aus (git reset auf origin + deploy/update.sh: SPA bauen, composer, migrate(app-1),
cache:clear), getriggert über var.deploy_version (z. B. Git-SHA). Server werden
NICHT ersetzt: hcloud_server.app ignoriert user_data-Änderungen (cloud-init nur
Erstboot). Gemeinsames deploy/update.sh (cloud-init ruft es ebenfalls auf).
Fix: ${PRIV:-} in der .tftpl als $${PRIV:-} escaped (templatefile-Kollision).
Workflow: tofu apply -var deploy_version=$(git rev-parse --short HEAD)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4c0aced823
commit
c49ff37746
@ -115,3 +115,20 @@ sobald die Zugangsdaten gesetzt sind. Ohne Konfiguration ist das Feature ausgebl
|
|||||||
Hinweis: Selbstsignierte Test-Zertifikate erzeugen ein technisch valides `.pkpass`/JWT,
|
Hinweis: Selbstsignierte Test-Zertifikate erzeugen ein technisch valides `.pkpass`/JWT,
|
||||||
werden aber von Apple/Google **nicht akzeptiert** — für die Produktion echte Zugangsdaten nötig.
|
werden aber von Apple/Google **nicht akzeptiert** — für die Produktion echte Zugangsdaten nötig.
|
||||||
Over-the-air-Sync (APNs/Objekt-Patch) ist noch nicht umgesetzt (nur Pass-Erstellung).
|
Over-the-air-Sync (APNs/Objekt-Patch) ist noch nicht umgesetzt (nur Pass-Erstellung).
|
||||||
|
|
||||||
|
## Code-Update ausrollen (ohne Neu-Provisionierung)
|
||||||
|
|
||||||
|
Nach `git push` den neuen Code auf die laufenden App-Nodes bringen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deploy/terraform
|
||||||
|
tofu apply -var deploy_version=$(git rev-parse --short HEAD)
|
||||||
|
```
|
||||||
|
|
||||||
|
`terraform_data.app_deploy` (per `var.deploy_version` getriggert) führt auf jedem
|
||||||
|
App-Node aus: `git fetch`/`reset` auf `origin/<branch>`, SPA neu bauen, Composer/
|
||||||
|
Autoloader auffrischen, Cache leeren – Migrationen + Seed nur auf app-1
|
||||||
|
(`deploy/update.sh`). Die Server bleiben erhalten (cloud-init `user_data` ist via
|
||||||
|
`ignore_changes` eingefroren; es zählt nur beim Erstboot). Gleiche `deploy_version`
|
||||||
|
= kein Rollout; neuer Git-SHA = Rollout. SSH-Key: `ssh_private_key_path`
|
||||||
|
(Default `~/.ssh/vcard4_deploy`).
|
||||||
|
|||||||
@ -51,29 +51,16 @@ write_files:
|
|||||||
base64 -d /opt/secrets/private.pem.b64 > backend/config/jwt/private.pem
|
base64 -d /opt/secrets/private.pem.b64 > backend/config/jwt/private.pem
|
||||||
base64 -d /opt/secrets/public.pem.b64 > backend/config/jwt/public.pem
|
base64 -d /opt/secrets/public.pem.b64 > backend/config/jwt/public.pem
|
||||||
chmod 640 backend/config/jwt/private.pem
|
chmod 640 backend/config/jwt/private.pem
|
||||||
# SPA bauen (Profil-/QR-Links zeigen auf die öffentliche Domain)
|
|
||||||
docker run --rm -e VITE_PUBLIC_BASE="https://$DOMAIN" -v "$PWD/frontend":/app -w /app node:25-alpine sh -c "npm ci && npm run build"
|
|
||||||
chown -R 1000:1000 /opt/vcard4
|
|
||||||
# Hetzner-Privatnetz-NIC (nicht eth0) sicher per DHCP hochziehen (für DB-Zugriff).
|
# Hetzner-Privatnetz-NIC (nicht eth0) sicher per DHCP hochziehen (für DB-Zugriff).
|
||||||
# Manchmal kommt das private Interface beim ersten Boot nicht hoch → DB unerreichbar.
|
# Manchmal kommt das private Interface beim ersten Boot nicht hoch → DB unerreichbar.
|
||||||
PRIV=$(ls /sys/class/net | grep -E '^(enp|ens)' | grep -v '^eth0$' | head -1 || true)
|
PRIV=$(ls /sys/class/net | grep -E '^(enp|ens)' | grep -v '^eth0$' | head -1 || true)
|
||||||
if [ -n "${PRIV:-}" ] && ! ip -4 addr show "$PRIV" | grep -q 'inet 10\.'; then
|
if [ -n "$${PRIV:-}" ] && ! ip -4 addr show "$PRIV" | grep -q 'inet 10\.'; then
|
||||||
printf '[Match]\nName=%s\n[Network]\nDHCP=ipv4\n' "$PRIV" > "/etc/systemd/network/10-$PRIV.network"
|
printf '[Match]\nName=%s\n[Network]\nDHCP=ipv4\n' "$PRIV" > "/etc/systemd/network/10-$PRIV.network"
|
||||||
ip link set "$PRIV" up || true
|
ip link set "$PRIV" up || true
|
||||||
systemctl restart systemd-networkd || true
|
systemctl restart systemd-networkd || true
|
||||||
for i in $(seq 1 30); do ip -4 addr | grep -q 'inet 10\.' && break; sleep 2; done
|
for i in $(seq 1 30); do ip -4 addr | grep -q 'inet 10\.' && break; sleep 2; done
|
||||||
fi
|
fi
|
||||||
COMPOSE="docker compose --project-directory /opt/vcard4 -f deploy/compose/docker-compose.prod.yml"
|
# Build + Deploy über das gemeinsame Skript (auch vom Terraform-Code-Rollout genutzt)
|
||||||
$COMPOSE up -d --build
|
DOMAIN="$DOMAIN" RUN_MIGRATIONS="$RUN_MIGRATIONS" bash /opt/vcard4/deploy/update.sh
|
||||||
sleep 20
|
|
||||||
# PHP-Abhängigkeiten installieren: vendor/ ist gitignored und /app ist als Volume
|
|
||||||
# gemountet (überdeckt ein im Image gebautes vendor) → hier in den Container hinein.
|
|
||||||
$COMPOSE exec -T -e COMPOSER_HOME=/tmp/composer php composer install --no-dev --optimize-autoloader --no-interaction --no-scripts
|
|
||||||
if [ "$RUN_MIGRATIONS" = "true" ]; then
|
|
||||||
$COMPOSE exec -T php php bin/console doctrine:migrations:migrate --no-interaction || true
|
|
||||||
# Erst-Befüllung (idempotent: überspringt, wenn admin@vcard4reseller.de existiert)
|
|
||||||
$COMPOSE exec -T php php bin/console app:seed || true
|
|
||||||
fi
|
|
||||||
$COMPOSE exec -T php php bin/console cache:clear || true
|
|
||||||
runcmd:
|
runcmd:
|
||||||
- bash /opt/deploy.sh > /var/log/vcard4-deploy.log 2>&1
|
- bash /opt/deploy.sh > /var/log/vcard4-deploy.log 2>&1
|
||||||
|
|||||||
29
deploy/terraform/deploy.tf
Normal file
29
deploy/terraform/deploy.tf
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Code-Rollout auf die App-Nodes ohne Neu-Provisionierung.
|
||||||
|
# Bei Änderung von var.deploy_version (z. B. Git-SHA) führt `tofu apply` auf jedem
|
||||||
|
# App-Node ein Update aus: neuesten Code holen, SPA neu bauen, Composer/Autoloader
|
||||||
|
# auffrischen, Cache leeren – Migrationen nur auf app-1. Server bleiben erhalten.
|
||||||
|
resource "terraform_data" "app_deploy" {
|
||||||
|
count = var.app_count
|
||||||
|
|
||||||
|
triggers_replace = [var.deploy_version]
|
||||||
|
|
||||||
|
depends_on = [hcloud_server.app]
|
||||||
|
|
||||||
|
connection {
|
||||||
|
type = "ssh"
|
||||||
|
host = hcloud_server.app[count.index].ipv4_address
|
||||||
|
user = "root"
|
||||||
|
private_key = file(pathexpand(var.ssh_private_key_path))
|
||||||
|
timeout = "5m"
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "remote-exec" {
|
||||||
|
inline = [
|
||||||
|
"set -e",
|
||||||
|
"cd /opt/vcard4",
|
||||||
|
"git fetch --depth 1 origin ${var.repo_branch}",
|
||||||
|
"git reset --hard origin/${var.repo_branch}",
|
||||||
|
"DOMAIN='${var.domain}' RUN_MIGRATIONS='${count.index == 0 ? "true" : "false"}' bash /opt/vcard4/deploy/update.sh",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -118,6 +118,12 @@ resource "hcloud_server" "app" {
|
|||||||
ip = "10.0.1.${local.app_base_ip + count.index}"
|
ip = "10.0.1.${local.app_base_ip + count.index}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# cloud-init zählt nur beim Erstboot; Code-Updates laufen über den Rollout
|
||||||
|
# (terraform_data.app_deploy) → Server nicht wegen user_data-Änderung ersetzen.
|
||||||
|
lifecycle {
|
||||||
|
ignore_changes = [user_data]
|
||||||
|
}
|
||||||
|
|
||||||
depends_on = [hcloud_network_subnet.subnet, hcloud_server.db]
|
depends_on = [hcloud_network_subnet.subnet, hcloud_server.db]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,3 +46,7 @@ s3_region = "nbg1"
|
|||||||
s3_bucket = "vcard4-card-assets"
|
s3_bucket = "vcard4-card-assets"
|
||||||
s3_key = "OBJECT_STORAGE_ACCESS_KEY"
|
s3_key = "OBJECT_STORAGE_ACCESS_KEY"
|
||||||
s3_secret = "OBJECT_STORAGE_SECRET_KEY"
|
s3_secret = "OBJECT_STORAGE_SECRET_KEY"
|
||||||
|
|
||||||
|
# Code-Rollout (optional): Standard-Key ~/.ssh/vcard4_deploy. Zum Ausrollen:
|
||||||
|
# tofu apply -var deploy_version=$(git rev-parse --short HEAD)
|
||||||
|
# ssh_private_key_path = "~/.ssh/vcard4_deploy"
|
||||||
|
|||||||
@ -154,3 +154,16 @@ variable "s3_secret" {
|
|||||||
type = string
|
type = string
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Code-Rollout ---
|
||||||
|
variable "deploy_version" {
|
||||||
|
description = "Code-Version (z. B. Git-SHA). Ändern → 'tofu apply' rollt den Code auf alle App-Nodes aus."
|
||||||
|
type = string
|
||||||
|
default = "init"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ssh_private_key_path" {
|
||||||
|
description = "Privater SSH-Key für den Code-Rollout (Gegenstück zu ssh_public_key)."
|
||||||
|
type = string
|
||||||
|
default = "~/.ssh/vcard4_deploy"
|
||||||
|
}
|
||||||
|
|||||||
34
deploy/update.sh
Normal file
34
deploy/update.sh
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Baut/aktualisiert die App auf einem App-Node aus dem bereits ausgecheckten Repo.
|
||||||
|
# Erwartet: Repo unter dem Repo-Root vorhanden, backend/.env.prod.local + JWT-Keys da.
|
||||||
|
# Aufruf: DOMAIN=<domain> RUN_MIGRATIONS=true|false bash deploy/update.sh
|
||||||
|
# Genutzt von cloud-init (Erstprovisionierung) und vom Terraform-Code-Rollout.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
DOMAIN="${DOMAIN:-localhost}"
|
||||||
|
RUN_MIGRATIONS="${RUN_MIGRATIONS:-false}"
|
||||||
|
COMPOSE="docker compose --project-directory $REPO_DIR -f deploy/compose/docker-compose.prod.yml"
|
||||||
|
|
||||||
|
# SPA bauen (Profil-/QR-Links zeigen auf die öffentliche Domain)
|
||||||
|
docker run --rm -e VITE_PUBLIC_BASE="https://$DOMAIN" -v "$REPO_DIR/frontend":/app -w /app \
|
||||||
|
node:25-alpine sh -c "npm ci && npm run build"
|
||||||
|
chown -R 1000:1000 "$REPO_DIR"
|
||||||
|
|
||||||
|
$COMPOSE up -d --build
|
||||||
|
sleep 8
|
||||||
|
|
||||||
|
# PHP-Abhängigkeiten + Autoloader (vendor/ gitignored, /app als Volume gemountet →
|
||||||
|
# im Container installieren; composer install erneuert den optimierten Autoloader,
|
||||||
|
# damit neue Klassen aus dem Update gefunden werden).
|
||||||
|
$COMPOSE exec -T -e COMPOSER_HOME=/tmp/composer php \
|
||||||
|
composer install --no-dev --optimize-autoloader --no-interaction --no-scripts
|
||||||
|
|
||||||
|
if [ "$RUN_MIGRATIONS" = "true" ]; then
|
||||||
|
$COMPOSE exec -T php php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
# Erst-Befüllung (idempotent: überspringt, wenn admin@vcard4reseller.de existiert)
|
||||||
|
$COMPOSE exec -T php php bin/console app:seed || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
$COMPOSE exec -T php php bin/console cache:clear
|
||||||
Loading…
Reference in New Issue
Block a user