Private Service Connect を試す

## はじめに
2021.04 に Google Cloud で [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect?hl=ja) が GA された
これまではオンプレミスから Google Cloud の API へは[オンプレミス ホスト用の限定公開の Google アクセス](https://cloud.google.com/vpc/docs/private-google-access-hybrid?hl=ja)を使用して Private にアクセスできたが、
今後この方法が主流になりそうなので試していく

今回やるイメージ (PSC : Private Service Connect)
![スクリーンショット 2021-05-16 21.56.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f0809adb-d25e-8c69-48e1-559bb6e0d271.png)


## Private Service Connect

### Private Service Connect とは ？ 
VPC ネットワーク内のグローバル内部 IP アドレスを使用してプライベート エンドポイントを作成できる
エンドポイントに配信されるトラフィックを制御し、トラフィックを Google Cloud 内にとどめることができる
[参考](https://cloud.google.com/vpc/docs/private-service-connect?hl=ja)

### ネットワーク要件
- 内部 IP のみのインスタンス
    - 限定公開の Google アクセスが有効になっているサブネットであること
- 外部 IP を持つインスタンス
    - 限定公開の Google アクセスが有効/無効関係なく利用できる
- VPC Peering
    - Private Service Connect エンドポイントは、**ピアリングされた VPC ネットワークからアクセスできない**

### サポートされている API
2種類選択可能 (従来の限定公開 Google アクセスと変わらなそう)
[詳細](https://cloud.google.com/vpc/docs/private-service-connect?hl=ja#supported-apis)

- all-apis
    - private.googleapis.com と同じ API にアクセスできる
- vpc-sc
    - restricted.googleapis.com と同じ API にアクセスできる

### 費用
[ドキュメント](https://cloud.google.com/vpc/docs/private-service-connect#pricing)によると、[Private Service Connect の転送ルール](https://cloud.google.com/vpc/pricing#psc-forwarding-rules)の費用が発生する
転送ルールあたり **$0.01 / 時間**
(月当たりだと大体 818.4円 ($0.0.1 * 24 * 31 * ¥110))
SKU : Networking Private Service Connect Consumer End Point

### ロール

必要になるロールは下記の通り [[参考](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis?hl=ja#roles)]

| タスク | ロール |
|:--|:--|
| Private Service Connect エンドポイントの作成 | 以下のすべてのロール:<br>Compute ネットワーク管理者（roles/compute.networkAdmin）、<br>Service Directory 編集者（roles/servicedirectory.editor）、<br>DNS 管理者（roles/dns.admin） |
| 限定公開の Google アクセスの構成（省略可） | Compute ネットワーク管理者（roles/compute.networkAdmin） |


### Google API
必要な Google API は自動では有効化されないので、下記を有効にする必要がある[参考](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis?hl=ja#begin)

- Compute Engine API
- Service Directory API
- Cloud DNS API

### Service Discovery
Private Service Connect エンドポイントは、[Service Directory](https://cloud.google.com/service-directory/docs/overview?hl=ja) に登録される [[参考](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis?hl=ja#service-directory)]

Service Directory とは、`サービスを保存、管理、公開するためのプラットフォーム`で、`リージョンサービス`となる

[概要](https://cloud.google.com/service-directory?hl=ja)

### DNS 構成

- p.googleapis.com に対する Service Directory の限定公開 DNS ゾーン
- エンドポイントで利用可能な各サービスの DNS レコード
    - SERVICE-ENDPOINT.p.googleapis.com として提供される
    - 例 (SERVICE: storage, ENDPOINT: pscendpoint (エンドポイントに登録する際の名前)の場合)
        - storage-pscendpoint.p.googleapis.com
- デフォルトの名称で名前解決したい場合は、googleapis.com ゾーンを作成して、エンドポイントにする IP で名前解決をできるように設定する

[参考ドキュメント](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis#using-endpoints)

### IP アドレス

- IPv4 /32 で指定する
- VPC 内のアドレスと重複できない、既存サブネット内からアサインはできない
- カスタム動的ルートと重複する場合、Private Service Connect エンドポイントが優先される
- RFC 1918 以外のアドレスを指定すると、予期せぬコストが発生する可能性がある

[参考ドキュメント](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis#ip-address-requirements)

## 構築

下記、実施内容

1. API 有効化、IAM 設定
1. Private Service Connect 設定
1. DNS ゾーン・レコードの追加を実施
1. オンプレ向けアドレス広報・DNS設定

Private Service Connect だけであれば、1.2. だけで OK
3.はデフォルトの DNS 名で利用したい場合は実施
4. はオンプレからアクセスを対応する場合は実施

### 0. 事前確認

事前の Google API 向けの名前解決の状況を確認する (例として storage)

```sh:実施前_GoogleCloud内の_GCE上
$ nslookup storage.googleapis.com
Server:		169.254.169.254
Address:	169.254.169.254#53

Non-authoritative answer:
Name:	storage.googleapis.com
Address: 74.125.142.128
Name:	storage.googleapis.com
Address: 74.125.195.128
Name:	storage.googleapis.com
Address: 74.125.20.128
Name:	storage.googleapis.com
Address: 108.177.98.128
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c07::80
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c08::80
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c09::80
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c00::80

$ nslookup storage-pscendpoint.p.googleapis.com
Server:		169.254.169.254
Address:	169.254.169.254#53

Non-authoritative answer:
Name:	storage-pscendpoint.p.googleapis.com
Address: 74.125.195.95
Name:	storage-pscendpoint.p.googleapis.com
Address: 2607:f8b0:400e:c07::5f
```

```sh:実施前_オンプレ内の_VM上
% nslookup storage.googleapis.com
Server:		192.168.129.254
Address:	192.168.129.254#53

Non-authoritative answer:
Name:	storage.googleapis.com
Address: 142.250.196.144
Name:	storage.googleapis.com
Address: 172.217.175.16
Name:	storage.googleapis.com
Address: 172.217.161.80
Name:	storage.googleapis.com
Address: 216.58.197.208
Name:	storage.googleapis.com
Address: 172.217.175.240
Name:	storage.googleapis.com
Address: 172.217.31.176
Name:	storage.googleapis.com
Address: 172.217.27.80
Name:	storage.googleapis.com
Address: 142.250.196.112
Name:	storage.googleapis.com
Address: 172.217.175.112
Name:	storage.googleapis.com
Address: 216.58.220.112
Name:	storage.googleapis.com
Address: 2404:6800:4004:80f::2010
Name:	storage.googleapis.com
Address: 2404:6800:4004:81b::2010
Name:	storage.googleapis.com
Address: 2404:6800:4004:818::2010
Name:	storage.googleapis.com
Address: 2404:6800:4004:819::2010

% nslookup storage-pscendpoint.p.googleapis.com
Server:		192.168.129.254
Address:	192.168.129.254#53

Non-authoritative answer:
Name:	storage-pscendpoint.p.googleapis.com
Address: 216.58.197.202
Name:	storage-pscendpoint.p.googleapis.com
Address: 2404:6800:4004:811::200a
```

上記のようにグローバルアドレス経由であることがわかる (上記のグローバルアドレスは、タイミングや環境によって異なると思われる)

### 1. API 有効化、IAM 設定

前述した必要な API を有効化する

```sh:API有効化
gcloud services enable compute.googleapis.com
gcloud services enable servicedirectory.googleapis.com
gcloud services enable dns.googleapis.com
```

前述した必要な ROLE を操作するユーザへ追加する[[参照](https://cloud.google.com/sdk/gcloud/reference/projects/add-iam-policy-binding)]

PROJECT : 対象のプロジェクト
MEMBER : 今回操作するアカウント
ROLE : roles/compute.networkAdmin, roles/servicedirectory.editor, roles/dns.admin, roles/compute.networkAdmin

```sh:ロール付与コマンド
gcloud projects add-iam-policy-binding PROJECT_ID --member=MEMBER --role=ROLE
```

### 2. Private Service Connect 設定

Private Service Connect の設定・エンドポイント作成を実施する

| パラメータ | 内容 | 今回使用するパラメータ |
|:--|:--|:--|
| ADDRESS_NAME | 予約済みの IP アドレスに割り当てる名前 | psc-address |
| ENDPOINT_IP | エンドポイントに予約する IP アドレス | 172.18.5.1 |
| NETWORK_NAME | エンドポイントの VPC ネットワークの名前 | projects/suzuyu-sharedhost-dev/global/networks/sharedvpc |

```sh:
gcloud compute addresses create ADDRESS_NAME \
  --global \
  --purpose=PRIVATE_SERVICE_CONNECT \
  --addresses=ENDPOINT_IP \
  --network=NETWORK_NAME
```

今回の実例でのパラメータを当てはめると下記の通り実施

```sh
gcloud compute addresses create psc-address \
  --global \
  --purpose=PRIVATE_SERVICE_CONNECT \
  --addresses=172.18.5.1 \
  --network=projects/suzuyu-sharedhost-dev/global/networks/sharedvpc
```

```sh:出力例
Created [https://www.googleapis.com/compute/v1/projects/suzuyu-sharedhost-dev/global/addresses/psc-address].
```

| パラメータ | 内容 | 今回使用するパラメータ |
|:--|:--|:--|
| ENDPOINT_NAME | エンドポイントに割り当てる名前<br>名前は 1～20 文字にし、小文字と数字のみを使用する<br>名前の先頭は文字 | pscendpoint |
| NETWORK_NAME | エンドポイントの VPC ネットワークの名前 | projects/suzuyu-sharedhost-dev/global/networks/sharedvpc |
| ADDRESS_NAME | 予約済みの IP アドレスに割り当てる名前 | psc-address |
| REGION_URI | 使用する Service Directory リージョンの URI<br>この URI は、Private Service Connect エンドポイントを作成するプロジェクトを参照する必要がある<br>REGION_URI の形式は projects/PROJECT_NAME/locations/REGION <br> | projects/suzuyu-sharedhost-dev/locations/us-west1 |
| API_BUNDLE | エンドポイントで使用できる API のバンドル。[サポートされている API のリストを参照](https://cloud.google.com/vpc/docs/private-service-connect#supported-apis)<br>　・ `all-apis` を使用して、サポートされているすべての API へのアクセスを許可<br>　・ `vpc-sc` を使用して、VPC Service Controls をサポートする Google API へのアクセスを制限 | all-apis |

```sh:フォワーディングルールの追加
gcloud compute forwarding-rules create ENDPOINT_NAME \
  --global \
  --network=NETWORK_NAME \
  --address=ADDRESS_NAME \
  --service-directory-registration=REGION_URI
  --target-google-apis-bundle=API_BUNDLE
```

今回の実例でのパラメータを当てはめると下記の通り実施


```sh:フォワーディングルールの追加(実施例)
gcloud --project suzuyu-sharedhost-dev compute forwarding-rules create pscendpoint \
  --global \
  --network=projects/suzuyu-sharedhost-dev/global/networks/sharedvpc \
  --address=psc-address \
  --service-directory-registration=projects/suzuyu-sharedhost-dev/locations/us-west1 \
  --target-google-apis-bundle=all-apis
```

```sh:フォワーディングルールの追加(出力例)
Created [https://www.googleapis.com/compute/v1/projects/suzuyu-sharedhost-dev/global/forwardingRules/pscendpoint].
```

```sh:削除する場合
gcloud --project suzuyu-sharedhost-dev compute forwarding-rules delete \
       pscendpoint --global
```

`curl -v ENDPOINT_IP/generate_204`で HTTP 204 レスポンス コードが表示されれば OK

```sh:実施例
% curl -v 172.18.5.1/generate_204
*   Trying 172.18.5.1...
* TCP_NODELAY set
* Connected to 172.18.5.1 (172.18.5.1) port 80 (#0)
> GET /generate_204 HTTP/1.1
> Host: 172.18.5.1
> User-Agent: curl/7.61.1
> Accept: */*
> 
< HTTP/1.1 204 No Content
< Content-Length: 0
< Date: Sat, 15 May 2021 15:36:51 GMT
< 
* Connection #0 to host 172.18.5.1 left intact
```

下記で、構成済みの Private Service Connect エンドポイントの一覧を取得できる

```sh
% gcloud --project suzuyu-sharedhost-dev compute forwarding-rules list  \
--filter target="(all-apis OR vpc-sc)" --global
NAME         REGION  IP_ADDRESS  IP_PROTOCOL  TARGET
pscendpoint          172.18.5.1  TCP          all-apis
```


自動で `SERVICE-ENDPOINT.p.googleapis.com`で名前解決できるようになっている

```sh:PSC設定後_GCE上
$ nslookup storage-pscendpoint.p.googleapis.com
Server:		169.254.169.254
Address:	169.254.169.254#53

Non-authoritative answer:
Name:	storage-pscendpoint.p.googleapis.com
Address: 172.18.5.1
```

ただし、`SERVICE-ENDPOINT.p.googleapis.com`ではない、`storage.googleapis.com` などでは実施前と変わらない

```sh:PSC設定後_DNS設定前_GCE上
$ nslookup storage.googleapis.com
Server:		169.254.169.254
Address:	169.254.169.254#53

Non-authoritative answer:
Name:	storage.googleapis.com
Address: 173.194.203.128
Name:	storage.googleapis.com
Address: 74.125.20.128
Name:	storage.googleapis.com
Address: 74.125.197.128
Name:	storage.googleapis.com
Address: 74.125.142.128
Name:	storage.googleapis.com
Address: 74.125.195.128
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c07::80
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c03::80
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c01::80
Name:	storage.googleapis.com
Address: 2607:f8b0:400e:c08::80
```

上記のように Private Service Connect のエンドポイントを作成までできた
デフォルトの DNS 名 (例：`storage.googleapis.com`など)で利用したい場合は、次の内容を実施する

### 3. DNS ゾーン・レコードの追加を実施
以降の手順は、従来の[オンプレミス ホスト用の限定公開の Google アクセスの構成](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#gcloud)と同じ方法([DNS 構成](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#config-domain),[VPC ネットワーク ルーティング](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#config-routing))となる

Qiita にも[限定公開の Google アクセスを使用したオンプレミスからの GCP プライベートアクセス](https://qiita.com/suzuyui/items/b69be2a50df9cbedf402) として記載したので、説明は省略してコマンドだけ書く (詳細はリンク先参照)


```sh:dns_gcloud
gcloud beta dns managed-zones create googleapis \
 --visibility=private \
 --networks=https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/vpc01 \
 --description=googleapis \
 --dns-name=googleapis.com.

gcloud dns record-sets transaction start --zone=googleapis

gcloud dns record-sets transaction add --name=\*.googleapis.com. \
    --type=CNAME pscendpoint.googleapis.com. \
    --zone=googleapis \
    --ttl=300

gcloud dns record-sets transaction add --name=pscendpoint.googleapis.com. \
    --type=A 172.18.5.1 \
    --zone=googleapis \
    --ttl=300

gcloud dns record-sets transaction execute --zone=googleapis

gcloud beta dns policies create apipolicy \
 --networks=https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/vpc01 \
 --enable-inbound-forwarding \
 --description=apipolicy
```

terraform だと下記のような感じ

```terraform:DNS設定例(google_project.host_projectはホストプロジェクトリソース.google_compute_network.host_sharedvpcは共有VPCリソース)
variable "private_service_connect_address" {
  type = string
  default = "172.18.5.1"
}

#api policy
resource "google_dns_policy" "googleapis_apipolicy" {
  name                      = "apipolicy"
  enable_inbound_forwarding = true
  project                   = google_project.host_project.name

  enable_logging = false

  networks {
    network_url = google_compute_network.host_sharedvpc.id
  }
}

## https://cloud.google.com/vpc/docs/configure-private-google-access?hl=ja
resource "google_dns_managed_zone" "googleapis" {
  name        = "googleapis"
  project     = google_project.host_project.name
  dns_name    = "googleapis.com."
  description = "Private Access googleapis"

  visibility = "private"

  private_visibility_config {
    networks {
      network_url = google_compute_network.host_sharedvpc.id
    }
  }
  depends_on = [
    google_project.host_project,
  ]
}

resource "google_dns_record_set" "googleapis_psc_a" {
  name         = "pscendpoint.googleapis.com."
  managed_zone = google_dns_managed_zone.googleapis.name
  type         = "A"
  ttl          = 300
  project      = google_project.host_project.name

  rrdatas = [var.private_service_connect_address]

  depends_on = [
    google_dns_managed_zone.googleapis,
    google_dns_record_set.googleapis_restricted_a,
  ]
}

resource "google_dns_record_set" "googleapis_cname" {
  name         = "*.googleapis.com."
  managed_zone = google_dns_managed_zone.googleapis.name
  type         = "CNAME"
  ttl          = 300
  project      = google_project.host_project.name
  # rrdatas      = ["restricted.googleapis.com."]
  rrdatas      = [google_dns_record_set.googleapis_psc_a.name]
  depends_on = [
    google_dns_managed_zone.googleapis,
    google_dns_record_set.googleapis_private_a,
  ]
}
```

実施後はGCE上ではデフォルトのDNS名でもアクセスできることが確認できる
gcloud コマンドでたたいて、API通信もできることも確認した

```sh:PSC設定・DNS設定後_GCE上
$ nslookup storage.googleapis.com
Server:		169.254.169.254
Address:	169.254.169.254#53

Non-authoritative answer:
storage.googleapis.com	canonical name = pscendpoint.googleapis.com.
Name:	pscendpoint.googleapis.com
Address: 172.18.5.1

$ gcloud projects list
PROJECT_ID             NAME                   PROJECT_NUMBER
suzuyu-service-dev     suzuyu-service-dev     xxxxxxxxxxxx

```

### 4. オンプレ向けアドレス広報・DNS設定
`3. DNS `同様に、従来の[オンプレミス ホスト用の限定公開の Google アクセスの構成](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#gcloud)と同じ方法([VPC ネットワーク ルーティング](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#config-routing))となる

Qiita [限定公開の Google アクセスを使用したオンプレミスからの GCP プライベートアクセス](https://qiita.com/suzuyui/items/b69be2a50df9cbedf402) でいうと、[3. [GCP] 199.36.153.4/30 のルート広報](https://qiita.com/suzuyui/items/b69be2a50df9cbedf402#3-gcp-19936153430-%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%88%E5%BA%83%E5%A0%B1)からの内容

gcloudだと下記の通り

```gcloud
gcloud compute routers update cloudrouter01 \
    --add-advertisement-ranges 172.18.5.1/32
```

terraform だと下記の通り (既存のオンプレ向けのルータにカスタム経路アドレスを追加する)

```diff_terraform
resource "google_compute_router" "ha_vpn_router1" {
#中略
  bgp {
#中略
    dynamic "advertised_ip_ranges" {
      for_each = {
#中略
+        pscendpoint       = "172.18.5.1/32"
      }
      content {
        range = advertised_ip_ranges.value
      }
    }
  }
}
```

オンプレ側の DNS リゾルバ側で、フォワーディング設定を実施する
まず、DNSフォワードのアドレス一覧を取得する

```
% gcloud --project suzuyu-sharedhost-dev compute addresses list \ 
    --filter='purpose = "DNS_RESOLVER"' \
    --format='csv(address, region, subnetwork)'
address,region,subnet
172.18.0.2,us-west1,service1-gce-subnetwork
#...略
```

表示されたアドレスのどれかを、フォワード先に設定する
(下記、オンプレルータが Edge Router で DNS リゾルバに設定している場合の設定例)

```sh:EdgeRouter_Set_DNS_Forwarding
set service dns forwarding options server=/googleapis.com/172.18.0.2@192.168.129.254
```

上記設定完了後に、DNS 解決などをすると下記のように変更を確認できる
gcloud コマンドで API アクセスもできていることも確認できた

```
% nslookup storage-pscendpoint.p.googleapis.com                    
Server:		192.168.129.254
Address:	192.168.129.254#53

Name:	storage-pscendpoint.p.googleapis.com
Address: 172.18.5.1

% nslookup storage.googleapis.com                                         
Server:		192.168.129.254
Address:	192.168.129.254#53

storage.googleapis.com	canonical name = pscendpoint.googleapis.com.
Name:	pscendpoint.googleapis.com
Address: 172.18.5.1

% gcloud services list
NAME                                 TITLE
accesscontextmanager.googleapis.com  Access Context Manager API
bigquery.googleapis.com              BigQuery API
bigquerystorage.googleapis.com       BigQuery Storage API
cloudbilling.googleapis.com          Cloud Billing API
cloudidentity.googleapis.com         Cloud Identity API
cloudresourcemanager.googleapis.com  Cloud Resource Manager API
compute.googleapis.com               Compute Engine API
container.googleapis.com             Kubernetes Engine API
containerregistry.googleapis.com     Container Registry API
dns.googleapis.com                   Cloud DNS API
iam.googleapis.com                   Identity and Access Management (IAM) API
iamcredentials.googleapis.com        IAM Service Account Credentials API
monitoring.googleapis.com            Cloud Monitoring API
oslogin.googleapis.com               Cloud OS Login API
pubsub.googleapis.com                Cloud Pub/Sub API
servicedirectory.googleapis.com      Service Directory API
serviceusage.googleapis.com          Service Usage API
storage-api.googleapis.com           Google Cloud Storage JSON API
```

以上で、オンプレ・GoogleCloud上両方で、Private IP で Google API へのアクセス経路作成、デフォルト DNS 名称でアクセスができるようになった

## まとめ
Private Service Connect を試してみた
今までは決まったアドレス (199.36.153.8/30, 199.36.153.4/30) を使用するしかなかったが、
プライベートアドレスでエンドポイントが作成できるようになり、ネットワーク設計が柔軟にできるようになったように感じる

個人でやる分には、費用としては、従来の限定公開 Google アクセスにプラスして大体819円/月かかる計算なので、
アドレスの制限(自宅ラボとかに Globalアドレス巻きたくないとか) なければ、現状は従来の方法でもいいのかなと思う
(その他、DNS を使うと DNS のクエリ量の課金などもある. 常時 ON は個人利用は低コストで放置は厳しい)


## 参考
Private Service Connect
https://cloud.google.com/vpc/docs/private-service-connect?hl=ja

サービスのプライベート アクセス オプション
https://cloud.google.com/vpc/docs/private-access-options?hl=ja

VPC の料金体系 > Private Service Connect
https://cloud.google.com/vpc/pricing?hl=ja#psc-forwarding-rules

Private Service Connect の構成
https://cloud.google.com/vpc/docs/configure-private-service-connect-apis

オンプレミス ホスト用の限定公開の Google アクセスの構成
https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid?hl=ja#gcloud