[アップデート] VPC のサブネットルートテーブルでローカルルートより具体的なルートを指定できるようになりました!

VPC Ingress Routing のようなカッコいい呼称があるとより嬉しかったです。 East-west traffic って呼んでみたいけどこの機能そのものを表すものではないんですよね。もしかして「 VPC More Specific Routing 」がそうなのか……!?

コンバンハ、千葉(幸)です。

VPC 内部のサブネット間の通信において、アプライアンスなどを経由させる構成が取りやすくなりました。

サブネットルートテーブルと言えばデフォルトで以下のルートを持ち、それより具体的な( VPC CIDR 範囲より小さい)ルートは追加できない、というのがこれまでの常識でした。ガラッとその前提が変わることになりました。

送信先 ターゲット
VPC 全体の CIDR 範囲 local

今回のアップデートで可能になった「より具体的なルート」を使用する機会は、アプライアンス製品を使用する場合など、少し特殊なケースに限定されるかと思います。とは言え、新たにできるようになったことは覚えておきましょう。

北-南のトラフィックと東-西のトラフィック

今回のアップデートを取り上げる上で切り離せないのは VPC Ingress Routing の話題です。

これはインターネットゲートウェイ(IGW)や仮想プライベートゲートウェイ(VGW)を経由して VPC 内に入ってくるトラフィックをルーティングできる機能で、2019年12月に発表されました。

当時の発表ブログ から画像引用すると、以下のイメージです。

クライアントから見た宛先は最下段のアプリケーションインスタンスですが、IGW を経由して VPC 内に入ってきた際にアプライアンスインスタンスへルーティングさせています。これは IGW にアタッチされたルートテーブル(ゲートウェイルートテーブル)の働きによるものです。この機能の発表と共に、IGW、VGWにルートテーブルをアタッチできるようになりました。

(これ以降、一般的な呼称か分かりませんが、アタッチする対象にあわせて「ゲートウェイルートテーブル」「サブネットルートテーブル」と呼び分けるようにわたしはしています。)

こういった VPC 外部からのインバウンド通信を、冒頭のブログでは North-south traffic と紹介しています。これは AWS 特有の表現ではなく、とある構成下におけるネットワークの通信方向を表す一般的なものです。北から南へのトラフィックは、外部のユーザーからデータセンター内部(今回では VPC が相当)への方向を表すようです。初めて知りましたがお洒落ですね。

それに対して East-west traffic はデータセンター内部の通信を表します。 VPC に置き換えて考えると、同一 VPC 内のサブネット間での通信が該当します。

East-west traffic におけるアプライアンスへのルーティングのイメージを今回のブログ から画像引用すると、以下となります。

最終的な宛先はアプリケーションインスタンスであるものの途中でアプライアンスインスタンスを経由させている、というのは北-南の場合と変わらないですね。

VPC Ingress Routing の場合はゲートウェイルートテーブルの働きにより実現されていましたが、今回はサブネットルートテーブル上のエントリにより実現されています。

この例での VPC の CIDR は10.0.0.0/16ですが、それより具体的な10.0.0.0/2410.0.1.0/24を宛先とするルートが追加されています。従来はこれを登録しようとするとエラー *1が発生していましたので、今回のアップデートによりその制約を受けなくなった、という考え方となります。

ミドルボックスアプライアンスのルーティングについての補足

ここまで見てきたような北-南、東-西のトラフィックをルーティングする構成については、以下ドキュメントで「ミドルボックスアプライアンスのルーティング」として取り上げられています。

イメージ図もより簡略化されて表現されていますので、あわせてご参照ください。

ゲートウェイとアプライアンス間のトラフィックのルーティング。

gateway-appliance-routing

サブネット間トラフィックのアプライアンスへのルーティング。

inter-subnet-appliance-routing

詳細な考慮事項は上記をご確認いただくとして、「より具体的なルート」でターゲットとして指定できるものとして以下のコンポーネントが挙げられています。

  • ゲートウェイロードバランサーエンドポイント
  • NAT ゲートウェイ
  • ネットワークファイアウォールエンドポイント
  • ネットワークインタフェース(ENI)

アプライアンスインスタンスの ENI をターゲットにする、というのは分かりやすいですが、他のコンポーネントを使うときはどういった形になるのでしょうか……。何か面白い使用例が出てくることに期待です。

やってみた

ブログの構成をほぼほぼ踏襲し、以下のような構成で踏み台からアプリケーションインスタンスへの通信を行います。

踏み台→アプリケーションインスタンスへの通信をアプライアンスインスタンス上で tcpdump で確認できることろをゴールとします。

ご丁寧にサンプルの構成の CDK スクリプトを用意してくれているのですが、今回はマネジメントコンソールから頑張ってポチポチして作りました。

ざっくり以下の条件を満たす構成にしているところから始めます。

  • VPC 一式作成済み
    • サブネットの CIDR はサンプルと同一
    • サブネットはすべてパブリックサブネット
      • (サンプルでは踏み台のみパブリックですが、サブネット間ルーティングの確認には影響ないので、、)
  • EC2 インスタンス構築済み
    • すべて Amazon Linux2
    • デフォルト状態
    • SecuriryGroup は同一のものを使用し、相互参照許可あり
      • 自身と同一の SecuriryGroup を持つものとイン/アウトすべて許可

サブネットルートテーブルの編集

たとえば踏み台サブネットに関連づけるルートテーブルであれば、以下のようにアプリケーションサブネットを指定したルートを追加しています。

VPC_Management_Console_Route

編集時のイメージは以下です。ENI に付与した Name タグをあわせて表示してくれるので、どのインスタンスの ENI か迷わず選択することができました。今回はもちろんアプライアンスインスタンスのものを選んでいます。

VPC_Management_Console_Route-0412512

同様のルートの編集を、アプリケーションサブネットに関連づくルートテーブルでも行っています。

アプライアンスインスタンスの設定

2点設定を行います。

まずはインスタンスの設定で、ソース/宛先チェックを無効化します。

Appliance

Appliance-0412682

これで自身が宛先でないトラフィックを受け入れられるようになりました。

続いて IP フォワードの設定です。セッションマネージャーで接続後、設定を行いました。

アプライアンスインスタンス

sh-4.2$ uname -a
Linux ip-10-0-2-167.ap-northeast-3.compute.internal 4.14.238-182.422.amzn2.x86_64 #1 SMP Tue Jul 20 20:35:54 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
sh-4.2$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

uname -aはホスト名の中に含まれる IP アドレスを確認する意図で叩いています。以降も出てきますが、そこまで気にしないでいただいて大丈夫です。)

アプリケーションインスタンスの設定

今回は httpd をインストールして起動させます。こちらもセッションマネージャーで接続後、作業を行いました。

アプリケーションインスタンス

sh-4.2$ uname -a
Linux ip-10-0-1-34.ap-northeast-3.compute.internal 4.14.238-182.422.amzn2.x86_64 #1 SMP Tue Jul 20 20:35:54 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
sh-4.2$ sudo yum install httpd
# 実行ログは省略
sh-4.2$ sudo systemctl start httpd
sh-4.2$ sudo systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2021-08-31 12:28:20 UTC; 4s ago
     Docs: man:httpd.service(8)
 Main PID: 2567 (httpd)
   Status: "Processing requests..."
   CGroup: /system.slice/httpd.service
           ├─2567 /usr/sbin/httpd -DFOREGROUND
           ├─2571 /usr/sbin/httpd -DFOREGROUND
           ├─2573 /usr/sbin/httpd -DFOREGROUND
           ├─2578 /usr/sbin/httpd -DFOREGROUND
           ├─2580 /usr/sbin/httpd -DFOREGROUND
           └─2585 /usr/sbin/httpd -DFOREGROUND

Aug 31 12:28:20 ip-10-0-1-34.ap-northeast-3.compute.internal systemd[1]: Starting The Apache HTTP Server...
Aug 31 12:28:20 ip-10-0-1-34.ap-northeast-3.compute.internal systemd[1]: Started The Apache HTTP Server.

中身は空ですがドキュメントルートにindex.htmlを作っておきます。(デフォルトだとステータスコードが 403 になってなんとなく収まりが悪いため。)

アプリケーションインスタンス

sh-4.2$ sudo touch /var/www/html/index.html
sh-4.2$ ls -la /var/www/html/
total 0
drwxr-xr-x 2 root root 24 Aug 31 12:53 .
drwxr-xr-x 4 root root 33 Aug 31 12:27 ..
-rw-r--r-- 1 root root  0 Aug 31 12:53 index.html

踏み台インスタンスからのアクセス

踏み台インスタンスに接続後、アプリケーションインスタンスに向けて接続を行います。

踏み台インスタンス

sh-4.2$ uname -a
Linux ip-10-0-0-146.ap-northeast-3.compute.internal 4.14.238-182.422.amzn2.x86_64 #1 SMP Tue Jul 20 20:35:54 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
sh-4.2$ curl -I -v 10.0.1.34
* Rebuilt URL to: 10.0.1.34/
*   Trying 10.0.1.34...
* TCP_NODELAY set
* Connected to 10.0.1.34 (10.0.1.34) port 80 (#0)
> HEAD / HTTP/1.1
> Host: 10.0.1.34
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Date: Tue, 31 Aug 2021 12:56:26 GMT
Date: Tue, 31 Aug 2021 12:56:26 GMT
< Server: Apache/2.4.48 ()
Server: Apache/2.4.48 ()
< Upgrade: h2,h2c
Upgrade: h2,h2c
< Connection: Upgrade
Connection: Upgrade
< Last-Modified: Tue, 31 Aug 2021 12:53:54 GMT
Last-Modified: Tue, 31 Aug 2021 12:53:54 GMT
< ETag: "0-5cada71337a9a"
ETag: "0-5cada71337a9a"
< Accept-Ranges: bytes
Accept-Ranges: bytes
< Content-Type: text/html; charset=UTF-8
Content-Type: text/html; charset=UTF-8

<
* Connection #0 to host 10.0.1.34 left intact

ひとまずここではステータスコード 200 が返ってきたことが確認できます。

アプライアンスインスタンスでの tcpdump

順番は前後するのですが、上記の操作を行う前にアプライアンスインスタンスで tcpdump を実行しています。

アプライアンスインスタンス

sh-4.2$ uname -a
Linux ip-10-0-2-167.ap-northeast-3.compute.internal 4.14.238-182.422.amzn2.x86_64 #1 SMP Tue Jul 20 20:35:54 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
sh-4.2$ sudo tcpdump -i eth0 host 10.0.0.146
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

↑host で指定しているのは踏み台インスタンスの IP アドレスです。

このリッスン状態で踏み台インスタンスから curl を行うと、以下のように通信の内容が確認できます。

アプライアンスインスタンス

12:56:26.976133 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [S], seq 1643808848, win 26883, options [mss 8961,sackOK,TS val 1240133501 ecr 0,nop,wscale 7], length 0
12:56:26.976151 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [S], seq 1643808848, win 26883, options [mss 8961,sackOK,TS val 1240133501 ecr 0,nop,wscale 7], length 0
12:56:26.976324 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [S.], seq 2834299818, ack 1643808849, win 26847, options [mss 8961,sackOK,TS val 2462081390 ecr 1240133501,nop,wscale 6], length 0
12:56:26.976334 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [S.], seq 2834299818, ack 1643808849, win 26847, options [mss 8961,sackOK,TS val 2462081390 ecr 1240133501,nop,wscale 6], length 0
12:56:26.976407 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [.], ack 1, win 211, options [nop,nop,TS val 1240133501 ecr 2462081390], length 0
12:56:26.976411 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [.], ack 1, win 211, options [nop,nop,TS val 1240133501 ecr 2462081390], length 0
12:56:26.976437 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [P.], seq 1:75, ack 1, win 211, options [nop,nop,TS val 1240133501 ecr 2462081390], length 74: HTTP: HEAD / HTTP/1.1
12:56:26.976439 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [P.], seq 1:75, ack 1, win 211, options [nop,nop,TS val 1240133501 ecr 2462081390], length 74: HTTP: HEAD / HTTP/1.1
12:56:26.976505 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [.], ack 75, win 420, options [nop,nop,TS val 2462081391 ecr 1240133501], length 0
12:56:26.976507 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [.], ack 75, win 420, options [nop,nop,TS val 2462081391 ecr 1240133501], length 0
12:56:26.976698 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [P.], seq 1:254, ack 75, win 420, options [nop,nop,TS val 2462081391 ecr 1240133501], length 253: HTTP: HTTP/1.1 200 OK
12:56:26.976701 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [P.], seq 1:254, ack 75, win 420, options [nop,nop,TS val 2462081391 ecr 1240133501], length 253: HTTP: HTTP/1.1 200 OK
12:56:26.976761 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [.], ack 254, win 219, options [nop,nop,TS val 1240133501 ecr 2462081391], length 0
12:56:26.976763 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [.], ack 254, win 219, options [nop,nop,TS val 1240133501 ecr 2462081391], length 0
12:56:26.976975 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [F.], seq 75, ack 254, win 219, options [nop,nop,TS val 1240133502 ecr 2462081391], length 0
12:56:26.976977 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [F.], seq 75, ack 254, win 219, options [nop,nop,TS val 1240133502 ecr 2462081391], length 0
12:56:26.977046 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [F.], seq 254, ack 76, win 420, options [nop,nop,TS val 2462081391 ecr 1240133502], length 0
12:56:26.977048 IP 10.0.1.34.http > 10.0.0.146.44714: Flags [F.], seq 254, ack 76, win 420, options [nop,nop,TS val 2462081391 ecr 1240133502], length 0
12:56:26.977097 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [.], ack 255, win 219, options [nop,nop,TS val 1240133502 ecr 2462081391], length 0
12:56:26.977099 IP 10.0.0.146.44714 > 10.0.1.34.http: Flags [.], ack 255, win 219, options [nop,nop,TS val 1240133502 ecr 2462081391], length 0

踏み台インスタンスとアプリケーションインスタンスの双方向で通信を行っている様が見て取れますね。

サブネット間のトラフィックをアプライアンスにルーティングできていることが確認できました!

ちなみに:サブネットルートテーブルの仕様

VPC 全体の CIDR を宛先とするルートが削除できないのは従来と変わりませんが、ターゲットはローカルである必要はなくなりました。

VPC_Management_Console_Route-0415596

この場合、対象のサブネットに配置されたリソースから VPC 内部宛のトラフィックはすべて、ターゲットに指定されたコンポーネントを向くことになります。

この状態で10.0.0.0/16からたとえば10.0.0.0/24のようなより具体的なルートに変更すると、エラーが発生します。

VPC_Management_Console_Route-0415679

cannot remove local route 10.0.0.0/16 in route table rtb-0xxxxxxxxxxxxx

たとえターゲットが「ローカル」でなくても、 VPC の CIDR 範囲全体を宛先とするルートは必要ということですね。

なるほど。覚えておきましょう。

終わりに

サブネットルートテーブルでローカルルートより具体的なルートを指定できるようになった、というアップデートでした。

VPC Ingress Routing の発表後、VPC 内のサブネット間の通信でもアプライアンスを経由させる構成をとりたい、という要望があったために追加された機能とのことです。

正直わたしはそのような製品を扱う環境に携わったことがないのでどのくらい要望があるのか見えていないのですが、いざという時に間違えないよう、「できること」を押さえておくのは重要かなと思います。

ターゲットの「ローカル」が固定でなくなった、というのはもろもろ影響ありそうですので、改めて仕様を覚えなおしておきましょう。

以上、 チバユキ (@batchicchi) がお送りしました。

参考

脚注

  1. This route table is used by a subnet, and doesn't support route destination which are more specific than VPC local CIDR.といったエラーが発生していました。