O app de três camadas é um aplicativo de tarefas simples, arquitetado como um aplicativo de três camadas simples:
- Back-end
- Banco de dados - MySQL - Cloud SQL
- Armazenamento em cache: Redis e Cloud Memorystore
- Middleware/API
- API hospedada em contêiner: Golang - Cloud Run
- Front-end/interface
- Interface hospedada em contêiner: Nginx + HTML/JS/CSS - Cloud Run
- Implantação
- Implantação contínua: Cloud Build
- Gerenciamento de secrets: Cloud Secret Manager
Primeiros passos
Clique no link a seguir para acessar uma cópia do código-fonte no Cloud Shell. Depois disso, um único comando vai criar uma cópia funcional do aplicativo no projeto.
Acessar o código-fonte no GitHub
Componentes de apps de três camadas
A arquitetura de três camadas do app usa vários produtos. A lista a seguir mostra os componentes e mais informações sobre eles, incluindo links para vídeos relacionados, documentação do produto e tutoriais interativos.Scripts
O script de instalação usa um executável escrito em go
e ferramentas da CLI do Terraform para
instalar o aplicativo em um projeto vazio. A saída deve ser um
aplicativo em funcionamento e um URL para o endereço IP do balanceamento de carga.
./main.tf
Ativar serviços
Os serviços do Google Cloud ficam desativados por padrão em um projeto. O ToDo exige que você ative os seguintes serviços:
- Service Networking e acesso VPC sem servidor: permite que o Cloud Run se comunique com o SQL e o Redis em uma rede particular, mantendo esses servidores inacessíveis de chamadas externas provenientes da API.
- Cloud Build: cria imagens de contêiner e implanta no Cloud Run.
- Cloud Memorystore: fornece uma camada de armazenamento em cache para o aplicativo.
- Cloud Run: a ferramenta sem servidor que hospeda os contêineres e fornece URLs para acessar o aplicativo.
- Cloud SQL: armazenamento de banco de dados para o aplicativo
- Cloud Storage: usado pelo Cloud Build e para carregar o esquema no banco de dados.
- Cloud Secret Manager: usado para injetar os ips do host para SQL e Redis no Cloud Build para Cloud Run.
- Artifact Registry: armazena as imagens do Docker para uso 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
}
Definir permissões
O comando a seguir define papéis e permissões do IAM que permitem que o Cloud Build implante serviços.
- Ativar a conta de serviço do Cloud Build para implantar no Cloud Run
- Ativar a conta de serviço do Cloud Build para definir o acesso VPN para o Cloud Run
- Ativar a conta de serviço do Cloud Build para realizar atividades de conta de serviço
- Ativar a conta de serviço do Cloud Build para atuar em nome da conta de serviço do Compute
- Ativar a conta de serviço do Cloud Build para publicar no Cloud Run
- Ativar a conta de serviço do Cloud Build para consumir segredos
- Ativar a conta de serviço do Cloud Build para armazenar contêineres 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]
}
Criar rede para a instância do SQL
O comando a seguir permite que o Cloud SQL seja acessível pelo 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]
}
Criar um conector de acesso à VPC
Conecta o Cloud Run ao banco de dados e à armazenagem 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]
}
Criar 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]
}
Criar o SQL Server
O comando a seguir configura e inicializa uma instância do 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}"
}
}
Criar repositório do Artifact Registry
O comando a seguir armazena imagens do Docker para uso 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]
}
Criar secrets
O comando a seguir armazena dados do host Redis e SQL no Cloud Secrets.
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]
}
Criar artefato para middleware
O comando a seguir cria a imagem Docker e a hospeda 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
]
}
Implantar o contêiner da API no Cloud Run
O comando a seguir usa o Cloud Build para iniciar um serviço no Cloud Run usando o contêiner que você 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 por todos.
Essa camada da API do aplicativo será chamada pelo navegador do usuário, mas, por padrão, os serviços do Cloud Run não são públicos. Para que os usuários consumam esse serviço, precisamos abrir as permissões para que esses serviços sejam acessíveis para 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
}
Criar artefato para o front-end
O comando a seguir cria a imagem Docker e a hospeda 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
]
}
Implantar o contêiner do front-end no Cloud Run
O próximo comando usa o Cloud Build para iniciar um serviço no Cloud Run usando o contêiner que acabamos 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]
}
Abrir o serviço de front-end do Cloud Run para que seja legível para todo o mundo
Esse é o front-end do aplicativo, que renderiza o HTML/JS/CSS com que o usuário interage com o aplicativo. Por padrão, os serviços do Cloud Run não são públicos. Para que esse aplicativo funcione, precisamos abrir permissões para que esses serviços sejam acessíveis ao mundo.
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
Inicializar o esquema do banco de dados
Esse comando cria um bucket temporário do Cloud Storage para fazer o upload do 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
Criar contêiner de API
Esse código cria uma imagem do Docker para a camada de 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']
Substituições
O código a seguir cria variáveis com valores padrão para que eles possam ser alterados no momento do deploy.
substitutions:
_REGION: us-central1
_BASENAME: todo
./code/frontend/clouldbuild.yaml
Massagem do conteúdo do código
O front-end é HTML/JS/CSS completamente estático. O app precisa apontar para o URL do serviço de API que acabamos de criar, mas os serviços do Cloud Run recebem um URL com uma string aleatória. Esse "script de massagem" captura esse URL aleatório e o injeta no código do JS estático nesse contêiner.
name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: bash
args: [ './massage.sh', '$_REGION' ]
Criar contêiner de API
O código a seguir cria uma imagem Docker para a camada de middleware:
name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe', '.' ]
Enviar o contêiner da API para o Artifact Registry
O envio do contêiner para o Artifact Registry permite que o Cloud Run receba a imagem e a disponibilize.
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 padrão para que esses valores possam ser alterados no momento da implantação.
substitutions:
_REGION: us-central1
_BASENAME: todo
./code/frontend/massage.sh
Editar JavaScript
Esse comando injeta o endpoint do middleware no JavaScript do 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
Conclusão
Agora você tem um aplicativo de tarefas de três níveis simples em execução no Cloud Run no seu projeto. Você também tem todo o código para modificar ou ampliar essa solução para se adequar ao seu ambiente.