Cloud Run Service から Cloud SQLに接続する

# 概要（タイトルの通り）
Google CloudのコンピューティングリソースであるCloud Runから、同一プロジェクト内にあるCloud SQLインスタンスへ接続する。

:::note info
開発目的でローカルからクラウド上のSQLインスタンスに接続する場合、Google Auth Proxyを用いて接続することが可能。これは本記事のスコープ外なので、下記記事を参照されたい。
:::

https://qiita.com/RjChiba/items/616168161c56ac52f7a2

## 環境・前提
- スタンダードなWeb3層(three-tier)アーキテクチャ
- コンピューティングにはCloud Run、データベースにはCloud SQLを使用
- データベースはインターネットから接続できないようにしたい
- GitHubなどを用いてCI/CDを実現したいが、接続先などの機密情報が漏洩しないようにしたい

## 結論
![clourSQL_cloudRun_connection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/a84aabfb-5dde-4f04-8a7f-bd6751b99963.png)

1. Cloud SQLにVPCのプライベートIPアドレスをアタッチする
2. Cloud Runの設定で、「Cloud SQL接続」を有効化する
3. Cloud Runのネットワーク設定で、「VPCに直接トラフィックを送信する」を指定する
4. Cloud RunからSQLに接続する際、プライベートIPアドレスを指定する

実際に環境を構築する際は
VPC設定（オプション） → Cloud SQL設定 → Secret管理 → Cloud Run設定
の順番で進めると良い。

## Cloud SQLにVPCのプライベートIPアドレスをアタッチする
「インスタンスの作成」-「接続」-「プライベートIP」を有効化する。
「プライベートサービスアクセス（PSA）の設定」で、アタッチするプライベートIPアドレスの範囲を指定することができる。
基本的に下記構成で動く。

| ネットワーク | 割り振られたIP範囲 | 
|---|---|
| default | 自動的に割り当てられたIP範囲を使用する |

![clourSQL_cloudRun_networking.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/45e306fa-b2fd-48a7-9673-e2e1cb366654.png)

上記の設定でインスタンスを作成すると、割り振られるIPアドレスは「10.50.XXX.XXX」などになっているはず。
詳細なネットワーク設計を行いたい場合は、あらかじめ「VPCネットワーク」-「VPCネットワーク」からネットワークセグメントを作成し、インスタンス作成時に指定する。

![cloudSQL_cloudRun_privateIP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/9cc4391f-c5ec-44a9-baa4-d3c805ab5682.png)


:::note warn
インスタンスのIPアドレスは、インスタンス作成後は変更できないので注意。
:::

## Cloud Runの設定で、「Cloud SQL接続」を有効化する
「Cloud Run」-「サービス」からCloud Runインスタンスを作成する。
「コンテナをデプロイ」「リポジトリを接続」「関数を作成」の選択肢があるが、ケースに応じて選択すれば良い。

![cloudSQL_cloudRun_toolbar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/2bda2117-a397-498c-9070-8b5e653c1b7a.png)

「コンテナ、ボリューム、ネットワーキング、セキュリティ」内「コンテナ」の最後に「Cloud SQL接続」があるので、先ほど作成したSQLインスタンスを指定する。

![cloudSQL_cloudRun_cloudSQLconnection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/0ebae51f-166f-4f59-8750-3e4e804b2fff.png)

次にネットワークの設定をするので、まだインスタンスは作成しない。

## 「アウトバウンドトラフィック用のVPCに接続する」を有効化する
先ほどの「コンテナ、ボリューム、ネットワーキング、セキュリティ」内「ネットワーキング」で「アウトバウンドトラフィック用のVPCに接続する」を有効化する。
「VPCに直接トラフィックを送信する」と「サーバーレスVPCアクセスコネクタを使用」の選択肢があるが、一般的に前者を選択すれば良い。

ここまでの設定が終われば、Cloud Runインスタンスを作成する。

:::note info
サーバーレスVPCアクセスコネクタは費用が若干嵩む点でも採用ハードルが高い。
https://cloud.google.com/vpc/docs/serverless-vpc-access?hl=ja

:::

![「アウトバウンド トラフィック用のVPCに接続する」を有効化する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/f5803c16-b34b-497d-a35d-5f315aff9a4b.png)

## プライベートIPアドレスを指定してCloud RunからCloud SQLに接続する

Cloud RunからCloud SQLへの接続方法は、ドキュメントに記載がある。

https://cloud.google.com/sql/docs/postgres/connect-run?hl=ja#connect

接続ホストにCloud SQLインスタンスのプライベートIPアドレスを指定すればよい。

:::note warn
接続先情報（IPアドレス、ユーザ名・パスワードなど）の管理についてはこの後説明する。
:::


① SQL Alchemyの場合
```python

import sqlalchemy

pool = sqlalchemy.create_engine(
    # Equivalent URL:
    # postgresql+pg8000://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
    sqlalchemy.engine.url.URL.create(
        drivername="postgresql+pg8000",
        username=db_user,
        password=db_pass,
        host=db_host, # db_host = "10.50.XXX.XXX"
        port=db_port,
        database=db_name,
    ),
    # ...
)

```

② Prismaの場合
```
# schema.prisma

generator client {
  provider = "prisma-client-py"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
```

```
# .env
DATABASE_URL="postgresql://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>"

# db_host = "10.50.XXX.XXX"
```

## おまけ：機密情報の扱い
データベースのIPやユーザ名・パスワードなどは、万一漏洩すると不正接続等を招く恐れがあり、GitHubリポジトリにアップロードすべき内容ではない。また、同一のGoogle Cloudプロジェクトに参加しているユーザーであっても、アクセス管理を行うべきケースが多々存在する。

機密情報をローカルからGoogle Cloud環境に配置する方法は下記のように複数存在するが、特段の理由がなければSecret Managerを使うのが良い。

https://cloud.google.com/secret-manager/docs/overview?hl=ja

| シークレットの保存先                               | 推奨 | 備考（理由）                                                            |
| ---------------------------------------- | -- | ----------------------------------------------------------------- |
| **Secret Manager**                  | ○  | GCP IAM によるアクセス制御、監査ログ、バージョン管理が可能なため最も安全・推奨される方法                  |
| **環境変数（Cloud Run/Cloud Functionsなどに設定）** | ○  | シークレット自体はSecret Managerから取得し、環境変数に渡す形が安全。|
| **GitHubの環境変数（Actions用など）**              | △  | GitHubリポジトリの権限に依存し、外部環境との統合や多環境デプロイにやや不向き。ビルドログに環境変数の値が残る |
| **コードに埋め込む**  | ×  | 誰でも読める状態でリポジトリに残り、セキュリティ上重大なリスク。絶対に避けるべき                          |
| **`.env`ファイル（GitHubにアップする）** | ×  | ファイルとして分離されていても、リポジトリに上がるため、コードに埋め込むのと同等。|
| **GCS（Cloud Storage）に保存**| ×  | バケットのアクセス制御が必要で、専用用途ではない。平文で置くことが多く非推奨 |

Secret Managerは「セキュリティ」-「Secret Manager」から利用できる。
Secret Managerで作成したシークレットは、Cloud Runの「コンテナ、ボリューム、ネットワーキング、セキュリティ」から環境変数としてコードに取り込むことができる。

https://cloud.google.com/run/docs/configuring/services/secrets?hl=ja

![シークレット管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611822/39492c6d-d761-4dee-961d-a4b3a0c30c47.png)

この設定を行えば、コードからは環境変数ファイル`.env`を利用するのと同様に、シークレットを扱える。

```python
from dotenv import load_dotenv

DATABASE_URL: str = os.getenv("DATABASE_URL", None)
```

## まとめ
Cloud RunとCloud SQLをセキュアに接続するためには、VPCネットワークやSecret Managerを活用した構成が不可欠です。本記事で紹介した設定を押さえておけば、安全かつ柔軟にサービスを展開できます。セキュリティと運用性の両立を意識しながら、ぜひ実践に取り入れてみてください。