メインコンテンツへスキップ

Rust で作った API サーバーを GCP Cloud Run で動作させる

·7 分
Rust GCP CloudRun CloudSQL Docker

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 のコマンド実行
#

リソースの依存関係の問題があるので、

  1. Secret Manager の設定反映
  2. Secret Manager に値を設定(コンソールかコマンドで手動で実施)
  3. VPC の設定反映
  4. Cloud SQL の設定反映
  5. 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 を触り始めたりする良いきっかけにもなりました。
もうちょっと色々とスマートなやり方がある気がするので、その辺りは研究しておきたいと思います。