Aplicativo de compartilhamento de fotos de ponta a ponta sem servidor

Arquitetura

O aplicativo de compartilhamento de fotos de ponta a ponta sem servidor cria um aplicativo de compartilhamento de fotos escalonável com 11 produtos do Google Cloud, Terraform e Django.

Essa pilha vai configurar e criar estes componentes:

  • Cloud Run: vai executar o app como servidor principal.
  • Cloud SQL: para armazenar um banco de dados relacional, como informações de usuários, postagens
  • Cloud Storage: para armazenar um banco de dados não relacional, como pós-mídia
  • Balanceador de carga do Cloud: para tráfego do servidor com várias regiões
  • Cloud DNS: para mapear o domínio personalizado
  • Cloud Build: para implantar automaticamente o app com a gcloud
  • Secret Manager: para melhorar a segurança do app
  • VPC do Cloud: para conectar o Cloud SQL com o Cloud Run usando o modo privado
  • Cloud DNS: para armazenar cache estático e ter conexões mais rápidas
  • API Translation: para traduzir a legenda da postagem, se ela estiver em outro idioma

Comece já

Clique no link a seguir para copiar o código-fonte no Cloud Shell. Depois, um único comando ativará uma cópia de trabalho do aplicativo no seu projeto.

Abrir no Cloud Shell

Ver código-fonte no GitHub (em inglês)


Componentes do aplicativo de compartilhamento de fotos de ponta a ponta sem servidor

A arquitetura do aplicativo de compartilhamento de fotos de ponta a ponta sem servidor usa vários produtos. Veja a seguir os componentes e mais informações sobre eles, incluindo links para vídeos relacionados, documentação do produto e tutoriais interativos.
Video Documentos Tutoriais
Cloud IAM O Identity and Access Management (IAM) oferece aos administradores visibilidade e controle de acesso detalhados, o que permite um gerenciamento centralizado dos recursos da nuvem empresarial.
Cloud Run Com o Cloud Run, é possível executar aplicativos em um contêiner, mas sem servidor, sem a necessidade de configurar o número de instâncias, processadores ou memória. Faça upload de um contêiner e receba um URL.
Cloud SQL O Cloud SQL é um SQL gerenciado que fornece MySQL, SQL Server ou Postgres para a camada de banco de dados dos seus aplicativos.
Cloud Storage O Cloud Storage oferece armazenamento de arquivos e veiculação pública de imagens por HTTP(s).
Compute Engine O Compute Engine é a tecnologia virtual do Google Cloud. Com ele, é possível ativar várias configurações diferentes de VM para atender às suas necessidades de computação.
Secret Manager O Cloud Secret Manager armazena dados confidenciais sobre o aplicativo no processo de compilação.

Scripts

O script de instalação usa um executável escrito em go e ferramentas da CLI do Terraform para pegar um projeto vazio e instalar o aplicativo nele. A saída será um aplicativo em funcionamento e um URL para o endereço IP de balanceamento de carga.

./main.tf

Ativar serviços

Os serviços do Google Cloud são desativados em um projeto por padrão. Para usar qualquer uma das soluções apresentadas aqui, temos que ativar o seguinte:

  • Cloud Run: a ferramenta sem servidor que hospedará os contêineres e fornecerá URLs para acessar o aplicativo.
  • Cloud SQL: armazenamento de banco de dados para o aplicativo
  • Compute Engine: máquinas virtuais e rede
  • Cloud Build: cria imagens de contêiner e implanta no Cloud Run
  • Cloud Secret Manager: usado para injetar os IPs de host para SQL e Redis no Cloud Build para Cloud Run.
  • Rede de serviços e acesso VPC sem servidor: permite que o Cloud Run se comunique com o SQL e o Redis em uma rede privada, mantendo esses servidores inacessíveis de chamadas externas provenientes da API.
  • Cloud IAM: a ferramenta para gerenciar o acesso e as permissões aos Recursos do Google Cloud.
# Step 2: Activate service APIs
resource "google_project_service" "run" {
  service            = "run.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "sql-component" {
  service            = "sql-component.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "sqladmin" {
  service            = "sqladmin.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "compute" {
  service            = "compute.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "cloudbuild" {
  service            = "cloudbuild.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "secretmanager" {
  service            = "secretmanager.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "vpcaccess" {
  service            = "vpcaccess.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "servicenetworking" {
  service            = "servicenetworking.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "iam" {
  service            = "iam.googleapis.com"
  disable_on_destroy = false
}

Cria uma rede particular

O comando a seguir cria uma rede privada para que todos os recursos possam usar para se comunicar com segurança no aplicativo.

resource "google_compute_network" "main" {
  provider   = google
  name       = "social-media-network-${random_id.name.hex}"
  depends_on = [google_project_iam_member.serviceagent]
}

Criar rede para instância SQL

O comando a seguir permite que o Cloud SQL seja acessível no Cloud Run:

resource "google_compute_global_address" "private_ip_address" {
  provider      = google-beta
  project       = var.project
  name          = local.private_ip_name
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 16
  network       = google_compute_network.main.id
  depends_on    = [google_project_service.vpcaccess, google_project_iam_member.serviceagent]
}

resource "google_service_networking_connection" "private_vpc_connection" {
  provider = google-beta

  network                 = google_compute_network.main.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
  depends_on              = [google_project_service.vpcaccess, google_project_iam_member.serviceagent]
}

Permitir que o agente de serviço acesse o projeto

Esse comando permite que o agente de serviço acesse o projeto para ativar a configuração do conector de VPC.

resource "google_project_iam_member" "serviceagent" {
  project = data.google_project.project.number
  role    = "roles/editor"
  member  = local.serviceagent_serviceaccount
}

Criar conector de acesso à VPC

Este comando conecta o Cloud Run ao Database

resource "google_vpc_access_connector" "connector" {
  for_each      = { "us-west1" : 8, "us-central1" : 9, "us-east1" : 10 }
  name          = "vpc-con-${each.key}"
  ip_cidr_range = "10.${each.value}.0.0/28"
  region        = each.key
  network       = google_compute_network.main.name
  depends_on    = [google_project_service.vpcaccess, google_project_iam_member.serviceagent]
}

Criar a conta de serviço

Esse comando cria uma conta de serviço para o aplicativo usar na autenticação de todos os recursos necessários para disponibilizar o aplicativo.

# Step 4: Create a custom Service Account
resource "google_service_account" "django" {
  account_id = "django"
  depends_on = [
    google_project_service.iam
  ]
}

Criar SQL Server

O conjunto de comandos a seguir configura e inicializa uma instância do SQL Server.

resource "random_string" "random" {
  length  = 4
  special = false
}

resource "random_password" "database_password" {
  length  = 32
  special = false
}

resource "random_id" "name" {
  byte_length = 2
}

resource "random_id" "db_name_suffix" {
  byte_length = 4
}

resource "google_sql_database_instance" "instance" {
  name             = local.sql_database_name
  database_version = "MYSQL_8_0"
  region           = var.region
  project          = var.project
  depends_on       = [google_vpc_access_connector.connector]
  settings {
    tier = "db-f1-micro"
    ip_configuration {
      ipv4_enabled    = "true"
      private_network = google_compute_network.main.id
    }
  }
  deletion_protection = false
}

resource "google_sql_database" "database" {
  name     = "django"
  instance = google_sql_database_instance.instance.name
}

resource "google_sql_user" "django" {
  name     = "django"
  instance = google_sql_database_instance.instance.name
  password = random_password.database_password.result
}

Criar buckets do Storage

Cria o local de armazenamento da mídia do app que será armazenada e veiculada aos usuários.

resource "google_storage_bucket" "media" {
  name     = "${var.project}-bucket"
  location = "US"
}

resource "google_storage_bucket_iam_binding" "main" {
  bucket = google_storage_bucket.media.name
  role   = "roles/storage.objectViewer"
  members = [
    "allUsers",
  ]
}

Criar secrets

O conjunto de comandos a seguir cria configurações e recursos de aplicativos confidenciais para armazená-los no Cloud Secrets.

resource "google_secret_manager_secret_version" "django_settings" {
  secret = google_secret_manager_secret.django_settings.id

  secret_data = templatefile("etc/env.tpl", {
    bucket     = google_storage_bucket.media.name
    secret_key = random_password.django_secret_key.result
    user       = google_sql_user.django
    instance   = google_sql_database_instance.instance
    database   = google_sql_database.database
  })
}

resource "random_password" "django_secret_key" {
  special = false
  length  = 50
}

resource "google_secret_manager_secret" "django_settings" {
  secret_id = "django_settings"

  replication {
    automatic = true
  }
  depends_on = [google_project_service.secretmanager]

}

Definir permissões da conta de serviço

Esse comando permite que a conta de serviço do Cloud Build e do aplicativo acesse o conteúdo no Cloud Secrets.

resource "google_secret_manager_secret_iam_binding" "django_settings" {
  secret_id = google_secret_manager_secret.django_settings.id
  role      = "roles/secretmanager.admin"
  members   = [local.cloudbuild_serviceaccount, local.django_serviceaccount]
}

Preencher secrets

O conjunto de comandos a seguir cria secrets do Cloud Secrets.

resource "google_secret_manager_secret" "main" {
  for_each = {
    "DATABASE_PASSWORD" : google_sql_user.django.password,
    "DATABASE_USER" : google_sql_user.django.name,
    "DATABASE_NAME" : google_sql_database.database.name,
    "DATABASE_HOST_PROD" : google_sql_database_instance.instance.private_ip_address,
    "DATABASE_PORT_PROD" : 3306,
    "PROJECT_ID" : var.project,
    "GS_BUCKET_NAME" : var.project,
  }
  secret_id = each.key
  replication {
    automatic = true
  }

  depends_on = [google_sql_user.django, google_sql_database.database, google_sql_database_instance.instance]

}

resource "google_secret_manager_secret" "network" {
  for_each = {
    "EXTERNAL_IP" : module.lb-http.external_ip,
  }
  secret_id = each.key
  replication {
    automatic = true
  }
  depends_on = [module.lb-http, google_compute_network.main, google_cloud_run_service.service]
}

resource "google_secret_manager_secret" "url" {
  for_each = {
    "WEBSITE_URL_US_CENTRAL1" : google_cloud_run_service.service["us-central1"].status[0].url,
    "WEBSITE_URL_US_WEST1" : google_cloud_run_service.service["us-west1"].status[0].url,
    "WEBSITE_URL_US_EAST1" : google_cloud_run_service.service["us-east1"].status[0].url,
  }
  secret_id = each.key
  replication {
    automatic = true
  }
  depends_on = [google_cloud_run_service.service]
}

resource "google_secret_manager_secret_version" "main" {
  for_each = { "DATABASE_PASSWORD" : google_sql_user.django.password,
    "DATABASE_USER" : google_sql_user.django.name,
    "DATABASE_NAME" : google_sql_database.database.name,
    "DATABASE_HOST_PROD" : google_sql_database_instance.instance.private_ip_address,
    "DATABASE_PORT_PROD" : 3306,
    "PROJECT_ID" : var.project,
    "GS_BUCKET_NAME" : var.project,
  }
  secret      = google_secret_manager_secret.main[each.key].id
  secret_data = each.value
}

resource "google_secret_manager_secret_version" "network" {
  for_each = {
    "EXTERNAL_IP" : module.lb-http.external_ip,
  }
  secret      = google_secret_manager_secret.network[each.key].id
  secret_data = each.value
}

resource "google_secret_manager_secret_version" "url" {
  for_each = {
    "WEBSITE_URL_US_CENTRAL1" : google_cloud_run_service.service["us-central1"].status[0].url,
    "WEBSITE_URL_US_WEST1" : google_cloud_run_service.service["us-west1"].status[0].url,
    "WEBSITE_URL_US_EAST1" : google_cloud_run_service.service["us-east1"].status[0].url,
  }
  secret      = google_secret_manager_secret.url[each.key].id
  secret_data = each.value
}

resource "google_secret_manager_secret_iam_binding" "main" {
  for_each = { "DATABASE_PASSWORD" : google_sql_user.django.password,
    "DATABASE_USER" : google_sql_user.django.name,
    "DATABASE_NAME" : google_sql_database.database.name,
    "DATABASE_HOST_PROD" : google_sql_database_instance.instance.private_ip_address,
    "DATABASE_PORT_PROD" : 3306,
    "PROJECT_ID" : var.project,
    "GS_BUCKET_NAME" : var.project,
  }
  secret_id = google_secret_manager_secret.main[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

resource "google_secret_manager_secret_iam_binding" "network" {
  for_each = {
    "EXTERNAL_IP" : module.lb-http.external_ip,
  }
  secret_id = google_secret_manager_secret.network[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

resource "google_secret_manager_secret_iam_binding" "url" {
  for_each = {
    "WEBSITE_URL_US_CENTRAL1" : google_cloud_run_service.service["us-central1"].status[0].url,
    "WEBSITE_URL_US_WEST1" : google_cloud_run_service.service["us-west1"].status[0].url,
    "WEBSITE_URL_US_EAST1" : google_cloud_run_service.service["us-east1"].status[0].url,
  }
  secret_id = google_secret_manager_secret.url[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

resource "random_password" "SUPERUSER_PASSWORD" {
  length  = 32
  special = false
}

resource "google_secret_manager_secret" "SUPERUSER_PASSWORD" {
  secret_id = "SUPERUSER_PASSWORD"
  replication {
    automatic = true
  }
  depends_on = [google_project_service.secretmanager]
}

resource "google_secret_manager_secret_version" "SUPERUSER_PASSWORD" {
  secret      = google_secret_manager_secret.SUPERUSER_PASSWORD.id
  secret_data = random_password.SUPERUSER_PASSWORD.result
}

resource "google_secret_manager_secret_iam_binding" "SUPERUSER_PASSWORD" {
  secret_id = google_secret_manager_secret.SUPERUSER_PASSWORD.id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

Criar um registro do Container Registry

Cria um registro no Container Registry para permitir a implantação de uma imagem de contêiner no Cloud Run

resource "google_container_registry" "main" {
  project  = var.project
  location = "US"
}

Tornar público o local de armazenamento do Container Registry

Concede acesso legível ao local do Container Registry para que qualquer pessoa possa usar a imagem do contêiner.

resource "google_storage_bucket_iam_member" "repo_public" {
  bucket = google_container_registry.main.id
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

Criar imagem do contêiner

O comando a seguir cria a imagem do Docker e a hospeda no Container Registry:

resource "null_resource" "cloudbuild_api" {
  provisioner "local-exec" {
    working_dir = path.module
    command     = "gcloud builds submit . "
  }

  depends_on = [
    google_container_registry.main
  ]
}

Implantar o contêiner no Cloud Run

O comando a seguir usa o Cloud Build para ativar um serviço no Cloud Run usando o contêiner que você acabou de criar.

resource "google_cloud_run_service" "service" {
  for_each                   = toset([for location in local.runlocations : location if can(regex("us-(?:west|central|east)1", location))])
  name                       = var.project
  location                   = each.value
  project                    = var.project
  autogenerate_revision_name = true
  depends_on = [
    # google_sql_database_instance.instance,
    google_service_account.django,
    google_sql_database_instance.instance,
    google_vpc_access_connector.connector,
  ]

  template {
    spec {
      service_account_name = google_service_account.django.email
      containers {
        image = "gcr.io/${var.project}/${var.service}:latest"
        env {
          name  = "PROJECT_ID"
          value = var.project
        }
      }
    }

    metadata {
      annotations = {
        "autoscaling.knative.dev/maxScale"        = "100"
        "run.googleapis.com/cloudsql-instances"   = google_sql_database_instance.instance.connection_name
        "run.googleapis.com/client-name"          = "terraform"
        "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector[each.key].name
        "run.googleapis.com/vpc-access-egress"    = "all-traffic"
      }
    }
  }

  traffic {
    percent         = 100
    latest_revision = true
  }
}

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

Essa camada de 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, temos que abrir permissões para que os serviços sejam acessíveis ao mundo.

resource "google_cloud_run_service_iam_policy" "noauth" {
  for_each = toset([for location in local.runlocations : location if can(regex("us-(?:west|central|east)1", location))])
  location = google_cloud_run_service.service[each.key].location
  project  = google_cloud_run_service.service[each.key].project
  service  = google_cloud_run_service.service[each.key].name

  policy_data = data.google_iam_policy.noauth.policy_data
}

Criar balanceador de carga

O comando a seguir cria um balanceador de carga e implementa verificações de integridade e serviços de back-end. Ele configura o balanceador de carga para se conectar ao serviço do Cloud Run.

# Step 11: Create Load Balancer to handle traffics from multiple regions
resource "google_compute_region_network_endpoint_group" "default" {
  for_each              = toset([for location in local.runlocations : location if can(regex("us-(?:west|central|east)1", location))])
  name                  = "${var.project}--neg--${each.key}"
  network_endpoint_type = "SERVERLESS"
  region                = google_cloud_run_service.service[each.key].location
  cloud_run {
    service = google_cloud_run_service.service[each.key].name
  }
  depends_on = [google_cloud_run_service.service]
}

module "lb-http" {
  source  = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs"
  version = "~> 4.5"

  project = var.project
  name    = var.project

  ssl                             = false
  https_redirect                  = true
  managed_ssl_certificate_domains = []
  use_ssl_certificates            = false
  backends = {
    default = {
      description            = null
      enable_cdn             = true
      custom_request_headers = null

      log_config = {
        enable      = true
        sample_rate = 1.0
      }

      groups = [
        for neg in google_compute_region_network_endpoint_group.default :
        {
          group = neg.id
        }
      ]

      iap_config = {
        enable               = false
        oauth2_client_id     = null
        oauth2_client_secret = null
      }
      security_policy = null
    }
  }
}

Conceder várias permissões

O conjunto de comandos a seguir concede permissões às contas do banco de dados e às contas de serviço do Cloud Build.

# Step 12: Grant access to the database
resource "google_project_iam_member" "service_permissions_cb_django" {
  for_each = toset([
    "run.admin", "cloudsql.client", "editor", "secretmanager.admin"
  ])

  role   = "roles/${each.key}"
  member = local.django_serviceaccount
}

resource "google_project_iam_member" "service_permissions_cb" {
  for_each = toset([
    "run.admin", "cloudsql.client", "editor", "secretmanager.admin"
  ])

  role   = "roles/${each.key}"
  member = local.cloudbuild_serviceaccount
}

resource "google_service_account_iam_binding" "cloudbuild_sa" {
  service_account_id = google_service_account.django.name
  role               = "roles/editor"

  members = [local.cloudbuild_serviceaccount]
}

Conclusão

Depois da execução, você terá um aplicativo completamente instalado em execução em várias regiões que permitem o compartilhamento em mídias sociais. Além disso, é necessário ter todo o código para modificar ou estender essa solução para se adequar ao seu ambiente.