Aplicación de tres niveles

+

Arquitectura

Three Tier App es una aplicación de tareas sencilla diseñada como una aplicación de tres niveles estándar:

  • Backend
    • Base de datos - MySQL - Cloud SQL
    • Almacenamiento en caché: Redis - Cloud Memorystore
  • Middleware/API
    • API alojada en un contenedor (Go) - Cloud Run
  • Front-end/UI
    • Interfaz de usuario alojada en un contenedor: Nginx + HTML/JS/CSS - Cloud Run
  • Despliegue
    • Despliegue continuo: Cloud Build
    • Gestión de secretos: Secret Manager de Cloud

Empezar

Haz clic en el siguiente enlace para acceder a una copia del código fuente en Cloud Shell. Una vez allí, con un solo comando se creará una copia de trabajo de la aplicación en tu proyecto.

Abrir en Cloud Shell

Ver el código fuente en GitHub


Componentes de la aplicación de tres niveles

La arquitectura de aplicaciones de tres niveles usa varios productos. A continuación, se enumeran los componentes y se incluye más información sobre ellos, como enlaces a vídeos relacionados, documentación del producto y tutoriales interactivos.
Vídeo Documentos Guías
Cloud SQL Cloud SQL es un servicio de SQL gestionado que proporciona MySQL, SQL Server o PostgreSQL para la capa de base de datos de tus aplicaciones.
Cloud Memorystore Cloud Memorystore, Redis gestionado, proporciona la capa de almacenamiento en caché de tus aplicaciones.
Cloud Run Cloud Run te permite ejecutar aplicaciones en un contenedor, pero de forma sin servidor, sin tener que configurar el número de instancias, procesadores o memoria. Sube un contenedor y obtén una URL.
Cloud Build Cloud Build es la herramienta que empaqueta los contenedores y los despliega para que estén disponibles como servicios de Cloud Run.
Secret Manager Cloud Secret Manager almacena información sensible sobre la aplicación para el proceso de compilación.

Secuencias de comandos

La secuencia de comandos de instalación usa un ejecutable escrito en go y las herramientas de la CLI de Terraform para tomar un proyecto vacío e instalar la aplicación en él. El resultado debe ser una aplicación que funcione y una URL para la dirección IP de balanceo de carga.

./main.tf

Activación de servicios

Los servicios de Google Cloud están inhabilitados en un proyecto de forma predeterminada. Para usar To Do, debes habilitar los siguientes servicios:

  • Service Networking y Acceso a VPC sin servidor: permite que Cloud Run se comunique con SQL y Redis en una red privada, lo que hace que estos servidores sean inaccesibles para las llamadas externas procedentes de la API.
  • Cloud Build: crea imágenes de contenedor y las despliega en Cloud Run
  • Cloud Memorystore: proporciona una capa de almacenamiento en caché para la aplicación.
  • Cloud Run: la herramienta sin servidor que alojará los contenedores y proporcionará URLs desde las que acceder a la aplicación.
  • Cloud SQL: almacenamiento de bases de datos para la aplicación
  • Cloud Storage: lo usa Cloud Build y se usa para cargar el esquema en la base de datos.
  • Cloud Secret Manager: se usa para insertar las IPs de host de SQL y Redis en Cloud Build para Cloud Run.
  • Artifact Registry: almacena las imágenes de Docker para usarlas 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
}

Definir permisos

El siguiente comando define los roles y permisos de gestión de identidades y accesos que permiten a Cloud Build implementar servicios.

  • Habilitar la cuenta de servicio de Cloud Build para desplegar en Cloud Run
  • Habilitar la cuenta de servicio de Cloud Build para configurar el acceso VPN de Cloud Run
  • Habilitar la cuenta de servicio de Cloud Build para realizar actividades de cuentas de servicio
  • Habilitar la cuenta de servicio de Cloud Build para que actúe en nombre de la cuenta de servicio de Compute
  • Habilitar la cuenta de servicio de Cloud Build para publicar en Cloud Run
  • Habilitar la cuenta de servicio de Cloud Build para que pueda usar secretos
  • Habilitar 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]
}

Crear una red 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 un conector de acceso a la VPC

Conecta Cloud Run con la base de datos y el 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 Redis

Configura e inicializa una instancia de servidor 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 SQL

El siguiente comando configura e 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}"
    }
}

Crear un repositorio de Artifact Registry

El siguiente comando almacena imágenes de Docker para usarlas 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]
}

Crear 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 un artefacto para el 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
  ]
}

Desplegar un contenedor de API en Cloud Run

El siguiente comando usa Cloud Build para poner en marcha un servicio en Cloud Run con el contenedor que acabas de crear.

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

Abrir el servicio de la API Cloud Run para que se pueda leer en todo el mundo.

El navegador del usuario llamará a esta capa de 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, tenemos que abrir los permisos de estos servicios para que sean accesibles en todo el mundo.

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

Desplegar el contenedor del frontend en Cloud Run

El siguiente comando usa Cloud Build para poner en marcha un servicio en Cloud Run con el contenedor que acabamos de crear.

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

Abrir el servicio frontend de Cloud Run para que sea legible por todo el mundo

Es el frontend de la aplicación, que renderizará el HTML, JS y CSS con los que 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 de estos servicios para que sean accesibles en todo el mundo.

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

Inicializar el esquema de la base de datos

Este comando crea un segmento de Cloud Storage temporal para subir el esquema a 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

Crear un contenedor de API

Este código crea una imagen Docker para la capa intermedia.

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 se puedan cambiar en el momento de la implementación.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/clouldbuild.yaml

Masajear el contenido del código

El frontend es completamente estático (HTML, JavaScript y CSS). La aplicación debe apuntar a la URL del servicio de la API que acabamos de crear, pero a los servicios de Cloud Run se les asigna una URL con una cadena aleatoria. Esta secuencia de comandos de "masaje" captura esa URL aleatoria y la inserta en el código del archivo JS estático de este contenedor.

./code/frontend/massage.sh

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

Crear un contenedor de API

El siguiente código crea una imagen de Docker para la capa intermedia:

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

Enviar el contenedor de la API a Artifact Registry

Al enviar el contenedor a Artifact Registry, Cloud Run puede obtener la imagen y servirla.

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 se puedan cambiar en el momento de la implementación.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/massage.sh

Editar JavaScript

Este comando inserta el endpoint del 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 de tareas sencilla de tres niveles que se ejecuta en Cloud Run en tu proyecto. También tienes todo el código para modificar o ampliar esta solución y adaptarla a tu entorno.