概要

VPC Lattice(ラティス:格子)とは、一言で言うとVPCを跨いだALBでリバースプロキシするようなもの
VPC LatticeがついにGAしたため、その機能を検証する。
VPC格子って何?って方はVPC Lattice のよくある質問参照

目的・やりたいこと

1.こちらのAWSブログの検証内容でまず同一サービス間の異なるVPC間のEC2とLambdaのロードバランシングの練習
2.同じIPレンジを持っているVPCを跨げるか

対象となる技術

  • VPC Lattice
  • サービスネットワーク
  • サービス
  • ターゲットグループ

条件(導入にあたって前提事項)

通信用のインスタンスやLambda関数を用意する

注意事項

サポートされているプロトコルは、HTTP/1.1HTTP/2、および gRPC で、TLS 対応サービス用の HTTPS を含む。

作業の流れ

概要図

事前準備

  • EC2(EC2-Aとします)

Linuxでpython環境を整備し、以下のスクリプトを実行して8080でWebサービスを待ち受ける。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
  return 'Hello from the instance'

@app.route('/<path>')
def somePath(path):
  return 'Hello from the instance at path "{}"'.format(path)

app.run(host='0.0.0.0', port=8080)
  • もう1つEC2(EC2-Bとします)

既存のEC2-Aとは別のVPCに同じプライベートサブネットアドレス帯のEC2 インスタンスEC2-Bを用意する。

  • Lambda関数

Node.js 18.x ランタイムを使用して以下の単純な関数を作成する。

exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from the function'),
    };
    return response;
};

ところがこれがトラップで、このまま保存しようとすると、Lambda関数の権限が足りないというエラーが出て保存できない。
Lambda で「The provided execution role does not have permissions to call CreateNetworkInterface on EC2」になったときの対処方法に従い、LambdaのロールにAWSLambdaVPCAccessExecutionRoleポリシーを直接追加して無事解決

次に、このLambda関数にアクセスすると以下エラーが

$ curl https://nozaki-service-0df31e742a9335c8f.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
{"errorType":"ReferenceError","errorMessage":"exports is not defined in ES module scope","trace":["ReferenceError: exports is not defined in ES module scope","    at file:///var/task/index.mjs:1:1","    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)"]}

こちらはexports is not defined in ES module scope AWS Lambdaに従い、index.jsの最初の「exports.handler」を「export const handler」に変えたら解消された。

検証1 同一サービス間の異なるVPC間のEC2とLambdaのロードバランシング

1.ターゲットグループの作成
EC2 インスタンス用と Lambda 関数用の 2 つのターゲットグループを作成します。ナビゲーションペインのVPC 格子から [ターゲットグループ]を選択し、次に[ターゲットグループの作成] を選択します。

最初のターゲットグループには、 [インスタンス]ターゲット タイプを選択し、名前を入力します。

インスタンスで実行されている Web アプリが使用するプロトコル ( HTTP ) とポート ( 8080 ) を選択します。インスタンスが実行されている VPC とプロトコル バージョン ( HTTP1 ) を選択します。
ここで、プロトコルはHTTP /HTTPSしか選択できませんでした。つまり利用できるサービスはL7のHTTP(S)に限られるということ

2.ターゲットの登録
リストから Web アプリが実行されているインスタンスを選択し、それを含めます。

この時、ちゃんと「Include as pending below」を選択し、下の「Review targets」にターゲットが表示されていることまで確認すること

するとプライベートサブネットのルーティングテーブルに、いつの間にかVpcLatticeの経路が追加されている

3.もう1つのターゲットグループの作成
同様に、Lambda 関数のターゲットグループを作成します。今回は一覧から関数を選択します。使用する関数バージョンまたは関数エイリアスを選択できます。$LATESTバージョンを使用します。

4.VPC Latticeサービスの作成
ターゲットグループの準備ができたので、VPC格子ナビゲーションペインで[サービス]を選択し、次に[サービスの作成] を選択します。名前を入力します。

5.認証タイプを選択
[なし]を選択すると、サービスネットワークはクライアントアクセスを認証せず、認証ポリシーが存在する場合は使用されません。
[AWS IAM]を選択すると、 [ポリシーテンプレートの適用]ドロップダウンから、認証済みアクセスと非認証アクセスの両方を許可するテンプレートを選択できます。

6.サービスのルーティングを定義
Add listenerを選択します。プロトコルについては、HTTPSを使用してリッスンするようにサービスを構成します。デフォルト アクションでは、リクエストの 3 分の 2 ( Weight 2 ) をインスタンス ターゲット グループに送信し、3 分の 1 ( Weight 1 ) を関数ターゲット グループに送信することを選択します。

7. 2 つのルールを追加
最初のルール ( Priority 1 ) は、パスが /to-instance インスタンス ターゲット グループにあるすべてのリクエストを送信します。
2 番目のルール ( Priority 2 ) は、パスが /to-function 機能ターゲット グループにあるすべてのトラフィックを送信します。

8.サービスを 1 つ以上のサービス ネットワークに関連付けるよう求められます。まだサービス ネットワークを作成していないため、ここではこの手順をスキップして[次へ]を選択します。構成を確認し、サービスを作成します。

9.VPC格子 サービスネットワークの作成
サービスネットワークを作成して、使用するサービスとVPCを関連付けできるようにします。ナビゲーション ペインから[Service network]を選択し、次に[Create service network] を選択します。サービス ネットワークの名前を入力します。

10.Associate servicesで、作成したばかりのサービスを選択します。

11.VPC 関連付けで、Web アプリが実行されるインスタンスで使用される VPC を選択します。これは、Web アプリがサービス ネットワークに関連付けられた他のサービスを呼び出すことができるため、将来的に役立ちます。
次に、いくつかのテストを実行するために使用する別の EC2 インスタンスがある 2 番目の VPC を選択します。

12.サービスの関連付けのリストからサービスのドメイン名を書き留めます。

13.VPC格子を使用したサービスへのアクセスのテスト
サービスの [Routing]タブを見て、リスナーがさまざまなターゲット グループへのルーティングをどのように処理しているかのサマリを確認

14.nozaki-vpc2で EC2 インスタンスにログインし、curlを使用してサービスドメイン名を呼び出します。予想どおり、インスタンスから2/3の応答が得られ、関数から1/3の応答が得られました。

$ curl https://nozaki-service-0df31e742a9335c8f.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
Hello from the instance

$ curl https://nozaki-service-0df31e742a9335c8f.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
Hello from the instance

$ curl https://nozaki-service-0df31e742a9335c8f.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
"Hello from the function"

/to-instanceパスと/to-functionパスを呼び出すと、追加のルールによってリクエストがインスタンスと関数にそれぞれ転送されます。

$ curl https://nozaki-service-0df31e742a9335c8f.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws/to-instance
Hello from the instance at path "to-instance"

$ curl https://nozaki-service-0df31e742a9335c8f.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws/to-function
"Hello from the function"

検証2 同じIPレンジを持っているVPCを跨げるか

ここでは、EC2-Aと全く同じサブネットアドレス帯(10.0.16.0/20)で立てたEC2-Bから、まず普通にEC2-AにVPC Lattice経由でアクセスできるかを検証

すると無事アクセスできました。

$ curl https://nozaki-service-0d7968.vpc-lattice-svcs.ap-northeast-1.on.aws
Hello from the instance

次に、EC2-Bもターゲットに登録してVPC Lattice配下として、同じ通信ができるかを検証します。

1.ターゲットグループの作成
EC2-Bインスタンス用のターゲットグループを作成します。ナビゲーションペインのVPC 格子から [ターゲットグループ]を選択し、次に[ターゲットグループの作成] を選択します。

2.ターゲットの登録
リストからEC2-Bインスタンスを選択し、それを含めます。

3.サービスの作成
ターゲットグループの準備ができたので、VPC格子ナビゲーションペインで[サービス]を選択し、次に[サービスの作成] を選択します。

4.認証タイプを選択
[なし]を選択

5.サービスのルーティングを定義
Add listenerを選択します。プロトコルについては、HTTPSを使用してリッスンするようにサービスを構成します。デフォルトアクションでは、リクエストの全てをEC2-Bターゲット グループに送信することを選択します。

6.サービスをサービスネットワークに関連付け
既存のサービスネットワークnozaki-servicenetworkに関連付けます。

7.テスト
試しにWebアクセス実行するも、なぜかService Unavailable・・

$ curl https://nozaki-service2-022281e341ce39d64.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
Service Unavailable

と思ったらEC2-Bのセキュリティグループ8080を10.0.0.0/16に制限していた。これを0.0.0.0に開放したらすんなりアクセスできたので、どうも実際はVPC Lattice独自のアドレス帯でアクセスしてるっぽい

curl https://nozaki-service2-022281e341ce39d64.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
Hello from the instance

逆に今度はEC2-BからEC2-Aにアクセスしてみたがこれも成功。これで双方向通信できた。

所要時間

検証1:3時間(作り込みでハマったため。設定自体は1時間)
検証2:3時間(作り込みでハマったため。設定自体は1時間)

ユースケース

気になる用途としては、ネットワーク関係なくリバースプロキシWeb接続させたいってのが一番多くなるかとは思います。
他は以下

  • サービスを大規模に接続する
    ネットワークを複雑にすることなく、VPC やアカウント全体で数千のサービスを接続します。
  • きめ細かいアクセス許可を適用する
    サービス間のセキュリティを強化し、一元化されたアクセスコントロール、認証、およびコンテキスト固有の承認により、ゼロトラストアーキテクチャをサポートします。
  • 高度なトラフィックコントロールを実装する
    リクエストレベルのルーティングや、ブルー/グリーンおよび canary デプロイ向けの重み付けされたターゲットなど、きめ細かなトラフィックコントロールを適用します。
  • サービス間のインタラクションを観察する
    リクエストタイプ、トラフィック量、エラー、応答時間などについて、サービス間の通信をモニタリングおよびトラブルシューティングします。

アクセスログの確認

サービスネットワークでアクセスログを有効にして、CloudWatchロググループに送信するようにしてみました。

すると以下のアクセスログを確認

{
    "startTime": "2023-04-06T01:32:42Z",
    "requestMethod": "GET",
    "requestPath": "/",
    "protocol": "HTTP/2",
    "responseCode": 200,
    "bytesReceived": 129,
    "bytesSent": 310,
    "duration": 5,
    "userAgent": "curl/7.87.0",
    "hostHeader": "nozaki-service2-022281e341ce39d64.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws",
    "targetIpPort": "10.0.22.207:8080",
    "targetGroupArn": "arn:aws:vpc-lattice:ap-northeast-1:532152701269:targetgroup/tg-0f3e7aac066e30485",
    "sourceIpPort": "10.0.18.254:42012",
    "serverNameIndication": "nozaki-service2-022281e341ce39d64.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws",
    "sourceVpcId": "vpc-0928c520838be8e3d",
    "destinationVpcId": "vpc-080ebe62e02793d0d",
    "serviceArn": "arn:aws:vpc-lattice:ap-northeast-1:532152701269:service/svc-022281e341ce39d64",
    "serviceNetworkArn": "arn:aws:vpc-lattice:ap-northeast-1:532152701269:servicenetwork/sn-00962b0558ee611cf",
    "requestToTargetDuration": 2,
    "responseFromTargetDuration": 0,
    "sslCipher": "ECDHE-RSA-AES128-GCM-SHA256",
    "tlsVersion": "TLSv1.2",
    "resolvedUser": "-",
    "authDeniedReason": "-"
}

IPはあくまでエンドのIPが表示されている。
一方受信側では、以下のVpcLatticeのIP(169.254.171.0/24)で受信していた。

169.254.171.193 - - [06/Apr/2023 01:40:00] "GET / HTTP/1.1" 200 -
169.254.171.194 - - [06/Apr/2023 01:40:07] "GET / HTTP/1.1" 200 -
169.254.171.192 - - [06/Apr/2023 01:40:08] "GET / HTTP/1.1" 200 -
169.254.171.193 - - [06/Apr/2023 01:40:10] "GET / HTTP/1.1" 200 -
169.254.171.194 - - [06/Apr/2023 01:40:17] "GET / HTTP/1.1" 200 -
169.254.171.192 - - [06/Apr/2023 01:40:18] "GET / HTTP/1.1" 200 -
169.254.171.193 - - [06/Apr/2023 01:40:20] "GET / HTTP/1.1" 200 -
169.254.171.194 - - [06/Apr/2023 01:40:27] "GET / HTTP/1.1" 200 -

気になる料金は

VPC Latticeのコストは、次の3つの要素によって決まります。
https://aws.amazon.com/jp/vpc/lattice/pricing/

  • プロビジョニングされたサービスの数(0.0325 USD/h)

サービス:インスタンス、コンテナ、またはサーバーレスコンピューティングで実行される特定のタスクまたは機能を提供する、独立してデプロイ可能なソフトウェアの単位
例)
1 か月は 730 時間であると仮定
2個のサービスと仮定
2 個のサービス×0.0325 USD/h×730 時間=6,168円/月

  • 各サービスとの間のトラフィックのデータ処理料金(0.0325 USD/GB)

各サービスを通じて転送されるデータ量=サービスが受け取る各HTTPリクエストのデータ量+サービスがクライアントに送信する対応するHTTPレスポンスのデータ量
例)
1 か月あたり 100 GBと仮定
2 個のサービス×0.0325 USD/GB×100 GB=845円/月

  • 各サービスが受け取るリクエストの数(0.13 USD/100万リクエスト)
    1 か月あたり100万リクエストと仮定
    1 時間あたり最初の 300,000 件のリクエストは無料のため、実質リクエスト料金 =1 か月あたり 0 USD

以上合計=6,168円/月+845円/月≒7,000円/月
2ALB+2Private Link=81.36 USD≒10,500円/月であるため、最小構成の場合はALBとPrivate Linkを組み合わせより安い