ECS Fargateのログ管理コスト削減(Firehose + S3 + Athena)


前提

ECS Fargateを利用していて、以下に当てはまる場合はコスト改善できるかもしれないので読み進めてもいいかもしれないです。

  • CloudWatch Logsのコストが高い。
  • CloudWatch Logsにログ連携しているがalermは設定していない。
  • リアルタムなログ閲覧は求めていない。

経緯

最近携わっているプロダクトではECS Fargateを利用しているのですが、請求書の内訳を確認してみたところ、CloudWatchLogsのPutLogEventsに、全体の1/3以上のコストが発生していました。ログにそんなにお金を払うのは辛いです。

原因

全TaskのLog driverがawslogになっており、ログがCloudwatch Logsに連携されいたことがコスト増加の原因でした。

Cloudwatch Logsは、ログを検索する際にはとても便利なのですが、ログをリアルタイムで監視するようなalermを設定していないのであれば、S3にログを保存しておいて、見たい時にAthenaで検索するというやり方でも十分運用できそうです。というAWS有識者からのアドバイスもあり、構成変更を実施してみました。

改善案

いままでは、FargateからCloudWatchに月間2TB(*1)ほどログを転送しており0.76USD/GBの課金が発生していましたが、変更後の構成ではFargate-Firehose-S3への転送で0.036USD/GBになりますので、ログの取り込みコストだけをみれば1556ドル->73ドルという予測になります。

他にも保存や分析にかかる費用がそれぞれありますが、取り込みコストの差を考えれば変更する価値は十分ありそうです。
詳しくはAWSのページをご確認ください。

https://aws.amazon.com/jp/cloudwatch/pricing/
https://aws.amazon.com/jp/kinesis/data-firehose/pricing/
https://aws.amazon.com/jp/athena/pricing/

*1 ログ取り込み量は以下のようなコマンドで1日分を出して、ざっくり31をかけて1ヶ月分としました。

aws cloudwatch get-metric-statistics \
--namespace "AWS/Logs" \
--dimensions Name=LogGroupName,Value="ロググループ名" \
--metric-name "IncomingBytes" \
--statistics "Sum" \
--start-time "2020-09-26T00:00:00Z" \
--end-time "2020-09-27T00:00:00Z" \
--period 86400 \
--query "reverse(sort_by(Datapoints,&Timestamp)[?Sum>\`0\`].{Sum:Sum,Timestamp:Timestamp})" \
--output text

やってみた

構成

検証用の環境は以下のような構成にしました。

手順

今回はAWSコンソール上から作成していきます。

1. S3 Bucketの作成

任意の名前でバケットを作成してください(作成手順は省略)

No
1 region ap-northeast-1
2 バケット名 example-bucket-20200929

2. Delivery streamの作成

Kinesisのコンソール画面から「Create delivery stream」をクリックする

ログの書き込み先はS3を指定しています。

S3 prefixはAthenaのパーテション分割が1時間単位になるように設定しました。
https://docs.aws.amazon.com/ja_jp/athena/latest/ug/partitions.html

■ Prefix
nginx-access/dt=!{timestamp:yyyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}-00/

■ Error prefix
nginx-error/dt=!{timestamp:yyyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}-00/!{firehose:error-output-type}

S3 buffer conditionsはバッファーサイズが5MBを超えた場合、または5分たったらS3にログを書き込むように設定しています。
圧縮と暗号化はDisabledにしています。

3. ALBの作成

4. Fargateの設定

Task Roleの作成

TaskRoleを作成して、Firehoseへのアクセスを許可しておく。

CloudFormationだと以下のようになります。

  ecsTaskRoleExample:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: 'Allow'
          Principal:
            Service: 'ecs-tasks.amazonaws.com'
          Action: 'sts:AssumeRole'
      Policies:
        - PolicyDocument:
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'firehose:PutRecordBatch'
                Resource: '*'
          PolicyName: 'firehose-example'
Clusterの作成

Task Definitionの作成

Task Roleには事前に作成したroleを割り当てます。

Task sizeは最小に設定しています。

Add containerをクリックしてコンテナを追加します。

Log configurationでawsfirelensを選択し、作成したDelivery streamの値を設定します。

Log Router Integrationは、今回はfluentbitを選択します。

Serviceの作成

5. Athenaで検索してみる

S3には以下のようにログが保存されます。

ログの出力結果は以下のようになります。

{
  "container_id": "xxxxxxxx",
  "container_name": "/ecs-example-task-2-nginx-xxxxxx",
  "ecs_cluster": "arn:aws:ecs:ap-southeast-1:XXXXXXX:cluster/example-cluster",
  "ecs_task_arn": "arn:aws:ecs:ap-southeast-1:XXXXXXX:task/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "ecs_task_definition": "example-task:2",
  "log": "172.31.42.126 - - [30/Sep/2020:04:12:04 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"ELB-HealthChecker/2.0\" \"-\"",
  "source": "stdout"
}

Athenaでテーブルとパーティションを作成します。

CREATE EXTERNAL TABLE nginx_log (
    container_id string,
    container_name string,
    ecs_cluster string,
    ecs_task_arn string,
    ecs_task_definition string,
    log string,
    source string)
PARTITIONED BY (dt string)
ROW FORMAT  serde 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://example-bucket-20200929/nginx-access/' ;

パーティションにデータをロードします。

MSCK REPAIR TABLE nginx_log;

期間を変更して検索してみると、スキャンしたデータのサイズが変わっていることが確認できると思います。

おわりに

正規表現を利用したログのフィルタリングも可能とのことなので、引き続き検証していきたいと思います。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/using_firelens.html