ELBとCloudFrontのアクセスログをサーバレスに集約させてみた
はじめに
AWSチームのすずきです。
ELB(ALB/CLB)とCloudFrontがS3に出力するアクセスログ、 その確認、解析を効率よく行うため、Firehoseを利用して集約する機会がありました。
Lambdaを利用したサーバレスなS3への集約の一例として紹介させて頂きます。
構成図
処理説明
1: S3
- ELBのログ格納用となるS3バケットです
- ELBのアクセスログ登録(PutObject)先として利用するためのバケットポリシーと、S3のレプリケーション先可能にするための、バージョニング設定を実施しています。
- 今回、既存のELBなどのログ出力設定の変更を回避するため、既存のアクセスログが出力されるS3バケットにレプリケーション設定を追加。レプリケーション先としてこのS3バケットを指定して利用しました。
2: SNS
- 1回のS3イベントから複数のLambda処理を実施する可能性や、イベント情報をデバッグ用に取得しやすくするため、Amazon SNSを利用しました。
- Amazon SNS、Lambdaへの配信課金は、無料で利用する事が可能です。
3: Lambda
- Lambda関数のコードは、CloudFormationのテンプレートにインライン記述できる4000文字制限に収まる実装としました。
- Lambda処理のタイムアウト時間は最大(300秒)まで延長。割り当てメモリは256MB、初期値の2倍に設定しました。
- 今回のFirehose投入を行うLambda関数の実装では、1ログファイルあたり30〜40万レコードが処理上限の目安となります。
- ELB(ALB)は5分毎にアクセスログをS3に出力する仕様です。1分あたり10万リクエストを超えるELBのアクセスログを処理する必要が有る場合、前処理としてファイル分割処理を行うLambdaの用意や、AWS Glueなどの利用をご検討ください。
Lamba割当メモリ別の所要時間
- ELB(ALB)アクセスログ、6万、30万レコードのファイルを用意、 Firehose投入用Lambda関数の割当メモリ別の所要時間(Duration)を測定しました
Memory Size | 6万レコード | 30万レコード |
---|---|---|
256 MB | 81秒 | (タイムアウト) |
512 MB | 53秒 | 269秒 |
3008 MB | 45秒 | 216秒 |
Lambda処理内容
- SNSのメッセージから、処理対象となるS3のバケット、キーを取得します。
- ログの時刻情報として期待するカラムのデータが日付フォーマットに一致しない場合や、カラム数が異常なレコードはエラーレコードとして除外します。
- 「request」カラムに含まれるURLを「urlparse」モジュールを利用してパースし、解析時に利用頻度の高い「HOST」や「PATH」を事前に抽出します。
- 「json」モジュールを利用してJSONエンコードを行い、Firehoseに対しバッチ転送を行います。
4: Firehose
- 今回、出力先はS3のみとしましたが、Amazon Elasticserach Service、Redshiftと連係も可能です。
- Firehoseのバッファ時間はELB(ALB)のログ出力間隔にあわせ、300秒としました。
- Firehoseの後処理をLambda関数で実装する可能性を想定し、ファイルサイズは一定(50MB)で抑止する指定を行いました。
5: S3
- Firehoseの出力先として利用します。
- Firehoseの出力先を対象としたイベントを設定し、後処理を行うLambda関数をSNS経由で連係させました。
6: SNS
- 後処理のLambda関数連係に利用します。
7: Lambda
- Firehoseで集約、S3に出力されたログファイルの後処理を実装しました。
- FirehoseよりS3に出力されたS3ファイルのキーに含まれる日付情報(「yyyy/mm/dd/HH24」)を、Hiveフォーマット(dt=yyyy-mm-dd-HH24/)に置換し、Athena処理用のS3にコピーします。
- Hiveフォーマットのキーを付与する事で、Athenaのスキャン対象を特定期間に限定でき、AWS利用費の抑制が可能になります。
Athenaの利用
テーブル作成
- JSONに変換済のアクセスログは、テーブル定義のみで利用する事が可能です。
- Firehoseの出力日時をLambdaで変換した「dt」を、パーティションとして利用する指定を行います。
ALB用
CREATE EXTERNAL TABLE IF NOT EXISTS alb_logs ( type string, timestamp string, elb string, client_port string, target_port string, request_processing_time float, target_processing_time float, response_processing_time float, elb_status_code string, target_status_code string, received_bytes float, sent_bytes float, request string, user_agent string, ssl_cipher string, ssl_protocol string, target_group_arn string, trace_id string, domain_name string, chosen_cert_arn string, matched_rule_priority string, client string, target string, request_method string, request_uri string, request_http_version string, request_uri_scheme string, request_uri_host string, request_uri_path string ) PARTITIONED BY (dt string) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' LOCATION 's3://<Athena用S3バケット名>/firehose/alb_logs/' ;
CLB用
CREATE EXTERNAL TABLE IF NOT EXISTS clb_logs ( timestamp string, elb string, client string, client_port string, backend string, backend_port string, request_processing_time float, backend_processing_time float, response_processing_time float, elb_status_code string, backend_status_code string, received_bytes float, sent_bytes float, request string, user_agent string, ssl_cipher string, ssl_protocol string, request_method string, request_uri string, request_http_version string, request_uri_scheme string, request_uri_host string, request_uri_path string ) PARTITIONED BY (dt string) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' LOCATION 's3://<Athena用S3バケット名>/firehose/clb_logs/' ;
CloudFront用
CREATE EXTERNAL TABLE IF NOT EXISTS cloudfront_logs ( timestamp string, date string, time string, x_edge_location string, sc_bytes float, c_ip string, cs_method string, cs_host string, cs_uri_stem string, cs_status string, cs_referer string, cs_user_agent string, cs_uri_query string, cs_cookie string, x_edge_result_type string, x_edge_request_id string, x_host_header string, cs_protocol string, cs_bytes float, time_taken float, x_forwarded_for string, ssl_protocol string, ssl_cipher string, x_edge_response_result_type string, fle_status string, fle_encrypted_fields string ) PARTITIONED BY (dt string) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' LOCATION 's3://<Athena用S3バケット名>/firehose/cloudfront_logs/' ;
パーティション情報更新
- テーブル作成直後や新たに追加されたパーティションを解析対象とする場合、「MSCK REPAIR TABLE」を実行します。
MSCK REPAIR TABLE alb_logs
MSCK REPAIR TABLE clb_logs
MSCK REPAIR TABLE cloudfront_logs
解析
- Athenaのコンソールなどを利用し、SQLによるアクセスログ解析が可能になります。
- 「target」「backend」など、カラム名が異なる項目を利用しない解析では、SQLの流用可能です。
エラー応答の抽出
- ELBがHTTP応答500番のステータスコードを戻したログを抽出します
SELECT * FROM alb_logs WHERE dt< '2018-05-16-15' and dt>='2018-05-14-15' and elb_status_code like '5%' order by timestamp limit 10;
- 指定した期間の5XX応答が確認できました。
高負荷発生源の疑いのある接続元の抽出
- EC2応答時間(target_processing_time)の長い接続元IPアドレス(client)、UserAgentを抽出します
SELECT sum(target_processing_time) as total_processing_time , count(1) as request_count , SUBSTR(timestamp,1,13) as date_time , client , user_agent FROM alb_logs WHERE dt< '2018-05-16-15' and dt>='2018-05-13-15' and request_uri_host = 'dev.classmethod.jp' and user_agent like '%bot%' GROUP BY user_agent, client, SUBSTR(timestamp,1,13) order by sum(target_processing_time) desc limit 10
- 指定した期間、UserAgentでBotを名乗る接続では、高負荷は発生していなかった事を確認できました。
AWSコスト
- 1日500万リクエストのELBアクセスログ、AWS東京リージョンのFirehose、Lambdaで利用した場合、1日あたりの費用は0.202USDの計算になりました。
- 多くの環境でAWSコストを圧迫する事なく利用できる可能性が高いと思われます。
内訳
Firehose
- 0.108($)
- 取り込みデータ、1GBあたりの単価は「0.036 USD」で計算
- 0.108($) = 3(GB) × 0.036($)
Lambda
- 無料枠は無い前提で計算しました。
- 0.0193364 ($)
-
Firehose連係用実行時間
- 0.0188032($) = 4520000(ミリ秒) × (0.000000208 / 100):(1ミリ秒価格USD) × 2 (Lambda割当メモリ128→256増強分)
- Athenaコピー用実行時間
-
0.000314($) = 0.0188($) = 151000(ミリ秒) × (0.000000208 / 100):(1ミリ秒価格USD)
-
Firehose連係用リクエスト
- 0.0001714($) = 857回 × (0.20($) / 1000000):(1回リクエスト単価)
- Firehose連係用リクエスト
- 0.0000478($) = 239回 × (0.20($) / 1000000):(1回リクエスト単価)
S3
- Firehoseから1日に出力されるログ(GZIP圧縮済)が、1日300MB。これをS3上で1ヶ月保持する想定でストレージ費用を計算しました。
- S3データの参照、PUT、GETなどのAPI費用は除外しました。
- 0.075 ($)
- 0.3GB (ログ1日分) × 0.025USD/GB(1GB保管)
まとめ
ELB(ALB)をのアクセスログをサーバレス、低コストで処理する一例を紹介させて頂きました。
アクセスログが非圧縮な状態でS3に出力されるELB(CLB)のアクセスログや、 S3に保存されるキーによる期間指定が困難、ログファイル中のヘッダ行の存在がAthena処理で問題になる事のあるCloudFrontのアクセスログなどでは、 より高い効果が期待出来るかと思われます。
今回、Firehoseで集約したアクセスログをAthenaで参照するまでを紹介させて頂きましたが、
- Firehoseのバッファ時間を利用した簡易タイムウィンドウ処理
- Firehoseの後にKinesisAnalyticsを設置、アクセスログのストリーム処理
- AWS Glueを利用した、アクセスログの長期利用を想定した最適化
などの応用も改めて紹介させて頂ければと思います。