🌥️

(GCP) CloudFunctionsからPrivateなCloudSQLへ接続する

2023/10/02に公開

はじめに

CloudSQLへの接続にPrivateIPを使用することには以下のようなメリットがあります。

セキュリティ面

  • PrivateIPを使用するとCloudSQLインスタンスはインターネットに公開されません。これにより、インターネット経由の不正アクセスのリスクが軽減する。

パフォーマンス面

  • VPC内のリソース間の通信が最適化されるため、レイテンシが低減するなどネットワークのパフォーマンスが向上する。

特にレイテンシに関してはPublicIP、PrivateIPそれぞれを比較した面白い記事もありました。
https://onl.bz/n9ReKuZ

その他コスト面やネットワーク構成を柔軟に設定できるなど様々ありますが、VPC内に設置されたプライベートなSQLインタンスへはその外部に存在するサーバーレスコンピューティングからはどのように接続すればよいでしょうか。今回はその方法や設定方法についてCloudFunctionsを使用した例でご紹介できればと思います。
Terraform(1.4.0) を使用します。GitHubリポジトリがあるのでそちらを参考にして頂ければと思います!!🙇

全体構成

前提として、CloudSQLはサービスプロデューサーネットワークと呼ばれるGoogleの管理下にあるネットワークに作成されるため実際には自作したVPC内に設置することはできません。

PrivateIPを使用してCloudSQLへ接続したい場合、VPCピアリングを用いて自身のVPCとサービスプロデューサーネットワークとの接続をピアリングする必要があります。
さらにサーバレスコンピューティングから接続する場合Serverless VPC Access connectorを使用します。VPC内に作成したコネクタを使用してCloudSQLに接続するように構成することで公開ネットワークを経由せずGoogleCloudの内部ネットワークに閉じた接続を実現できます。

各モジュールと変数

構築に使用する各モジュールと変数(variables)を簡単に紹介します。

Network

ネットワークを構成するモジュール

./modules/network/main.tf
resource "google_compute_network" "this" {
  name                    = "${var.app_name}-vpc"
  routing_mode            = "REGIONAL"
  auto_create_subnetworks = false
}

resource "google_compute_global_address" "this" {
  name          = "${var.app_name}-private-ip-block"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  ip_version    = "IPV4"
  address       = var.peering_ip_range
  prefix_length = 16
  network       = google_compute_network.this.self_link
}

resource "google_service_networking_connection" "this" {
  network                 = google_compute_network.this.self_link
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.this.name]
}

resource "google_vpc_access_connector" "this" {
  name          = "${var.app_name}-connector"
  network       = google_compute_network.this.name
  ip_cidr_range = "${var.connector_ip_range}/28"
}

google_compute_network

VPCを作成
今回はサブネットが不要なためauto_create_subnetworksをfalseにする

google_compute_global_address

VPCピアリングに使うためのプライベートIPアドレスの範囲を確保

google_service_networking_connection

サービスプロデューサーネットワークとのピアリングを作成
(service = "servicenetworking.googleapis.com")

google_vpc_access_connector

Serverless VPC Access connectorを作成
ip_cidr_rangegoogle_compute_global_addressのIPrangeと重複しないように指定(後述)かつprefix長は28である必要があります。

CloudSQL

CloudSQLインスタンスを作成するモジュール(MySQL5.7を使用)

./modules/cloud_sql/main.tf
resource "google_sql_database_instance" "this" {
  name             = "${var.app_name}-db-instance"
  database_version = "MYSQL_5_7"

  settings {
    tier              = "db-f1-micro"
    availability_type = "ZONAL"
    disk_size         = 10

    ip_configuration {
      ipv4_enabled    = false
      private_network = var.vpc_self_link
    }
  }

  deletion_protection = true
}

resource "google_sql_database" "this" {
  name     = "${var.app_name}-db"
  instance = google_sql_database_instance.this.name
}

resource "google_sql_user" "this" {
  name     = var.db_user
  password = var.db_password

  instance = google_sql_database_instance.this.name
}

特筆する箇所はありませんがip_configurationブロックにてネットワークに関する設定を行なっています。

  • ipv4_enabled = false PublicIPの割り当てを行わない
  • private_network = var.vpc_self_link 先ほどのVPCを指定

変数(variables)

各種変数を定義

./variables.tf
variable "project_id" {
  description = "project id"
  type        = string
}

variable "db_user" {
  description = "db user name"
  type        = string
  sensitive   = true
}

variable "db_password" {
  description = "db user name"
  type        = string
  sensitive   = true
}

variable "peering_ip_range" {
  description = "ip range for peering"
  type        = string
  sensitive   = true

}
variable "connector_ip_range" {
  description = "ip range for connector"
  type        = string
  sensitive   = true
}

variable "app_name" {
  description = "app name which is used for prefix of resources"
  type        = string
  default     = "private-sql"
}

variable "region" {
  description = "region"
  type        = string
  default     = "asia-northeast1"
}

default値が無いものは後述するterraform.tfvarsファイルから値を渡します。

Terraformの実行

実際にTerraformを実行して構成を作成します。

main.tf

先述の二つのモジュールを配置したシンプルな内容になります。

./main.tf
module "network" {
  source             = "./modules/network"
  app_name           = var.app_name
  peering_ip_range   = var.peering_ip_range
  connector_ip_range = var.connector_ip_range
}

module "cloud_sql" {
  source        = "./modules/cloud_sql"
  app_name      = var.app_name
  db_user       = var.db_user
  db_password   = var.db_password
  vpc_self_link = module.network.vpc_self_link
}

terraform.tfvarsの作成

terraform.tfvarsファイルをルートディレクトリに作成して各値を埋めます。
peering_ip_rangeconnector_ip_rangeは競合しないように指定してください。
(また、RFC1918に沿ったプライベートIPrangeを指定する必要があります。)

./terraform.tfvars
project_id         = "your-project-id" # 自身のプロジェクトID
db_user            = "your-db-user-name" # 任意のDBユーザー名
db_password        = "your-db-secure-password" # 任意のDBパスワード
peering_ip_range   = "10.1.0.0" # connector_ip_rangeと競合しないよう任意指定
connector_ip_range = "10.2.0.0" # peering_ip_rangeと競合しないよう任意指定

必要なAPIサービスを有効化

構成を作成する前に必要なAPIサービスを有効化しておく必要があります。必要なものは以下の通りです。

  • compute.googleapis.com
  • servicenetworking.googleapis.com
  • vpcaccess.googleapis.com

※下記はCloudFunctionsのデプロイに必要

  • cloudfunctions.googleapis.com
  • cloudbuild.googleapis.com

リポジトリに上記APIサービスを有効化するシェルがあるのでそちらを使用します。

$ sh enable_apis.sh

実行

initplanapplyを実行して構成を作成します。
※DBインスタンスの作成にはしばらく時間がかかります。

$ terraform init
$ terraform plan
$ terraform apply

確認

GoolgeCloudのSQLコンソールに移動し作成されたインスタンスにPrivateIPのみが割り当てられていることを確認します。(※PrivateIPはCloudFunctionsのデプロイ時に使用するので控えておきます。)

CloudFunctions

HTTPSトリガーの関数(Go)をデプロイしCloudSQLとの疎通を確認します。

関数

CloudSQLに接続し簡単なクエリを実行するシンプルな関数です。

./functions/private-sql/exec_query.go
package private_sql

 :
(省略) 
 :

func ExecQueryHTTP(w http.ResponseWriter, r *http.Request) {
	conn, err := connectTCPSocket()
	if err != nil {
		log.Fatal(err)
	}
	var result string
	err = conn.QueryRow("SELECT 'Hello, World!'").Scan(&result)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Fprint(w, result)
}

デプロイ

先述の関数をデプロイします。

$ gcloud functions deploy PrivateSqlFunc \
	--entry-point ExecQueryHTTP \
	--vpc-connector private-sql-connector \
	--region asia-northeast1 \
	--runtime go121 \
	--trigger-http \
	--allow-unauthenticated \
	--source ./functions/private-sql \
	--set-env-vars DB_USER=<your-db-user-name>,DB_PASS=<your-db-secure-password>,DB_NAME=private-sql-db,DB_PORT=3306,INSTANCE_HOST=<your-private-ip>

--vpc-connectorオプションへ先ほど作成したVPC Access connectorの名前を指定しています。
--set-env-varsへは各値を埋めてDBへの接続情報を渡します。

CloudSQLとの接続を確認

CloudFunctionsのHTTPSトリガーURLへアクセスしクエリ結果が返ることを確認します。
URLはデプロイ実施後のターミナルもしくはコンソールから取得できます。

$ curl https://<your-function-url>
Hello, World!

おわりに

以上になります。CloudFunctionsからプライベートなCloudSQLインスタンスに接続する方法をご紹介しましたが、Serverless VPC Access connectorを使用したこの方法は同じくサーバーレスコンピューティングであるAppEngineやCloudRunでも応用できます。気になる方はぜひ試して頂けたら幸いです。🙇

参考

https://cloud.google.com/sql/docs/mysql/connect-functions?hl=ja#go_2


株式会社クロスビットでは、デスクレスワーカーのためのHR管理プラットフォームを開発しています。
一緒に開発を行ってくれる各ポジションのエンジニアを募集中です。
https://x-bit.co.jp/recruit/
https://herp.careers/v1/xbit
https://note.com/xbit_recruit

クロスビットテックブログ

Discussion