App de tres niveles

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Arquitectura

La app de tres niveles es una aplicación simple de tareas pendientes diseñada como una aplicación de vainilla de 3 niveles:

  • Backend
    • Base de datos - MySQL - Cloud SQL
    • Almacenamiento en caché: Redis (Cloud Memorystore)
  • Middleware/API
    • API alojada en contenedor - Golang - Cloud Run
  • Interfaz/IU
    • IU alojada en contenedores - Nginx + HTML/JS/CSS - Cloud Run
  • Implementación
    • Implementación continua: Cloud Build
    • Administración de secretos: Cloud Secret Manager

Comenzar

Haga clic en el siguiente vínculo para obtener una copia del código fuente en Cloud Shell. Una vez ahí, un solo comando iniciará una copia de la aplicación en funcionamiento en tu proyecto.

Abrir en Cloud Shell

Ver 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, junto con más información sobre los componentes, incluidos los vínculos a videos relacionados, documentación del producto y explicaciones interactivas.
Video Documentos Explicaciones
Cloud SQL Cloud SQL es SQL administrado que proporciona MySQL, SQL Server o Postgres para la capa de la base de datos de tus aplicaciones.
Cloud Memorystore Cloud Memorystore, administrado por Redis, proporciona la capa de almacenamiento en caché para tus aplicaciones.
Cloud Run Cloud Run te permite ejecutar aplicaciones en un contenedor, pero sin servidores, sin tener que configurar la cantidad 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 implementa para que estén disponibles como servicios de Cloud Run.
Secret Manager Cloud Secret Manager almacena detalles sensibles 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 y, luego, instalar la aplicación en él. El resultado debe ser una aplicación en funcionamiento y una URL para la dirección IP de balanceo de cargas.

./main.tf

Habilita los servicios

Los servicios de Google Cloud están inhabilitados en un proyecto de forma predeterminada. Las tareas pendientes requieren 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 redes privadas, lo que mantiene inaccesibles a estos servidores desde las llamadas externas que provienen de la API.
  • Cloud Build: Crea imágenes de contenedor y se implementa en Cloud Run.
  • Cloud Memorystore: Proporciona una capa de almacenamiento en caché para la aplicación.
  • Cloud Run: la herramienta sin servidores que alojará los contenedores, y proporcionará las URL desde las que se accederá a la aplicación.
  • Cloud SQL: almacenamiento de la base de datos para la aplicación
  • Cloud Storage: Cloud Build lo usa para cargar el esquema en la base de datos.
  • Cloud Secret Manager: Se usa para insertar las IP 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
}

Configurar permisos

El siguiente comando configura las funciones de IAM y los permisos que permiten que Cloud Build implemente servicios.

  • Habilita la cuenta de servicio de Cloud Build para implementarla en Cloud Run
  • Habilita la cuenta de servicio de Cloud Build a fin de configurar el acceso de VPN para Cloud Run
  • Habilita la cuenta de servicio de Cloud Build para realizar actividades de la cuenta de servicio
  • Habilitar la cuenta de servicio de Cloud Build para que actúe en nombre de la cuenta de servicio de Compute
  • Habilita la cuenta de servicio de Cloud Build para publicar en Cloud Run
  • Habilita la cuenta de servicio de Cloud Build para consumir 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 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 de Redis

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

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

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

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

Implementa el contenedor de la API en Cloud Run

El siguiente comando usa Cloud Build para iniciar un servicio en Cloud Run mediante el contenedor que acaba 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 API de la aplicación, pero, de forma predeterminada, los servicios de Cloud Run no son públicos. Para que los usuarios consuman este servicio, debemos abrir los permisos a fin de que sean accesibles para 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 artefacto para la interfaz

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 mediante 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 mostrará el HTML/JS/CSS a través del 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, debemos abrir los permisos de estos servicios a fin de que sean accesibles para 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

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

Compila un contenedor de API

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 se puedan cambiar 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 aplicación 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 masa” captura esa URL aleatoria y la inserta en el código del JS estático en este contenedor.

./code/frontend/massage.sh

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

Compila un contenedor de API

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

Editar JavaScript

Este comando inserta el extremo del middleware en el código JavaScript de la interfaz.

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