3계층 앱은 바닐라 3계층 애플리케이션으로 설계된 간단한 할일 애플리케이션입니다.
- 백엔드
- 데이터베이스 - MySQL - Cloud SQL
- 캐싱 - Redis - Cloud Memorystore
- 미들웨어/API
- 컨테이너 호스팅 API - Golang - Cloud Run
- 프런트엔드/UI
- 컨테이너 호스팅 UI - Nginx + HTML/JS/CSS - Cloud Run
- 배포
- 지속적 배포 - Cloud Build
- 보안 비밀 관리 - Cloud Secret Manager
시작하기
Cloud Shell에서 소스 코드의 복사본에 대해 다음 링크를 클릭합니다. 그러면 단일 명령어로 프로젝트에서 애플리케이션의 작업 복사본이 작동합니다.
3계층 앱 구성요소
3계층 앱 아키텍처에는 여러 제품이 사용됩니다. 다음은 관련 동영상 링크, 제품 문서 및 대화형 둘러보기를 포함하여 구성 요소에 대한 자세한 정보와 함께 구성요소 목록을 보여줍니다.스크립트
설치 스크립트는 go
및 Terraform CLI 도구로 작성된 실행 파일을 사용하여 빈 프로젝트를 가져오고 여기에 애플리케이션을 설치합니다. 출력은 작동하는 애플리케이션과 부하 분산 IP 주소의 URL입니다.
./main.tf
서비스 사용 설정
Google Cloud 서비스는 기본적으로 프로젝트에서 사용 중지되어 있습니다. ToDo에서는 다음 서비스를 사용 설정해야 합니다.
- 서비스 네트워킹 및 서버리스 VPC 액세스 - Cloud Run이 비공개 네트워크에서 SQL 및 Redis와 통신하도록 허용하고, API에서 들어오는 외부 호출이 이러한 서버에 액세스하지 못하도록 합니다.
- Cloud Build - 컨테이너 이미지를 만들고 Cloud Run에 배포합니다.
- Cloud Memorystore - 애플리케이션의 캐싱 레이어를 제공합니다.
- Cloud Run - 컨테이너를 호스팅하는 서버리스 도구이며 애플리케이션에 액세스하기 위한 URL을 제공합니다.
- 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
}
권한 설정
다음 명령어는 Cloud Build가 서비스를 배포하도록 허용하는 IAM 역할 및 권한을 설정합니다.
- Cloud Run에 배포하도록 Cloud Build 서비스 계정 사용 설정
- Cloud Run용 VPN 액세스 설정을 위해 Cloud Build 서비스 계정 사용 설정
- 서비스 계정 활동 수행을 위해 Cloud Build 서비스 계정 사용 설정
- Compute 서비스 계정 대신 작동하도록 Cloud Build 서비스 계정 사용 설정
- Cloud Run에 게시하도록 Cloud Build 서비스 계정 사용 설정
- 보안 비밀을 사용하도록 Cloud Build 서비스 계정 사용 설정
- Artifact Registry에 컨테이너를 저장하도록 Cloud Build 서비스 계정 사용 설정
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 서버 만들기
다음 명령어는 SQL 서버 인스턴스를 구성하고 초기화합니다.
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 저장소 만들기
다음 명령어는 Cloud Run에 사용할 Docker 이미지를 저장합니다.
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 호스트 데이터를 클라우드 보안 비밀에 저장합니다.
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
]
}
Cloud Run에 API 컨테이너 배포
다음 명령어는 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 SQL에 업로드하기 위해 임시 Cloud Storage 버킷을 만듭니다.
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
Build 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
Massage 코드 콘텐츠
프런트엔드는 완전히 정적 HTML/JS/CSS입니다. 앱이 바로 전에 만든 API 서비스의 URL을 가리켜야 하지만 Cloud Run 서비스에는 무작위 문자열이 포함된 URL이 할당됩니다. 'massage 스크립트'는 무작위 URL을 캡처하고 이 컨테이너에서 정적 JS의 코드에 삽입합니다.
name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: bash
args: [ './massage.sh', '$_REGION' ]
Build 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
자바스크립트 수정
이 명령어는 미들웨어에 대해 엔드포인트를 프런트엔드의 자바스크립트에 삽입합니다.
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계층 할일 애플리케이션이 만들어졌습니다. 또한 환경에 맞게 이 솔루션을 수정하거나 확장할 수 있도록 모든 코드가 준비되었습니다.