【AWS】PrivateLinkに詳しくなりたい人へ

## はじめに

みなさん、AWS PrivateLinkは利用していますか？
私は業務で利用しているのですが、簡単に設定出来すぎてあまり深く理解していなかったのでこのタイミングでPrivateLinkの気になることを調べてみました。

私の中にあった疑問を順番に調べていくという形式で、PrivateLinkの理解を深めていこうと思います。

想定読者は

- AWS PrivateLinkをチョット使ったことある
- AWS PrivateLinkをこれから使おうと思っている

方々になります。では早速。

## PrivateLinkってVPCエンドポイントのことだっけ？
ちょっと違います。

PrivateLinkはインターフェースVPCエンドポイントと同義だとよく思い込まれがちですが、実際には以下の2つをセットで「AWS PrivateLink」となります。
- インターフェースVPCエンドポイント（サービス利用側のVPC内で作成）
- VPCエンドポイントサービス（サービス提供側のVPC内で作成）


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/97e2df3a-2596-d6dc-21ed-3560f1e2499e.png)


以下の記事がとても参考になりました。

https://dev.classmethod.jp/articles/aws-vpcendpoint-privatelink-beginner/

## インターフェースVPCエンドポイントの正体は？

インターフェースVPCエンドポイントを作成すると、ENI（Elastic Network Interface）が作成（アタッチ）されます。つまり、インターフェースVPCエンドポイントの正体はENIです。

VPCエンドポイントを作成するとき1つ以上のサブネットを選択しますが、選択したサブネット上にそれぞれENIが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/a367c411-9bdb-5fde-29a1-2990bfc0fdaa.png)

VPCエンドポイントのイメージとしては以下のような感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/996ac250-937b-402c-7451-148d74412101.png)

VPCエンドポイント作成時に指定できるAZは、後述のエンドポイントサービス側で設定されているAZの範囲でしか指定できません。

ちなみに作成時にENIのIPアドレスを指定できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/5a30b9cc-0d18-dd0d-7181-966a9a1c80f6.png)

これによりエンドポイント対してIPでのアクセスを行いたい場合、IPを固定できるようになります。
もちろん選択しているサブネットのCIDR内で空いているIPしか指定できません。

IPを指定しない場合はAWS側でランダムにIPが決定されます。

## なんでDNS名が複数あるの？

VPCエンドポイントを作成すると2つ以上のDNS名が発行されることにお気づきでしょうか。これは一体何でしょう？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/8cf1f1c0-eaaa-0c5d-f9be-dbd6d289c3b9.png)

実は、一言でDNS名と言ってもインターフェースVPCエンドポイントのDNS名には
- リージョンDNS名
- AZごとに生成したエンドポイント固有のDNS名

の2種類が存在するのです。


つまり、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/f0128e9d-9c9a-b4ca-c370-1ff6bc94e4aa.png)

というわけです。イメージとしては以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/fc27a726-0220-480f-4041-360bdc592fde.png)

リージョンDNS名は全てのAZに存在するENIのプライベートIPを返し、AZ固有のDNS名は各AZに存在するENIのプライベートIPを返します。

例1. リージョンDNS名

```shell
[ec2-user@ip-10-0-135-15 ~]$ nslookup vpce-xxx.vpce-svc-vpce-xxx.ap-northeast-1.vpce.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   vpce-xxx.vpce-svc-vpce-xxx.ap-northeast-1.vpce.amazonaws.com
Address: 10.0.129.123 #ap-northeast-1aのENIのプライベートIP
Name:   vpce-xxx.vpce-svc-vpce-xxx.ap-northeast-1.vpce.amazonaws.com
Address: 10.0.150.143 #ap-northeast-1cのENIのプライベートIP
```

例2. AZ固有のDNS名
```shell
[ec2-user@ip-10-0-135-15 ~]$ nslookup vpce-vpce-xxx-ap-northeast-1a.vpce-svc-vpce-xxx.ap-northeast-1.vpce.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   vpce-vpce-xxx-ap-northeast-1a.vpce-svc-vpce-xxx.ap-northeast-1.vpce.amazonaws.com
Address: 10.0.129.123 #ap-northeast-1のENIのプライベートIP
```

ちなみに各DNS名はよくよく見てみると

**{エンドポイント識別子}.{サービス識別子}.{リージョン}.vpce.amazonaws.com**

で構成されており、先頭の **{エンドポイント識別子}** に注目すると各DNSがどこを指しているのかは把握できます。

例1. リージョンDNS名

`vpce-0fe5b17a0707d6abc-29p5708s.ec2.us-east-1.vpce.amazonaws.com`

→エンドポイント識別子の最後に何もついていないので`us-east-1`のリージョンDNS名

例2. AZ固有のDNS名

`vpce-0fe5b17a0707d6abc-29p5708s-us-east-1a.ec2.us-east-1.vpce.amazonaws.com`

→エンドポイント識別子の最後にAZの記載があるので`us-east-1a`のAZ固有のDNS名

## どのDNS名を使うべき？

大抵の場合、リージョンDNS名を使っているのがほとんどだと思います。

でも、**本当にそれでいいんでしたっけ？**

答えに迫るため、次はエンドポイントサービスを理解しておきましょう。

## エンドポイントサービスの正体は？

公式に

>エンドポイントサービスには、Network Load Balancer または Gateway Load Balancer のいずれかが必要です。

とあるように、エンドポイントサービスは一言で行ってしまえばロードバランサー（群）です。

https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/create-endpoint-service.html

先ほどのインターフェースVPCエンドポイントの理解と合わせれば、例えば2つのAZに跨るPrivateLinkの全容は以下のようなイメージになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/71126263-afd2-e354-8a71-2cfa2cc1f533.png)

もう少し厳密に書くと、NLBもENIで構成されるのでより正確に書くなら次の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/73d2572a-30bd-496a-1cea-8dde706fae86.png)


## ネットワークの経路はどうなるの？

エンドポイントENIからの最初の接続が開始されると、エンドポイントENIと同じAZにあるNLBが選択されます。

つまり同じAZ間でのみ接続を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/9185d8bc-49be-d4ec-9826-1846dbc2690d.png)

ネットワーク経路についてもう少し踏み込んで考えるために、次の構成を例に考えてみます。

左側がサービス**利用**側のVPC、右側がサービス**提供**側のVPCと仮定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/93f5cb8a-4aea-6195-df62-6bc2583d411c.png)

サービス**利用**側は`ap-northeast-1a`および`ap-northeast-1c`にあるEC2インスタンスからPrivateLinkを経由してサービスにhttpアクセスします。

サービス**提供**側はサービスに冗長性を持たせるため、`ap-northeast-1a`および`ap-northeast-1c`のマルチAZ構成をとっています。

サービスはhttpのリクエストを受けると、リクエストを受けたEC2インスタンスの起動しているAZに応じて

```
This is ap-northeast-1a Service
```

もしくは

```
This is ap-northeast-1c Service
```

を表示させるだけの簡単なサービスとします。

この構成を例に、PrivateLinkを利用してサービスにアクセスした際のネットワークを経路を考えてみます。

まず、前述の通りVPCエンドポイントENIからの接続は同一AZ内のNLBが選択されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/82a6a351-0add-3f56-f978-cbf6058b6bda.png)


そしてNLBとターゲットグループ間の通信はというと

>デフォルトでは、各ロードバランサーノードは、アベイラビリティーゾーン内の登録済みターゲット間でのみトラフィックを分散します。

とあるように**クロスゾーン負荷分散を無効にしている**場合はNLBからEC2への接続も同一AZ内に限られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/51d6d57b-c446-101d-ed76-fbbb790f9a58.png)

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/network-load-balancers.html#cross-zone-load-balancing


ということは、

「サービス側のEC2インスタンスのうち、どのAZにあるEC2へ到達するか」

は、

「どのAZにあるVPCエンドポイント（のENI）を利用するか」

に依存しそうです。

となるとAZ固有のDNS名、リージョンDNS名の振る舞いが重要になりそうです。

### AZ固有のDNS名を利用した場合

はじめに左上の`ap-northeast-1a`上にあるEC2からそれぞれのAZ固有のDNS名経由でサービスにアクセスしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/1e8bfa4b-4e62-6d07-cb4c-72adcf843140.png)

```shell
[ec2-user@ip-10-0-135-15 ~]$ curl vpce-xxx-ap-northeast-1a.vpce-svc-xxx.ap-northeast-1.vpce.amazonaws.com
This is ap-northeast-1a Service
```

予想通り、名前解決の先で到達したサービス側のEC2は、選択したDNS名と同じAZ上に存在するEC2となりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/8130029a-85bc-fd25-f850-ecdabb0786ad.png)

次にDNS名を使わずにENIのプライベートIPを利用してアクセスしてみましょう。

```shell
[ec2-user@ip-10-0-135-15 ~]$ curl 10.0.129.123
This is ap-northeast-1a Service
```

DNS名を利用したときと同様にEC2にアクセスできました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/7ab76466-1be6-16e0-cb21-81188c80d030.png)

※以降、コマンドの実行結果は適宜省略していきます

つまり、AZ固有のDNS名を利用することでネットワーク経路を同一AZ内に留めることができ、レイテンシーや料金に貢献できそうです。

**、、、と思っていたのですが！**

>データ処理料金は、トラフィックの送信元か送信先かにかかわらず、VPC エンドポイントで処理されたデータ量 (ギガバイト単位) に対して請求されます。1 時間未満の VPC エンドポイント使用時間は、1 時間分として請求されます。 

とあるように、インターフェースVPCエンドポイントのデータ転送料金は同一AZ内の通信だとしても課金されるようです。
同一AZ内だからお金かからないと思っていたので、大きな勘違いでした。

### リージョンDNS名を利用した場合

次にリージョンDNS名経由でサービスにアクセスしてみます。どちらのAZのEC2に到達するのでしょう？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/e8c515f5-2e86-c2f7-ca5b-2abf64fc3acc.png)

```shell
[ec2-user@ip-10-0-135-15 ~]$ curl vpce-xxx.vpce-svc-xxx.ap-northeast-1.vpce.amazonaws.com
This is ap-northeast-1a Service
```

答えは、`ap-northeast-1a`でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/f8ab4419-5429-d615-c1f1-e9ae1364d6d9.png)

何度繰り返してもリージョンDNS名を利用した場合は`ap-northeast-1a`にあるEC2に到達しました。

では、左下の`ap-northeast-1c`上にあるEC2から同じくリージョンDNS名経由でサービスにアクセスするとどうでしょう？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/c7128b63-ed64-bfea-c1e0-7cfae11dd9d5.png)

```shell
[ec2-user@ip-10-0-149-188 ~]$ curl vpce-xxx.vpce-svc-xxx.ap-northeast-1.vpce.amazonaws.com
This is ap-northeast-1c Service
```

到達したのは`ap-northeast-1c`上のEC2でした。こちらも何度繰り返しても同じ結果となりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/f31cc118-a0cf-7c22-03c3-058aa26c470e.png)

つまり、リクエストしたEC2と同じAZにあるVPCエンドポイントにリクエストは振り分けられるように見えます。

では、同一AZにあるサービス側のEC2が障害などで停止している場合はどうなるでしょう？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/66390f52-4441-5fde-2afc-5b277c4b7921.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/2968e90b-f9ed-30f7-4d94-751e0b1ae0dc.png)

```shell
[ec2-user@ip-10-0-135-15 ~]$ curl vpce-xxx.vpce-svc-xxx.ap-northeast-1.vpce.amazonaws.com
This is ap-northeast-1c Service
```

この時はちゃんと異なるAZの方に到達してくれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/1942139d-e859-ba86-4a03-9dfa54d51278.png)


どういう仕組みなんでしょう？

## ちょっと考察①

ここからはドキュメントを見つけられなかったので推測を込めた考察をしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471646/1942139d-e859-ba86-4a03-9dfa54d51278.png)

まず、上記の状態でリージョンDNS名に名前解決の問い合わせをしてみると

```shell
[ec2-user@ip-10-0-135-15 ~]$ nslookup vpce-xxx.vpce-svc-xxx.ap-northeast-1.vpce.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   vpce-xxx.vpce-svc-xxx.ap-northeast-1.vpce.amazonaws.com
Address: 10.0.150.143 #ap-northeast-1cのENIのプライベートIP
```

ap-northeast-1**c** のENIのプライベートIPのみ返ってきました。
NLBのヘルスチェックに失敗している ap-northeast-1**a** のENIのプライベートIPは返ってきません。

ということは、インターフェースVPCエンドポイントの各ENIは自分が接続しているNLBのヘルスチェックの結果を引き継いでいて、リージョンDNS名はその結果を踏まえてルーティングしているのか、あるいは内部的に接続先のNLBのリスナーを見て、勝手にヘルスチェックしてくれているのか。いずれにせよリージョンDNS名にヘルスチェック機能は備わっていそうです。

## ちょっと考察②

複数のAZが利用可能な場合はリクエスト元と同じAZ経由で通信する挙動も確認できました。

ただし、これについては正確に確認しておきたくAWSのサポートに問い合わせてみたところ

>VPC エンドポイントの DNS 名は、 VPC エンドポイントの全ての ENI の IP アドレスに名前解決され、またその中でどの IP アドレスを利用して通信を行うか、といった部分については、クライアント側であるソフトウェアが決定いたします。

ということだったので、リージョンDNS名を利用した場合にVPCエンドポイントのどのENIを利用するかは完全にクライアント側に依存するようです。

今回の場合は `curl` がうまくやってくれていたのでしょうか。ここは調べきれなかったので今後調査しようと思います。

## 結局どのDNS名を使うべき？

レイテンシーの厳しい要件が無ければ、リージョンDNS名を利用すればよいと思います（金額は変わらないので）。

## さいごに

利用すべきDNS名について結論が出たので今回はここまでとします。
今後はNLBのクロスゾーン負荷分散が有効になっている場合など、他のパターンも考察していきたいと思います。

最後まで読んでくださり、ありがとうございました！

## 参考記事

https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/create-endpoint-service.html

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/network-load-balancers.html#cross-zone-load-balancing

https://dev.classmethod.jp/articles/aws-vpcendpoint-privatelink-beginner/

https://blog.serverworks.co.jp/tech/2018/12/06/vpc-endpoint-dns-names/