Google Cloud プライベートサービスアクセス

## はじめに
Google またはサードパーティが所有するネットワークとプライベート接続ができる、[プライベート サービス アクセス](https://cloud.google.com/vpc/docs/private-services-access)がある
オンプレミスと接続した[共有 VPC 構成](https://qiita.com/suzuyui/items/947867f52897417ee31b)と、プライベート サービス アクセスを実施したので、設定内容を記載する
また、試しに CloudSQL をプライベートに作成してアクセスするテストも実施したのでその内容も記載する
(情報は、2021.10.31 記載時点)

## プライベート サービス アクセス

下記、[ドキュメント](https://cloud.google.com/vpc/docs/private-services-access)から抜粋

- Google とサードパーティ（サービス プロデューサー）は、VPC ネットワークでホストされている内部 IP アドレスでサービスを提供できる
- プライベート サービス アクセスを使用すると、これらの内部 IP アドレスにアクセスできる

### サービス プロデューサー ネットワーク
- プライベート接続のサービス プロデューサー 側には VPC ネットワークがある
- それぞれのコンシューマー専用に作成され、そこにはコンシューマーのリソースのみが含まれる

### プライベート サービス アクセスとオンプレミス接続
- デフォルトではオンプレミスホストはサービス プロデューサー側へ到達できない
- オンプレミスホストから接続するにはカスタムルートのエクスポートをする必要がある

### サポート対象のサービス
現状、対応しているサービスは下記のサービス

- Cloud SQL
- Memorystore for Redis
- Memorystore for Memcached
- AI Platform Training
- Cloud Build

## 構築
実際にプライベートサービスアクセスを構築して試した内容を記載する

###  構成
今回、下記構成で実施した

![スクリーンショット 2021-10-31 21.43.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/19026528-fce8-9898-9057-ef74e3cc75ae.png)


- Host Project
    - コンシューマー VPC プロジェクト
    - Host Project / Service Project の構成で、SharedVPC で構築している
    - オンプレミス (Home Lab) とは VPN 接続経由で接続している
    - us-west1 に主なリソースを立てている
    - カスタムルート(経路)広報としてサービスプロデューサー側にアサインしたアドレスを追加広報する
    - 全体の構築例の[記事はこちらのリンク](https://qiita.com/suzuyui/items/879dfa5d837a19c39a54)を参照
- Google Project (Service producer project for customer)
    - プロデューサー VPC
    - Google のサービスネットワークとなる
    - us-west1 と us-west2 の 2リージョンで試す
    - オンプレミスとも通信できるようにカスタムルート(経路)も経路交換する
    - アイコンとしては現在対応しているサービスをマップしているが、今回は CloudSQL (MySQL) を構築してアクセス確認のみ実施する

実施環境で利用した変数は下記の通り

- 共有VPC 名： sharedvpc
- 追加サブネット1： 172.20.0.0/24
- 追加サブネット2： 172.20.10.0/24
- ホスト プロジェクト： suzuyu-sharedhost-dev
- サービス プロジェクト： suzuyu-service-dev

以降で、構築を進める
作成方法は、gcloud コマンドと、terraform の 2つの方法で記載する

### サブネットアサイン
https://cloud.google.com/vpc/docs/configure-private-services-access#procedure

まず、利用するサブネットをアサインする
ここでアサインするサブネットは**リージョン固定できず** Google 利用時に**自動でアサイン**となる
/24が最小単位となり、/16が推奨だが、ここでは最小限(/24) を 2つ用意して、リージョン単位で利用する
(必要に応じて追加する運用を想定)

#### gcloud

```
gcloud compute addresses create google-managed-services-us-west1-subnet1 \
    --global \
    --purpose=VPC_PEERING \
    --addresses=172.20.0.0 \
    --prefix-length=24 \
    --description="peering range for Google us-west1" \
    --network=sharedvpc \
    --project=suzuyu-sharedhost-dev
```

```
gcloud compute addresses create google-managed-services-us-west2-subnet1 \
    --global \
    --purpose=VPC_PEERING \
    --addresses=172.20.10.0 \
    --prefix-length=24 \
    --description="peering range for Google us-west2" \
    --network=sharedvpc \
    --project=suzuyu-sharedhost-dev
```

下記で反映を確認する

```
gcloud compute addresses list --global --filter="purpose=VPC_PEERING" --project suzuyu-sharedhost-dev
```

```
% gcloud compute addresses list --global --filter="purpose=VPC_PEERING" --project suzuyu-sharedhost-dev

NAME                                      ADDRESS/RANGE   TYPE      PURPOSE      NETWORK    REGION  SUBNET  STATUS
google-managed-services-us-west1-subnet1  172.20.0.0/24   INTERNAL  VPC_PEERING  sharedvpc                  RESERVED
google-managed-services-us-west2-subnet1  172.20.10.0/24  INTERNAL  VPC_PEERING  sharedvpc                  RESERVED
```

##### 切り戻し

```
gcloud compute addresses delete google-managed-services-us-west1-subnet1  \
    --project=suzuyu-sharedhost-dev \
    --global
```
```
gcloud compute addresses delete google-managed-services-us-west2-subnet1  \
    --project=suzuyu-sharedhost-dev \
    --global
```

#### Terraform

```tf
resource "google_compute_global_address" "google-managed-services-us-west1-subnet1" {
  name          = "google-managed-services-us-west1-subnet1"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  address       = "172.20.0.0"
  prefix_length = 24
  project       = google_project.host_project.name
  network       = google_compute_network.host_sharedvpc.id
}

resource "google_compute_global_address" "google-managed-services-us-west2-subnet1" {
  name          = "google-managed-services-us-west2-subnet1"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  address       = "172.20.10.0"
  prefix_length = 24
  project       = google_project.host_project.name
  network       = google_compute_network.host_sharedvpc.id
}
```

Terraform はコード削除でリソース削除になるので切り戻し手順不要

### プライベート接続

#### API サービスの有効化 (共有 VPC のホストプロジェクトとリソースを構築するサービスプロジェクト両方)

##### gcloud
```
gcloud services enable --project=suzuyu-sharedhost-dev servicenetworking.googleapis.com
gcloud services enable --project=suzuyu-service-dev servicenetworking.googleapis.com
```

##### terraform
```tf
resource "google_project_service" "host_servicenetworking" {
  project                    = google_project.host_project.id
  disable_dependent_services = true
  service = "servicenetworking.googleapis.com"
}

resource "google_project_service" "service_servicenetworking" {
  project                    = google_project.service_project.id
  disable_dependent_services = true
  service = "servicenetworking.googleapis.com"
}
```

#### サービスプロデューサ側とのピアリングを実施し、サブネットを追加する

##### gcloud
```
gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=google-managed-services-us-west1-subnet1 \
    --network=sharedvpc \
    --project=suzuyu-sharedhost-dev
```

サブネットを追加する場合は下記のようにアップデートで可能

```
gcloud services vpc-peerings update \
    --service=servicenetworking.googleapis.com \
    --ranges=google-managed-services-us-west1-subnet1,google-managed-services-us-west2-subnet1 \
    --network=sharedvpc \
    --project=suzuyu-sharedhost-dev
```

##### terraform

```tf
resource "google_service_networking_connection" "google_producer" {
  network = google_compute_network.host_sharedvpc.id
  service = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [
    google_compute_global_address.google-managed-services-us-west1-subnet1.name,
    google_compute_global_address.google-managed-services-us-west2-subnet1.name,
  ]
}
```

追加は、上記の`reserved_peering_ranges`リストに追加すればよい

#### オンプレへの経路広報を update で追加する

##### gcloud

```
gcloud compute routers update ha-vpn-router1 \
   --region=us-west1 \
   --add-advertisement-ranges 172.20.0.0/24,172.20.10.0/24 \
   --project=suzuyu-sharedhost-dev
```

##### terraform
既存設定へ追加する例 (追加前の全体コード例： [9. HA VPN の作成 (vpn.tf)](https://qiita.com/suzuyui/items/879dfa5d837a19c39a54#9-ha-vpn-%E3%81%AE%E4%BD%9C%E6%88%90-vpntf))

```diff_terraform
resource "google_compute_router" "ha_vpn_router1" {
  name    = "ha-vpn-router1"
  network = google_compute_network.host_sharedvpc.name
  project = google_project.host_project.name
  region  = var.gcp_common.region

  bgp {
    asn               = var.vpn.asn
    advertise_mode    = "CUSTOM"
    advertised_groups = ["ALL_SUBNETS"]
    dynamic "advertised_ip_ranges" {
      for_each = {
~中略~
+       privateservice1   = "${google_compute_global_address.google-managed-services-us-west1-subnet1.address}/${google_compute_global_address.google-managed-services-us-west1-subnet1.prefix_length}"
+       privateservice2   = "${google_compute_global_address.google-managed-services-us-west2-subnet1.address}/${google_compute_global_address.google-managed-services-us-west2-subnet1.prefix_length}"
      }
      content {
        range = advertised_ip_ranges.value
      }
    }
  }
}
```

オンプレ側での経路受信結果は下記の通り

```
ERX01:~$ show ip route
Codes: K - kernel, C - connected, S - static, R - RIP, B - BGP
       O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       > - selected route, * - FIB route, p - stale info

IP Route Table for VRF "default"
~中略~
B    *> 172.20.0.0/24 [20/100] via 169.254.0.1, vti0, 00:00:35
B    *> 172.20.10.0/24 [20/100] via 169.254.0.1, vti0, 00:00:35
```

下記で反映を確認する

```
gcloud services vpc-peerings list \
    --network=VPC_NETWORK \
    --project=PROJECT_ID
```

下記表示例

```
% gcloud services vpc-peerings list --network=sharedvpc --project=suzuyu-sharedhost-dev
---
network: projects/xxxxxxxxxx/global/networks/sharedvpc
peering: servicenetworking-googleapis-com
reservedPeeringRanges:
- google-managed-services-us-west1-subnet1
- google-managed-services-us-west2-subnet1
service: services/servicenetworking.googleapis.com
```

```
gcloud compute networks peerings update servicenetworking-googleapis-com \
    --network=sharedvpc \
    --project=suzuyu-sharedhost-dev \
    --import-custom-routes \
    --export-custom-routes
```

#### 切り戻し

ピアリング削除する場合は下記コマンド


```
gcloud services vpc-peerings delete \
        --service=servicenetworking.googleapis.com \
        --network=sharedvpc \
        --project=suzuyu-sharedhost-dev
```

### Cloud Console での状態確認

構築した結果を Cloud Console でも確認したので下記に例として記載する

#### VPC ネットワーク > プライベート サービス接続

![スクリーンショット 2021-10-31 20.14.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/d967ade8-dab9-910a-7706-b10afb513a9e.png)


#### VPC ネットワーク ピアリング

servicenetworking-googleapis-com という名前でピアリングが作成されていることを確認できる

![スクリーンショット 2021-10-31 19.57.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/996f1da8-7f01-cd0e-05c5-23c6b333615b.png)


#### Cloud Router > アドバタイズされた IP 範囲

オンプレミス向けに今回作成したサブネットを広報(アドバタイズ)されていることを確認できる

![スクリーンショット 2021-10-31 20.04.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/be05224a-e0f6-5bc0-9e9e-37e0ee348330.png)


### 接続試験

プライベートサービスアクセスを利用した CloudSQL での MySQL 接続を試すため、
インスタンスを作成してテストアクセスを実施する
構築は Cloud Console 上で GUI で実施した

#### テスト用 CloudSQL MySQL 作成

Cloud Console で `SQL` を選択して、「インスタンスを作成」をクリック

![スクリーンショット 2021-10-31 20.25.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/109d9f8b-71f0-2a18-19f2-feff5fbed981.png)

`MySQL を選択`

![スクリーンショット 2021-10-31 20.27.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ccf5d551-cc0c-ba50-2a17-58db36592595.png)

パラメータを設定する
`インスタンス ID`, `パスワード`, `リージョン` を設定する

![スクリーンショット 2021-10-31 21.03.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/1b6771f0-9e59-e8e5-7869-32a0cdc6ce27.png)

インスタンスのカスタマイズにある`構成オプションを表示`をクリックする

![スクリーンショット 2021-10-31 21.05.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/46fb583e-f0dd-6abc-b5b9-78b3fa632887.png)

`接続` を開いて、`共有ネットワーク` を選択して `ネットワーク` で共有 VPC を選択し、`割り振られているIP範囲`で今回作成したサブネットを選択する

![スクリーンショット 2021-10-31 20.31.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/04e8f343-79e6-a655-a074-f8726916c5eb.png)

`バックアップ` を開いて、ロケーションを選択しておく (しないとエラーとなったため)

![スクリーンショット 2021-10-31 20.39.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/5d498864-431a-27ae-3a06-ff0f3da9f52b.png)

最後に`インスタンスの作成`をクリックして作成する

![スクリーンショット 2021-10-31 20.41.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8378b4ef-612e-d846-64de-c6e7e20bcfa3.png)

2台目も `リージョン` と `割り振られているIP範囲` を変えて構築する

![スクリーンショット 2021-10-31 21.08.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/1058f828-3ec7-2856-e4b7-113935ef38fa.png)

![スクリーンショット 2021-10-31 21.09.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/760aa975-3d39-a9d2-08ad-4229b5ac286f.png)


作成完了後にアサインされたアドレスを確認する

![スクリーンショット 2021-10-31 21.16.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/c62aefef-19ec-5da9-30cc-df31a46bfb2c.png)

![スクリーンショット 2021-10-31 21.17.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f9f8bcc0-b29a-1c20-a9ed-f9260200a92c.png)


#### mysql 接続試験

下記、オンプレ VM, GCP Service Project 内 VM から実施 (結果はほぼ同じ)
※ IP での接続のため、IP レベルの Firewall Rules で許可がされていれば接続可能

```sh:mysql-test1へテストアクセス(パスワードは作成時に設定したもの)
% mysql --host=172.20.0.2 --user=root --password
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 463
Server version: 5.7.34-google-log (Google)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT Host, User, password_last_changed FROM mysql.user;
+-----------+---------------+-----------------------+
| Host      | User          | password_last_changed |
+-----------+---------------+-----------------------+
| %         | root          | 2021-10-31 12:12:16   |
| localhost | mysql.sys     | 2021-10-31 12:10:56   |
| localhost | mysql.session | 2021-10-31 12:11:07   |
+-----------+---------------+-----------------------+
3 rows in set (0.13 sec)

mysql> exit
Bye

```

```sh:mysql-test2へテストアクセス(パスワードは作成時に設定したもの)
% mysql --host=172.20.10.2 --user=root --password
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 448
Server version: 5.7.34-google-log (Google)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT Host, User, password_last_changed FROM mysql.user;
+-----------+---------------+-----------------------+
| Host      | User          | password_last_changed |
+-----------+---------------+-----------------------+
| %         | root          | 2021-10-31 12:15:41   |
| localhost | mysql.sys     | 2021-10-31 12:14:14   |
| localhost | mysql.session | 2021-10-31 12:14:24   |
+-----------+---------------+-----------------------+
3 rows in set (0.15 sec)

mysql> exit
Bye

```

以上で作成して疎通確認まで完了

### リソース作成後のサブネット作成・ルート反映

サブネットの作成やピアリングしたプロデューサ VPC からルート広報がされるのは、インスタンスを初回作成される時なので、
テストで作成した後に反映されたピアリングからのサブネット状態を確認する

下記、ピアリングのルートとして、サブネットが追加されたことが確認できる

![スクリーンショット 2021-10-31 21.46.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/68b94d20-54b4-2606-3507-214504fc9087.png)

下記、ピアリング接続の情報に指定したリージョンでサブネットが /24 で作成され、インポートされたことが確認できる

![スクリーンショット 2021-10-31 21.48.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/a1675be4-f734-2997-88f6-023fc6b21735.png)


## まとめ
プライベートサービスアクセスの設定とオンプレミス接続環境での経路交換も実施した
CloudSQL を試験作成してプライベートにアクセスまで確認できた

一度作成して削除したサブネットが、サービスプロデューサ側のルートから消えない状態を確認したので、削除方法を確認など、運用についてはこれから調べていく予定

## 参照

https://cloud.google.com/vpc/docs/private-services-access

https://cloud.google.com/vpc/docs/configure-private-services-access

https://cloud.google.com/vpc/docs/using-vpc-peering#update-peer-connection

https://cloud.google.com/service-infrastructure/docs/enabling-private-services-access

https://cloud.google.com/network-connectivity/docs/router/how-to/advertising-subnets#cloud-router

https://cloud.google.com/sdk/gcloud/reference/services/vpc-peerings/update

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_networking_connection

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address