
はじめに
こんにちは。ITインフラ本部 SRE部の佐々木です。
本記事では、AWSのコスト最適化の手段として、NAT Gatewayの通信内容を調査し、コストを削減する方法を紹介させていただきます。
背景
私が担当する「とあるプロダクト」において、AWS Cost Explorerを用いたコスト分析したところ、「EC2-その他」のカテゴリがもっとも高額でした。

「EC2-その他」の内訳を確認すると、その多くが「APN1-NatGateway-Bytes(東京リージョンのNAT Gatewayのデータ処理料金)」によるものでした。

つまり、NAT Gatewayを経由する通信量を削減することで、AWSのコストを大幅に削減することが可能ということがわかりました。
NAT Gatewayの通信分析
調査のために必要なリソース構成
AWS Cost ExplorerやCloudWatchの情報だけでは、NAT Gatewayの詳細な内容を調査できません。
NAT Gatewayの通信を調査するには、以下を活用します。
VPCフローログ(以下、フローログ): NAT Gatewayの通信先のIPアドレスを記録Route53クエリログ(以下、クエリログ): DNSの名前解決結果を記録し、通信先のドメインを特定

NAT Gatewayの通信分析を行うにあたり、以下の2種類の通信について分析します。
- VPC内 ←(NAT Gateway)→ AWSとの通信
- VPC内 ←(NAT Gateway)→ AWS以外との通信
VPC内のリソースがNAT Gatewayを介してどの宛先と通信しているのかを分析し、その結果に基づいてコスト最適化を検討します。 また、フローログとクエリログについては、Athenaにて以下のDDLでテーブルを作成した前提とします。
フローログ
CREATE EXTERNAL TABLE `flowlogs`( `version` int, `account_id` string, `interface_id` string, `srcaddr` string, `dstaddr` string, `srcport` int, `dstport` int, `protocol` bigint, `packets` bigint, `bytes` bigint, `start` bigint, `end` bigint, `action` string, `log_status` string, `pkt_src_aws_service` string, `pkt_dst_aws_service` string, `flow_direction` string, `traffic_path` int, `pkt_srcaddr` string, `pkt_dstaddr` string ) PARTITIONED BY ( `year` int, `month` int, `day` int, `hour` int ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ' ' STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' LOCATION '${S3_LOCATION}' TBLPROPERTIES ( 'projection.day.digits'='2', 'projection.day.interval'='1', 'projection.day.range'='1,31', 'projection.day.type'='integer', 'projection.enabled'='true', 'projection.hour.digits'='2', 'projection.hour.interval'='1', 'projection.hour.range'='0,23', 'projection.hour.type'='integer', 'projection.month.digits'='2', 'projection.month.interval'='1', 'projection.month.range'='1,12', 'projection.month.type'='integer', 'projection.year.digits'='4', 'projection.year.interval'='1', 'projection.year.range'='2020,2120', 'projection.year.type'='integer', 'skip.header.line.count'='1', 'storage.location.template'='${S3_LOCATION}/${year}/${month}/${day}/${hour}' )
クエリログ
CREATE EXTERNAL TABLE `querylogs`( `version` string, `account_id` string, `region` string, `vpc_id` string, `query_timestamp` string, `query_name` string, `query_type` string, `query_class` string, `rcode` string, `answers` array<struct<rdata:string,type:string,class:string>>, `srcaddr` string, `srcport` int, `transport` string, `srcids` struct<instance:string,resolver_endpoint:string>, `firewall_rule_action` string, `firewall_rule_group_id` string, `firewall_domain_list_id` string ) PARTITIONED BY ( `year` int, `month` int, `day` int ) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' LOCATION '${S3_LOCATION}' TBLPROPERTIES ( 'projection.day.digits'='2', 'projection.day.interval'='1', 'projection.day.range'='1,31', 'projection.day.type'='integer', 'projection.enabled'='true', 'projection.month.digits'='2', 'projection.month.interval'='1', 'projection.month.range'='1,12', 'projection.month.type'='integer', 'projection.year.digits'='4', 'projection.year.interval'='1', 'projection.year.range'='2020,2120', 'projection.year.type'='integer', 'skip.header.line.count'='1', 'storage.location.template'='${S3_LOCATION}/${year}/${month}/${day}' )
調査1: AWS内の通信
VPC内のリソースが、NAT Gatewayを経由して、AWSのサービスと通信するトラフィック量を確認します。以下のクエリをAthenaで実行し、通信先AWSサービスごとの通信量を分析します。
VPC内のリソースからNAT Gatewayを経由してAWSサービスに送信した通信量
SELECT pkt_dst_aws_service, SUM(bytes) AS "Bytes/day", ROUND(SUM(bytes) * 30.0 / (1024 * 1024 * 1024)) AS "GB/month", ROUND(SUM(bytes) * 30.0 / (1024 * 1024 * 1024) * 0.062) AS "$/month" FROM example.vpc_flowlogs WHERE srcaddr LIKE '172.16.%' -- VPCのCIDRのネットワーク部分 AND dstaddr in ('172.16.0.100','172.16.1.100') --NATゲートウェイのローカルIP AND action = 'ACCEPT' AND year = 2025 AND month = 1 AND day = 1 -- 分析対象の日付 GROUP BY pkt_dst_aws_service ORDER BY "Bytes/day" DESC
VPC内のリソースがNAT Gatewayを経由してAWSサービスから受信した通信量
SUM(bytes) AS "Bytes/day", ROUND(SUM(bytes) * 30.0 / (1024 * 1024 * 1024)) AS "GB/month", ROUND(SUM(bytes) * 30.0 / (1024 * 1024 * 1024) * 0.062) AS "$/month" FROM example.vpc_flowlogs WHERE pkt_dstaddr LIKE '172.16.%' -- VPCのCIDRのネットワーク部分 AND dstaddr in ('172.16.0.100','172.16.1.100') -- NAT GatewayのローカルIP AND action = 'ACCEPT' AND year = 2025 AND month = 1 AND day = 1 -- 分析対象の日付 GROUP BY pkt_src_aws_service ORDER BY "Bytes/day" DESC
なお、フローログは非常にデータ量が多いため、このクエリでは1日分のログを集計し、その結果を単純に30倍して月間の概算通信量として算出しています。
AWS内通信の調査結果例
| # | pkt_dst_aws_service | Bytes/day | GB/month | $/month |
|---|---|---|---|---|
| 1 | - | 329,324,558,249 | 9,201.0 | 570.0 |
| 2 | EC2 | 293,154,580,089 | 8,191.0 | 508.0 |
| 3 | CLOUDFRONT | 25,283,836,498 | 706.0 | 44.0 |
| 4 | S3 | 9,883,647,552 | 276.0 | 17.0 |
| 5 | AMAZON | 1,725,014,613 | 48.0 | 3.0 |
| 6 | GLOBALACCELERATOR | 656,437 | 0.0 | 0.0 |
※ pkt_dst_aws_serviceが-となっている通信は、AWSサービスではないIPに向けた通信です。
調査2: AWS以外との通信
次に、AWS以外の通信を調査します。フローログでは通信先のIPしか分からないため、クエリログを活用して通信先ドメインを特定します。
VPC内のリソースからNAT Gatewayを経由して外部に送信した通信量
SELECT Q.query_name AS Domain, SUM(F.bytesTransferred) AS "Bytes/day", ROUND(SUM(F.bytesTransferred) * 30.0 / (1024 * 1024 * 1024)) AS "GB/month", ROUND(SUM(F.bytesTransferred) * 30.0 / (1024 * 1024 * 1024) * 0.062) AS "$/month" FROM ( SELECT pkt_dstaddr, SUM(bytes) AS bytesTransferred FROM example.vpc_flowlogs WHERE srcaddr LIKE '172.16.%' -- VPCのCIDRのネットワーク部分 AND dstaddr in ('172.16.0.100','172.16.1.100') -- NAT GatewayのローカルIP AND action = 'ACCEPT' AND year = 2025 AND month = 1 AND day = 1 -- 分析対象の日付 GROUP BY pkt_dstaddr ) F LEFT JOIN ( SELECT DISTINCT query_name, answer.rdata FROM example.vpc_querylogs CROSS JOIN UNNEST(answers) as st(answer) WHERE answer.type = 'A' AND year = 2025 AND month = 1 AND day = 1 -- 分析対象の日付 ) Q ON F.pkt_dstaddr = Q.rdata GROUP BY Q.query_name ORDER BY "Bytes/day" DESC
VPC内のリソースがNATゲートウェイを経由して外部から受信した通信量
SELECT Q.query_name AS Domin, SUM(F.bytesTransferred) AS "Bytes/day", ROUND(SUM(F.bytesTransferred) * 30.0 / (1024 * 1024 * 1024)) AS "GB/month", ROUND(SUM(F.bytesTransferred) * 30.0 / (1024 * 1024 * 1024) * 0.062) AS "$/month" FROM ( SELECT srcaddr, SUM(bytes) AS bytesTransferred FROM sre.sample_vpc_flowlogs WHERE srcaddr NOT LIKE '172.16.%' -- VPCのCIDRのネットワーク部分 AND dstaddr in ('172.16.0.100','172.16.1.100') -- NAT GatewayのローカルIP AND pkt_dstaddr LIKE '172.16.%' -- VPCのCIDRのネットワーク部分 AND action = 'ACCEPT' AND year = 2025 AND month = 1 AND day =1 -- 分析対象の日付 GROUP BY srcaddr ) F LEFT JOIN ( SELECT DISTINCT query_name, answer.rdata FROM sre.sample_vpc_querylogs CROSS JOIN UNNEST(answers) as st(answer) WHERE answer.type = 'A' AND year = 2025 AND month = 1 AND day = 1 -- 分析対象の日付 ) Q ON F.srcaddr = Q.rdata GROUP BY Q.query_name ORDER BY "Bytes/day" DESC
サブクエリFでは、フローログから、NATGatewayを経由する送信先IPアドレスを取得します。
サブクエリQでは、クエリログから、名前解決されたドメインとIPアドレスの対応表を作成します。
そしてFとQのIPアドレスを結合し、送信先ドメインごとの通信量を算出しています。
AWS以外との通信調査結果例
| # | Domain | Bytes/day | GB/month | $/month |
|---|---|---|---|---|
| 1 | api.***.dmm.com. | 962,287,739,074 | 26886.0 | 1667.0 |
| 2 | search.***.dmm.com. | 692,055,957,960 | 19336.0 | 1199.0 |
| 3 | d5l0dvt14r5h8.cloudfront.net. | 537,844,325,426 | 15027.0 | 932.0 |
| 4 | ***.apis.example.com. | 415,192,126,662 | 11600.0 | 719.0 |
| 5 | member.***.example.com. | 285,772,088,747 | 7984.0 | 495.0 |
| 6 | gw.***.example.com. | 247,207,068,107 | 6907.0 | 428.0 |
| 7 | tag.***.example.com. | 62,365,075,103 | 1742.0 | 108.0 |
| 8 | tag.example.***.com. | 62,365,075,103 | 1742.0 | 108.0 |
| 9 | app-api.***.example.com. | 19,713,528,653 | 551.0 | 34.0 |
| 10 | www.example.co.jp | 19,003,891,554 | 531.0 | 33.0 |
NAT Gatewayの通信量削減案
調査結果をもとに、通信量を削減する方法を検討します。状況によって対策は異なりますが、ここではいくつかの削減案を紹介します。
案1: VPCエンドポイントの作成
調査1: AWS内の通信の調査結果の例を見ると、S3との通信が発生していることがわかります。 S3のVPCエンドポイントを作成することで、NAT Gatewayを経由しない経路にできます。 また、S3,DynamoDBのVPCエンドポイントは、ゲートウェイ型のため、コストがかかりません。 よって丸々費用を削減できます。
案2: 通信内部化
調査2: AWS以外の通信の調査結果の例を見ると、1位にapi.***.dmm.com.が挙がっています。このドメインは、同じプロダクト内のAPIです。
以下の図のように、通信をVPC内で完結させることで、NAT Gatewayを経由しない経路にできます。

ECSのサービスディスカバリ機能を使用する方法もありますが、 サービスディスカバリ機能はRoute 53を利用したシンプルな仕組みであるため、ALBのロギング機能や柔軟な負荷分散機能やヘルスチェック機能を活用できません。これらの機能を引き続き利用したかったため、今回は内部ALBを構築する方法を選択しました。
案3: VPC Peeringの活用
調査2: AWS以外の通信の調査結果の例を見ると、2位にsearch.***.dmm.com.が挙がっています。このドメインは、他プロダクトの検索サービスのものでした。
もしこの他プロダクトがAWSを使用していれば、VPCピアリングをすれば費用削減できる可能性があります。

同一リージョンのVPC間のピアリング料金は以下の通りです。
| 種類 | 単価 |
|---|---|
| ピアリング接続の確立 | $0.00 |
| 同一AZ間の1GBあたりのデータ通信料金 | $0.00 |
| 別のAZ間の1GBあたりのデータ通信料金 | $0.01 |
NAT Gatewayは1GBあたりのデータ通信料金が$0.062なので、VPCピアリング確立後の通信が、仮に全て別のAZ間だったとしても、料金を約1/6にできます。
案4: Docker Pullの通信を削減
調査2: AWS以外の通信の調査結果の例を見ると、3位にd5l0dvt14r5h8.cloudfront.net.が挙がっています。
このドメインを調べたところ、AWSのパブリックECRからコンテナイメージを配信するエンドポイントであることがわかりました。
このプロダクトでは、ECSのログルーターとしてAWS FireLensを使用しているため、ECSタスクの起動時にこのエンドポイントからAWS FireLensのイメージをNAT Gateway経由で取得(Pull)することになります。
ECRのプルスルーキャッシュを活用し、パブリックなコンテナイメージをプライベートECRにキャッシュすることで、 プライベートサブネットからはECR経由でイメージを取得できるようになります。
これにより、NAT Gatewayを介した通信量を削減し、コストの抑制が可能になります。
詳細については、SRE部の湯浅さんが投稿した以下の記事をご参照ください。
まとめ
フローログとクエリログを活用することで、NAT Gatewayの通信内容を詳細に分析し、コスト削減の施策を講じることが可能です。もし「EC2-その他」の料金が高額になっている場合は、一度調査してみてはいかがでしょうか。
SRE部では、一緒に働く仲間を募集しています。ご興味のある方はこちらへ!
dmm-corp.com