🌤️
Terraform + Cloud SQL + Cloud Run
概要
さくっとwebサーバーを立てたいとき、この構成が一番速いと思うのでTerraformの構成を共有します。
フォルダ構成
以下のようなフォルダ構成をとっています
プロジェクトID/
├ docker-compose.yml ← ローカル開発用
├ backend/ ← アプリ
│ ├ Dockerfile
│ └ app
├ mysql/ ← DB用
│ └ my.cnf
└ terraform/ ← クラウドクラウドデプロイ用
├ main.tf
└ secret.tfvars
アプリ
アプリはRubyでもGoでもRustでも構わないので、PORT環境変数のポートで待ち受けるようにしてdocker buildします。
ローカルで動作が確認できたら、以下のようにGoogle Container Registryにプッシュします。
このコンテナがCloud Run上で動作します。
cd backend
docker build -t backend:latest .
docker tag backend:latest asia.gcr.io/(プロジェクトID)/backend:latest
docker push asia.gcr.io/(プロジェクトID)/backend:latest
GCPのAPIの有効化
以下のAPIを有効にします。
- Cloud Run Admin API
- Cloud DNS API
- Compute Engine API
- Cloud Domains API
- Cloud SQL Admin API
- Network Management API
terraform
以下の2ファイルを同じフォルダに置いて、以下のコマンドを実行すれば環境が作られます。
cd terraform
terraform init
terraform plan -var-file="secret.tfvars"
terraform apply -var-file="secret.tfvars"
- secret.tfvars(.gitignoreすること)
project_id = "(プロジェクトID)sampla-app"
project_id_underscore = "(アンダースコア区切りのプロジェクトID )sampla_app"
location = "asia-northeast1"
zone = "asia-northeast-b"
domain_root = "(Cloud Domainsで取得したドメイン)"
gcr_app_server_tag = "(gcrにプッシュしたコンテナのタグ)"
app_db_user_password = "xxxx"
- main.tf
provider "google" {
project = var.project_id
region = var.location
zone = var.zone
}
#
# Variables
#
variable "project_id" {
type = string
}
variable "project_id_underscore" {
type = string
}
variable "location" {
type = string
}
variable "zone" {
type = string
}
variable "domain_root" {
type = string
}
variable "gcr_app_server_tag" {
type = string
}
variable "app_db_user_password" {
type = string
}
#
# Cloud SQL
#
resource "google_sql_database_instance" "master_instance" {
name = "${var.project_id}-instance"
database_version = "MYSQL_8_0_31"
settings {
tier = "db-f1-micro"
}
deletion_protection = "true"
}
resource "google_sql_database" "main_database" {
name = "${var.project_id_underscore}_database"
instance = google_sql_database_instance.master_instance.name
charset = "utf8mb4"
collation = "utf8mb4_bin"
}
resource "google_sql_user" "app_db_user" {
name = "${var.project_id_underscore}_db_user"
instance = google_sql_database_instance.master_instance.name
host = "%"
password = var.app_db_user_password
}
#
# Cloud Run
#
resource "google_cloud_run_service" "app_server" {
name = "${var.project_id}-service"
location = var.location
template {
spec {
containers {
image = var.gcr_app_server_tag
env {
name = "DATABASE_AUTO_MIGRATION"
value = "true"
}
env {
name = "DATABASE_PROTOCOL"
value = "cloudsql"
}
env {
name = "DATABASE_HOST"
value = google_sql_database_instance.master_instance.connection_name
}
env {
name = "DATABASE_TIMEZONE"
value = "UTC"
}
env {
name = "DATABASE_NAME"
value = google_sql_database.main_database.name
}
env {
name = "DATABASE_USER"
value = google_sql_user.app_db_user.name
}
env {
name = "DATABASE_PASSWORD"
value = var.app_db_user_password
}
}
}
metadata {
annotations = {
"autoscaling.knative.dev/maxScale" = "1000"
"run.googleapis.com/cloudsql-instances" = google_sql_database_instance.master_instance.connection_name
"run.googleapis.com/client-name" = "terraform"
}
}
}
metadata {
annotations = {
"run.googleapis.com/launch-stage" = "BETA"
}
}
}
data "google_iam_policy" "noauth" {
binding {
role = "roles/run.invoker"
members = [
"allUsers",
]
}
}
resource "google_cloud_run_service_iam_policy" "noauth" {
location = google_cloud_run_service.app_server.location
project = google_cloud_run_service.app_server.project
service = google_cloud_run_service.app_server.name
policy_data = data.google_iam_policy.noauth.policy_data
}
// ------------------------------------
// Cloud DNSの設定
// カスタムドメインを使わない場合は以降不要
// ------------------------------------
resource "google_dns_managed_zone" "app_server_dns_zone" {
name = "app-server-zone"
dns_name = "${var.domain_root}."
description = "DNS zone (${var.domain_root})"
dnssec_config {
state = "on"
}
}
resource "google_cloud_run_domain_mapping" "domain_mapping" {
location = var.location
name = "${var.project_id}.${var.domain_root}"
metadata {
namespace = var.project_id
}
spec {
route_name = google_cloud_run_service.app_server.name
}
}
resource "google_dns_record_set" "app_server_domain" {
managed_zone = google_dns_managed_zone.app_server_dns_zone.name
name = "${var.project_id}.${google_dns_managed_zone.app_server_dns_zone.dns_name}"
type = "CNAME"
ttl = 300
rrdatas = ["ghs.googlehosted.com."]
}
resource "google_compute_managed_ssl_certificate" "app_server_cert" {
provider = google
name = "${var.project_id}-ssl-cert"
managed {
domains = ["${var.project_id}.${var.domain_root}"]
}
}
CNAMEが反映されるまで30分ほどかかりますが、反映されればSSLを使ってカスタムドメイン経由でCloud Runに接続できるようになります。
MySQL用
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
[client]
default-character-set=utf8mb4
docker-compose.yml
こちらはローカル開発用です。
version: '3.7'
services:
server:
depends_on:
- mysql
image: backend/latest
build:
context: ./backend
environment:
- TZ=UTC
- HOST=0.0.0.0
- DATABASE_AUTO_MIGRATION=true
- DATABASE_HOST=mysql
- DATABASE_NAME=xxxx
- DATABASE_TIMEZONE=UTC
- DATABASE_USER=user
- DATABASE_PASSWORD=password
ports:
- "8080:8080"
mysql:
image: mysql:latest
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- ./mysql/init:/docker-entrypoint-initdb.d
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: xxxx
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: UTC
Discussion
Cloud Runにカスタムドメインをマップする場合、対象のドメインをGoogle Domainsで管理していると、Cloud DNSを使わなくても正しく機能するようになります
※どちらもネームサーバーがghs.googlehosted.com.のため
Terraformとの相性考えるとCloud DNSが便利なんですけどね。