אפליקציה לשיתוף תמונות מקצה לקצה ללא שרת

ארכיטקטורה

אפליקציה לשיתוף תמונות מקצה לקצה ללא שרת (serverless) יוצרת אפליקציה ניתנת להרחבה לשיתוף תמונות מקצה לקצה באמצעות 11 מוצרים של Google Cloud,‏ Terraform ו-Django.

חבילת הפתרון הזו תגדיר ותיצור את הרכיבים הבאים:

  • ‫Cloud Run – שבו האפליקציה תפעל כשרת הראשי
  • ‫Cloud SQL – לאחסון מסד נתונים רלציוני כמו פרטי משתמשים, פוסטים
  • ‫Cloud Storage – לאחסון מסד נתונים לא רלציוני, כמו מדיה בפוסט
  • Cloud Load Balancer – להפניית תנועה לשרתים בכמה אזורים
  • ‫Cloud DNS – למיפוי דומיין מותאם אישית
  • ‫Cloud Build – לפריסה אוטומטית של האפליקציה מ-gcloud
  • ‫Secret Manager – כדי לשפר את האבטחה של האפליקציה
  • Cloud VPC – כדי לקשר בין Cloud SQL ל-Cloud Run באמצעות שיפור פרטי
  • ‫Cloud DNS – לאחסון מטמון סטטי לחיבורים מהירים יותר
  • ‫Translation API – לתרגום הכיתוב של הפוסט אם הוא בשפה אחרת

שנתחיל?

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

Cloud Shell-פתיחה ב

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


רכיבים של אפליקציה לשיתוף תמונות מקצה לקצה ללא שרת

ארכיטקטורת האפליקציה לשיתוף תמונות מקצה לקצה בלי שרת (serverless) מתבססת על כמה מוצרים. ברשימה הבאה מפורטים הרכיבים, וגם מידע נוסף על הרכיבים, כולל קישורים לסרטונים קשורים, למסמכי מוצר ולמדריכים אינטראקטיביים.
וידאו Docs הדרכות מפורטות
Cloud IAM הפלטפורמה לניהול זהויות והרשאות גישה (IAM) מאפשרת לאדמינים לנהל באופן מרכזי משאבי ענן ארגוניים, עם בקרת גישה פרטנית והרשאות גישה.
Cloud Run ב-Cloud Run אפשר להריץ אפליקציות בקונטיינר, אבל בצורה חסרת שרת, בלי צורך להגדיר את מספר המופעים, המעבדים או הזיכרון. מעלים מאגר תגים ומקבלים כתובת URL.
Cloud SQL ‫Cloud SQL הוא שירות SQL מנוהל שמספק MySQL,‏ SQL Server או Postgres לשכבת מסד הנתונים של האפליקציות שלכם.
Cloud Storage ‫Cloud Storage מספק אחסון קבצים והצגה ציבורית של תמונות באמצעות http(s).
Compute Engine ‫Compute Engine היא טכנולוגיה וירטואלית של Google Cloud. בעזרת התכונה הזו תוכלו להגדיר הרבה מכונות וירטואליות שונות בהתאם לצרכים שלכם.
Secret Manager ‫Cloud Secret Manager מאחסן פרטים רגישים על האפליקציה לתהליך build.

סקריפטים

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

./main.tf

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

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

  • Cloud Run – הכלי בלי שרת (serverless) שיארח את הקונטיינרים ויספק כתובות URL לגישה לאפליקציה.
  • Cloud SQL – אחסון מסד נתונים לאפליקציה
  • Compute Engine – מכונות וירטואליות ורשתות
  • Cloud Build – יוצר קובצי אימג' של קונטיינרים ופורס אותם ב-Cloud Run
  • Cloud Secret Manager – משמש להחדרת כתובות ה-IP של המארחים עבור SQL ו-Redis אל Cloud Build עבור Cloud Run.
  • Service Networking & Serverless VPC Access – מאפשרים ל-Cloud Run לתקשר עם SQL ו-Redis ברשת פרטית, כך שהשרתים האלה לא נגישים לקריאות חיצוניות שמגיעות מממשק ה-API.
  • Cloud IAM – הכלי לניהול הגישה וההרשאות למשאבים ב-Google Cloud
# Step 2: Activate service APIs
resource "google_project_service" "run" {
  service            = "run.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "sql-component" {
  service            = "sql-component.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "sqladmin" {
  service            = "sqladmin.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "compute" {
  service            = "compute.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "cloudbuild" {
  service            = "cloudbuild.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "secretmanager" {
  service            = "secretmanager.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "vpcaccess" {
  service            = "vpcaccess.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "servicenetworking" {
  service            = "servicenetworking.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "iam" {
  service            = "iam.googleapis.com"
  disable_on_destroy = false
}

יצירת רשת פרטית

הפקודה הבאה יוצרת רשת פרטית שכל המשאבים יכולים להשתמש בה כדי לתקשר בצורה מאובטחת בתוך האפליקציה.

resource "google_compute_network" "main" {
  provider   = google
  name       = "social-media-network-${random_id.name.hex}"
  depends_on = [google_project_iam_member.serviceagent]
}

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

הפקודה הבאה מאפשרת גישה ל-Cloud SQL מ-Cloud Run:

resource "google_compute_global_address" "private_ip_address" {
  provider      = google-beta
  project       = var.project
  name          = local.private_ip_name
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 16
  network       = google_compute_network.main.id
  depends_on    = [google_project_service.vpcaccess, google_project_iam_member.serviceagent]
}

resource "google_service_networking_connection" "private_vpc_connection" {
  provider = google-beta

  network                 = google_compute_network.main.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
  depends_on              = [google_project_service.vpcaccess, google_project_iam_member.serviceagent]
}

אישור גישה של סוכני שירות לפרויקט

הפקודה הזו מאפשרת לסוכן השירות לגשת לפרויקט כדי להפעיל את ההגדרה של מחבר ה-VPC.

resource "google_project_iam_member" "serviceagent" {
  project = data.google_project.project.number
  role    = "roles/editor"
  member  = local.serviceagent_serviceaccount
}

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

הפקודה הזו מחברת את Cloud Run למסד הנתונים

resource "google_vpc_access_connector" "connector" {
  for_each      = { "us-west1" : 8, "us-central1" : 9, "us-east1" : 10 }
  name          = "vpc-con-${each.key}"
  ip_cidr_range = "10.${each.value}.0.0/28"
  region        = each.key
  network       = google_compute_network.main.name
  depends_on    = [google_project_service.vpcaccess, google_project_iam_member.serviceagent]
}

יצירת חשבון שירות

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

# Step 4: Create a custom Service Account
resource "google_service_account" "django" {
  account_id = "django"
  depends_on = [
    google_project_service.iam
  ]
}

יצירת שרת SQL

קבוצת הפקודות הבאה מגדירה ומפעילה מופע של שרת SQL.

resource "random_string" "random" {
  length  = 4
  special = false
}

resource "random_password" "database_password" {
  length  = 32
  special = false
}

resource "random_id" "name" {
  byte_length = 2
}

resource "random_id" "db_name_suffix" {
  byte_length = 4
}

resource "google_sql_database_instance" "instance" {
  name             = local.sql_database_name
  database_version = "MYSQL_8_0"
  region           = var.region
  project          = var.project
  depends_on       = [google_vpc_access_connector.connector]
  settings {
    tier = "db-f1-micro"
    ip_configuration {
      ipv4_enabled    = "true"
      private_network = google_compute_network.main.id
    }
  }
  deletion_protection = false
}

resource "google_sql_database" "database" {
  name     = "django"
  instance = google_sql_database_instance.instance.name
}

resource "google_sql_user" "django" {
  name     = "django"
  instance = google_sql_database_instance.instance.name
  password = random_password.database_password.result
}

יצירת קטגוריות אחסון

יוצר את מיקום האחסון של המדיה כדי שהאפליקציה תאחסן אותה ותציג אותה למשתמשים.

resource "google_storage_bucket" "media" {
  name     = "${var.project}-bucket"
  location = "US"
}

resource "google_storage_bucket_iam_binding" "main" {
  bucket = google_storage_bucket.media.name
  role   = "roles/storage.objectViewer"
  members = [
    "allUsers",
  ]
}

יצירת סודות

קבוצת הפקודות הבאה יוצרת הגדרות רגישות של אפליקציות ומשאבים לאחסון שלהן ב-Cloud Secrets.

resource "google_secret_manager_secret_version" "django_settings" {
  secret = google_secret_manager_secret.django_settings.id

  secret_data = templatefile("etc/env.tpl", {
    bucket     = google_storage_bucket.media.name
    secret_key = random_password.django_secret_key.result
    user       = google_sql_user.django
    instance   = google_sql_database_instance.instance
    database   = google_sql_database.database
  })
}

resource "random_password" "django_secret_key" {
  special = false
  length  = 50
}

resource "google_secret_manager_secret" "django_settings" {
  secret_id = "django_settings"

  replication {
    automatic = true
  }
  depends_on = [google_project_service.secretmanager]

}

הגדרת הרשאות לחשבון שירות

הפקודה הזו מאפשרת לחשבון השירות של Cloud Build ולחשבון השירות של האפליקציה לגשת לתוכן ב-Cloud Secrets.

resource "google_secret_manager_secret_iam_binding" "django_settings" {
  secret_id = google_secret_manager_secret.django_settings.id
  role      = "roles/secretmanager.admin"
  members   = [local.cloudbuild_serviceaccount, local.django_serviceaccount]
}

אכלוס סודות

קבוצת הפקודות הבאה יוצרת חנויות של סודות אפליקציות ב-Cloud Secrets.

resource "google_secret_manager_secret" "main" {
  for_each = {
    "DATABASE_PASSWORD" : google_sql_user.django.password,
    "DATABASE_USER" : google_sql_user.django.name,
    "DATABASE_NAME" : google_sql_database.database.name,
    "DATABASE_HOST_PROD" : google_sql_database_instance.instance.private_ip_address,
    "DATABASE_PORT_PROD" : 3306,
    "PROJECT_ID" : var.project,
    "GS_BUCKET_NAME" : var.project,
  }
  secret_id = each.key
  replication {
    automatic = true
  }

  depends_on = [google_sql_user.django, google_sql_database.database, google_sql_database_instance.instance]

}

resource "google_secret_manager_secret" "network" {
  for_each = {
    "EXTERNAL_IP" : module.lb-http.external_ip,
  }
  secret_id = each.key
  replication {
    automatic = true
  }
  depends_on = [module.lb-http, google_compute_network.main, google_cloud_run_service.service]
}

resource "google_secret_manager_secret" "url" {
  for_each = {
    "WEBSITE_URL_US_CENTRAL1" : google_cloud_run_service.service["us-central1"].status[0].url,
    "WEBSITE_URL_US_WEST1" : google_cloud_run_service.service["us-west1"].status[0].url,
    "WEBSITE_URL_US_EAST1" : google_cloud_run_service.service["us-east1"].status[0].url,
  }
  secret_id = each.key
  replication {
    automatic = true
  }
  depends_on = [google_cloud_run_service.service]
}

resource "google_secret_manager_secret_version" "main" {
  for_each = { "DATABASE_PASSWORD" : google_sql_user.django.password,
    "DATABASE_USER" : google_sql_user.django.name,
    "DATABASE_NAME" : google_sql_database.database.name,
    "DATABASE_HOST_PROD" : google_sql_database_instance.instance.private_ip_address,
    "DATABASE_PORT_PROD" : 3306,
    "PROJECT_ID" : var.project,
    "GS_BUCKET_NAME" : var.project,
  }
  secret      = google_secret_manager_secret.main[each.key].id
  secret_data = each.value
}

resource "google_secret_manager_secret_version" "network" {
  for_each = {
    "EXTERNAL_IP" : module.lb-http.external_ip,
  }
  secret      = google_secret_manager_secret.network[each.key].id
  secret_data = each.value
}

resource "google_secret_manager_secret_version" "url" {
  for_each = {
    "WEBSITE_URL_US_CENTRAL1" : google_cloud_run_service.service["us-central1"].status[0].url,
    "WEBSITE_URL_US_WEST1" : google_cloud_run_service.service["us-west1"].status[0].url,
    "WEBSITE_URL_US_EAST1" : google_cloud_run_service.service["us-east1"].status[0].url,
  }
  secret      = google_secret_manager_secret.url[each.key].id
  secret_data = each.value
}

resource "google_secret_manager_secret_iam_binding" "main" {
  for_each = { "DATABASE_PASSWORD" : google_sql_user.django.password,
    "DATABASE_USER" : google_sql_user.django.name,
    "DATABASE_NAME" : google_sql_database.database.name,
    "DATABASE_HOST_PROD" : google_sql_database_instance.instance.private_ip_address,
    "DATABASE_PORT_PROD" : 3306,
    "PROJECT_ID" : var.project,
    "GS_BUCKET_NAME" : var.project,
  }
  secret_id = google_secret_manager_secret.main[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

resource "google_secret_manager_secret_iam_binding" "network" {
  for_each = {
    "EXTERNAL_IP" : module.lb-http.external_ip,
  }
  secret_id = google_secret_manager_secret.network[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

resource "google_secret_manager_secret_iam_binding" "url" {
  for_each = {
    "WEBSITE_URL_US_CENTRAL1" : google_cloud_run_service.service["us-central1"].status[0].url,
    "WEBSITE_URL_US_WEST1" : google_cloud_run_service.service["us-west1"].status[0].url,
    "WEBSITE_URL_US_EAST1" : google_cloud_run_service.service["us-east1"].status[0].url,
  }
  secret_id = google_secret_manager_secret.url[each.key].id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

resource "random_password" "SUPERUSER_PASSWORD" {
  length  = 32
  special = false
}

resource "google_secret_manager_secret" "SUPERUSER_PASSWORD" {
  secret_id = "SUPERUSER_PASSWORD"
  replication {
    automatic = true
  }
  depends_on = [google_project_service.secretmanager]
}

resource "google_secret_manager_secret_version" "SUPERUSER_PASSWORD" {
  secret      = google_secret_manager_secret.SUPERUSER_PASSWORD.id
  secret_data = random_password.SUPERUSER_PASSWORD.result
}

resource "google_secret_manager_secret_iam_binding" "SUPERUSER_PASSWORD" {
  secret_id = google_secret_manager_secret.SUPERUSER_PASSWORD.id
  role      = "roles/secretmanager.secretAccessor"
  members   = [local.cloudbuild_serviceaccount]
}

יצירת רשומה ב-Container Registry

יוצר רשומה ב-Container Registry כדי לאפשר פריסה של קובץ אימג' של קונטיינר ב-Cloud Run

resource "google_container_registry" "main" {
  project  = var.project
  location = "US"
}

הגדרת מיקום האחסון ב-Container Registry כציבורי

נותן גישת קריאה לכל העולם למיקום ב-Container Registry, כך שכל אחד יכול להשתמש בקובץ האימג' של הקונטיינר.

resource "google_storage_bucket_iam_member" "repo_public" {
  bucket = google_container_registry.main.id
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

יצירת קובץ אימג' של קונטיינר

הפקודה הבאה יוצרת את קובץ האימג' של Docker ומארחת אותו ב-Container Registry:

resource "null_resource" "cloudbuild_api" {
  provisioner "local-exec" {
    working_dir = path.module
    command     = "gcloud builds submit . "
  }

  depends_on = [
    google_container_registry.main
  ]
}

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

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

resource "google_cloud_run_service" "service" {
  for_each                   = toset([for location in local.runlocations : location if can(regex("us-(?:west|central|east)1", location))])
  name                       = var.project
  location                   = each.value
  project                    = var.project
  autogenerate_revision_name = true
  depends_on = [
    # google_sql_database_instance.instance,
    google_service_account.django,
    google_sql_database_instance.instance,
    google_vpc_access_connector.connector,
  ]

  template {
    spec {
      service_account_name = google_service_account.django.email
      containers {
        image = "gcr.io/${var.project}/${var.service}:latest"
        env {
          name  = "PROJECT_ID"
          value = var.project
        }
      }
    }

    metadata {
      annotations = {
        "autoscaling.knative.dev/maxScale"        = "100"
        "run.googleapis.com/cloudsql-instances"   = google_sql_database_instance.instance.connection_name
        "run.googleapis.com/client-name"          = "terraform"
        "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector[each.key].name
        "run.googleapis.com/vpc-access-egress"    = "all-traffic"
      }
    }
  }

  traffic {
    percent         = 100
    latest_revision = true
  }
}

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

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

resource "google_cloud_run_service_iam_policy" "noauth" {
  for_each = toset([for location in local.runlocations : location if can(regex("us-(?:west|central|east)1", location))])
  location = google_cloud_run_service.service[each.key].location
  project  = google_cloud_run_service.service[each.key].project
  service  = google_cloud_run_service.service[each.key].name

  policy_data = data.google_iam_policy.noauth.policy_data
}

יצירת מאזן עומסים

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

# Step 11: Create Load Balancer to handle traffics from multiple regions 
resource "google_compute_region_network_endpoint_group" "default" {
  for_each              = toset([for location in local.runlocations : location if can(regex("us-(?:west|central|east)1", location))])
  name                  = "${var.project}--neg--${each.key}"
  network_endpoint_type = "SERVERLESS"
  region                = google_cloud_run_service.service[each.key].location
  cloud_run {
    service = google_cloud_run_service.service[each.key].name
  }
  depends_on = [google_cloud_run_service.service]
}

module "lb-http" {
  source  = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs"
  version = "~> 4.5"

  project = var.project
  name    = var.project

  ssl                             = false
  https_redirect                  = true
  managed_ssl_certificate_domains = []
  use_ssl_certificates            = false
  backends = {
    default = {
      description            = null
      enable_cdn             = true
      custom_request_headers = null

      log_config = {
        enable      = true
        sample_rate = 1.0
      }

      groups = [
        for neg in google_compute_region_network_endpoint_group.default :
        {
          group = neg.id
        }
      ]

      iap_config = {
        enable               = false
        oauth2_client_id     = null
        oauth2_client_secret = null
      }
      security_policy = null
    }
  }
}

מתן הרשאות שונות

קבוצת הפקודות הבאה מעניקה הרשאות לחשבונות של מסד הנתונים ולחשבונות השירות של Cloud Build.

# Step 12: Grant access to the database
resource "google_project_iam_member" "service_permissions_cb_django" {
  for_each = toset([
    "run.admin", "cloudsql.client", "editor", "secretmanager.admin"
  ])



  role   = "roles/${each.key}"
  member = local.django_serviceaccount
}

resource "google_project_iam_member" "service_permissions_cb" {
  for_each = toset([
    "run.admin", "cloudsql.client", "editor", "secretmanager.admin"
  ])



  role   = "roles/${each.key}"
  member = local.cloudbuild_serviceaccount
}

resource "google_service_account_iam_binding" "cloudbuild_sa" {
  service_account_id = google_service_account.django.name
  role               = "roles/editor"

  members = [local.cloudbuild_serviceaccount]
}

סיכום

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