AWS NAT Gatewayの通信分析とコスト最適化

サムネイル

はじめに

こんにちは。ITインフラ本部 SRE部の佐々木です。
本記事では、AWSのコスト最適化の手段として、NAT Gatewayの通信内容を調査し、コストを削減する方法を紹介させていただきます。

背景

私が担当する「とあるプロダクト」において、AWS Cost Explorerを用いたコスト分析したところ、「EC2-その他」のカテゴリがもっとも高額でした。

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

つまり、NAT Gatewayを経由する通信量を削減することで、AWSのコストを大幅に削減することが可能ということがわかりました。

NAT Gatewayの通信分析

調査のために必要なリソース構成

AWS Cost ExplorerCloudWatchの情報だけでは、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アドレスの対応表を作成します。
そしてFQの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 Peeringの活用

同一リージョンの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部の湯浅さんが投稿した以下の記事をご参照ください。

developersblog.dmm.com

まとめ

フローログとクエリログを活用することで、NAT Gatewayの通信内容を詳細に分析し、コスト削減の施策を講じることが可能です。もし「EC2-その他」の料金が高額になっている場合は、一度調査してみてはいかがでしょうか。

SRE部では、一緒に働く仲間を募集しています。ご興味のある方はこちらへ!
dmm-corp.com