Heroku の無料プランが終わるということで、個人開発で使うための代替のサービスを検討してました。
どうせなら全部クラウドにしてしまえ、ということで
以前作った Rust で実装した API サーバーを GCP に載せてみました。
GCP を始めて触る機会のもなったかな、と。とは言え、 Cloud SQL が高いので、他にもっと良い手はないか検討中です。
今回のソース#
今回作ったものは GitHub のリポジトリに置いておきました。
構成#
今回の構成は以下のような感じです(GCPのリソースの構成図書くの初めてなのでこれでお作法的にあってるか不安ですが):
ユーザーからのリクエストを Cloud Run に載せた Rust で実装した API サーバーで受け付けます。
Cloud Run で動かすコンテナのイメージは Artifact Registry から取得します。
データは Private な VPC 上にある Cloud SQL に格納します。
Cloud Run から Cloud SQL にアクセスする際には
サーバーレス VPC コネクタを作成する必要があります。
動かすものを実装#
まずは、以前作った Rust の API サーバーのソースを基に、 API サーバーを実装します。
(
GitHub のリポジトリの app/src
ディレクトリ配下)
以前と同様に、Docker Compose でローカルで立ち上がるようにしておきます。
Artifact Registry に Docker イメージに push する#
こちらの内容を参考に Docker を Artifact Registry に push します。
Artifact Registry に Docker リポジトリを作成する#
以下のコマンドを実行します:
1gcloud artifacts repositories create [リポジトリ名] --repository-format=docker \
2--location=us-central1 --description="[リポジトリの説明]"
認証を構成#
以下のコマンドで、Artifact Registry へのリクエストを認証するように Docker を構成します。これで、GCR イメージを push または pull することができるようになります。
以下は、リージョンは us-central1 とする場合のコマンドです:
1$ gcloud auth configure-docker us-central1-docker.pkg.dev
Dockerfile を build#
app/src
ディレクトリ配下で次のコマンドど実行し、 Dockerfile を build します。
1$ docker build -t sample-app/actix-web-sample:latest .
イメージにレジストリ名をタグ付けする#
以下のコマンドで先ほど build したイメージをタグ付けします。
1$ docker tag sample-app/actix-web-sample:latest us-central1-docker.pkg.dev/[プロジェクト ID]/[リポジトリ名]/[イメージ名]:[タグ名]
例えば、こんな感じです:
1docker tag sample-app/actix-web-sample:latest us-central1-docker.pkg.dev/tsuchinoko/actix-web-on-gcp/sample-image:0.0.1
イメージを Artifact Registry に push#
以下の形式のコマンドを実行しイメージを push します:
1$ docker push us-central1-docker.pkg.dev/[プロジェクト ID]/[リポジトリ名]/[イメージ名]:[タグ名]
具体的にはこんな感じです:
1$ docker push us-central1-docker.pkg.dev/tsuchinoko/actix-web-on-gcp/sample-image:0.0.1
成功すれば、以下のように Artifact Registry でイメージが確認できます:
インフラコードの作成#
あとは、インフラコードを作成していきます。
こちらの内容を大いに参考にさせていただきました。
作成したインフラコードは infra
ディレクトリ以下に格納しています。
以下、ポイントを絞ってコードを解説します(バックエンドの設定などの解説は省略)
Secret Manager の設定#
DB のパスワードなどの機密情報を格納するためのハコを用意します。
Terraform のコードに機密情報を書くわけにはいかないので、 Terraform のコマンド実行後、手動で値を設定します。
1# -----
2# シークレットに関する定義
3# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret
4# -----
5
6# データベース名
7resource "google_secret_manager_secret" "db_name" {
8 secret_id = local.secret_id_db_name
9
10 labels = {
11 label = var.service_name
12 }
13
14 replication {
15 automatic = true
16 }
17}
18
19# データベースユーザー名
20resource "google_secret_manager_secret" "db_user" {
21 secret_id = local.secret_id_db_user
22
23 labels = {
24 label = var.service_name
25 }
26
27 replication {
28 automatic = true
29 }
30}
31
32# データベースのパスワード
33resource "google_secret_manager_secret" "db_password" {
34 secret_id = local.secret_id_db_password
35
36 labels = {
37 label = var.service_name
38 }
39
40 replication {
41 automatic = true
42 }
43}
VPC の設定#
Cloud SQL を配置する Private な VPC の作成と、 Cloud Run から接続するために必要な設定を記述します。
1# ----------
2# ネットワークの設定
3# ----------
4
5# ----- 以下、Terraform の公式の example を参考にネットワークの設定作成 -----
6# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/vpc_access_connector#example-usage---cloudrun-vpc-access-connector
7
8# Serverless VPC Access API の有効化
9resource "google_project_service" "vpcaccess_api" {
10 provider = google-beta
11
12 service = "vpcaccess.googleapis.com"
13 disable_on_destroy = false
14}
15
16# VPC の作成
17# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network
18resource "google_compute_network" "vpc" {
19 name = local.vpc_name
20 project = var.project_id
21 auto_create_subnetworks = false
22}
23
24# サブネットの作成
25# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_subnetwork
26resource "google_compute_subnetwork" "vpc" {
27 name = local.subnet_name
28 ip_cidr_range = var.subnet_cidr_range
29 network = google_compute_network.vpc.self_link
30 region = var.region
31 private_ip_google_access = true
32}
33
34# VPC アクセスコネクターの作成
35# CloudRun からプライベートな Cloud SQL に接続するためには必須
36# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/vpc_access_connector
37resource "google_vpc_access_connector" "connector" {
38 provider = google-beta
39
40 name = local.vpc_connector_name
41 region = var.region
42 ip_cidr_range = var.vpc_connector_cidr_range
43 network = google_compute_network.vpc.name
44 machine_type = var.vpc_connector_machine_type
45 min_instances = 2
46 max_instances = 10
47
48 depends_on = [google_project_service.vpcaccess_api]
49}
50
51# Cloud Router の設定
52# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/vpc_access_connector
53resource "google_compute_router" "router" {
54 provider = google-beta
55
56 name = local.router_name
57 region = var.region
58 network = google_compute_network.vpc.id
59}
60
61# NAT の設定
62# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_router_nat
63resource "google_compute_router_nat" "router_nat" {
64 provider = google-beta
65
66 name = local.nat_name
67 region = var.region
68 router = google_compute_router.router.name
69 source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
70 nat_ip_allocate_option = "AUTO_ONLY"
71}
Cloud SQL の設定#
Cloud SQL のインスタンス、データベース、ユーザーを作成します。
今回は、 プライベート IP アドレスのみをもつ構成とします。
また、機密情報は先ほど作成した Secret Manager から取得することとします。
インスタンスの構成は、なるべくお金のかからない構成になるように設定しています。
1# -----
2# Cloud SQL の設定
3#-----
4
5# Secret Manager から必要な情報を取得する
6data "google_secret_manager_secret_version" "db_name" {
7 secret = local.secret_id_db_name
8}
9
10data "google_secret_manager_secret_version" "db_user" {
11 secret = local.secret_id_db_user
12}
13
14data "google_secret_manager_secret_version" "db_password" {
15 secret = local.secret_id_db_password
16}
17
18# VPC の設定を取得
19data "terraform_remote_state" "vpc" {
20 backend = "gcs"
21
22 config = {
23 bucket = "dev-tsuchinoko-tfstate"
24 prefix = "actix-web-sample/vpc"
25 }
26}
27
28# -----
29# Cloud SQL 用にプライベート サービス アクセスを構成する
30# https://cloud.google.com/sql/docs/postgres/configure-private-services-access#terraform
31# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance#private-ip-instance
32
33# IP アドレス範囲を割り振る
34# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address
35resource "google_compute_global_address" "private_ip_address" {
36 name = "private-ip-address"
37 purpose = "VPC_PEERING"
38 address_type = "INTERNAL"
39 prefix_length = 16
40 network = data.terraform_remote_state.vpc.outputs.vpc_id
41}
42
43# プライベート接続の作成
44# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_networking_connection
45resource "google_service_networking_connection" "private_vpc_connection" {
46 network = data.terraform_remote_state.vpc.outputs.vpc_id
47 service = "servicenetworking.googleapis.com"
48 reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
49}
50
51# -----
52# Cloud SQL のインスタンスの作成
53# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance
54resource "google_sql_database_instance" "app" {
55 name = local.cloud_sql_instance_name
56 database_version = "POSTGRES_14"
57 region = var.region
58
59 depends_on = [google_service_networking_connection.private_vpc_connection]
60
61 settings {
62 tier = var.db_instance_machine_type
63 disk_autoresize = true
64 availability_type = var.db_availability_type
65 disk_type = var.db_disk_type
66
67 backup_configuration {
68 enabled = var.db_backup_enabled
69 point_in_time_recovery_enabled = var.db_point_in_time_recovery_enabled
70 }
71
72 ip_configuration {
73 ipv4_enabled = false
74 private_network = data.terraform_remote_state.vpc.outputs.vpc_id // Specify VPC name
75 }
76 }
77
78 deletion_protection = var.db_delete_protection
79}
80
81# データベースの作成
82# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database
83resource "google_sql_database" "database" {
84 name = data.google_secret_manager_secret_version.db_name.secret_data
85 instance = google_sql_database_instance.app.name
86}
87
88# ユーザーの作成
89# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_user
90resource "google_sql_user" "users" {
91 name = data.google_secret_manager_secret_version.db_user.secret_data
92 instance = google_sql_database_instance.app.name
93 password = data.google_secret_manager_secret_version.db_password.secret_data
94}
Cloud Run の設定#
Artifact Refistry のイメージを利用するように設定します。また、環境変数についてもここで設定します。
外部から、API に認証無しアクセスを許可する設定も入れてます。この設定をするためには、 Terraform を実行するサービスアカウントに Cloud Run の管理者権限を付与しておく必要があります。
1# -----
2# Cloud Run の設定
3# -----
4
5# VPC の設定を取得
6data "terraform_remote_state" "vpc" {
7 backend = "gcs"
8
9 config = {
10 bucket = "dev-tsuchinoko-tfstate"
11 prefix = "actix-web-sample/vpc"
12 }
13}
14
15# DB の設定を取得
16data "terraform_remote_state" "cloud_sql" {
17 backend = "gcs"
18
19 config = {
20 bucket = "dev-tsuchinoko-tfstate"
21 prefix = "actix-web-sample/cloud-sql"
22 }
23}
24
25# Secret Manager から必要な情報を取得する
26data "google_secret_manager_secret_version" "db_name" {
27 secret = local.secret_id_db_name
28}
29
30data "google_secret_manager_secret_version" "db_user" {
31 secret = local.secret_id_db_user
32}
33
34data "google_secret_manager_secret_version" "db_password" {
35 secret = local.secret_id_db_password
36}
37
38# Cloud Run のサービスの設定
39# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service
40resource "google_cloud_run_service" "app" {
41 name = var.service_name
42 location = var.region
43
44 template {
45 spec {
46 containers {
47 image = var.gcr_image
48 ports {
49 container_port = 8080
50 }
51 env {
52 name = "SERVER_ADDRESS"
53 value = "0.0.0.0"
54 }
55 env {
56 name = "DATABASE_URL"
57 value = "postgres://${data.google_secret_manager_secret_version.db_user.secret_data}:${data.google_secret_manager_secret_version.db_password.secret_data}@${data.terraform_remote_state.cloud_sql.outputs.db_private_ip}:5432/${data.google_secret_manager_secret_version.db_name.secret_data}"
58 }
59 }
60 }
61
62 metadata {
63 annotations = {
64 # Use the VPC Connector
65 "run.googleapis.com/vpc-access-connector" = data.terraform_remote_state.vpc.outputs.connector_name // Specify VPC connector
66 # all egress from the service should go through the VPC Connector
67 "run.googleapis.com/vpc-access-egress" = "all"
68 # If this resource is created by gcloud, this client-name will be gcloud
69 "run.googleapis.com/client-name" = "terraform"
70 # Disallow direct access from IP
71 # "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing"
72 }
73 }
74 }
75
76 traffic {
77 percent = 100
78 latest_revision = true
79 }
80
81 lifecycle {
82 ignore_changes = [
83 # For update of gcloud cli version
84 template[0].metadata[0].annotations["run.googleapis.com/client-version"]
85 ]
86 }
87
88 autogenerate_revision_name = true
89}
90
91# 認証なしアクセスを許可するためのポリシー
92data "google_iam_policy" "noauth" {
93 binding {
94 role = "roles/run.invoker"
95 members = [
96 "allUsers",
97 ]
98 }
99}
100
101# 認証なしのアクセスを可能にする
102# 実行には Terraform 実行するための IAM ロールに Cloud Run の管理者権限が必要
103resource "google_cloud_run_service_iam_policy" "noauth" {
104 location = google_cloud_run_service.app.location
105 project = google_cloud_run_service.app.project
106 service = google_cloud_run_service.app.name
107
108 policy_data = data.google_iam_policy.noauth.policy_data
109}
Terraform のコマンド実行#
リソースの依存関係の問題があるので、
- Secret Manager の設定反映
- Secret Manager に値を設定(コンソールかコマンドで手動で実施)
- VPC の設定反映
- Cloud SQL の設定反映
- Cloud Run の設定反映
の順に行います。3. 以降は Makefile に依存関係を記載したので、 make all CMD="terraform init && terraform apply -auto-approve"
で実行可能です。
Makefile の中身:
1CMD = ls
2
3RUN_CMD = @cd $@ && \
4 echo "Run command in \"$@\"" && \
5 ${CMD}
6
7help:
8 @echo "This is a Makefile for executing commands according to AWS resource dependencies."
9 @echo ""
10 @echo "To deploy all AWS resources, run make as follows:"
11 @tput setaf 2 && \
12 echo "make all CMD=\"terraform init && terraform apply -auto-approve\"" && \
13 tput sgr0
14
15usage: help
16
17### Below, the target resources
18.PHONY: cloud_run cloud_sql secret_manager vpc
19cloud_run: cloud_sql
20 ${RUN_CMD}
21
22cloud_sql: secret_manager vpc
23 ${RUN_CMD}
24
25vpc:
26 ${RUN_CMD}
27
28all: cloud_run
Terraform の実行が成功すると、以下のように Cloud Run のアプリにアクセスするための URL が発行されるので、アクセスしたり curl できたら成功です。
ただし、今回の設定だと、 diesel のマイグレーションが最初に走っていないため、アプリを動かすためには src/migrations
配下にある up.sql
を流す必要があります。
私の場合は、一旦、Cloud SQL の Public IP アドレスを有効にし、 Cloud SQL Auth Proxy を利用してローカル環境の DB クライアントからアクセスし、 SQL を実行しました。
この辺り、もうちょっとスマートなやり方(Dockerの設定か何かかな?)があるような気がしています。
まとめ#
Rust で実装した API サーバーを GCP Cloud Run で実行することに成功しました。
GCP を触り始めたりする良いきっかけにもなりました。
もうちょっと色々とスマートなやり方がある気がするので、その辺りは研究しておきたいと思います。