ELBとCloudFrontのアクセスログをサーバレスに集約させてみた

116件のシェア(ちょっぴり話題の記事)

はじめに

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利用費の抑制が可能になります。

Amazon Athenaのパーティションを理解する #reinvent

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を利用した、アクセスログの長期利用を想定した最適化

などの応用も改めて紹介させて頂ければと思います。

CloudFormationテンプレート

ALB用

CLB用

CloudFront用