서버리스 엔드 투 엔드 사진 공유 애플리케이션

아키텍처

서버리스 엔드 투 엔드 사진 공유 애플리케이션은 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에서 소스 코드 보기


서버리스 엔드 투 엔드 사진 공유 애플리케이션 구성요소

서버리스 엔드 투 엔드 사진 공유 애플리케이션 아키텍처는 여러 제품을 사용합니다. 다음은 관련 동영상 링크, 제품 문서 및 대화형 둘러보기를 포함하여 구성 요소에 대한 자세한 정보와 함께 구성요소 목록을 보여줍니다.
동영상 문서 둘러보기
Cloud IAM Identity and Access Management(IAM)는 관리자에게 중앙에서 관리하는 엔터프라이즈 클라우드 리소스에 대한 정밀한 액세스 제어와 가시성을 제공합니다.
Cloud Run Cloud Run을 사용하면 컨테이너에서 애플리케이션을 실행할 수 있지만 서버리스 방식으로 인스턴스, 프로세스, 메모리 수를 구성할 필요가 없습니다. 컨테이너를 업로드하고 URL을 가져옵니다.
Cloud SQL Cloud SQL은 애플리케이션의 데이터베이스 레이어에 MySQL, SQL Server 또는 Postgres를 제공하는 관리형 SQL입니다.
Cloud Storage Cloud Storage는 파일 스토리지 및 http(s)를 통한 공개 이미지 제공을 지원합니다.
Compute Engine Compute Engine은 Google Cloud의 가상 기술입니다. 이를 사용하면 어떤 컴퓨팅 요구든 그에 맞게 여러 다른 VM 구성을 가동할 수 있습니다.
Secret Manager Cloud Secret Manager는 빌드 프로세스를 위해 애플리케이션에 대해 민감한 세부 정보를 저장합니다.

스크립트

설치 스크립트는 go 및 Terraform CLI 도구로 작성된 실행 파일을 사용하여 빈 프로젝트를 가져오고 여기에 애플리케이션을 설치합니다. 출력은 작동하는 애플리케이션과 부하 분산 IP 주소의 URL입니다.

./main.tf

서비스 사용 설정

Google Cloud 서비스는 기본적으로 프로젝트에서 사용 중지되어 있습니다. 여기에서 솔루션을 사용하려면 다음을 활성화해야 합니다.

  • Cloud Run — 컨테이너를 호스팅하는 서버리스 도구이며 애플리케이션에 액세스하기 위한 URL을 제공합니다.
  • Cloud SQL — 애플리케이션의 데이터베이스 스토리지
  • Compute Engine - 가상 머신 및 네트워킹
  • Cloud Build — 컨테이너 이미지를 만들고 Cloud Run에 배포
  • Cloud Secret Manager - SQL 및 Redis에 대해 호스트 IP를 Cloud Run용 Cloud Build에 삽입하는 데 사용됩니다.
  • 서비스 네트워킹 및 서버리스 VPC 액세스 - 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 Run에서 Cloud SQL에 액세스하도록 허용합니다.

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 Secret에 저장합니다.

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 Secret의 콘텐츠에 액세스할 수 있습니다.

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 Secret에 애플리케이션 보안 비밀을 만듭니다.

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 레코드 만들기

Cloud Run에 컨테이너 이미지를 배포할 수 있도록 Container Registry에 레코드를 만듭니다.

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

결론

실행이 완료되면 소셜 미디어 공유를 허용하는 여러 리전에서 애플리케이션이 완전히 설치된 것입니다. 또한 환경에 맞게 이 솔루션을 수정하거나 확장할 수 있도록 모든 코드가 준비되었습니다.