VPCエンドポイントでの「AWSサービス」と「サービスを名前で検索」の違い

## 概要

AWS VPCエンドポイントのインターフェース型(PrivateLink)には、3種類あります。

 - AWSサービス（AWSプリンシパルサービス）
 - サービスを名前で検索（独自サービス）
 - ご使用のAWS Marketplaceサービス（マーケットプレイスサービス）

このうち、座学だけだと、AWSプリンシパルサービスのEC2エンドポイントと、EC2上に独自で作ったAPIに対するエンドポイントでは何が違うのかわからず小一時間悩んだので、違いと構築した結果をメモします。なお、PrivateLinkそのものに対する説明は、以下の記事が非常に参考になりましたので、こちらを見ていただけれなと思います。

[AWS PrivateLinkの使い方と注意点 ～VPCピアリングとの使い分け～](https://devlog.arksystems.co.jp/2018/05/11/4896/)


## 結論

 - AWSプリンシパルサービスは、VPCエンドポイントを通じて、AWSサービスのAPIが利用できる
   - EC2なら、aws cliからEC2のAPIが利用できる
 - 独自サービスは、自分で構築したAPIをVPCエンドポイントを通じて別VPCから利用できる
   - EC2上に構築したREST API等を利用できる

## AWSプリンシパルサービス

例として、EC2にAPIで作成します。実装方法は以下の記事が大変参考になりました。
[AWS PrivateLinkが登場 EC2やELBでどのように使うか](https://qiita.com/hayao_k/items/2d64c180f6c6ca57e41b)

### 構成
Privateなサブネットに配置したEC2から、aws cliを実行してEC2のAPIを実行します。(EC2のAPIを書く位置がここで良いかはわからないですが...)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/aa880395-ad2d-fd51-0ff9-0a45d956caeb.png)

### 実行結果

#### Private DNSをtrueにした場合

```bash:endpointを指定しない場合、PrivateDNSが利用される
 aws ec2 describe-availability-zones
{
    "AvailabilityZones": [
        {
            "OptInStatus": "opt-in-not-required",
            "Messages": [],
            "ZoneId": "use2-az1",
            "GroupName": "us-east-2",
            "State": "available",
            "NetworkBorderGroup": "us-east-2",
            "ZoneName": "us-east-2a",
            "RegionName": "us-east-2"
        },
# 以下、省略
```

```bash:PrivateDNSをendpointに指定(固有のendpointでもOK)
$ aws ec2 describe-availability-zones --endpoint-url https://ec2.us-east-2.amazonaws.com
{
    "AvailabilityZones": [
        {
            "OptInStatus": "opt-in-not-required",
            "Messages": [],
# 以下、省略
```


#### Private DNSをfalseにした場合

```bash:endpoint指定無しの場合通らない(PrivateDNSがfalseなため)
[ec2-user@ip-10-0-20-44 ~]$ aws ec2 describe-availability-zones
# レスポンス無し
```

```bash:固有のendpoint指定ありでレスポンスが来る
aws ec2 describe-availability-zones --endpoint-url https://XXXXXXXXX.ec2.us-east-2.vpce.amazonaws.com
{
    "AvailabilityZones": [
        {
            "OptInStatus": "opt-in-not-required",
            "Messages": [],
# 以下、省略
```

## 独自サービス

手順の概要は以下の通りです。

 1. 独自のAPIを構築する側のVPCにNLBをターゲットにしたエンドポイントサービスを作成する。
 2. アクセス元のVPCにインターフェース型VPCエンドポイントを作成する。作成時に、1のエンドポイントサービスを指定する。

詳細な手順は以下を参考にさせていただきました。
[【新機能】PrivateLinkで独自エンドポイントを作ってアプリをプライベート公開する #reinvent](https://dev.classmethod.jp/articles/aws-reinvent-vpc-privatelink-endpoint/)

### 構成図
Privateなサブネットから、別VPCのPrivateサブネット上にあるEC2にアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/e675abd3-19ff-347e-3cea-77ff9273ca5f.png)


### EC2に構築したREST API

PythonのFlaskを使って、`/hello`のリソースに5000番でGETしたらmessageを返すだけのAPIです。このAPIに対してNLBを作成します。

```python:hello.py
import json

def get_hello():
    message = "Hello, good evening"
    return_json = json.dumps({"message": message})

    return return_json
```

```python:server.py
from flask import Flask
from hello import get_hello

app = Flask(__name__)

@app.route('/hello', methods=['GET'])
def local_endpoint():
    return get_hello()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```

```bash:publicサブネットに用意した踏み台サーバからのリクエスト結果
[ec2-user@ip-10-0-10-166 ~]$ curl http://10.0.30.21:5000/hello
{"message": "Hello, good evening"}
```

NLBからアクセスできているかは、ヘルスチェックを参照しましょう。unhealthyの場合、EC2に対するセキュリティグループの設定が上手くできていない可能性があります。今回のAPIで言ったら、同じサブネットである`10.0.30.0/24`から5000番を許可する必要があります。(下図の3行目)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/8650fe0c-4f2a-5979-72b5-1ee785a3d1fc.png)

### 実行結果

作成したエンドポイントに表示されたDNS名に対してリクエストを投げます。

```bash:別VPCに用意したインスタンスからのリクエスト結果
[ec2-user@ip-10-0-20-44 ~]$ curl http://XXXXXXXXX.XXXXXXXXXXXX.us-east-2.vpce.amazonaws.com/hello
{"message": "Hello, good evening"}
```

ハマり所として、VPCエンドポイントのセキュリティグループに対して、アクセス元になるEC2からの通信を許可する必要があります。defaultでVPCエンドポイントを作ると設定されないので、明示的に作成しましょう。ポートは、NLBのリスナーと合わせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/a5d34168-9158-f9dd-58bf-3af389e58116.png)

#### プライベートDNS名を利用した場合

以下記事で紹介されている通り、2020/1のアップデートで、設定したプライベートなDNS名を、リクエスト側のエンドポイントにすることが可能になったようです。VPCエンドポイントで作成されるDNS名はランダムなIDが入ってわかりにくいので、アクセスしやすくなりましたね。
[[新機能] PrivateLinkの公開サービスにプライベートDNS名が指定可能になりました](https://dev.classmethod.jp/articles/private-dns-name-for-privatelink/)

例えば、`example.com`というドメイン名を持っている場合、エンドポイントサービスの作成時のPrivate DNS nameに設定することで、以下の通りアクセスすることが可能となります。なお、Route53のTXTレコード登録も必要となります。

 - 名前:`[Domain verification name]`.example.com
 - 値:`Domain verification value`

```bash:PrivateDNS名でのアクセス結果
[ec2-user@ip-10-0-20-44 ~]$ curl http://example.com/hello
{"message": "Hello, good evening"}
```


## 終わりに

作ってるうちにわかりましたが、全くの別物でしたね...
また、独自サービスを作ると、VPCエンドポイントとエンドポイントサービスの違いも理解できました。やはり手を動かして理解することが重要ですね。