App a tre livelli

Architettura

L'app a tre livelli è una semplice applicazione di promemoria progettata come applicazione a 3 livelli Vanilla:

  • Backend
    • Database - MySQL - Cloud SQL
    • Memorizzazione nella cache - Redis - Cloud Memorystore
  • Middleware/API
    • API ospitata in container - Golang - Cloud Run
  • Front-end/UI
    • UI ospitata in container - Nginx + HTML/JS/CSS - Cloud Run
  • Deployment
    • Deployment continuo - Cloud Build
    • Gestione dei secret - Cloud Secret Manager

Inizia

Fai clic sul link seguente a una copia del codice sorgente in Cloud Shell. Da lì, un singolo comando avvierà una copia funzionante dell'applicazione nel tuo progetto.

Apri in Cloud Shell

Visualizza il codice sorgente su GitHub


Componenti dell'app a tre livelli

L'architettura dell'app a tre livelli utilizza diversi prodotti. Di seguito sono elencati i componenti, insieme a ulteriori informazioni sui componenti, tra cui link a video correlati, documentazione del prodotto e procedure dettagliate interattive.
Video Documenti Procedure dettagliate
Cloud SQL Cloud SQL è un servizio SQL gestito che fornisce MySQL, SQL Server o Postgres per il livello di database delle tue applicazioni.
Cloud Memorystore Cloud Memorystore, una versione gestita di Redis, fornisce il livello di memorizzazione nella cache per le tue applicazioni.
Cloud Run Cloud Run ti consente di eseguire applicazioni in un container, ma in modo serverless, senza dover configurare il numero di istanze, processori o memoria. Carica un contenitore, recupera un URL.
Cloud Build Cloud Build è lo strumento che pacchettizza i container e ne esegue il deployment in modo che siano disponibili come servizi Cloud Run.
Secret Manager Cloud Secret Manager archivia informazioni sensibili sull'applicazione per il processo di compilazione.

Script

Lo script di installazione utilizza un eseguibile scritto in go e negli strumenti dell'interfaccia a riga di comando di Terraform per acquisire un progetto vuoto e installare l'applicazione al suo interno. L'output deve essere un'applicazione funzionante e un URL per l'indirizzo IP di bilanciamento del carico.

./main.tf

Abilitazione dei servizi

I servizi Google Cloud sono disabilitati in un progetto per impostazione predefinita. Da fare è necessario abilitare i seguenti servizi:

  • Networking di servizi e accesso VPC serverless: consente a Cloud Run di comunicare con SQL e Redis sulla rete privata, mantenendo questi server inaccessibili da chiamate esterne provenienti dall'API.
  • Cloud Build: crea immagini container ed esegue il deployment in Cloud Run
  • Cloud Memorystore: fornisce un livello di memorizzazione nella cache per l'applicazione.
  • Cloud Run: lo strumento serverless che ospita i container e fornisce gli URL da cui accedere all'applicazione.
  • Cloud SQL: spazio di archiviazione del database per l'applicazione
  • Cloud Storage: utilizzato da Cloud Build e per caricare lo schema nel database
  • Cloud Secret Manager: utilizzato per inserire gli IP dell'host per SQL e Redis in Cloud Build per Cloud Run.
  • Artifact Registry: archivia le immagini Docker da utilizzare con Cloud Build.
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
}

Imposta autorizzazioni

Il seguente comando imposta i ruoli e le autorizzazioni IAM che consentono a Cloud Build di eseguire il deployment dei servizi.

  • Abilita l'account di servizio Cloud Build per eseguire il deployment in Cloud Run
  • Abilita l'account di servizio Cloud Build per impostare l'accesso VPN per Cloud Run
  • Abilita l'account di servizio Cloud Build per eseguire attività dell'account di servizio
  • Abilita l'account di servizio Cloud Build ad agire per conto dell'account di servizio Compute
  • Abilita l'account di servizio Cloud Build per pubblicare in Cloud Run
  • Abilita l'account di servizio Cloud Build per consumare i secret
  • Abilita l'account di servizio Cloud Build per archiviare i container in Artifact Registry
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]
}

Crea networking per un'istanza SQL

Il seguente comando consente di accedere a Cloud SQL da Cloud Run:

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]
}

Crea connettore di accesso VPC

Connette Cloud Run al database e alla memorizzazione nella cache

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]
}

Crea server Redis

Configura e inizializza un'istanza del server Redis.

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]
}

Crea SQL Server

Il seguente comando configura e inizializza un'istanza SQL Server.

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}"
    }
}

Crea repository Artifact Registry

Il comando seguente archivia le immagini Docker da utilizzare con Cloud Run.

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]
}

Crea secret

Il seguente comando archivia i dati host Redis e SQL in Cloud Secret.

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]
}

Crea artefatto per il middleware

Il seguente comando crea l'immagine Docker e la ospita su Artifact Registry: ./code/frontend/clouldbuild.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
  ]
}

Esegui il deployment del container API in Cloud Run

Il seguente comando utilizza Cloud Build per avviare un servizio su Cloud Run utilizzando il container che hai appena creato.

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
    ]
}

Apri il servizio API Cloud Run per la leggibilità in tutto il mondo.

Questo livello API dell'applicazione verrà chiamato dal browser dell'utente, ma per impostazione predefinita i servizi Cloud Run non sono pubblici. Per consentire agli utenti di utilizzare questo servizio, dobbiamo concedere le autorizzazioni necessarie a questi servizi affinché siano accessibili a tutti.

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
}

Crea artefatto per il front-end

Il seguente comando crea l'immagine Docker e la ospita su Artifact Registry: ./code/frontend/clouldbuild.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
    ]
}

Esegui il deployment di un container frontend in Cloud Run

Il comando successivo utilizza Cloud Build per avviare un servizio su Cloud Run utilizzando il container che abbiamo appena creato

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]
}

Apri il servizio frontend Cloud Run per la leggibilità in tutto il mondo

Questo è il front-end dell'applicazione, che eseguirà il rendering del codice HTML/JS/CSS con cui l'utente interagisce con l'applicazione. Per impostazione predefinita i servizi Cloud Run non sono pubblici. Affinché questa applicazione funzioni, dobbiamo concedere le autorizzazioni per questi servizi affinché siano accessibili a tutti.

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

Inizializza schema del database

Questo comando crea un bucket Cloud Storage temporaneo per caricare lo schema in Cloud SQL.

PROJECT=$1
SQLNAME=$2

SQLSERVICEACCOUNT=$(gcloud sql instances describe $SQLNAME --format="value(serviceAccountEmailAddress)" | xargs)
gsutil mb gs://$PROJECT-temp
gsutil cp schema.sql gs://$PROJECT-temp/schema.sql
gsutil iam ch serviceAccount:$SQLSERVICEACCOUNT:objectViewer gs://$PROJECT-temp/
gcloud sql import sql $SQLNAME gs://$PROJECT-temp/schema.sql -q
gsutil rm gs://$PROJECT-temp/schema.sql
gsutil rb gs://$PROJECT-temp

./code/middleware/clouldbuild.yaml

Container API di build

Questo codice crea un'immagine Docker per il livello middleware.

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']

Sostituzioni

Il codice seguente crea variabili con valori predefiniti in modo che questi valori possano essere modificati al momento del deployment.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/clouldbuild.yaml

Contenuto codice massaggio

Il front-end è HTML/JS/CSS completamente statico. L'app deve puntare all'URL del servizio API appena creato, ma ai servizi Cloud Run viene assegnato un URL con una stringa casuale. Questo "script di massaggio" acquisisce l'URL randomizzato e lo inserisce nel codice del codice JS statico in questo container.

./code/frontend/massage.sh

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

Container API di build

Il codice seguente crea un'immagine Docker per il livello middleware:

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

Esegui il push del container API in Artifact Registry

Il push del container ad Artifact Registry consente a Cloud Run di ottenere l'immagine e gestirla.

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

Sostituzioni

Crea una variabile con un valore predefinito in modo che questi valori possano essere modificati in una data/ora di deployment.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/massage.sh

Modifica JavaScript

Questo comando inserisce l'endpoint per il middleware nel codice JavaScript del front-end.

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

Conclusione

Ora puoi usare una semplice applicazione di promemoria a tre livelli in esecuzione su Cloud Run nel tuo progetto. Inoltre, hai a disposizione tutto il codice per modificare o estendere questa soluzione in base all'ambiente.