今さらだけど、AWS ELBのログ解析にAthenaを用いたら、簡単で便利だった

#きっかけ

トラブルの原因を調べるなどの理由で、ELBのアクセスログを解析する必要に迫られることは多いと思います。
私は、当初、ログを格納しているS3から、ファイルをLinuxサーバーにダウンロードしてgrepやawkで集計したり、Windows PCに移してExcelのピボットで集計したりしていました。
ただ、ログファイルは、ノードや時間ごとに細かく分かれており、意外と手間がかかります。

AWSには、S3上のデータを、S3に格納したままSQLで操作できるAthenaというサービスがあります。このAthenaを使って、ログ解析を試みたところ、非常に簡単で便利でした。
SQLクエリーを工夫することで、毎時ごとのアクセス数なども手軽に集計できます。

##ALBとCLB

Application Load BalancerとClassic Load Balancerとでは、アクセスログに記録される項目や、項目名が異なります。ここでは、Application Load Balancerで説明しますが、ここで紹介するSQLクエリーは、項目名を変更すれば、Classic Load Balancerでも利用できます。

#テーブル定義

ELBのアクセスログは、スペース区切りの単なるテキストファイルです。まずは、SQLで扱うために、テーブルの構造を定義しましょう。
具体的には、`CREATE EXTERNAL TABLE`文を使います。

```
CREATE EXTERNAL TABLE IF NOT EXISTS <table_name> (
    type string,
    time string,
    elb string,
    client_ip string,
    client_port int,
    target_ip string,
    target_port int,
    request_processing_time double,
    target_processing_time double,
    response_processing_time double,
    elb_status_code string,
    target_status_code string,
    received_bytes bigint,
    sent_bytes bigint,
    request_verb string,
    request_url string,
    request_proto 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,
    request_creation_time string,
    actions_executed string,
    redirect_url string,
    new_field string
    )
    ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
    WITH SERDEPROPERTIES (
    'serialization.format' = '1',
    'input.regex' = 
'([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) \"([^ ]*) ([^ ]*) (- |[^ ]*)\" \"([^\"]*)\" ([A-Z0-9-]+) ([A-Za-z0-9.-]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" ([-.0-9]*) ([^ ]*) \"([^\"]*)\"($| \"[^ ]*\")(.*)')
    LOCATION 's3://<bucket_name>/AWSLogs/<account_id>/elasticloadbalancing/<region>/yyyy/mm/dd/';
```

最初の`CREATE EXTERNAL TABLE`文と、最後の`LOCATION`文は、お使いの環境に合わせて修正してください（それぞれテーブル名とログのリソースURIを指定します）。

なお、リソースURIですが、`s3://<bucket_name>/AWSLogs/<account_id>/elasticloadbalancing/<region>/`であれば、そのELBの全ログが対象になり、日付を加えて`s3://<bucket_name>/AWSLogs/<account_id>/elasticloadbalancing/<region>/yyyy/mm/dd/`とすると、その日付（UTC）に保管されたログだけに限定されます。Athenaはスキャンするデータの容量で課金されるため、私は、日付を指定し、スキャン対象を限定して使っています。

#SQLクエリーの実行

準備ができたら、SQLクエリーを実行しましょう。

▼指定時間内で、アクセスが多いIPのトップ100

```
SELECT client_ip,
       count(*) AS request_count
  FROM <table_name>
 WHERE time >= '2018-10-01T03:00'
   AND time <  '2018-10-01T09:00'
 GROUP BY client_ip
 ORDER BY request_count DESC limit 100;
```

▼ターゲット処理時間が1秒以上のURL（指定時間内／処理時間が大きい順）

```
SELECT time,
       client_ip,
       request_url,
       target_processing_time
  FROM <table_name>
 WHERE time >= '2018-10-01T03:00'
   AND time <  '2018-10-01T09:00'
   AND target_processing_time >= 1
 ORDER BY target_processing_time DESC;
```

##毎時ごとの集計

ネットで調べて簡単には見つからなかったのが、この「毎時ごと」の集計でした。
Athenaの場合、substr関数が利用できるため、timeフィールドの`yyyy/mm/dd:hh`を切り出して`GROUP BY`句を使っています。

▼毎時ごとのターゲット処理時間とアクセス数（タイムアウト分は除外）

```
SELECT substr(time,1,13) AS every_hour,
       avg(target_processing_time) AS average_time,
       count(*) AS request_count
  FROM <table_name>
 WHERE target_processing_time > 0
 GROUP BY substr(time,1,13)
 ORDER BY every_hour;
```

▼毎時ごとのタイムアウト数

```
SELECT substr(time,1,13) AS every_hour,
       count(*) AS request_count
  FROM <table_name>
 WHERE target_processing_time < 0
 GROUP BY substr(time,1,13)
 ORDER BY every_hour;
```

##ダウンロード用CSVファイル

どうしても手元にログファイルが必要になったら、下記のSQLクエリーで1つのCSVファイルとしてダウンロードすることもできます。

▼指定時間内の全項目

```
SELECT *
 FROM <table_name>
WHERE time >= '2018-10-01T03:00'
  AND time <  '2018-10-01T09:00'
ORDER BY time;
```

#まとめ

Athenaを使うことで、さまざまな集計が手軽に行えます。
SQLクエリー次第では、さらに複雑な集計も可能です。
今後、役立つクエリーができたら、ここで紹介したいと思っています。

#参考

▼Amazon Athena ユーザーガイド - Application Load Balancer ログのクエリ
https://docs.aws.amazon.com/ja_jp/athena/latest/ug/application-load-balancer-logs.html