App de três níveis

+

Arquitetura

A app de três camadas é uma simples aplicação de tarefas arquitetada como uma aplicação de três camadas simples:

  • Back-end
    • Base de dados – MySQL – Cloud SQL
    • Colocar em cache – Redis – Cloud Memorystore
  • Middleware/API
    • API alojada em contentor – Golang – Cloud Run
  • Front-end/IU
    • IU alojada no contentor – Nginx + HTML/JS/CSS – Cloud Run
  • Implementação
    • Implementação contínua – Cloud Build
    • Gestão de Secrets – Cloud Secret Manager

Começar

Clique no seguinte link para aceder a uma cópia do código fonte no Cloud Shell. Quando estiver lá, um único comando vai iniciar uma cópia funcional da aplicação no seu projeto.

Abra no Cloud Shell

Veja o código fonte no GitHub


Componentes de apps de três camadas

A arquitetura de apps de três camadas usa vários produtos. A lista seguinte apresenta os componentes, juntamente com mais informações sobre os componentes, incluindo links para vídeos relacionados, documentação do produto e visitas guiadas interativas.
Vídeo Docs Instruções passo a passo
Cloud SQL O Cloud SQL é um SQL gerido que fornece MySQL, SQL Server ou Postgres para a camada de base de dados das suas aplicações.
Cloud Memorystore O Cloud Memorystore, o Redis gerido, fornece a camada de cache para as suas aplicações.
Cloud Run O Cloud Run permite-lhe executar aplicações num contentor, mas de forma sem servidor, sem ter de configurar o número de instâncias, processadores ou memória. Carregue um contentor e obtenha um URL.
Cloud Build O Cloud Build é a ferramenta que agrupa os contentores e os implementa para ficarem disponíveis como serviços do Cloud Run.
Secret Manager O Cloud Secret Manager armazena detalhes confidenciais sobre a aplicação para o processo de compilação.

Scripts

O script de instalação usa um executável escrito em go e ferramentas da CLI do Terraform para usar um projeto vazio e instalar a aplicação no mesmo. A saída deve ser uma aplicação funcional e um URL para o endereço IP de equilíbrio de carga.

./main.tf

Ative os serviços

Os serviços Google Cloud estão desativados num projeto por predefinição. O ToDo requer que ative os seguintes serviços:

  • Redes de serviços e acesso à VPC sem servidor: permite que o Cloud Run comunique com o SQL e o Redis na rede privada, mantendo estes servidores inacessíveis a chamadas externas provenientes da API.
  • Cloud Build: cria imagens de contentores e implementa no Cloud Run
  • Cloud Memorystore: fornece uma camada de cache para a aplicação.
  • Cloud Run: a ferramenta sem servidor que vai alojar os contentores e fornecer URLs a partir dos quais aceder à aplicação.
  • Cloud SQL: armazenamento de base de dados para a aplicação
  • Cloud Storage: usado pelo Cloud Build e para carregar o esquema na base de dados
  • Cloud Secret Manager: usado para injetar os IPs do anfitrião para SQL e Redis no Cloud Build para o Cloud Run.
  • Artifact Registry: armazena as imagens Docker para utilização com o 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
}

Defina autorizações

O comando seguinte define as funções e as autorizações de IAM que permitem ao Cloud Build implementar serviços.

  • Ative a conta de serviço do Cloud Build para implementar no Cloud Run
  • Ative a conta de serviço do Cloud Build para definir o acesso VPN para o Cloud Run
  • Ative a conta de serviço do Cloud Build para realizar atividades da conta de serviço
  • Ative a conta de serviço do Cloud Build para agir em nome da conta de serviço do Compute
  • Ative a conta de serviço do Cloud Build para publicar no Cloud Run
  • Permita que a conta de serviço do Cloud Build consuma segredos
  • Permita que a conta de serviço do Cloud Build armazene contentores no 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]
}

Crie uma rede para a instância do SQL

O seguinte comando permite que o Cloud SQL seja acessível a partir do 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]
}

Crie um conetor de acesso à VPC

Associa o Cloud Run à base de dados e ao armazenamento em 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]
}

Crie um servidor Redis

Configura e inicializa uma instância do servidor 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]
}

Crie um servidor SQL

O seguinte comando configura e inicializa uma instância do servidor SQL.

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

Crie um repositório do Artifact Registry

O comando seguinte armazena imagens do Docker para utilização com o 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]
}

Crie segredos

O comando seguinte armazena dados de anfitriões Redis e SQL em segredos da nuvem.

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

Crie um artefacto para middleware

O comando seguinte cria a imagem do Docker e aloja-a no 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
  ]
}

Implemente o contentor da API no Cloud Run

O comando seguinte usa o Cloud Build para iniciar um serviço no Cloud Run através do contentor que acabou de criar.

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

Abra o serviço da API Cloud Run para que seja legível a nível mundial.

Esta camada de API da aplicação é chamada pelo navegador do utilizador, mas, por predefinição, os serviços do Cloud Run não são públicos. Para que os utilizadores possam utilizar este serviço, temos de abrir autorizações nestes serviços para que sejam acessíveis a todos.

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
}

Cria artefacto para a parte frontal

O comando seguinte cria a imagem do Docker e aloja-a no 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
    ]
}

Implemente o contentor de front-end no Cloud Run

O comando seguinte usa o Cloud Build para iniciar um serviço no Cloud Run através do contentor que acabámos de criar

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

Abra o serviço de front-end do Cloud Run para ser legível a nível mundial

Este é o front-end da aplicação, que vai renderizar o HTML/JS/CSS com o qual o utilizador interage com a aplicação. Por predefinição, os serviços do Cloud Run não são públicos. Para que esta aplicação funcione, temos de abrir autorizações nestes serviços para serem acessíveis a todos.

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

Inicialize o esquema da base de dados

Este comando cria um contentor do Cloud Storage temporário para carregar o esquema para o Cloud SQL.

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

Crie um contentor de API

Este código cria uma imagem do Docker para a camada de software intermédio.

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

Substituições

O código seguinte cria variáveis com valores predefinidos para que estes valores possam ser alterados no momento da implementação.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/clouldbuild.yaml

Conteúdo do código de massagem

O front-end é HTML/JS/CSS completamente estático. A app tem de apontar para o URL do serviço de API que acabámos de criar, mas aos serviços do Cloud Run é atribuído um URL com uma string aleatória. Este "script de processamento" captura esse URL aleatório e injeta-o no código do JS estático neste contentor.

./code/frontend/massage.sh

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

Crie um contentor de API

O código seguinte cria uma imagem do Docker para a camada intermédia:

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

Envie o contentor da API para o Artifact Registry

O envio do contentor para o Artifact Registry permite que o Cloud Run obtenha a imagem e a publique.

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

Substituições

Crie uma variável com um valor predefinido para que estes valores possam ser alterados no momento da implementação.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/massage.sh

Edite JavaScript

Este comando injeta o ponto final do middleware no JavaScript da interface de utilizador.

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

Conclusão

Agora, tem uma aplicação de tarefas simples de 3 camadas em execução no Cloud Run no seu projeto. Também tem todo o código para modificar ou expandir esta solução de acordo com o seu ambiente.