AWS Lambda in VPC から、IPv6 で Egress-only internet gateway を通って外に出ていけるようになりました。
それに関連して、APIエンドポイントと NAT G/W を絡めて確認したことをまとめておきます。よくわからないタイトルになりましたが、雑学用の材料ということで:-)
リリース
公式情報はこちら。- Announcing AWS Lambda’s support for Internet Protocol Version 6 (IPv6) for outbound connections in VPC
- 英語版はLambda の Dual-stack が Yes に
AWS services that support IPv6 – Amazon Virtual Private Cloud
内容的に、確認したいことはすぐ成功して終わるだろうと Twitter でボヤいてたら、想定外に調べることが出てきて最終的に願いは叶わずでした。
Lambda が egress-only internet gateway から出れるってことは、少なくとも VPC 内から AWS API を叩くだけなら NAT G/W はいらんってことやな! https://t.co/CaNsoe9FTi
— 外道父 | Noko (@GedowFather) October 13, 2023
目的
過去記事 AWSのPublic IPv4構成をIPv6に切り替える | 外道父の匠 にて、IPv6 構成にしたら NAT G/W 無くせるんちゃうん、無理だと思うけど! → やっぱダメでした、をやりました。PublicIPv4 を持たず、Private で IPv6 にした場合、インターネットへ出ていく際に IPv6 なら Egress-only IGW を通れば出ていけますが、送信先が IPv4 しか持たない場合は Global IPv4 がないため通信できなく、NAT G/W (NAT64) を用意する必要がありました(復習終)。
仮にアプリケーションの外部接続が IPv6 のみだったとしても、Lambda in VPC が外部接続するには、NAT G/W が必要という仕様のために、NAT G/W を撤去することは叶わぬ夢となっていました。
しかし、ここに本件リリースがきて、Lambda in VPC から AWS API を叩く程度なら、NAT G/W 不要で Egress-only IGW だけで済ませられるんちゃうか、と思って試したところ見事に撃沈した流れになります。
IPv6 通信は可能
とりあえずリリースどおりに NAT G/W 無しの Lambda in VPC → EIGW → Internet IPv6 がイケるのか?を試します。Lambda 関数の設定 → VPC で、サブネット『IPv6 トラフィックを許可』という項目が増えているので、true にして下記を実行すると、
1 2 3 4 5 6 7 8 9 10 11 12 |
import urllib def lambda_handler(event, context): d = "www.amazon.com" d = "google.com" i = socket.getaddrinfo (d, None) print(i) url = f"https://{d}" r = urllib.request.urlopen(url, timeout=3).read() print(r) return |
問題なく IPv6 で名前解決できて、レスポンスも正常に返ってきました。google.com にしたのは、www.amazon.com だと 503 が返るからで、そんなことはどうでもいいです。
これで道が見えた!かというと、そんな甘いものではなく。
AWS API は通らない
では、勢いで同環境(IN 東京)で AWS API を叩いてみると
1 2 3 4 5 6 |
import boto3 def lambda_handler(event, context): ec2 = boto3.client('ec2') vpcs = ec2.describe_vpcs() print(vpcs) |
タイムアウトします。これは Lambda だからとか Python3 boto3 だからではなく、Private Subnet の IPv6 環境かつ NAT G/W 不在だからで、EC2 で AWS CLI を叩いても同じ状況になります。
理由は簡単で、API を叩く時は EC2 の場合 ec2.ap-northeast-1.amazonaws.com にリクエストされるのですが、この宛先は IPv4 にしか対応していないことがドキュメントにかかれています。
そして肝心の Dual-stack (IPv4 and IPv6) endpoints が、ap-northeast-1 (東京) で対応されていない、というオチもあります。
リージョン格差を確認する
ドキュメントの例題通り、us-west-2 に投げると通り、ap-northeast-1 に投げると失敗します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# オレゴンでも IPv4 用エンドポイントに IPv6 で投げると通らない aws ec2 describe-vpcs --region us-west-2 # --endpoint-url https://ec2.us-west-2.amazonaws.com 省略可 # オレゴンの新エンドポイントは IPv6 対応なので通る aws ec2 describe-vpcs --region us-west-2 --endpoint-url https://ec2.us-west-2.api.aws # 東京も IPv4 用エンドポイントには IPv6 では通らない aws ec2 describe-vpcs --region ap-northeast-1 # --endpoint-url https://ec2.ap-northeast-1.amazonaws.com 省略可 # 東京 IPv6 対応エンドポイントはそもそも存在すらしない aws ec2 describe-vpcs --region ap-northeast-1 --endpoint-url https://ec2.ap-northeast-1.api.aws Could not connect to the endpoint URL: "https://ec2.ap-northeast-1.api.aws/" $ host ec2.ap-northeast-1.api.aws Host ec2.ap-northeast-1.api.aws not found: 3(NXDOMAIN) |
なまじ、IPv4 用エンドポイントが IPv6 も返してくるので期待しちゃいますが、IPv4 でアクセスしないと返事してくれません。一応、リダイレクト用のWEBサイトとしても機能している URL なので、IPv6 にも対応しているだけって感じでしょうか。
……AWS大先生はなぜ IPv4 エンドポイントをそのまま IPv6 に対応しなかったんでしょうね。まぁ普通に考えて、技術的負債とまでは言わなくとも、過去の気に食わない設計を *.api.aws に分けたほうが都合が良かった、とかありそうですけど。いつかは Dual-stack Endpoint の方がデフォルトに変更されたりするのかな?
IPv6 化の注意点
デフォルトの IPv4 用エンドポイントは IPv6 も返してくるので、IPv4 のみと判断して NAT64 を通すような挙動にはならないはずで、常に IPv6 優先でアクセスすることになります。また、接続情報のエンドポイントに Dual-stack エンドポイントを明示的に指定しても、東京はそもそも未対応ということもあります。
EIGW や NAT G/W の有無が、自分たちの扱う外部接続にどう影響するのか、は正しく認識しておくべきでしょう。
とはいえ、NAT G/W があれば基本は大丈夫です。IPv6環境+NAT G/W 無しの場合、以下のような結果になります。
1 2 3 4 5 6 7 8 9 10 11 |
# 3つとも無応答で停止 $ curl -v https://ec2.us-west-2.amazonaws.com * Trying [64:ff9b::345e:d658]:443... * Trying 52.94.181.25:443... $ aws ec2 describe-vpcs --region us-west-2 >>> import boto3 >>> ec2 = boto3.client('ec2', region_name="us-west-2") >>> vpc = ec2.describe_vpcs() |
しかし、IPv6環境+NAT G/W 有りにすると、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 全部レスポンスが返る $ curl -v https://ec2.us-west-2.amazonaws.com * Trying [64:ff9b::3477:af0a]:443... * Trying 52.94.184.137:443... * Connected to ec2.us-west-2.amazonaws.com (52.94.184.137) port 443 ... $ aws ec2 describe-vpcs --region us-west-2 { "Vpcs": [ ... >>> import boto3 >>> ec2 = boto3.client('ec2', region_name="us-west-2") >>> vpc = ec2.describe_vpcs() >>> print(vpc) {'Vpcs': [ ... |
curl の結果が最もわかりやすいですが、他も tcpdump で処理を追うと、最初に IPv6 で接続を試みて、ダメなら IPv4 でリトライするようになっています。
要はクライアントがちゃんとしていれば、NAT G/W を置いてあげれば AWS API のエンドポイントはそのままでも動くということです。よかったですね。
おまけ
ただこの件は AWS API に限った話ではないので、やはりよく扱うアプリケーション環境・よくアクセスする外部API があれば、NAT G/W 無し/有りで接続がどうなるかってのは、軽く確認して挙動を把握しておく方が無難と言えそうです。あと、今回ので知れたこととして、送信先の挙動には以下のパターンが考えられるということです。
- IPv4 しか返さない
- IPv6 しか返さない
- IPv4 & IPv6 を返し、両方とも応答する
- IPv4 & IPv6 を返すが、IPv4 だけ応答する
- IPv4 & IPv6 を返すが、IPv6 だけ応答する
まとめ
東京リージョンで Dual-stack Endpoint が対応されたら、API接続情報に明示的に新 Endpoint URL を指定すれば、NAT G/W を撤去する第一歩となるだろう。俺たちは今しばらく、NAT G/W と命運をともにすることになる;-)