TypeORMでCloud RunからCloud SQLへ接続する(ソケット接続)


# はじめに

Node.js+Express（TypeScript）プロジェクトをGoogle Cloud Run で動かしつつ、TypeORMでGoogle Cloud SQLに接続するするのに少し詰まったため、ポイントを簡単にまとめてみました。


本記事の内容に入る前に[公式ドキュメント](https://cloud.google.com/sql/docs/mysql/connect-run?hl=ja)の「Cloud Run を構成する」のセクションまで行ってください。本記事はこのドキュメント内の「[Cloud SQL への接続](https://cloud.google.com/sql/docs/mysql/connect-run?hl=ja#connect_to)」と同じ作業をTypeORM向きに書いたものです。


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

DBエンジンはMySQLを前提としています。
TypeORMについては以下も合わせて参照してください。

https://typeorm.io/

# 結論

TypeORMのDataSourceオブジェクトに渡すオプションに以下を追加し、**`host`と`port`の2つのプロパティを削除**することで、ソケット接続ができました。`host`と`port`のいずれか片方でも設定している（`undefined`でない）と`socketPath`は無視され`127.0.0.1:3306`を標準値としたIPアドレスでの接続になってしまうようです。

```ts
{
  socketPath: `/cloudsql/{Cloud SQLインスタンスの接続名}`,
}
```

つまり、以下のようにすればOK。（もちろん`socketPath`以外の項目は各自の環境に合わせて適切なものにしてください。）

```ts
const options: DataSourceOptions = {
  type: "mysql",
  synchronize: true,
  logging: true,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: [path.join(__dirname, "/entity/*.ts")],
  socketPath: `/cloudsql/{Cloud SQLインスタンスの接続名}`,
};

const dataSource = new DataSource(options);

dataSource.initialize(); // 接続
```


## 環境

package.jsonから関係するものだけ抜粋しました。TypeORMはバージョン`0.3.6`、MySQLドライバーは`2.18.1`です。

```package.json
"dependencies": {
    "@types/express": "^4.17.13",
    "@types/node": "^17.0.41",
    "express": "^4.18.1",
    "mysql": "^2.18.1",
    "ts-node": "^10.8.1",
    "typeorm": "^0.3.6",
    "typescript": "^4.7.3",
  }
```

# Cloud Run から Cloud SQl への接続

[公式ドキュメント](https://cloud.google.com/sql/docs/mysql/connect-run?hl=ja#connect_to)によれば、ローカルからアクセスする時（ipとポートを指定してhttp接続）と異なりUnix ソケット経由での接続になるようです。

> パブリック IP パスの場合は、Cloud Run によって暗号化が行われ、Cloud SQL Auth Proxy を使用して Unix ソケット経由で接続します。

ローカルからCloud SQLへアクセスする場合、SSLを用いていればTypeORMの接続設定は概ね以下のようになっているかと思います。（ユーザー名やPWなどの関係ないプロパティは省略しています。）
```ts
const options: DataSourceOptions = {
  type: "mysql",
  host: "123.123.123.123",
  port: 3306,
  ssl: {
    // Cloud SQLのコンソールから発行したSSL接続用の証明書類
    ca: fs.readFileSync(path.join(__dirname, "/server-ca.pem")),
    cert: fs.readFileSync(path.join(__dirname, "/client-cert.pem")),
    key: fs.readFileSync(path.join(__dirname, "/client-key.pem")),
  }
 //...ユーザー名やPWなど
}
```

しかしCloud Run内からのアクセスの場合、SSLに必要な証明書類を持っていくことなくセキュアな環境でソケット接続によって通信が可能になっているようです。
ソケット通信を利用するために、TypeORMの設定（DataSourceOptions）を以下のように書き換えてください。
`host`と`port`の2つのプロパティを削除（`undefined`）にしておくのがポイントです。これらに値が入ったままだとソケット接続になりません。`ssl`も`undefined`にしておきましょう。


```ts
const options: DataSourceOptions = {
  type: "mysql",
  socketPath: `/cloudsql/{Cloud SQLインスタンスの接続名}` // 追加
 //...ユーザー名やPWなど
};
```

## Cloud SQLインスタンスの接続名

Cloud SQLインスタンスの接続名はCloud SQLの管理画面に出てくる「このインスタンスとの接続」の「接続名」の部分に置き換えてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512171/8eb8b334-0916-b680-eebd-c55c44b500d9.png)


# 最後に

ググっていると、以下のように`extends`にネストさせないとダメという情報を見かけましたが、バージョン`0.3.6`の時点ではその必要は無いようです。

```ts
const options: DataSourceOptions = {
  extends:{
    socketPath: `/cloudsql/${process.env.DB_HOST}`,
  }
}
```
TypeORMのソースを見たところ、DataSourceOptions（共用体型/union）のうち1つである`MysqlConnectionOptions`の継承元の`MysqlConnectionCredentialsOptions`に`socketPath`というプロパティがちゃんと定義されていました。

ソースはこちら。

https://github.com/typeorm/typeorm/blob/941b584ba135617e55d6685caef671172ec1dc03/src/driver/mysql/MysqlConnectionCredentialsOptions.ts