Geschrieben von

Google Tag Manager-Server in der GCP über Terraform bereitstellen

Webtracking

Den Google Tag Manager Server-Container kann man auf Diensten in der Google Cloud auf verschiedene Wege bereitstellen, zum Beispiel:

Großer Vorteil von Terraform ist, dass man über die Terraform-Konfigurationssprache Cloud-Ressourcen über die API des jeweiligen Anbieters (GCP, AWS, etc.) programmatisch und automatisch bereitstellen kann. Man muss also die Instanzen und Dienste nicht mehr händisch über die Weboberfläche konfigurieren. Das hat diverse Vorteile: Skalierbarkeit, einfach erweiterbar, hoher Automatisierungsgrad, etc. Mehr zu Terraform findest du auf der Terraform-Website (auf der Startseite gibt es gute Infos zum Einstieg).

Initial stellt sich die Frage welche Terraform-Option man nutzen möchte. Zum einen gibt es die Terraform Cloud, die sich gut für Teams eignet und mit der man eine CI/CD-Pipeline mit GitHub Actions integrieren kann. Oder man nutzt direkt die Terraform CLI (Command Line Integration). Diese verwendet einen lokalen Workflow und führt die Vorgänge auf der Station aus, auf der sie aufgerufen wird und speichert die Konfigurationen sowie deren Zustände in einem lokalen Verzeichnis. Terraform Cloud nutzt ebenfalls die CLI, jedoch ist dort die Cloud die genannte “Station”. Dieser Beitrag legt den Fokus direkt auf die Terraform CLI und wie man damit den GTM-Container auf einer App Engine Flexible-Umgebung deployen kann. Zu Cloud Run gibt es am Ende des Beitrags Hinweise und weiterführende Informationen.

Voraussetzungen für das Setup mit Terraform (TF)

Ich setze voraus, dass du schon ein gewisses Grundverständnis des Google Tag Manager-Servers hast. Hier eine Übersicht, was schon vorhanden sein muss (inkl. Links falls noch nicht vorhanden), bevor du mit dieser Anleitung startest.

  • Du solltest schon ein Google Cloud Projekt inkl. verknüpften Rechnungskonto haben. Falls nicht, siehe hier.
  • Dein Cloud-Projekt braucht eine aktivierte Cloud Build-, App Engine Admin-, Google App Engine Flexible Environment und Cloud Resource Manager-API (könnte auch alternativ über den TF-Block google_project_service aktiviert werden).
  • Du hast schon einen Google Tag Manager Server-Container eingerichtet und hältst den Containerkonfigurations-Key bereit. Falls nicht siehe hier unter “Server-Container erstellen”.
  • Du brauchst einen Code-Editor wie z.B. Visual Code Studio oder Sublime Text.

Wenn alles bereitsteht, können wir loslegen:

Lokales Setup vorbereiten

Der erste Schritt besteht darin das lokale Setup vorzubereiten:

  • Lege einen Projektordner an, wo die Terraform-Konfiguration liegen soll.
  • Öffne den Ordner in der IDE.
  • Hole dir ggf. eine Extension wie “HashiCorp Terraform” (Visual Code Studio) für eine Syntaxhervorhebung der TF-Dateien.
  • Lade dir die Terraform CLI herunter.

Die heruntergeladene Datei der Terraform CLI solltest du dann in den Order /usr/local/bin ziehen (bei MacOS und Linux) bzw. bei Windows die Umgebungsvariablen anpassen, sodass die Terraform-Variable zur Binärdatei zeigt. Ob die Terraform-Installation erfolgreich war, kannst du mit folgendem Kommando prüfen:

terraform -v
Terraform v1.2.6 // Beispiel-Ausgabe

Ausgegeben wird dann die aktuell verwendete Version des Terraform CLI.

Dienstkonto und Key erstellen

Als nächstes brauchen wir in unserem Cloud-Projekt ein Dienstkonto für Terraform, damit Terraform die GCP-Ressourcen erstellen und verwalten kann. Gehe dazu im erstellten Cloud-Projekt auf “IAM und Verwaltung” und dann auf “Dienstkonsten”:

Klicke dann auf “Dienstkonto erstellen”:

Vergebe daraufhin einen Namen für das Dienstkonto, klicke auf “Erstellen und Fortfahren”. Vergebe anschließend die Inhaber-Rolle:

Klicke zum Schluss auf “Fertig” und du landest auf der Dienstkonten-Übersichtsseite. Danach klickst du das soeben erstellte Dientkonto an und erstellst wie folgt einen neuen Schlüssel:

Als Schlüsseltyp wählst du “JSON” und speicherst die Datei mit dem Namen “credentials.json” in den anfangs erstellen Projektordner. Wichtig ist, dass du diese Datei sicher aufbewahrst, da darin alle wichtigen Informationen für den Zugriff auf dein Projekt enthalten sind!

Domaininhaberschaft bestätigen

Als nächsten Schritt legen wir für unsere Domain einen Endpunkt im First-Party-Kontext an. Bei meiner Test-Domain www.demir-jasarevic.com wähle ich als Endpunkt sst.demir-jasarevic.com. Damit ich das später mit der App Engine mappen kann, muss ich die Inhaberschaft dieser Domain bestätigen. Dies kann ich über die Webmaster-Zentrale tun. Klicke dazu auf “Property hinzufügen” und setze deine gewünschte Domain in das Feld – in meinem Fall sst.demir-jasarevic.com. Klicke auf “Weiter” und wähle eines der Bestätigungsmethoden, um die Domain zu verifizieren.

In meinem Fall habe ich das über die DNS-Einstellungen gemacht und bekomme als erfolgreiche Bestätigung folgende Meldung:

Wichtig im finalen Schritt hier: Klicke auf “Zusätzliche Inhaber zu … hinzufügen” und füge als Inhaber die E-Mail-Adresse des Dienstkontos hinzu.

Terraform Provider Konfiguration

Nun können wir mit der Konfiguration des Providers (also Google Cloud) starten. Terraform liest dabei alle .tf-Dateien im Projektordner aus und berücksichtigt deren Konfiguration. Üblich hat man eine main.tf-Datei mit allen Einstellungen. Genauso könnte man aber das Projekt wie folgt strukturieren:

  • main.tf für die Haupt-Konfigurationen
  • appengine.tf für die Konfiguration der App Engine
  • variables.tf für alle Variablen
  • Etc.

Zur einfachen Demonstration werde ich jedoch nur main.tf anlegen und dort all meinen Programmcode schreiben. Daher – starte mit der Erstellung der Datei main.tf im Projektordner:

Nun können wir den Provider-Block angeben:

provider "google" {
 
}

Der Provider-Block ist zuständig für die Erstellung der Ressourcen. Benötigt werden dabei das Projekt, wo die Ressourcen bereitgestellt werden sollten und die vorher erstellten Credentials. Dies geschieht nun wie folgt:

provider "google" {
  project = "gtm-357810" // Diesen Wert mit deiner Projekt ID anpassen
  credentials = "${file("credentials.json")}"
  region = "europe-west3"
}

Die Projekt-ID bekommst aus der Google Cloud Konsole. Alternativ könnte man ein neues Projekt auch direkt mit Terraform erstellen, Grundvoraussetzung ist dann jedoch, dass man Terraform mit einer bestehenden Google Cloud-Organisation verknüpft. Die Credentials haben wir anfangs in unseren Projektordner abgelegt – und verweisen darauf über die Interpolation Syntax. Zusätzlich gebe ich die gewünschte standardmäßige Region meiner Ressourcen an (europe-west3 = Frankfurt).

Mit diesen Einstellungen kann nun Terraform initialisiert werden.

Terraform initialisieren

Im nächsten Schritt navigiere ich über den Terminal zu meinem Projektordner, wo die 2 Dateien liegen:

Führe dann folgendes Kommando aus, genau dort wo die main.tf liegt:

terraform init

Mit der Meldung “Terraform has been successfully initialized!” war alles erfolgreich:

Dieses Kommando initialisiert verschiedene lokale Einstellungen und Daten, die später benötigt werden. Terraform lädt damit dann auch den Google-Provider herunter und speichert alle Konfigurationen in einem versteckten Ordner innerhalb des Projektordners. Der neue Ordner und einige notwendige Dateien sollten anschließend im Ordner zu sehen sein:

Terraform App Engine Konfiguration

Ähnlich wie der Provider-Block gibt es auch den Ressourcen-Block, um einzelne Ressourcen bereitzustellen. Die Syntax ist wie folgt:

resource "resource_type" "resource_name" {
 
}

Neben dem resource-Keyword sind weitere 2 Strings notwendig: Einmal den Ressourcen-Typ und dann einen Namen, den wir dafür vergeben können. In unserem Fall brauchen wir nun folgenden Ressourcen-Typen:

Weitere Konfigurationsmöglichkeiten und App Engine-relevante Ressourcen findest du in der entsprechenden Terraform-Dokumentation.

Direkt unter dem Provider-Block wird zunächst die App Engine Applikation mit folgendem Kommando erstellt:

resource "google_app_engine_application" "app" {
  project = "gtm-357810"
  location_id = "europe-west3"
}

Projekt und Location ID sind Pflichtfelder, die hier angegeben werden müssen. Diese können alternativ mittels Variablen übergeben werden, zu einfachen Demonstrationszwecken übergebe ich die Werte direkt als Strings. Im nächsten Schritt – unter dem vorherigen Block – kommt nun die Angabe zum Domain-Mapping. Hier übergeben wir unsere vorhin gewählte Domain und setzen “MANAGED” als SSL-Management-Type (damit vergibt uns Google direkt ein SSL-Zertifikat):

resource "google_app_engine_domain_mapping" "domain_mapping" {
  domain_name = "sst.demir-jasarevic.com"
 
  ssl_settings {
    ssl_management_type = "AUTOMATIC"
  }
}

Dann werden 2 App Engine Flexible Ressourcen-Blocks definiert. Ein Ressourcen-Block für den Tagging-Dienst, ein Ressourcen-Block für den Debug-Dienst. Wichtig dabei ist die Übergabe des Docker-Images, der GTM-Server-Config und weitere App Engine Konfigurationen wie Autoscaling, min. und max. Instanzen, etc. Die entsprechenden Felder im nächsten Code solltest du deinen Anforderungen nach anpassen:

  • Setze “CONTAINER_CONFIG” gleich deiner GTM-Server-Containerkonfiguration, die du in den Containereinstellungen findest.
  • Beim Debug-Server ist die Variable “RUN_AS_DEBUG_SERVER” wichtig.
  • Unter dem Attribut “resources” kannst du CPU und Co. anpassen.
  • Unter dem Attribut “automatic_scaling” findest du die Mindest- und Maximal-Instanzen, die du angeben kannst. Beim Tagging-Server empfiehlt Google für den Start min. 3 und max. 6 Instanzen. Für den Debug-Server ist 1 ausreichend.

resource "google_app_engine_flexible_app_version" "gtm_server_tagging" {
  version_id = "gtm-v-1"
  project = "gtm-357810"
  service = "default"
  runtime = "nodejs"
 
  deployment {
    container {
      image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"
    }
  }
 
  env_variables = {
    CONTAINER_CONFIG = "xxx" // Hier deine Container-Config angeben
  }
 
    liveness_check {
    path = "/healthz"
  }
 
  readiness_check {
    path = "/healthz"
  }
 
  resources {
    cpu = 1
    memory_gb = 0.5
    disk_gb = 10
 
  }
 
  automatic_scaling {
    cpu_utilization {
      target_utilization = 0.5
    }
    min_total_instances = 3
    max_total_instances = 6
  }
 
  noop_on_destroy = true
}
 
resource "google_app_engine_flexible_app_version" "gtm_server_debug" {
  version_id = "gtm-v-1"
  project = "gtm-357810"
  service = "debug"
  runtime = "nodejs"
 
  deployment {
    container {
      image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"
    }
  }
 
  env_variables = {
    CONTAINER_CONFIG = "xxx" // Hier deine Container-Config angeben
    RUN_AS_DEBUG_SERVER: true
  }
 
  liveness_check {
    path = "/healthz"
  }
 
  readiness_check {
    path = "/healthz"
  }
 
  resources {
    cpu = 1
    memory_gb = 0.5
    disk_gb = 10
  }
 
  automatic_scaling {
    cpu_utilization {
      target_utilization = 0.5
    }
    min_total_instances = 1
    max_total_instances = 1
  }
 
  noop_on_destroy = true
}

Zum Schluss kommt noch der Block, mit den Regeln zum Zuordnen einer HTTP-Anforderung und zum Weiterleiten dieser Anforderung an einen Dienst:

resource "google_app_engine_application_url_dispatch_rules" "gtm_server_dispatch" {
  dispatch_rules {
    path = "/gtm/*"
    service = "debug"
  }
 
  dispatch_rules {
    path = "/*"
    service = "default"
  }
}

Diese Codes kommen alle direkt in die main.tf-Datei (alternativ könnten sie auch aufteilt und in eigene tf-Dateien eingefügt werden). Danach kommen wir zum Deployment.

Terraform Deployment

Mit folgendem Kommando können wir zunächst prüfen, welche Ressourcen Terraform auf Basis unserer main.tf bereitstellen wird:

terraform plan

Die Übersicht am Ende zeigt dir die Anzahl der geplanten Ressourcen:

Das Deployment können wir nun mit folgendem Kommando anstoßen:

terraform apply

Dabei werden wir nochmal gefragt, ob wir das tatsächlich möchten:

Antworte mit “yes”, um nun die Konfig zu deployen. Im Anschluss kann es einige Minuten dauern, bis alles erfolgreich von Terraform bereitgestellt wurde. Den Prozess kannst du ganz unten verfolgen:

Sobald alles verfügbar ist, bekommst du eine entsprechende Meldung in der Bash.

Testing und Rest-Todos

Direkt im Anschluss kannst du dich in deine Cloud Konsole einloggen, um zu prüfen, ob alles da ist. Zunächst solltest du eine App Engine in der von dir gewählten Region haben:

Beide App Engine Dienste inkl. den Weiterleitungsregeln sollten bereitstehen:

Ebenso die Instanzen auf der gewählten Umgebung (Flexible):

Unter den App Engine-Einstellungen und im Tab “Benutzerdefinierte Domains” solltest du nun die von dir übergebene Domain sehen inkl. den DNS-Daten.

Damit hier das SSL-Zertifikat ausgestellt wird, solltest du diese nun anlegen inkl. CNAME der www-Variante auf ghs.googlehosted.com. In meinem Fall wäre das:

www.sst.demir-jasarevic.com CNAME ghs.googlehosted.com.

Sind die DNS-Records gesetzt und greifen sie, sollte auch noch einmal in den Vorschaumodus gewechselt werden für einen letzten Check:

Sieht gut aus. Auch die Hits werden verarbeitet:

Abschließende Worte

Ich hoffe ich konnte dir mit dieser Anleitung eine gute Einführung in Terraform geben und wie man damit Cloud-Ressourcen (in diesem Fall App Engine) bereitstellen kann. Das Setup könnte man nun noch optimieren mit dem Einsatz von Variablen oder indem man das GCP-Projekt ebenfalls über Terraform “erstellen lässt”. Dies würde den Rahmen des Artikel sprengen, weshalb ich die Setup-Anleitung zunächst einfach gehalten habe. Nimm gerne Kontakt mit mir auf bei Fragen zu Details. Bez. eines GTM-Server-Deployment auf Cloud Run läuft es zunächst ähnlich ab. Die benötigte Ressource ist dabei google_cloud_run_service. Man muss jedoch berücksichtigen, dass eine benutzerdefinierte Domainzuordnung für die Cloud Run-Dienste nur eingeschränkt verfügbar ist. Über die Funktion “Cloud Run-Domainzuordnung”, worüber man eine eigene Domain in wenigen Schritten hinzufügen kann (ähnlich wie bei der App Engine), lassen sich Domainzuordnungen nur für Cloud Run Dienste durchführen, die in bestimmten Regionen liegen. Darunter fallen:

  • asia-east1
  • asia-northeast1
  • asia-southeast1
  • europe-north1
  • europe-west1
  • europe-west4
  • us-central1
  • us-east1
  • us-east4
  • us-west1

Von Google ist das auch sauber dokumentiert auf der entsprechenden GCP-Hilfeseite “Benutzerdefinierte Domain mit Cloud Run-Domainzuordnung zuordnen (eingeschränkte Verfügbarkeit)”. Es werden zwar einige EU-Standorte unterstützt, jedoch sind das Hamina in Finnland (europe-north1), Madrid in Spanien (europe-west1) und London in England (europe-west4). Falls ich nun Cloud Run-Dienste aus anderen Regionen (z.B. europe-west3 für Frankfurt, Deutschland) über die Cloud Run-Domainzuordnungsfunktion mappen möchte bekomme ich eine entsprechende Fehlermeldung von der Funktion:

Wir werden an dieser Stelle also an andere Optionen verwiesen:

  • Globalen externen HTTP(S)-Load-Balancer verwenden
  • Firebase Hosting nutzen

Um eine Custom-Domain-Mapping für Cloud Run hinzubekommen, müssen also Load Balancer und Co. konfiguriert werden. Mittels Terraform sind dabei folgende Ressourcen notwendig:

Ich hoffe diese Tipps helfen dir weiter.