AWSの月額通信量が2倍以上!? —— AWS Batchの隠れたコストの正体とは
こんにちは!
RemitAid CTOの@iTakacです。
今回はAWS Batchを使った機能で想定外の通信量ならびに課金が発生した事象の原因究明から解決までの経験を共有します。
バッチの要件
私たちのサービスではパートナーのAPIを活用して、決済や資金移動を実現しています。
多くの場合、資金移動した結果や状態を正確かつリアルタイムにユーザーが確認できるよう、弊社システム内でのステータス管理が欠かせません。
ステータスを管理する上では、パートナーがwebhookを提供していればwebhookを受け付けるエントリーポイントを開発するだけで済みます。
ところがwebhookを提供していないシステムの場合は、こちらから取得しにいく必要があり、常時ポーリングする方式が必要となりました。
従って数分に1度パートナーのAPIをリクエストするバッチ処理を構築することになりました。
当時設計したバッチのシーケンス図
リリース
仕様は難しいものではありませんでした。
パートナーが提供するAPIからのレスポンスを解析して、弊社のデータベースと照合しステータスを登録ないし更新するのみでした。
実際には2週間程度で設計からローンチまでできていたと記憶しています。
晴れて本番環境でもリアルタイムにステータス管理ができるようになりました。
気づきと違和感
ある日、AWSの課金量をなんとなく眺めているとサチってはいるものの、前々月、前月と比べて2倍、2倍と増えていることに気づきました。
青色部分がNAT Gatewayの課金量
これはおかしいぞ、、と思い、対象のリソースを特定すると、NAT Gatewayであることが発覚。
NAT Gateway??とある種困惑しながら原因調査に乗り出しました。
Batch起動中の問題ではなく、コールドスタート中のコンテナダウンロードが原因だった
バッチリリース後に起きた事象なので、なんとなくバッチが怪しいなと思いつつも確証がありません。我々がどんな思考プロセスで確証を得たのか紹介します。
切り分け
インバウンド通信なのか、アウトバウンド通信なのかで原因が異なるのでまずはどちらの通信経路なのかを特定しました。
Cloud Watchのメトリクスを使って BytesInFromDestination
つまりNATを経由したインバウンドの通信量が圧倒的に多いことを特定しました。
常時77.7MB近くの通信が発生
原因仮説
一方で原因仮説については特定に時間がかかりました。
もしバッチが原因である場合、パートナーからのAPIレスポンスが77MB相当になりますが、
REST APIによるJSONフォーマットの軽量なデータのみだったので、到底数十MBにはなりえなかったのです。
最終的な原因はAWS Batch -> NAT Gateway -> ECRの経路によるコンテナイメージのダウンロードであると判明しました。
AWS Batchはジョブが起動すると、ECRからコンテナイメージをダウンロードし、Fargateに展開してプロセスが開始します。
このダウンロード部分が原因であることが判明しました。
1つのコンテナイメージがおよそ35~40MB弱に加え、常時2本のジョブが起動していたことで計算が合いました。
全く別の文脈で、コンテナイメージの容量を確認したことと、今回の事象がたまたまconnecting the dotsしたのです。
バッチを1つ止めるとサイズ分へこんだことを確認
Batchをpublic subnetに移設
このバッチに関してはセキュリティレビューも加えて、public subnetに移設して問題ないと判断し、subnet移設と、起動したFargateに対するpublic IPのアサインにて対応しました。
# コンピュート環境
resource "aws_batch_compute_environment" "batch" {
name = "${local.name}-batch"
type = "MANAGED"
state = "ENABLED"
# Use Service Linked Role
compute_resources {
type = "FARGATE"
max_vcpus = 256
subnets = module.vpc.public_subnets # パブリックサブネットに移設
security_group_ids = [aws_security_group.batch_sg.id]
}
}
# Fargateジョブ定義
resource "aws_batch_job_definition" "this" {
name = var.job_name
type = "container"
platform_capabilities = ["FARGATE"]
tags = var.tags
# ref. https://docs.aws.amazon.com/batch/latest/APIReference/API_EcsTaskProperties.html
ecs_properties = jsonencode({
taskProperties = [
{
executionRoleArn = var.job_execution_role_arn
taskRoleArn = var.job_task_role_arn
networkConfiguration = {
assignPublicIp = "ENABLED" # Public IPアドレスをアサイン
}
containers = [
{
name = "batch-job"
command = var.job_command
dependsOn = [
{
condition = "START"
containerName = "adot-collector"
}
]
environment = var.job_environment
fargatePlatformConfiguration = {
platformVersion = "LATEST"
}
# ...以下省略
}
]
},
]
})
}
AWS Batchの隠れコストにご注意を!
構成にもよるので一概には言えませんが、機能要件を満たすだけがソフトウェア開発ではないことを学びました。
AWS Batchは便利なサービスですが、コンテナイメージのダウンロードというコストが見えにくい部分があります。
特にprivate subnetで運用している場合、NAT Gatewayの通信料が想定以上にかかる可能性があるので、ご利用の際はご注意ください!
告知
Podcast 「RemiTalk」を最近始めましたので、もし良ければ聴いてみてください!
Podcast 文字起こしはこちらまたRemitAid では一緒に働く仲間を募集しています。
興味がある方はこちらからどうぞ!
Discussion