この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
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を利用した、アクセスログの長期利用を想定した最適化
などの応用も改めて紹介させて頂ければと思います。