La app de tres niveles es una aplicación simple de tareas pendientes diseñada como una aplicación de nivel 3 estándar:
- Backend
- Base de datos, MySQL y Cloud SQL
- Almacenamiento en caché - Redis - Cloud Memorystore
- Middleware y API
- API alojada en contenedor - Golang - Cloud Run
- Frontend/IU
- IU alojada en contenedor - Nginx + HTML/JS/CSS - Cloud Run
- Deployment
- Implementación continua: Cloud Build
- Administración de secretos - Cloud Secret Manager
Comenzar
Haz clic en el siguiente vínculo para obtener una copia del código fuente en Cloud Shell. Una vez allí, un solo comando iniciará una copia de trabajo de la aplicación en tu proyecto.
Consulta el código fuente en GitHub
Componentes de la app de tres niveles
La arquitectura de la app de tres niveles usa varios productos. A continuación, se enumeran los componentes y más información sobre ellos, además de vínculos a videos relacionados, documentación del producto y explicaciones interactivas.Secuencias de comandos
La secuencia de comandos de instalación usa un archivo ejecutable escrito en go
y las herramientas de la CLI de Terraform para
tomar un proyecto vacío y, luego, instalar la aplicación en él. El resultado debe ser una aplicación que funcione y una URL para la dirección IP del balanceo de cargas.
./main.tf
Habilita los servicios
Los servicios de Google Cloud están inhabilitados en un proyecto de forma predeterminada. ToDo requiere que habilites los siguientes servicios:
- Herramientas de redes de servicio y acceso a VPC sin servidores: Permite que Cloud Run se comunique con SQL y Redis en una red privada, lo que mantiene inaccesibles estos servidores desde llamadas externas provenientes de la API.
- Cloud Build: Crea imágenes de contenedor y, luego, impleméntalas en Cloud Run.
- Cloud Memorystore: Proporciona una capa de almacenamiento en caché para la aplicación.
- Cloud Run: Es la herramienta sin servidores que alojará los contenedores y proporcionará las URLs desde las que se accederá a la aplicación.
- Cloud SQL: almacenamiento de la base de datos para la aplicación
- Cloud Storage: lo usa Cloud Build y para cargar el esquema en la base de datos
- Cloud Secret Manager: se usa a fin de insertar las IP del host para SQL y Redis en Cloud Build para Cloud Run.
- Artifact Registry: almacena las imágenes de Docker para usar con 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
}
Configurar permisos
El siguiente comando establece roles y permisos de IAM que permiten que Cloud Build implemente servicios.
- Habilita la cuenta de servicio de Cloud Build para implementar en Cloud Run
- Habilita la cuenta de servicio de Cloud Build a fin de configurar el acceso a VPN para Cloud Run
- Habilitar que la cuenta de servicio de Cloud Build realice las actividades de la cuenta de servicio
- Habilitar que la cuenta de servicio de Cloud Build actúe en nombre de la cuenta de servicio de Compute
- Habilita la cuenta de servicio de Cloud Build para publicar en Cloud Run
- Habilitar que la cuenta de servicio de Cloud Build consuma secretos
- Habilita la cuenta de servicio de Cloud Build para almacenar contenedores en 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]
}
Crea herramientas de redes para la instancia de SQL
El siguiente comando permite que se pueda acceder a Cloud SQL desde 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]
}
Crear conector de acceso a VPC
Conecta Cloud Run a la base de datos y al almacenamiento en caché
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]
}
Crear servidor de Redis
Configura e inicializa una instancia del servidor de 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]
}
Crear un servidor de SQL
El siguiente comando configura y, luego, inicializa una instancia de 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}"
}
}
Crea el repositorio de Artifact Registry
El siguiente comando almacena imágenes de Docker para usar con 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]
}
Crea secretos
El siguiente comando almacena datos de host de Redis y SQL en 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]
}
Crear artefacto para middleware
El siguiente comando crea la imagen de Docker y la aloja en 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
]
}
Implementar el contenedor de la API en Cloud Run
El siguiente comando usa Cloud Build para iniciar un servicio en Cloud Run con el contenedor que acabas de compilar.
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
]
}
Abre el servicio de la API de Cloud Run para que sea legible en todo el mundo.
El navegador del usuario llamará a esta capa de la API de la aplicación, pero los servicios de Cloud Run no son públicos de forma predeterminada. Para que los usuarios puedan consumir este servicio, debemos abrir los permisos de estos servicios a fin de que todo el mundo pueda acceder a ellos.
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
}
Crear artefacto para el frontend
El siguiente comando crea la imagen de Docker y la aloja en 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
]
}
Implementa el contenedor de frontend en Cloud Run
El siguiente comando usa Cloud Build para iniciar un servicio en Cloud Run con el contenedor que acabamos de compilar.
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]
}
Abre el servicio de frontend de Cloud Run para que sea legible en todo el mundo
Este es el frontend de la aplicación, que renderizará el código HTML/JS/CSS mediante el cual el usuario interactúa con la aplicación. De forma predeterminada, los servicios de Cloud Run no son públicos. Para que esta aplicación funcione, tenemos que abrir los permisos en estos servicios para que todo el mundo pueda acceder a ellos.
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
Inicializa el esquema de la base de datos
Este comando crea un bucket temporal de Cloud Storage para subir el esquema a 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
Contenedor de API de compilación
Este código crea una imagen de Docker para la capa 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']
Sustituciones
El siguiente código crea variables con valores predeterminados para que estos valores puedan modificarse en el momento de la implementación.
substitutions:
_REGION: us-central1
_BASENAME: todo
./code/frontend/clouldbuild.yaml
Contenido del código de masajes
El frontend es HTML/JS/CSS completamente estático. La app debe apuntar a la URL del servicio de API que acabamos de crear, pero a los servicios de Cloud Run se les asigna una URL con una string aleatoria. Esta "secuencia de comandos de masaje" captura esa URL aleatoria y la inserta en el código del JS estático de este contenedor.
name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: bash
args: [ './massage.sh', '$_REGION' ]
Contenedor de API de compilación
El siguiente código crea una imagen de Docker para la capa de middleware:
name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe', '.' ]
Envía el contenedor de la API a Artifact Registry
Enviar el contenedor a Artifact Registry permite que Cloud Run obtenga la imagen y la entregue.
name: 'gcr.io/cloud-builders/docker'
args: ['push', '$_REGION-docker.pkg.dev/$PROJECT_ID/$_BASENAME-app/fe']
Sustituciones
Crea una variable con un valor predeterminado para que estos valores puedan cambiarse en el momento de la implementación.
substitutions:
_REGION: us-central1
_BASENAME: todo
./code/frontend/massage.sh
Edita JavaScript
Este comando inserta el extremo para el middleware en el JavaScript del frontend.
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
Conclusión
Ahora tienes una aplicación simple de tareas pendientes de 3 niveles que se ejecuta en Cloud Run en tu proyecto. También tienes todo el código para modificar o extender esta solución a fin de que se ajuste a tu entorno.