三層式應用程式是簡單的待辦事項應用程式,其架構為一般 3 層式應用程式:
- 後端
- 資料庫 - MySQL - Cloud SQL
- 快取 - Redis - Cloud Memorystore
- 中介軟體/API
- 容器代管 API - Go 語言 - Cloud Run
- 前端/使用者介面
- 容器代管 UI - Nginx + HTML/JS/CSS - Cloud Run
- 部署
- 持續部署 - Cloud Build
- 密鑰管理 - Cloud Secret Manager
開始使用
按一下以下連結,前往 Cloud Shell 中的原始碼副本。完成後,您只需執行單一指令,即可在專案中啟動應用程式的可用副本。
三層式應用程式元件
三層式應用程式架構會使用多項產品。以下列出元件,並提供相關資訊,包括相關影片、產品說明文件和互動式操作說明的連結。指令碼
安裝指令碼會使用以 go
和 Terraform CLI 工具編寫的可執行檔,取得空白專案並在其中安裝應用程式。輸出內容應為可運作的應用程式,以及負載平衡 IP 位址的網址。
./main.tf
啟用服務
根據預設,Google Cloud 服務會在專案中停用。ToDo 需要你啟用下列服務:
- Service Networking 和無伺服器虛擬私人雲端存取:允許 Cloud Run 與私人網路上的 SQL 和 Redis 通訊,確保這些伺服器不會受到來自 API 的其他呼叫影響。
- Cloud Build:建立容器映像檔並部署至 Cloud Run
- Cloud Memorystore:為應用程式提供快取層。
- Cloud Run:這是無伺服器工具,可代管容器,並提供可用於存取應用程式的網址。
- Cloud SQL:應用程式的資料庫儲存空間
- Cloud Storage:由 Cloud Build 使用,用於載入資料庫中的結構定義
- Cloud Secret Manager:用於將 SQL 和 Redis 的主機 IP 插入 Cloud Run 的 Cloud Build。
- Artifact Registry:儲存可與 Cloud Build 搭配使用的 Docker 映像檔。
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 服務帳戶,設定 Cloud Run 的 VPN 存取權
- 啟用 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 Run 存取 Cloud SQL:
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]
}
建立虛擬私有雲存取連接器
將 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 Server
下列指令可設定及初始化 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}"
}
}
建立 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]
}
為中介軟體建立構件
下列指令會建立 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。應用程式需要指向我們剛建立的 API 服務網址,但 Cloud Run 服務會指派網址,其中包含隨機字串。這段「按摩指令碼」會擷取隨機網址,並將該網址插入這個容器中靜態 JS 的程式碼。
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', '.' ]
將 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
這個指令會將 Middleware 的端點插入前端的 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
結論
您現在在專案中擁有在 Cloud Run 上執行的簡易 3 層待辦事項應用程式。您也擁有所有程式碼,可修改或擴充這個解決方案,以便配合您的環境。