三层应用是一款简单的待办事项应用,其架构设计为原始 3 层应用:
- 后端
- 数据库 - MySQL - Cloud SQL
- 缓存 - Redis - Cloud Memorystore
- 中间件/API
- 容器托管 API - Golang - Cloud Run
- 前端/界面
- 容器托管界面 - Nginx + HTML/JS/CSS - Cloud Run
- 部署
- 持续部署 - Cloud Build
- 密钥管理 - Cloud Secret Manager
开始使用
点击以下链接即可转到 Cloud Shell 中的源代码副本。之后,只需一个命令即可在项目中启动应用的工作副本。
三层应用组件
三层应用架构使用了多种产品。下面列出了组件以及组件的详细信息,包括相关视频链接、产品文档和互动式演示。脚本
安装脚本使用 go
和 Terraform CLI 工具编写的可执行文件接受一个空项目,并在其中安装应用。输出结果应该是正常运行的应用以及负载均衡 IP 地址的网址。
./main.tf
启用服务
默认情况下,Google Cloud 服务在项目中处于停用状态。ToDo 要求您启用以下服务:
- 服务网络和无服务器 VPC 访问通道 - 允许 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 Build for 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
}
设置权限
以下命令可设置允许 Cloud Build 部署服务的 IAM 角色和权限。
- 启用 Cloud Build 服务账号以部署到 Cloud Run
- 启用 Cloud Build 服务账号以便为 Cloud Run 设置 VPN 访问权限
- 启用 Cloud Build 服务账号以执行服务账号活动
- 启用 Cloud Build 服务账号以代表计算服务账号执行操作
- 启用 Cloud Build 服务账号以发布到 Cloud Run
- 启用 Cloud Build 服务账号以使用 Secret
- 启用 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]
}
创建 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 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]
}
创建 Secret
以下命令会将 Redis 和 SQL 主机数据存储在 Cloud Secret 中。
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)
gsutil mb gs://$PROJECT-temp
gsutil cp schema.sql gs://$PROJECT-temp/schema.sql
gsutil iam ch serviceAccount:$SQLSERVICEACCOUNT:objectViewer gs://$PROJECT-temp/
gcloud sql import sql $SQLNAME gs://$PROJECT-temp/schema.sql -q
gsutil rm gs://$PROJECT-temp/schema.sql
gsutil rb gs://$PROJECT-temp
./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
此命令将中间件的端点注入前端的 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 上运行。您还可以使用所有代码来修改或扩展此解决方案,使其适合您的环境。