Snowplow

Jan. 8, 2023·
Julio Batista Silva
Julio Batista Silva
· 9 Min Lesezeit

Snowplow ist ein Unternehmen mit Sitz in London, das eine Cloud-basierte „Behavioral Data Platform“ anbietet und außerdem eine Open-Source-Version. Laut der offiziellen Website ist es das drittmeist genutzte Web-Tracking-Tool, hinter Google Analytics und Facebook.

Motivation für diesen Beitrag

Die Installation und Konfiguration von Snowplow Open Source in der Google Cloud ist recht komplex – und zusätzlich ist die Dokumentation stark veraltet.

Die Dokumentation ist ebenfalls Open Source und ich habe Pull Requests eingereicht, um einige der Fehler zu korrigieren, die ich gefunden habe.
Jedenfalls habe ich hier alles dokumentiert – zur späteren Referenz.
Ich hoffe, es hilft auch anderen.

Ich habe den gesamten Prozess zwischen Dezember 2022 und Januar 2023 auf einem MacBook Pro M1 durchgeführt. Für meinen konkreten Anwendungsfall habe ich sowohl BigQuery als auch PostgreSQL aktiviert – das kostet etwa 240 US-Dollar pro Monat.

Normalerweise nutzt man nur ein Data Warehouse. In der GCP empfehle ich BigQuery, denn:

  • Es kommt gut mit großen Datenmengen zurecht.
  • Einige dbt-Pakete, wie snowplow_ecommerce, sind mit PostgreSQL noch nicht kompatibel.
  • Es kann – je nach Nutzung – günstiger sein (selbst mit zusätzlichem Pub/Sub):
    • BigQuery wird nach Nutzung abgerechnet, nicht nach Laufzeit.
    • Das erste Terabyte an Speicher ist kostenlos.

Überblick

Erläuterung der Abbildung, Schritt für Schritt:

  • Trackers: Quellen, von denen wir Daten sammeln möchten.
    Man kann alles tracken, was REST-Aufrufe machen kann: Websites, Mobile-Apps und Datenbanken.
    Die SDKs helfen dabei stark.
    Die Daten werden anhand eines expliziten Schemas gesendet, das du selbst definierst.

  • Iglu Server: Compute Engine (mit Load Balancer) und Cloud SQL.
    Es ist ein Repository für Schemas.
    Sie werden in JSON Schema-Dateien definiert und können versioniert werden, z. B.: YouTube JSON Schema und GA Ecommerce Schema.

  • Stream collector app: Compute Engine (mit Load Balancer).
    Enthält den HTTP-Endpunkt, der rohe Events entgegennimmt, serialisiert und an Pub/Sub-Themen weiterleitet (sp-raw-topic oder sp-bad-1-topic).

    Wenn ein Event beispielsweise zu groß ist, wird es in das Pub/Sub-Thema für „bad data“ geschickt.

  • Enrich PubSub: JVM-Anwendung auf Compute Engine (eine oder mehrere VMs).
    In diesem Schritt werden Events aus dem „Raw Stream“ (sp-raw-topic, geschrieben vom Collector) gelesen und validiert.

    • Wenn das Event keinem Schema im Iglu Server entspricht, wird es in den „Bad Stream“ gesendet.
      Das verhindert, dass falsche oder minderwertige Daten in die Produktion gelangen, und ermöglicht die Analyse des empfangenen Schemas, was bei der Fehlersuche hilft.
      In Google Analytics würden solche Daten einfach ignoriert.
    • Wenn das Event einem Schema entspricht, wird es gemäß deiner Konfiguration „angereichert“.
      Zum Beispiel:

    Es gibt viele verfügbare Enrichments.

    Nach der „Anreicherung“ wird das Event an das Thema „Good Enriched“ (sp-enriched-topic) gesendet.

  • Loader: lädt in Tabellen.
    Wenn der Loader Zeilen nicht in BigQuery einfügen kann, werden sie an einen „failed inserts“-Stream gesendet und das Einfügen wird erneut versucht.

  • Mutator: Wird verwendet, wenn ein Schema geändert oder erweitert werden muss. Alle Events gehen in eine einzige große Tabelle. Eine Schemaänderung kann große Auswirkungen haben.

Terraform

Anstatt jeden Google-Cloud-Ressourcen manuell über die Weboberfläche oder die Kommandozeile zu erstellen und zu konfigurieren, definierst du mit Terraform die Infrastruktur deklarativ als Code in einer eigenen Sprache, die auf HCL (HashiCorp Configuration Language) basiert.

Wie jeder Code können auch Terraform-Dateien versioniert werden, was Änderungen und Rollbacks vereinfacht.

Es gibt einen Quick Start, der das Deployment mit Terraform durchführt. Leider decken die aktuellen Module nicht alle Setups ab und einige sind ziemlich veraltet. Ich verwende sie trotzdem als Basis, denn Terraform reduziert das Fehlerrisiko und erleichtert die Wartung der Infrastruktur.

Voraussetzungen

  1. Projekt erstellen

    Erstelle ein Projekt in der Google Cloud und aktiviere Billing.
    Ich verwende in diesem Beitrag den Namen projeto-snowplow.

  2. Terraform installieren

    brew install terraform
    
  3. Google Cloud CLI installieren

    brew install google-cloud-sdk
    
    $ gcloud version
    Google Cloud SDK 412.0.0
    bq 2.0.83
    core 2022.12.09
    gcloud-crc32c 1.0.0
    gsutil 5.17
    
    gcloud auth login
    

    Füge in ~/.zshrc hinzu:

    source "$(brew --prefix)/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.zsh.inc
    

    Dieser Befehl fügt das Verzeichnis /opt/homebrew/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin deinem PATH hinzu.

  4. Service Account

    1. Lege einen Service Account namens spsetup an
      Menu » IAM and admin » Service accounts » Create Service Account

    2. Erzeuge einen Schlüssel
      KEYS » Add Key » Key type: JSON
      Speichere die Datei als ~/Snowplow/key.json.

    3. Setze die Umgebungsvariable auf den Dateipfad

      export GOOGLE_APPLICATION_CREDENTIALS="/Users/julio/Snowplow/key.json"
      
  5. APIs aktivieren

    Rufe die folgenden Links auf und aktiviere die APIs:

  6. VPC und Subnetze

    Die „sichere“ Snowplow-Konfiguration erfordert eine VPC. Du kannst sie über die Weboberfläche, die Kommandozeile oder per Terraform erstellen.

    • Über die Kommandozeile:

      gcloud compute networks create \
          vpc-snowplow \
          --project=projeto-snowplow \
          --description=Snowplow\ VPC \
          --subnet-mode=custom \
          --mtu=1460 \
          --bgp-routing-mode=regional
      
      gcloud compute networks subnets create \
          vpc-snowplow-europe-west3 \
          --project=projeto-snowplow \
          --range=10.0.0.0/24 \
          --stack-type=IPV4_ONLY \
          --network=vpc-snowplow \
          --region=europe-west3 \
          --enable-private-ip-google-access
      
    • Mit Terraform:

      # VPC
      # https://www.terraform.io/docs/providers/google/r/compute_network.html
      # Keep the default route (0.0.0.0/0) to allow internet access
      resource "google_compute_network" "snowplow_vpc" {
        name                            = "snowplow-vpc"
        routing_mode                    = "REGIONAL"
        auto_create_subnetworks         = false
        delete_default_routes_on_create = false
      }
      
      # Private Subnet
      # https://www.terraform.io/docs/providers/google/r/compute_subnetwork.html
      resource "google_compute_subnetwork" "snowplow_private_subnet" {
        name                     = "snowplow-private-subnet-europe-west3"
        ip_cidr_range            = "10.0.0.0/24"
        region                   = var.region
        network                  = google_compute_network.snowplow_vpc.id
        private_ip_google_access = true
      }
      
  7. Cloud Router

    • Über die Kommandozeile:

      gcloud compute routers create router-snowplow \
      --project=projeto-snowplow \
      --description=To\ allow\ access\ to\ the\ internet \
      --region=europe-west3 \
      --network=vpc-snowplow \
      --set-advertisement-mode=custom
      
    • Mit Terraform:

      # Cloud Router
      # https://www.terraform.io/docs/providers/google/r/compute_router.html
      resource "google_compute_router" "snowplow_router" {
        name    = "snowplow-router"
        region  = var.region
        network = google_compute_network.snowplow_vpc.id
        bgp {
          asn            = 64514
          advertise_mode = "CUSTOM"
        }
      }
      
  8. Cloud NAT

    Erstelle ein NAT-Gateway. Darüber erhalten die VMs Zugang zum Internet.

    • Mit Terraform:

      # NAT Gateway
      # https://www.terraform.io/docs/providers/google/r/compute_router_nat.html
      resource "google_compute_router_nat" "snowplow_nat" {
        name                               = "snowplow-nat"
        router                             = google_compute_router.snowplow_router.name
        region                             = google_compute_router.snowplow_router.region
        nat_ip_allocate_option             = "AUTO_ONLY"
        source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"
      
        subnetwork {
          name                    = google_compute_subnetwork.snowplow_private_subnet.id
          source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
        }
      
        log_config {
          enable = true
          filter = "ERRORS_ONLY"
        }
      }
      
  9. Java

    brew install java
    brew install openjdk
    
    sudo ln -sfn \
      $(brew --prefix)/opt/openjdk@11/libexec/openjdk.jdk \
      /Library/Java/JavaVirtualMachines/openjdk-11.jdk
    
  10. Igluctl

    Kopiere den Link zur neuesten Version von https://github.com/snowplow/igluctl/releases/latest.

    wget https://github.com/snowplow/igluctl/releases/download/0.10.2/igluctl_0.10.2.zip
    unzip igluctl_0.10.2.zip
    rm igluctl_0.10.2.zip
    
    chmod +x igluctl
    mv igluctl ~/bin
    
    igluctl --version
    0.10.2
    

    Verschiebe die ausführbare Datei in ein Verzeichnis, das im PATH liegt – bei mir ~/bin.

Quick Start

  1. Repository

    Klone das Repository:

    git clone https://github.com/snowplow/quickstart-examples
    
  2. Projektordner

    Kopiere nur die Teile, die du verwenden wirst, in einen neuen Ordner:

    cp -r quickstart-examples/terraform/gcp/iglu_server/secure ~/Snowplow/iglu_server
    cp -r quickstart-examples/terraform/gcp/pipeline/secure ~/Snowplow/pipeline
    

    Ich habe nur die Ordner secure kopiert, da diese für Produktionsumgebungen empfohlen werden. Der Unterschied ist, dass Snowplow in der VPC konfiguriert wird, die wir zuvor erstellt haben.

Iglu Server

  1. Datei terraform.tfvars

    Bearbeite ~/Snowplow/iglu_server/terraform.tfvars:

    prefix = "sp"
    project_id = "projeto-snowplow"
    region = "europe-west3"
    network    = "snowplow-vpc"
    subnetwork = "snowplow-private-subnet-europe-west3"
    ssh_ip_allowlist = ["88.217.100.200/32", "35.235.240.0/20"]
    ssh_key_pairs = [
      {
        user_name  = "snowplow"
        public_key = "ssh-ed25519 AAAAC3NzC1lZDI…E5Gv8/gQzPr3H/9B+Nxc/GInziPJX"
      }
    ]
    
    # --- Snowplow Iglu Server
    iglu_db_name     = "iglu"
    iglu_db_username = "iglu"
    iglu_db_password = "SENHA_DO_IGLU_BD"
    iglu_super_api_key = "01234567-890a-bcde-f012-34567890abcd"
    
    user_provided_id  = ""
    telemetry_enabled = false
    
    # --- SSL Configuration (optional)
    ssl_information = {
      certificate_id = ""
      enabled        = false
    }
    
    # --- Extra Labels to append to created resources (optional)
    labels = {}
    

    SSH-Schlüssel erzeugen:

    ssh-keygen -t rsa -b 4096 -f snowplow -C "Snowplow"
    

    Zufällige UUID generieren: https://duckduckgo.com/?q=uuid

  2. Terraform-Befehle

    cd ~/Snowplow/iglu_server
    terraform init
    terraform plan -out plan
    terraform apply "plan"
    

    Notiere die Ausgabe – das ist die IP des Iglu Servers.
    Die Ausgabe wird auch in terraform.tfstate gespeichert:

    $ grep -A1 iglu_server_ip_address terraform.tfstate
        "iglu_server_ip_address": {
          "value": "34.100.100.200",
    
  3. Schemas zum Iglu Server hinzufügen

    git clone https://github.com/snowplow/iglu-central
    cp -r iglu-central/schemas ~/Snowplow/
    rm -rf iglu-central
    
    cd ~/Snowplow
    rm -rf schemas/SCHEMAS_QUE_NAO_SERAO_USADOS
    igluctl static push --public schemas/ http://IP_DO_IGLU_SERVER 01234567-890a-bcde-f012-34567890abcd
    

    Lasse nur die Schemas in schemas, die du verwenden wirst.
    Die UUID am Ende des Befehls ist der iglu_super_api_key, den wir zuvor generiert haben.

  4. Status des Iglu Servers prüfen

    Der Iglu Server bietet einige Endpoints, um seinen eigenen Status zu inspizieren.

    $ curl http://34.100.100.200/api/meta/health/db
    OK
    
    $ curl -s http://34.100.100.200/api/meta/server \
        -H "apikey: 01234567-890a-bcde-f012-34567890abcd" | jq
    {
      "version": "0.8.7",
      "authInfo": {
        "vendor": "",
        "schema": "CREATE_VENDOR",
        "key": [
          "CREATE",
          "DELETE"
        ]
      },
      "database": "postgres",
      "schemaCount": 0,
      "debug": false,
      "patchesAllowed": true
    }
    

Pipeline

  1. Datei bigquery.terraform.tfvars

    Bearbeite ~/Snowplow/pipeline/bigquery.terraform.tfvars:

    prefix = "sp"
    project_id = "projeto-snowplow"
    region = "europe-west3"
    
    network    = "snowplow-vpc"
    subnetwork = "snowplow-private-subnet-europe-west3"
    
    ssh_ip_allowlist = ["88.217.100.200/32", "35.235.240.0/20"]
    
    ssh_key_pairs = [
      {
        user_name  = "snowplow"
        public_key = "ssh-ed25519 AAAAC3NzC1lZDI…E5Gv8/gQzPr3H/9B+Nxc/GInziPJX"
      }
    ]
    
    iglu_server_dns_name = "http://34.100.100.200"
    iglu_super_api_key = "01234567-890a-bcde-f012-34567890abcd"
    
    bigquery_db_enabled  = true
    bigquery_loader_dead_letter_bucket_deploy = true
    bigquery_loader_dead_letter_bucket_name = "sp-bq-loader-dead-letter_bucket"
    
    user_provided_id  = ""
    telemetry_enabled = false
    
    ssl_information = {
      certificate_id = ""
      enabled        = false
    }
    
    labels = {}
    
    ###########################################################################
    
    postgres_db_enabled  = true
    
    postgres_db_name     = "snowplow"
    postgres_db_username = "snowplow"
    postgres_db_password = "TYPE_PG_PASSWORD_HERE"
    
    postgres_db_authorized_networks = [
      {
        name  = "julio"
        value = "88.217.100.200/32"
      }
    ]
    
    postgres_db_tier = "db-g1-small"
    
  2. Terraform ausführen

    cd ~/Snowplow/pipeline
    terraform init
    terraform apply
    
  3. Test-Events

    Ermittle die Collector-Adresse:

    $ grep -A1 collector_ip_address terraform.tfstate
        "collector_ip_address": {
          "value": "34.111.120.123",
    

    Sende die folgenden Test-Events:

    curl 'http://34.111.120.123/com.snowplowanalytics.snowplow/tp2' \
       -H 'Content-Type: application/json; charset=UTF-8' \
       -H 'Cookie: _sp=305902ac-8d59-479c-ad4c-82d4a2e6bb9c' \
       --data-raw \
      '{
        "schema": "iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4",
        "data": [
          {
            "e": "pv",
            "url": "/docs/open-source-quick-start/quick-start-installation-guide-on-aws/send-test-events-to-your-pipeline/",
            "page": "Send test events to your pipeline - Snowplow Docs",
            "refr": "https://docs.snowplow.io/",
            "tv": "js-2.17.2",
            "tna": "spExample",
            "aid": "docs-example",
            "p": "web",
            "tz": "Europe/London",
            "lang": "en-GB",
            "cs": "UTF-8",
            "res": "3440x1440",
            "cd": "24",
            "cookie": "1",
            "eid": "4e35e8c6-03c4-4c17-8202-80de5bd9d953",
            "dtm": "1626182778191",
            "cx": "eyJzY2hlbWEiOiJpZ2x1Om…YS03ZjRlNzk2OTM3ZmEifX1dfQ",
            "vp": "863x1299",
            "ds": "848x5315",
            "vid": "3",
            "sid": "87c18fc8-2055-4ec4-8ad6-fff64081c2f3",
            "duid": "5f06dbb0-a893-472b-b61a-7844032ab3d6",
            "stm": "1626182778194"
          },
          {
            "e": "ue",
            "ue_px": "eyJzY2hlbWEiOiJpZ2x1Om…IlB1cnBsZSBTbm93cGxvdyBIb29kaWUifX19",
            "tv": "js-2.17.2",
            "tna": "spExample",
            "aid": "docs-example",
            "p": "web",
            "tz": "Europe/London",
            "lang": "en-GB",
            "cs": "UTF-8",
            "res": "3440x1440",
            "cd": "24",
            "cookie": "1",
            "eid": "542a79d3-a3b8-421c-99d6-543ff140a56a",
            "dtm": "1626182778193",
            "cx": "eyJzY2hlbWEiOiJpZ2x1Om…lNzk2OTM3ZmEifX1dfQ",
            "vp": "863x1299",
            "ds": "848x5315",
            "vid": "3",
            "sid": "87c18fc8-2055-4ec4-8ad6-fff64081c2f3",
            "duid": "5f06dbb0-a893-472b-b61a-7844032ab3d6",
            "refr": "https://docs.snowplow.io/",
            "url": "/docs/open-source-quick-start/quick-start-installation-guide-on-aws/send-test-events-to-your-pipeline/",
            "stm": "1626182778194"
          }
        ]
      }'
    

    Der obige Befehl sendet ein gültiges Event und eines, das keinem Schema entspricht.

  4. Überprüfung in BigQuery

    Gehe zu https://console.cloud.google.com/bigquery?project=projeto-snowplow und suche die Tabelle events im Dataset sp_snowplow_db. Prüfe unter PREVIEW, ob Daten vorhanden sind.

    Das ist die Tabelle mit den „good data“.

    Die Pipeline für „bad data“ ist im Quick Start nicht enthalten und muss manuell erstellt werden.

  5. Überprüfung in PostgreSQL

    Ermittle die IP der Datenbank:

    $ grep -A1 postgres_db_ip_address terraform.tfstate
      "postgres_db_ip_address": {
        "value": "34.89.100.100",
    

    Verbinde dich mit einem Tool wie DBeaver:

    • Host: 34.89.100.100
    • Database: snowplow
    • Port: 5432
    • Username: snowplow
    • Password: TYPE_PG_PASSWORD_HERE

    Die „good data“-Tabelle ist atomic.events.
    Sie dürfte leer sein, weil die Queue bereits von BigQuery konsumiert wurde.
    Um PostgreSQL ebenfalls mit Events zu versorgen, muss der Stream dupliziert werden.

    Es gibt mehrere „bad data“-Tabellen, z. B. die atomic_bad.com_snowplowanalytics_snowplow_badrows_schema_violations_1.

Nächste Schritte

  • Domain konfigurieren und SSL aktivieren
  • Eigene Schemas erstellen
  • Bad Data in BigQuery ablegen

Julio Batista Silva
Autoren
Senior Cloud-Entwickler
comments powered by Disqus