サーバーレスのエンドツーエンドの写真共有アプリケーション

アーキテクチャ

サーバーレスのエンドツーエンドの写真共有アプリケーションによって、11 の Google Cloud プロダクト、Terraform、Django によるスケーラブルなエンドツーエンドの写真共有アプリケーションが作成されます。

このスタックは次のコンポーネントを構成、作成します。

  • Cloud Run - アプリをメインサーバーとして実行する
  • Cloud SQL - ユーザー情報、投稿などのリレーショナル データベースを保存する
  • Cloud Storage - ポストメディアなどの非リレーショナル データベースを保存する
  • Cloud Load Balancer - 複数のリージョンでサーバー トラフィックを送信する
  • Cloud DNS - カスタム ドメインをマッピングする
  • Cloud Build - gcloud からアプリを自動的にデプロイする
  • Secret Manager - アプリのセキュリティを強化する
  • Cloud VPC - Private SQL を使用して Cloud SQL を Cloud Run に接続する
  • Cloud DNS - 接続を高速化するために静的キャッシュに保存する
  • Translation API - 投稿の字幕が別の言語の場合、翻訳する

使ってみる

Cloud Shell でソースコードのコピーへの次のリンクをクリックします。その後、1 つのコマンドでプロジェクト内のアプリケーションの作業コピーがスピンアップされます。

Cloud Shell で開く

GitHub でソースコードを見る


サーバーレスのエンドツーエンドの写真共有アプリケーション コンポーネント

サーバーレスのエンドツーエンドの写真共有アプリケーション アーキテクチャでは、いくつかのプロダクトを使用しています。以下に、関連動画、プロダクト ドキュメント、インタラクティブ チュートリアルへのリンクを含めた、コンポーネントの詳細を示します。
動画 ドキュメント チュートリアル
Cloud IAM Identity and Access Management(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 の仮想技術です。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 Server を作成する

次のコマンドセットでは、SQL Server インスタンスを構成して初期化します。

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

Secret を作成する

次の一連のコマンドは、機密性の高いアプリケーション設定とリソースを作成して、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]
}

Secret を入力する

次の一連のコマンドを使用すると、アプリケーションの Secret が 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 上でサービスをスピンアップします。

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 Service を公開読み取り可能にします。

こちらのアプリケーションの 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]
}

まとめ

これで、複数のリージョンで実行する完全にインストールされたアプリケーションが作成され、ソーシャル メディアの共有が可能になります。さらに、環境に合わせてソリューションを変更または拡張するためのコードもすべて用意されています。