Aplicação de partilha de fotos ponto a ponto sem servidor

+

Arquitetura

A aplicação de partilha de fotos ponto a ponto sem servidor cria uma aplicação de partilha de fotos ponto a ponto escalável com 11 produtos do Google Cloud, Terraform e Django.

Esta pilha vai configurar e criar os seguintes componentes:

  • Cloud Run, que vai executar a app como o servidor principal
  • Cloud SQL: para armazenar bases de dados relacionais, como informações do utilizador e publicações
  • Cloud Storage: para armazenar bases de dados não relacionais, como conteúdo multimédia de publicações
  • Balanceador de carga da nuvem: para servir tráfego com várias regiões
  • Cloud DNS: para mapear o domínio personalizado
  • Cloud Build: para implementar automaticamente a sua app a partir do gcloud
  • Secret Manager: para melhorar a segurança da app
  • VPC do Google Cloud: para ligar o Cloud SQL ao Cloud Run através de um endereço IP privado
  • Cloud DNS: para armazenar a cache estática para ligações mais rápidas
  • API Translation: para traduzir a legenda da publicação se estiver noutro idioma

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 da aplicação de partilha de fotos ponto a ponto sem servidor

A arquitetura da aplicação de partilha de fotos ponto a ponto sem servidor 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 IAM A gestão de identidade e de acesso (IAM) oferece aos administradores um controlo de acesso detalhado e visibilidade para gerir centralmente os recursos na nuvem empresarial.
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 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 Storage O Cloud Storage oferece armazenamento de ficheiros e publicação pública de imagens através de http(s).
Compute Engine O Compute Engine é a tecnologia virtual do Google Cloud. Com ele, pode criar muitas configurações diferentes de VMs para se adequarem às suas necessidades de computação.
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. Para usar qualquer uma das soluções aqui, temos de ativar o seguinte:

  • Cloud Run: a ferramenta sem servidor que aloja os contentores e fornece URLs a partir dos quais aceder à aplicação.
  • Cloud SQL: armazenamento de base de dados para a aplicação
  • Compute Engine: máquinas virtuais e redes
  • Cloud Build: cria imagens de contentores e implementa no Cloud Run
  • Cloud Secret Manager: usado para injetar os IPs do anfitrião para SQL e Redis no Cloud Build para o Cloud Run.
  • 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 IAM: a ferramenta para gerir o acesso e as autorizaçõ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
}

Crie uma rede privada

O comando seguinte cria uma rede privada para todos os recursos a usar para comunicar em segurança na aplicação.

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

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

Permita que o agente de serviço aceda ao projeto

Este comando permite que o agente de serviço aceda ao projeto para ativar a configuração do conetor de VPC.

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

Crie um conetor de acesso à VPC

Este comando associa o Cloud Run à base de dados

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 conta de serviço

Este comando cria uma conta de serviço para a aplicação usar para autenticar todos os recursos de que precisa para publicar a app.

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

Crie um servidor SQL

O seguinte conjunto de comandos configura e inicializa uma instância do servidor SQL.

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
}

Crie contentores de armazenamento

Cria a localização de armazenamento dos conteúdos multimédia para que a aplicação os armazene e apresente aos utilizadores.

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

Crie segredos

O seguinte conjunto de comandos cria definições confidenciais da aplicação e recursos para as armazenar 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]

}

Defina as autorizações da conta de serviço

Este comando permite que a conta de serviço do Cloud Build e da aplicação aceda ao conteúdo nos segredos da nuvem.

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

Preencha segredos

O seguinte conjunto de comandos cria segredos da aplicação no 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]
}

Crie um registo do Container Registry

Cria um registo no Container Registry para permitir a implementação de uma imagem de contentor no Cloud Run

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

Torne a localização de armazenamento do Container Registry pública

Concede acesso de leitura mundial à localização do Container Registry para que qualquer pessoa possa usar a imagem do contentor.

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

Crie uma imagem de contentor

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

Implemente o contentor 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" "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 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" {
  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
}

Crie um balanceador de carga

O comando seguinte cria um balanceador de carga e implementa verificações de funcionamento e serviços de back-end. Configura o balanceador de carga para se ligar 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
    }
  }
}

Conceda várias autorizações

O seguinte conjunto de comandos concede autorizações às contas da base 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 de executada, deve ter uma aplicação totalmente instalada em execução em várias regiões que permite a partilha nas redes sociais. Além disso, deve ter todo o código para modificar ou expandir esta solução de forma a adequar-se ao seu ambiente.