こんにちは。crowdworks.jp SREチームの田中(kangaechu)です。先日RubyKaigi2023に参加するため、松本に行きました。街を歩いていると目線の先に山が見えたり、温泉が近くにあったりなど松本の自然が近くにある感じがとてもよかったです。ちょっと住みたくなって不動産屋さんで家賃を眺めたりしておりました。
今回は小ネタとして、AWSのCloudWatchの料金が高くなったことで見つけたCloudTrailの設定についてです。
あなたのCloudWatch LogsとCloudTrailの料金、高くないですか?
先日、AWSのコスト異常検知サービスのCost Anomaly Detection からCloudWatchとCloudTrailのコストが跳ね上がっているとの通知が来ました。 CloudWatchは1日で通常の111.34%、CloudTrailについては通常の863.85%となっており、明らかに異常です。 何が起きているの?と思い調査を始めました。
まずはCost ExplorerでCloudWatchのAPIオペレーションを確認。明らかに特定日だけ紫の項目(PutLogEvents)が跳ね上がっています。CloudWatch Logsへのログ取り込みのコストが増えていることがわかりました。 それでは、どのロググループに対してのコストが増えているのでしょうか?
次に見たのはCloudWatch Metricsです。全てのメトリクス→ログ→ロググループメトリクスで全てのロググループのIncomingBytesを確認します。 明らかにIncomingBytesが一つだけ飛び抜けて大きいロググループがあります。それがCloudTrailのログでした。
なぜCloudWatch Logsに送られたCloudTrailのログの量が増えるのか
ここで思い出したのがCloudTrailのデータイベントでした。crowdworks.jpではS3に保存したデータが何らかのインシデントにより流出した場合に備え、S3のオブジェクトレベルのイベント(GetObject, PutObjectなど)であるデータイベントをCloudTrailに保存しています。 docs.aws.amazon.com つまりS3に対して大量のアクセスが発生するとCloudTrailに大量のログが保存されることになります。CloudTrailの料金が増えた理由は多分このためでしょう。
しかし、CloudTrailのログ保存先はS3のはず。なぜCloudWatch LogsのPutLogEvents料金が増加するのでしょうか。 CloudTrailの設定を再度見て気がつきましたが、CloudTrailのイベントログをCloudWatch Logsに送信していました。
また、crowdworks.jpではさまざまな調査のため、Railsログやアクセスログをクエリすることがあります。 ログはS3に保管しており、クエリにはAthenaを使っています。 engineer.crowdworks.jp
つまり、このようなことが発生していました。
- 調査の要件により、調査対象期間が長い場合には大量のS3オブジェクトに対してクエリをする
- S3オブジェクトに対するアクセスログがCloudTrailに保管される
- CloudTrailのログはCloudWatch Logsに転送される
- CloudWatch Logsの料金が跳ね上がる
料金を減らすためのしくみ
原因はわかりました。それでは料金を減らすためにはどうすれば良いでしょうか。 私はAWSのアカウントを新規に作成し、CloudTrailを手動で設定するという経験がないため、まずはAWSのCloudTrail周りのドキュメントを読みました。 docs.aws.amazon.com
それにより、以下のことがわかりました。
- CloudTrailの証跡は複数作成できる
- 証跡ごとに送信対象のイベントを指定できる
- CloudWatch Logsへの送信はオプションである
CloudTrailは一度設定した後は変更する要件がほぼなく、また不用意に変更すると証跡が取得できなくなるかも、と思わせる怖いリソースです。 その怖さからマネジメントコンソールの編集ボタンも押すのをためらい、このような設定ができることを知りませんでした。
次に、なぜCloudTrailからCloudWatch Logsにログを転送していたのでしょうか。 これはセキュリティ標準の一つであるCIS AWS Foundations Benchmark の「3.4 - CloudTrail 証跡が CloudWatch ログと統合されているようにする」に準拠するためです。 docs.aws.amazon.com そのため、CloudTrailからCloudWatch Logsへのログ送信は引き続き必要です。ただ、データイベントをCloudWatch Logsに送信する必要はありません。 そのため、以下のように修正を行いました。
- データイベント専用の証跡を追加する
- デフォルトの証跡でデータイベントの取得をやめる
- データイベント専用の証跡を保存するログをCloudTrail用S3バケットの特定のパスに保管。保管期間を3年に設定する
- データイベント用のAthenaのPartition Projectionを設定し、データイベントをクエリできるようにする
弊社はTerraform大好き会社なので、Terraformでささっと作っていきます。
resource "aws_cloudtrail" "default" { name = "Default" enable_logging = true include_global_service_events = true is_multi_region_trail = true enable_log_file_validation = true s3_bucket_name = var.cloudtrail_bucket_name cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*" cloud_watch_logs_role_arn = aws_iam_role.cloud_watch_logs.arn } resource "aws_cloudtrail" "data_events" { name = "data-events" enable_logging = true include_global_service_events = true is_multi_region_trail = true enable_log_file_validation = true s3_bucket_name = var.cloudtrail_bucket_name s3_key_prefix = "data-events" advanced_event_selector { name = "Log all S3 objects events" field_selector { field = "eventCategory" equals = ["Data"] } field_selector { field = "resources.type" equals = ["AWS::S3::Object"] } } }
それでどうなった?
設定変更後に大量のログ検索を行っていないので、ログ検索によりCloudWatch Logsの料金が減らせたかの確認はできていません。 (この検証のためにわざわざ大量のログ検索を行うのはちょっと違いますよね)
また、嬉しい副次効果として、ログ検索が大量に行われなかった時でもCloudWatch Logsに送信されたCloudTrailロググループのIncomingBytesを大幅に下げることができました。 CloudWatch Logsの取り込み料金は東京リージョンで0.76USD/GBと割と高いので、料金の削減につながったのもよかったです。
まとめ
CloudTrailのログをCloudWatch Logsに送信する場合、証跡を分離することで必要な項目のみ送信することができました。CloudTrailの設定はアカウント作成後にあまり変更することのないリソースなので、改めて調べてみることで知見を得ることができました。 また、CloudTrailのデータイベントをCloudWatch Logsに送信することをやめることでコストの削減につながりました。
CIS AWS Foundations Benchmark には「CloudTrailとCloudWatch Logsを統合する」という要件がありますが、CloudWatch Alarmに通知されるまでに数分のラグが発生します。crowdworks.jpでは、一部のAWSイベントについてはEventBridge+Lambdaを使用したSlack通知を実装しており、そちらはほぼリアルタイムでの通知が可能となっています。将来的にはCloudTrailからCloudWatch Logsへの転送をやめ、EventBridge+Lambdaに移行したいと考えています。
こんなん知ってるわいとマサカリを投げたくなった方、それ以外の方もクラウドワークスのSREへの応募お待ちしております。 herp.careers