Dreistufige App

+

Architektur

Die dreistufige App ist eine einfache To-do-Anwendung, die als einfache dreistufige Anwendung konzipiert wurde:

  • Backend
    • Datenbank – MySQL – Cloud SQL
    • Caching – Redis – Cloud Memorystore
  • Middleware/API
    • Containergehostete API – Golang – Cloud Run
  • Frontend/UI
    • Containergehostete Benutzeroberfläche – Nginx + HTML/JS/CSS – Cloud Run
  • Bereitstellung
    • Kontinuierliche Bereitstellung – Cloud Build
    • Secret Management – Cloud Secret Manager

Jetzt starten

Klicken Sie auf den folgenden Link, um eine Kopie des Quellcodes in Cloud Shell aufzurufen. Dort können Sie mit einem einzigen Befehl eine funktionierende Kopie der Anwendung in Ihrem Projekt erstellen.

In Cloud Shell öffnen

Quellcode auf GitHub ansehen


Dreistufige App-Komponenten

Die dreistufige App-Architektur nutzt mehrere Produkte. Im Folgenden finden Sie eine Liste der Komponenten sowie weitere Informationen zu den Komponenten, einschließlich Links zu ähnlichen Videos, Produktdokumentationen und interaktiven Schritt-für-Schritt-Anleitungen.
Video Docs Schritt-für-Schritt-Anleitungen
Cloud SQL Cloud SQL ist ein verwalteter SQL-Dienst, der MySQL, SQL Server oder Postgres für die Datenbankebene Ihrer Anwendungen bereitstellt.
Cloud Memorystore Cloud Memorystore, verwalteter Redis, stellt die Caching-Ebene für Ihre Anwendungen bereit.
Cloud Run Mit Cloud Run können Sie Anwendungen in einem Container ausführen, aber auf serverlose Weise, ohne die Anzahl der Instanzen, Prozessoren oder des Arbeitsspeichers konfigurieren zu müssen. Container hochladen, URL abrufen
Cloud Build Cloud Build ist das Tool, mit dem die Container verpackt und bereitgestellt werden, damit sie als Cloud Run-Dienste verfügbar sind.
Secret Manager Cloud Secret Manager speichert vertrauliche Details zur Anwendung für den Build-Prozess.

Skripts

Das Installationsskript verwendet eine in go geschriebene ausführbare Datei und Terraform-Befehlszeilentools, um ein leeres Projekt zu erstellen und die Anwendung darin zu installieren. Die Ausgabe sollte eine funktionierende Anwendung und eine URL für die Load Balancing-IP-Adresse sein.

./main.tf

Dienste aktivieren

Google Cloud-Dienste sind in einem Projekt standardmäßig deaktiviert. Für Google ToDo müssen Sie die folgenden Dienste aktivieren:

  • Service Networking und serverloser VPC-Zugriff: Damit kann Cloud Run über ein privates Netzwerk mit SQL und Redis kommunizieren, während diese Server für externe Aufrufe von der API aus nicht zugänglich sind.
  • Cloud Build: Erstellt Container-Images und stellt sie in Cloud Run bereit
  • Cloud Memorystore: Bietet eine Caching-Ebene für die Anwendung.
  • Cloud Run: Das serverlose Tool, das die Container hostet und URLs bereitstellt, über die auf die Anwendung zugegriffen werden kann.
  • Cloud SQL: Datenbankspeicher für die Anwendung
  • Cloud Storage: Wird von Cloud Build verwendet und zum Laden des Schemas in die Datenbank
  • Cloud Secret Manager: Wird verwendet, um die Host-IP-Adressen für SQL und Redis in Cloud Build for Cloud Run einzuschleusen.
  • Artifact Registry: Hier werden die Docker-Images für die Verwendung mit Cloud Build gespeichert.
variable "gcp_service_list" {
    description = "The list of apis necessary for the project"
    type        = list(string)
    default = [
        "compute.googleapis.com",
        "cloudapis.googleapis.com",
        "vpcaccess.googleapis.com",
        "servicenetworking.googleapis.com",
        "cloudbuild.googleapis.com",
        "sql-component.googleapis.com",
        "sqladmin.googleapis.com",
        "storage.googleapis.com",
        "secretmanager.googleapis.com",
        "run.googleapis.com",
        "artifactregistry.googleapis.com",
        "redis.googleapis.com"
    ]
}

resource "google_project_service" "all" {
    for_each                   = toset(var.gcp_service_list)
    project                    = var.project_number
    service                    = each.key
    disable_on_destroy = false
}

Berechtigungen festlegen

Mit dem folgenden Befehl werden IAM-Rollen und ‑Berechtigungen festgelegt, die es Cloud Build ermöglichen, Dienste bereitzustellen.

  • Cloud Build-Dienstkonto für die Bereitstellung in Cloud Run aktivieren
  • Cloud Build-Dienstkonto aktivieren, um VPN-Zugriff für Cloud Run festzulegen
  • Cloud Build-Dienstkonto für Dienstkontoaktivitäten aktivieren
  • Cloud Build-Dienstkonto zulassen, im Namen des Compute-Dienstkontos zu handeln
  • Cloud Build-Dienstkonto für die Veröffentlichung in Cloud Run aktivieren
  • Cloud Build-Dienstkonto zum Verwenden von Secrets aktivieren
  • Cloud Build-Dienstkonto für das Speichern von Containern in Artifact Registry aktivieren
variable "build_roles_list" {
    description = "The list of roles that build needs for"
    type        = list(string)
    default = [
        "roles/run.developer",
        "roles/vpaccess.user",
        "roles/iam.serviceAccountUser",
        "roles/run.admin",
        "roles/secretmanager.secretAccessor",
        "roles/artifactregistry.admin",
    ]
}
resource "google_project_iam_member" "allbuild" {
    for_each   = toset(var.build_roles_list)
    project    = var.project_number
    role       = each.key
    member     = "serviceAccount:${local.sabuild}"
    depends_on = [google_project_service.all]
}

Netzwerk für SQL-Instanz erstellen

Mit dem folgenden Befehl können Sie Cloud SQL von Cloud Run aus erreichen:

resource "google_compute_global_address" "google_managed_services_vpn_connector" {
    name          = "google-managed-services-vpn-connector"
    purpose       = "VPC_PEERING"
    address_type  = "INTERNAL"
    prefix_length = 16
    network       = local.defaultnetwork
    project       = var.project_id
    depends_on    = [google_project_service.all]
}
resource "google_service_networking_connection" "vpcpeerings" {
    network                 = local.defaultnetwork
    service                 = "servicenetworking.googleapis.com"
    reserved_peering_ranges = [google_compute_global_address.google_managed_services_vpn_connector.name]
}

VPC-Zugangsconnector erstellen

Cloud Run mit Datenbank und Caching verbinden

resource "google_vpc_access_connector" "connector" {
    provider      = google-beta
    project       = var.project_id
    name          = "vpc-connector"
    ip_cidr_range = "10.8.0.0/28"
    network       = "default"
    region        = var.region
    depends_on    = [google_compute_global_address.google_managed_services_vpn_connector, google_project_service.all]
}

Redis-Server erstellen

Konfiguriert und initialisiert eine Redis-Serverinstanz.

resource "google_redis_instance" "todo_cache" {
    authorized_network      = local.defaultnetwork
    connect_mode            = "DIRECT_PEERING"
    location_id             = var.zone
    memory_size_gb          = 1
    name                    = "${var.basename}-cache"
    project                 = var.project_id
    redis_version           = "REDIS_6_X"
    region                  = var.region
    reserved_ip_range       = "10.137.125.88/29"
    tier                    = "BASIC"
    transit_encryption_mode = "DISABLED"
    depends_on              = [google_project_service.all]
}

SQL-Server erstellen

Mit dem folgenden Befehl wird eine SQL Server-Instanz konfiguriert und initialisiert.

resource "google_sql_database_instance" "todo_database" {
    name="${var.basename}-db-${random_id.id.hex}"
    database_version = "MYSQL_5_7"
    region           = var.region
    project          = var.project_id
    settings {
        tier                  = "db-g1-small"
        disk_autoresize       = true
        disk_autoresize_limit = 0
        disk_size             = 10
        disk_type             = "PD_SSD"
        ip_configuration {
            ipv4_enabled    = false
            private_network = local.defaultnetwork
        }
        location_preference {
            zone = var.zone
        }
    }
    deletion_protection = false
    depends_on = [
        google_project_service.all,
        google_service_networking_connection.vpcpeerings
    ]
    # This handles loading the schema after the database installs.
    provisioner "local-exec" {
        working_dir = "${path.module}/code/database"
        command     = "./load_schema.sh ${var.project_id} ${google_sql_database_instance.todo_database.name}"
    }
}

Artifact Registry-Repository erstellen

Mit dem folgenden Befehl werden Docker-Images für die Verwendung mit Cloud Run gespeichert.

resource "google_artifact_registry_repository" "todo_app" {
    provider      = google-beta
    format        = "DOCKER"
    location      = var.region
    project       = var.project_id
    repository_id = "${var.basename}-app"
    depends_on    = [google_project_service.all]
}

Secrets erstellen

Mit dem folgenden Befehl werden Redis- und SQL-Hostdaten in Cloud Secrets gespeichert.

resource "google_secret_manager_secret" "redishost" {
    project = var.project_number
    replication {
        automatic = true
    }
    secret_id  = "redishost"
    depends_on = [google_project_service.all]
}
resource "google_secret_manager_secret_version" "redishost" {
    enabled     = true
    secret      = "projects/${var.project_number}/secrets/redishost"
    secret_data = google_redis_instance.todo_cache.host
    depends_on  = [google_project_service.all, google_redis_instance.todo_cache, google_secret_manager_secret.redishost]
}
resource "google_secret_manager_secret" "sqlhost" {
    project = var.project_number
    replication {
        automatic = true
    }
    secret_id  = "sqlhost"
    depends_on = [google_project_service.all]
}
resource "google_secret_manager_secret_version" "sqlhost" {
    enabled     = true
    secret      = "projects/${var.project_number}/secrets/sqlhost"
    secret_data = google_sql_database_instance.todo_database.private_ip_address
    depends_on  = [google_project_service.all, google_sql_database_instance.todo_database, google_secret_manager_secret.sqlhost]
}

Artefakt für die Middleware erstellen

Mit dem folgenden Befehl wird das Docker-Image erstellt und in Artifact Registry gehostet: ./code/frontend/cloudbuild.yaml

resource "null_resource" "cloudbuild_api" {
  provisioner "local-exec" {
    working_dir = "${path.module}/code/middleware"
    command     = "gcloud builds submit . --substitutions=_REGION=${var.region},_BASENAME=${var.basename}"
  }
  depends_on = [
    google_artifact_registry_repository.todo_app,
    google_secret_manager_secret_version.redishost,
    google_secret_manager_secret_version.sqlhost,
    google_project_service.all
  ]
}

API-Container in Cloud Run bereitstellen

Mit dem folgenden Befehl wird mit Cloud Build ein Dienst in Cloud Run mit dem gerade erstellten Container gestartet.

resource "google_cloud_run_service" "api" {
    name     = "${var.basename}-api"
    location = var.region
    project  = var.project_id

    template {
        spec {
            containers {
                image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.basename}-app/api"
                env {
                    name = "REDISHOST"
                    value_from {
                        secret_key_ref {
                            name = google_secret_manager_secret.redishost.secret_id
                            key  = "latest"
                        }
                    }
                }
                env {
                    name = "todo_host"
                    value_from {
                        secret_key_ref {
                        name = google_secret_manager_secret.sqlhost.secret_id
                        key  = "latest"
                        }
                    }
                }
                env {
                    name  = "todo_user"
                    value = "todo_user"
                }
                env {
                    name  = "todo_pass"
                    value = "todo_pass"
                }
                env {
                    name  = "todo_name"
                    value = "todo"
                }
                env {
                    name  = "REDISPORT"
                    value = "6379"
                }
            }
        }
        metadata {
            annotations = {
                "autoscaling.knative.dev/maxScale"        = "1000"
                "run.googleapis.com/cloudsql-instances"   = google_sql_database_instance.todo_database.connection_name
                "run.googleapis.com/client-name"          = "terraform"
                "run.googleapis.com/vpc-access-egress"    = "all"
                "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector.id
            }
        }
    }
    autogenerate_revision_name = true
    depends_on = [
        null_resource.cloudbuild_api,
        google_project_iam_member.secretmanager_secretAccessor
    ]
} 

Öffnen Sie den Cloud Run API-Dienst, damit er von allen gelesen werden kann.

Diese API-Ebene der Anwendung wird vom Browser des Nutzers aufgerufen. Cloud Run-Dienste sind jedoch standardmäßig nicht öffentlich. Damit Nutzer diesen Dienst nutzen können, müssen wir Berechtigungen für diese Dienste erteilen, damit sie weltweit zugänglich sind.

resource "google_cloud_run_service_iam_policy" "noauth_api" {
    location    = google_cloud_run_service.api.location
    project     = google_cloud_run_service.api.project
    service     = google_cloud_run_service.api.name
    policy_data = data.google_iam_policy.noauth.policy_data
}

Artefakt für das Frontend erstellen

Mit dem folgenden Befehl wird das Docker-Image erstellt und in Artifact Registry gehostet: ./code/frontend/cloudbuild.yaml

resource "null_resource" "cloudbuild_fe" {
    provisioner "local-exec" {
        working_dir = "${path.module}/code/frontend"
        command     = "gcloud builds submit . --substitutions=_REGION=${var.region},_BASENAME=${var.basename}"
    }
    depends_on = [
        google_artifact_registry_repository.todo_app,
        google_cloud_run_service.api
    ]
}

Frontend-Container in Cloud Run bereitstellen

Mit dem nächsten Befehl wird mit Cloud Build ein Dienst in Cloud Run mit dem gerade erstellten Container gestartet.

resource "google_cloud_run_service" "fe" {
    name     = "${var.basename}-fe"
    location = var.region
    project  = var.project_id
    template {
        spec {
            containers {
                image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.basename}-app/fe"
                ports {
                    container_port = 80
                }
            }
        }
    }
    depends_on = [null_resource.cloudbuild_fe]
}

Cloud Run-Frontenddienst für alle lesbar öffnen

Das ist das Frontend der Anwendung, das das HTML/JS/CSS rendert, über das der Nutzer mit der Anwendung interagiert. Cloud Run-Dienste sind standardmäßig nicht öffentlich. Damit diese Anwendung funktioniert, müssen wir Berechtigungen für diese Dienste gewähren, damit sie für alle zugänglich sind.

resource "google_cloud_run_service_iam_policy" "noauth_fe" {
    location    = google_cloud_run_service.fe.location
    project     = google_cloud_run_service.fe.project
    service     = google_cloud_run_service.fe.name
    policy_data = data.google_iam_policy.noauth.policy_data
}

./code/database/load_schema.sh

Datenbankschema initialisieren

Mit diesem Befehl wird ein temporärer Cloud Storage-Bucket erstellt, um das Schema in Cloud SQL hochzuladen.

PROJECT=$1
SQLNAME=$2

SQLSERVICEACCOUNT=$(gcloud sql instances describe $SQLNAME --format="value(serviceAccountEmailAddress)" | xargs)
gcloud storage buckets create gs://$PROJECT-temp
gcloud storage cp schema.sql gs://$PROJECT-temp/schema.sql
gcloud storage buckets add-iam-policy-binding gs://$PROJECT-temp/ --member=serviceAccount:$SQLSERVICEACCOUNT --role=roles/storage.objectViewer
gcloud sql import sql $SQLNAME gs://$PROJECT-temp/schema.sql -q
gcloud storage rm gs://$PROJECT-temp --recursive

./code/middleware/clouldbuild.yaml

API-Container erstellen

Mit diesem Code wird ein Docker-Image für die Middleware-Ebene erstellt.

name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/api', '.' ]
  ```
#### Push API container to Artifact Registry
Pushing the container to Artifact Registry makes it possible for Cloud Run to 
get the image and serve it.

``` yaml
name: 'gcr.io/cloud-builders/docker'
args: ['push', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/api']  

Ersetzungen

Im folgenden Code werden Variablen mit Standardwerten erstellt, damit diese Werte zum Zeitpunkt der Bereitstellung geändert werden können.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/clouldbuild.yaml

Inhalt des Massagecodes

Das Frontend ist vollständig statisch (HTML/JS/CSS). Die App muss auf die URL des API-Dienstes verweisen, den wir gerade erstellt haben. Cloud Run-Diensten wird jedoch eine URL mit einem zufälligen String zugewiesen. Dieses „Massage-Script“ erfasst diese zufällige URL und fügt sie in den Code des statischen JS in diesem Container ein.

./code/frontend/massage.sh

name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: bash
args: [ './massage.sh', '$_REGION' ]

API-Container erstellen

Mit dem folgenden Code wird ein Docker-Image für die Middleware-Ebene erstellt:

name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe', '.' ]

API-Container per Push in die Artifact Registry übertragen

Durch das Pushen des Containers in die Artifact Registry kann Cloud Run das Image abrufen und bereitstellen.

name: 'gcr.io/cloud-builders/docker'
args: ['push', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe']

Ersetzungen

Erstellen Sie eine Variable mit einem Standardwert, damit diese Werte bei der Bereitstellung geändert werden können.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/massage.sh

JavaScript bearbeiten

Mit diesem Befehl wird der Endpunkt für die Middleware in das JavaScript des Front-Ends eingefügt.

API=$(gcloud run services describe todo-api --region=$1 --format="value(status.url)")
stripped=$(echo ${API/https:\/\//})
sed -i"" -e "s/127.0.0.1:9000/$stripped/" www/js/main.js

Fazit

Sie haben jetzt eine einfache dreistufige To-do-Anwendung, die in Ihrem Projekt in Cloud Run ausgeführt wird. Außerdem haben Sie Zugriff auf den gesamten Code, um diese Lösung an Ihre Umgebung anzupassen oder zu erweitern.