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.
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.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.