Application à trois niveaux

+

Architecture

L'application à trois niveaux est une application de tâches simples conçue comme une application à trois niveaux standard:

  • Backend
    • Base de données - MySQL - Cloud SQL
    • Mise en cache - Redis - Cloud Memorystore
  • Middleware/API
    • API hébergée dans un conteneur : Golang - Cloud Run
  • Interface utilisateur/Partie avant
    • IUG hébergée dans un conteneur : Nginx + HTML/JS/CSS - Cloud Run
  • Déploiement
    • Déploiement continu : Cloud Build
    • Gestion des secrets : Cloud Secret Manager

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 à trois niveaux

L'architecture d'application à trois niveaux 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 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 Memorystore Cloud Memorystore, Redis géré, fournit la couche de mise en cache pour vos applications.
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 Build Cloud Build est l'outil qui empaquette les conteneurs et les déploie pour qu'ils soient disponibles en tant que services Cloud Run.
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. ToDo nécessite d'activer les services suivants:

  • 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 Build : crée des images de conteneur et les déploie dans Cloud Run
  • Cloud Memorystore : fournit une couche de mise en cache pour l'application.
  • Cloud Run : outil sans serveur qui hébergera les conteneurs et fournira des URL permettant d'accéder à l'application.
  • Cloud SQL : stockage de la base de données pour l'application
  • Cloud Storage : utilisé par Cloud Build et pour charger le schéma dans la base de données
  • Cloud Secret Manager : permet d'injecter les adresses IP de l'hôte pour SQL et Redis dans Cloud Build pour Cloud Run.
  • Artifact Registry : stocke les images Docker à utiliser avec 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
}

Définir des autorisations

La commande suivante définit les rôles et autorisations IAM qui permettent à Cloud Build de déployer des services.

  • Activer le compte de service Cloud Build pour le déploiement sur Cloud Run
  • Activer le compte de service Cloud Build pour définir l'accès VPN à Cloud Run
  • Autoriser le compte de service Cloud Build à effectuer des activités de compte de service
  • Autoriser le compte de service Cloud Build à agir au nom du compte de service Compute
  • Activer le compte de service Cloud Build pour publier sur Cloud Run
  • Autoriser le compte de service Cloud Build à consommer des secrets
  • Activer le compte de service Cloud Build pour stocker des conteneurs dans 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]
}

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

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

Connecte Cloud Run à la base de données et au 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]
}

Créer un serveur Redis

Configure et initialise une instance de serveur 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]
}

Créer un serveur SQL

La commande suivante configure et initialise une instance de serveur SQL.

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

Créer un dépôt Artifact Registry

La commande suivante stocke des images Docker à utiliser avec 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]
}

Créer des secrets

La commande suivante stocke les données d'hôte Redis et SQL dans 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]
}

Créer un artefact pour le middleware

La commande suivante crée l'image Docker et l'héberge sur 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
  ]
}

Déployer le conteneur de l'API 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" "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
    ]
} 

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

Créer un artefact pour le frontend

La commande suivante crée l'image Docker et l'héberge sur 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
    ]
}

Déployer le conteneur d'interface utilisateur dans Cloud Run

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

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

Ouvrir le service d'interface utilisateur Cloud Run pour qu'il soit lisible par tous

Il s'agit de l'interface utilisateur de l'application, qui affichera le code HTML/JS/CSS avec lequel l'utilisateur interagit avec l'application. Par défaut, les services Cloud Run ne sont pas publics. Pour que cette application fonctionne, nous devons ouvrir les autorisations sur ces services pour qu'ils soient accessibles à tous.

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

Initialiser le schéma de la base de données

Cette commande crée un bucket Cloud Storage temporaire pour importer le schéma dans 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

Créer un conteneur d'API

Ce code crée une image Docker pour la couche 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']  

Substitutions

Le code suivant crée des variables avec des valeurs par défaut afin qu'elles puissent être modifiées au moment du déploiement.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/clouldbuild.yaml

Contenu du code de massage

L'interface utilisateur est entièrement statique en HTML/JS/CSS. L'application doit pointer vers l'URL du service d'API que nous venons de créer, mais une URL avec une chaîne aléatoire est attribuée aux services Cloud Run. Ce "script de massage" capture cette URL aléatoire et l'injecte dans le code du code JavaScript statique de ce conteneur.

./code/frontend/massage.sh

name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: bash
args: [ './massage.sh', '$_REGION' ]

Créer un conteneur d'API

Le code suivant crée une image Docker pour la couche middleware:

name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe', '.' ]

Transférer le conteneur de l'API vers Artifact Registry

Transférer le conteneur vers Artifact Registry permet à Cloud Run d'obtenir l'image et de l'afficher.

name: 'gcr.io/cloud-builders/docker'
args: ['push', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe']

Substitutions

Créez une variable avec une valeur par défaut afin que ces valeurs puissent être modifiées au moment du déploiement.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/massage.sh

Modifier le code JavaScript

Cette commande injecte le point de terminaison du middleware dans le code JavaScript du 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

Conclusion

Vous disposez désormais d'une application de tâches à trois niveaux simple exécutée sur Cloud Run dans votre projet. Vous disposez également de tout le code nécessaire pour modifier ou étendre cette solution en fonction de votre environnement.