Application de partage de photos de bout en bout sans serveur

+

Architecture

L'application de partage de photos de bout en bout sans serveur crée une application de partage de photos évolutive de bout en bout avec 11 produits Google Cloud, Terraform et Django.

Cette pile configurera et créera les composants suivants:

  • Cloud Run, qui exécutera l'application en tant que serveur principal
  • Cloud SQL : pour stocker une base de données relationnelle, comme des informations utilisateur ou des posts
  • Cloud Storage : pour stocker des bases de données non relationnelles telles que les médias de post
  • Cloud Load Balancer : pour acheminer le trafic vers plusieurs régions
  • Cloud DNS : mapper un domaine personnalisé
  • Cloud Build : déployer automatiquement votre application à partir de gcloud
  • Secret Manager : pour améliorer la sécurité de l'application
  • Cloud VPC - To connect Cloud SQL with Cloud Run via Private improve
  • Cloud DNS : pour stocker le cache statique afin d'accélérer les connexions
  • API Translation : pour traduire la légende du post si elle est dans une autre langue

Premiers pas

Cliquez sur le lien suivant pour obtenir une copie du code source dans Cloud Shell. Une seule commande permet de démarrer une copie fonctionnelle de l'application dans votre projet.

Ouvrir dans Cloud Shell

Afficher le code source sur GitHub


Composants d'une application de partage de photos de bout en bout sans serveur

L'architecture de l'application de partage de photos de bout en bout sans serveur utilise plusieurs produits. Vous trouverez ci-dessous la liste des composants, ainsi que des informations supplémentaires à leur sujet, y compris des liens vers des vidéos, des documentations produit et des tutoriels interactifs associés.
Vidéo Docs Tutoriels
Cloud IAM Identity and Access Management (IAM) offre aux administrateurs une visibilité et un contrôle des accès ultraprécis pour une gestion centralisée des ressources cloud de l'entreprise.
Cloud Run Cloud Run vous permet d'exécuter des applications dans un conteneur, mais de manière sans serveur, sans avoir à configurer le nombre d'instances, de processeurs ni de mémoire. Importez un conteneur, obtenez une URL.
Cloud SQL Cloud SQL est un SQL géré qui fournit MySQL, SQL Server ou Postgres pour la couche de base de données de vos applications.
Cloud Storage Cloud Storage permet de stocker des fichiers et de diffuser des images publiquement via HTTP(S).
Compute Engine Compute Engine est la technologie virtuelle de Google Cloud. Vous pouvez ainsi créer de nombreuses configurations de VM différentes pour répondre à vos besoins de calcul.
Secret Manager Cloud Secret Manager stocke des informations sensibles sur l'application pour le processus de compilation.

Scripts

Le script d'installation utilise un exécutable écrit en go et des outils de la CLI Terraform pour prendre un projet vide et y installer l'application. La sortie doit être une application fonctionnelle et une URL pour l'adresse IP d'équilibrage de charge.

./main.tf

Activer les services

Les services Google Cloud sont désactivés par défaut dans un projet. Pour utiliser l'une des solutions ci-dessous, nous devons activer les éléments suivants:

  • Cloud Run : outil sans serveur qui hébergera les conteneurs et fournira des URL permettant d'accéder à l'application.
  • Cloud SQL : stockage de base de données pour l'application
  • Compute Engine : machines virtuelles et mise en réseau
  • Cloud Build : crée des images de conteneur et les déploie dans Cloud Run
  • Cloud Secret Manager : permet d'injecter les adresses IP de l'hôte pour SQL et Redis dans Cloud Build pour Cloud Run.
  • Service Networking et accès au VPC sans serveur : permet à Cloud Run de communiquer avec SQL et Redis sur un réseau privé, ce qui rend ces serveurs inaccessibles aux appels externes provenant de l'API.
  • Cloud IAM : outil permettant de gérer les accès et les autorisations aux ressources 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
}

Créer un réseau privé

La commande suivante crée un réseau privé que toutes les ressources peuvent utiliser pour communiquer de manière sécurisée dans l'application.

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

Créer un réseau pour une instance SQL

La commande suivante permet d'accéder à Cloud SQL depuis 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]
}

Autoriser l'agent de service à accéder au projet

Cette commande permet à l'agent de service d'accéder au projet pour activer la configuration du connecteur VPC.

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

Créer un connecteur d'accès au VPC

Cette commande connecte Cloud Run à la base de données.

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

Créer un compte de service

Cette commande crée un compte de service que l'application utilisera pour s'authentifier auprès de toutes les ressources dont elle a besoin pour diffuser l'application.

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

Créer un serveur SQL

L'ensemble de commandes suivant configure et initialise une instance de serveur 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
}

Créer des buckets de stockage

Crée l'emplacement de stockage des contenus multimédias pour l'application afin qu'ils soient stockés et diffusés auprès des utilisateurs.

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

Créer des secrets

L'ensemble de commandes suivant crée des paramètres et des ressources d'application sensibles pour les stocker dans 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]

}

Définir les autorisations du compte de service

Cette commande permet au compte Cloud Build et au compte de service d'application d'accéder au contenu de 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]
}

Remplir les secrets

L'ensemble de commandes suivant crée et stocke des secrets d'application dans 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]
}

Créer un enregistrement Container Registry

Crée un enregistrement dans Container Registry pour permettre le déploiement d'une image de conteneur dans Cloud Run.

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

Rendre l'emplacement de stockage Container Registry public

Accorde un accès en lecture publique à l'emplacement de Container Registry afin que tout le monde puisse utiliser l'image de conteneur.

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

Créer une image de conteneur

La commande suivante crée l'image Docker et l'héberge dans Container Registry:

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

  depends_on = [
    google_container_registry.main
  ]
}

Déployer un conteneur dans Cloud Run

La commande suivante utilise Cloud Build pour démarrer un service sur Cloud Run à l'aide du conteneur que vous venez de créer.

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

Ouvrez le service de l'API Cloud Run pour qu'il soit accessible à tous.

Cette couche d'API de l'application sera appelée par le navigateur de l'utilisateur, mais par défaut, les services Cloud Run ne sont pas publics. Pour que les utilisateurs puissent utiliser ce service, nous devons ouvrir les autorisations sur ces services afin qu'ils soient accessibles à tous.

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
}

Créer un équilibreur de charge

La commande suivante crée un équilibreur de charge, et implémente des vérifications de l'état et des services de backend. Il configure l'équilibreur de charge pour qu'il se connecte au service 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
    }
  }
}

Accorder différentes autorisations

L'ensemble de commandes suivant accorde des autorisations aux comptes de base de données et aux comptes de service 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]
}

Conclusion

Une fois l'application exécutée, vous devriez disposer d'une application complètement installée dans plusieurs régions qui permet de partager du contenu sur les réseaux sociaux. De plus, vous devez disposer de tout le code nécessaire pour modifier ou étendre cette solution afin qu'elle s'adapte à votre environnement.