אפליקציה תלת-שכבתית

ארכיטקטורה

אפליקציה תלת-שכבתית היא אפליקציית מטלות פשוטה שתוכננה כאפליקציה תלת-שכבתית רגילה:

  • בק-אנד
    • מסד נתונים – MySQL – Cloud SQL
    • שמירה במטמון – Redis – Cloud Memorystore
  • Middleware/API
    • ‫API שמתארח בקונטיינר – Golang – Cloud Run
  • חלק קדמי/ממשק משתמש
    • ממשק משתמש שמתארח בקונטיינר – Nginx + HTML/JS/CSS – Cloud Run
  • פריסה
    • פריסה רציפה (CD) – Cloud Build
    • ניהול סודות – Cloud Secret Manager

שנתחיל?

כדי להעתיק את קוד המקור ב-Cloud Shell, לוחצים על הקישור הבא. אחרי שמגיעים לשם, פקודה אחת תתחיל הרצה של עותק עובד של האפליקציה בפרויקט.

Cloud Shell-פתיחה ב

צפייה בקוד המקור ב-GitHub


רכיבים של אפליקציה עם שלוש שכבות

ארכיטקטורת האפליקציה בת שלוש השכבות משתמשת בכמה מוצרים. ברשימה הבאה מפורטים הרכיבים, וגם מידע נוסף על הרכיבים, כולל קישורים לסרטונים קשורים, למסמכי מוצר ולמדריכים אינטראקטיביים.
וידאו Docs הדרכות מפורטות
Cloud SQL ‫Cloud SQL הוא שירות SQL מנוהל שמספק MySQL,‏ SQL Server או Postgres לשכבת מסד הנתונים של האפליקציות שלכם.
Cloud Memorystore שירות Cloud Memorystore, שהוא Redis מנוהל, מספק את שכבת ה-caching לאפליקציות שלכם.
Cloud Run ב-Cloud Run אפשר להריץ אפליקציות בקונטיינר, אבל בצורה חסרת שרת, בלי צורך להגדיר את מספר המופעים, המעבדים או הזיכרון. מעלים מאגר תגים ומקבלים כתובת URL.
Cloud Build ‫Cloud Build הוא הכלי שמאגד את הקונטיינרים ופורס אותם כך שיהיו זמינים כשירותי Cloud Run.
Secret Manager ‫Cloud Secret Manager מאחסן פרטים רגישים על האפליקציה לתהליך build.

סקריפטים

סקריפט ההתקנה משתמש בקובץ הפעלה שנכתב ב-go ובכלים של Terraform CLI כדי לקחת פרויקט ריק ולהתקין בו את האפליקציה. הפלט צריך להיות אפליקציה פעילה וכתובת URL לכתובת ה-IP של איזון העומסים.

./main.tf

הפעלת שירותים

שירותי Google Cloud מושבתים בפרויקט כברירת מחדל. כדי להשתמש ב-ToDo, צריך להפעיל את השירותים הבאים:

  • Service Networking & Serverless VPC Access – מאפשרים ל-Cloud Run לתקשר עם SQL ו-Redis ברשת פרטית, כך שהשרתים האלה לא נגישים לקריאות חיצוניות שמגיעות מממשק ה-API.
  • Cloud Build – יוצר תמונות של קונטיינרים ופורס אותן ב-Cloud Run
  • Cloud Memorystore – מספק שכבת מטמון לאפליקציה.
  • Cloud Run – הכלי בלי שרת (serverless) שיארח את הקונטיינרים ויספק כתובות URL לגישה לאפליקציה.
  • Cloud SQL – אחסון מסד נתונים לאפליקציה
  • Cloud Storage – משמש את Cloud Build, וגם לטעינת הסכימה במסד הנתונים
  • Cloud Secret Manager – משמש להחדרת כתובות ה-IP של המארחים עבור SQL ו-Redis אל Cloud Build עבור Cloud Run.
  • Artifact Registry – מאחסן את קובצי האימג' של Docker לשימוש עם 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
}

הגדרת ההרשאות

הפקודה הבאה מגדירה הרשאות ותפקידים ב-IAM שמאפשרים ל-Cloud Build לפרוס שירותים.

  • הפעלת חשבון השירות של Cloud Build כדי לפרוס ל-Cloud Run
  • הפעלת חשבון השירות של Cloud Build כדי להגדיר גישת VPN ל-Cloud Run
  • הפעלת האפשרות לחשבון השירות של Cloud Build לבצע פעולות בחשבון השירות
  • הפעלת חשבון השירות של Cloud Build כדי שיפעל בשם חשבון השירות של Compute
  • הפעלת האפשרות לחשבון השירות ב-Cloud Build לפרסם ב-Cloud Run
  • הפעלת חשבון השירות של Cloud Build כדי להשתמש בסודות
  • הפעלת חשבון השירות של Cloud Build כדי לאחסן קונטיינרים ב-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]
}

יצירת רשת עבור מופע SQL

הפקודה הבאה מאפשרת גישה ל-Cloud SQL מ-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]
}

יצירת חיבור לרשת VPC

חיבור Cloud Run למסד נתונים ולשמירת נתונים במטמון

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

יצירת שרת Redis

הגדרת מופע של שרת 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]
}

יצירת שרת SQL

הפקודה הבאה מגדירה ומפעילה מכונת שרת 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}"
    }
}

יצירת מאגר Artifact Registry

הפקודה הבאה שומרת תמונות Docker לשימוש ב-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]
}

יצירת סודות

הפקודה הבאה מאחסנת נתוני מארח של Redis ו-SQL ב-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]
}

יצירת פריט מידע שנוצר בתהליך פיתוח (Artifact) לתוכנת ביניים

הפקודה הבאה יוצרת את קובץ האימג' של Docker ומארחת אותו ב-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
  ]
}

פריסת קונטיינר של API ב-Cloud Run

הפקודה הבאה משתמשת ב-Cloud Build כדי להפעיל שירות ב-Cloud Run באמצעות הקונטיינר שיצרתם.

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

פותחים את שירות Cloud Run API כדי שיהיה ניתן לקריאה על ידי כל העולם.

הדפדפן של המשתמש ישלח קריאה לשכבת ה-API הזו של האפליקציה, אבל כברירת מחדל שירותי Cloud Run לא פתוחים לציבור. כדי שהמשתמשים יוכלו להשתמש בשירות הזה, אנחנו צריכים לפתוח את ההרשאות בשירותים האלה כדי שיהיו נגישים לכולם.

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
}

יצירת ארטיפקט לממשק קצה

הפקודה הבאה יוצרת את קובץ האימג' של Docker ומארחת אותו ב-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
    ]
}

פריסת קונטיינר של חזית האתר ב-Cloud Run

הפקודה הבאה משתמשת ב-Cloud Build כדי להתחיל הרצה של שירות ב-Cloud Run באמצעות הקונטיינר שיצרנו

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

פתיחת שירות ממשק קצה של Cloud Run לקריאה על ידי כל העולם

זהו ממשק הקצה של האפליקציה, שדרכו מוצגים קובצי ה-HTML/JS/CSS שהמשתמשים מקיימים איתם אינטראקציה. כברירת מחדל, שירותי Cloud Run לא פתוחים לציבור. כדי שהאפליקציה הזו תפעל, אנחנו צריכים לפתוח הרשאות בשירותים האלה כדי שיהיו נגישים לכולם.

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

הפעלת סכימת מסד הנתונים

הפקודה הזו יוצרת קטגוריה זמנית ב-Cloud Storage כדי להעלות את הסכימה ל-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

יצירת מאגר API

הקוד הזה יוצר קובץ אימג' של Docker לשכבת התוכנה.

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:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/clouldbuild.yaml

תוכן קוד העיסוי

ממשק הקצה הוא HTML/JS/CSS סטטי לחלוטין. האפליקציה צריכה להפנות לכתובת ה-URL של שירות ה-API שיצרנו, אבל לשירותי Cloud Run מוקצית כתובת URL עם מחרוזת אקראית. הסקריפט הזה של 'עיבוד נתונים' מתעד את כתובת ה-URL האקראית ומחדיר אותה לקוד של ה-JS הסטטי במאגר התגים הזה.

./code/frontend/massage.sh

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

יצירת מאגר API

הקוד הבא יוצר קובץ אימג' של Docker לשכבת התוכנה:

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

העברת קונטיינר של Push API אל Artifact Registry

העברת הקונטיינר ל-Artifact Registry מאפשרת ל-Cloud Run לקבל את האימג' ולהציג אותו.

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

החלפות

יוצרים משתנה עם ערך ברירת מחדל כדי שאפשר יהיה לשנות את הערכים האלה בזמן הפריסה.

substitutions:
  _REGION: us-central1
  _BASENAME: todo

./code/frontend/massage.sh

עריכת JavaScript

הפקודה הזו מזריקה את נקודת הקצה של תוכנת הביניים ל-JavaScript של ממשק הקצה.

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

סיכום

עכשיו יש לכם אפליקציית רשימת מטלות פשוטה עם 3 רמות שפועלת ב-Cloud Run בפרויקט שלכם. יש לכם גם את כל הקוד כדי לשנות או להרחיב את הפתרון הזה כך שיתאים לסביבה שלכם.