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