Application à trois niveaux

Architecture

Une application à trois niveaux est une application de tâches simple, conçue comme une application standard à trois niveaux:

  • Backend
    • Base de données - MySQL - Cloud SQL
    • Mise en cache - Redis - Cloud Memorystore
  • Intergiciel/API
    • API Container Hosting – Golang – Cloud Run
  • Frontal/UI
    • UI hébergée en conteneur – Nginx + HTML/JS/CSS – Cloud Run
  • Déploiement
    • Déploiement continu : Cloud Build
    • Gestion des secrets – Cloud Secret Manager

Commencer

Cliquez sur le lien suivant pour copier le code source dans Cloud Shell. Une seule commande permet alors de lancer une copie de travail de l'application dans votre projet.

Ouvrir dans Cloud Shell

Afficher le code source sur GitHub


Composants d'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 et des liens vers des vidéos associées, de la documentation produit et des tutoriels interactifs.
Vidéo Docs Tutoriels
Cloud SQL Cloud SQL est un service 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, un service 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 la mémoire. Importez un conteneur et 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 dans go et les outils de la CLI Terraform pour prendre un projet vide et y installer l'application. Le résultat doit correspondre à une application fonctionnelle et à une URL pour l'adresse IP de l'équilibrage de charge.

./main.tf

Activer les services

Les services Google Cloud sont désactivés par défaut dans les projets. La fonctionnalité ToDo nécessite l'activation des 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 les rend inaccessibles aux appels externes provenant de l'API.
  • Cloud Build : crée des images de conteneurs et les déploie sur 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 les URL permettant d'accéder à l'application.
  • Cloud SQL : stockage de 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 d'hôte pour SQL et Redis dans Cloud Build pour Cloud Run.
  • Artifact Registry : stocke les images Docker pour les 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 les 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
  • Activez le compte de service Cloud Build pour définir l'accès VPN pour Cloud Run
  • Activer le compte de service Cloud Build pour effectuer des activités de compte de service
  • Permettre au compte de service Cloud Build d'agir au nom du compte de service Compute
  • Activer le compte de service Cloud Build pour publier sur Cloud Run
  • Activer le compte de service Cloud Build pour utiliser les secrets
  • Activez 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 une mise en réseau pour l'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 VPC

Connecte Cloud Run à la base de données et à la mise en 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 SQL Server.

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 les 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 de l'hôte Redis et SQL dans des secrets Cloud.

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 un conteneur d'API dans Cloud Run

La commande suivante utilise Cloud Build pour lancer 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 d'API Cloud Run pour qu'il soit lisible par tous.

Cette couche 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 des 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 l'interface

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 un conteneur d'interface dans Cloud Run

La commande suivante utilise Cloud Build pour lancer 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 Cloud Run pour qu'il soit lisible par tous

Il s'agit de l'interface de l'application, qui affiche le code HTML/JS/CSS via 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 des autorisations sur ces services afin 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 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)
gsutil mb gs://$PROJECT-temp
gsutil cp schema.sql gs://$PROJECT-temp/schema.sql
gsutil iam ch serviceAccount:$SQLSERVICEACCOUNT:objectViewer gs://$PROJECT-temp/
gcloud sql import sql $SQLNAME gs://$PROJECT-temp/schema.sql -q
gsutil rm gs://$PROJECT-temp/schema.sql
gsutil rb gs://$PROJECT-temp

./code/middleware/clouldbuild.yaml

Créer un conteneur d'API

Ce code crée une image Docker pour la couche de 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 que ces valeurs 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 est entièrement statique (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 JavaScript statique dans 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 de middleware:

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

Transférer le conteneur d'API vers Artifact Registry

Le transfert du conteneur vers Artifact Registry permet à Cloud Run d'obtenir l'image et de la diffuser.

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 de l'interface.

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 simple à trois niveaux exécutée sur Cloud Run dans votre projet. Vous disposez également de tout le code nécessaire pour modifier ou étendre cette solution afin de l'adapter à votre environnement.