Athena Partition Projectionを使用しNginx アクセスログをクエリする

こんにちは、香田です。

今回はAthena Partition Projectionを使用しNginx アクセスログをクエリする方法について紹介していきます。

AthenaよりクエリするNginx アクセスログは、ローカル環境でDockerを利用しデータを準備していく流れとなります。

Athena Partition Projectionについて

Athena Partition Projectionについて簡単に解説すると、

Partition Projectionが提供される前は、Athenaでパーティション分割されたデータを管理する場合、MSCK REPAIR TABLEALTER TABLE ADD PARTITIONを使用し、分割したいデータ毎にパーティションを追加する必要がありました。

Partition Projectionを使用することで、パーティションを手動で追加する必要がなく、パーティション管理を自動化することが可能となります。

パーティション分割については詳しくは下記を参考にしてみてください。

S3バケットの作成

はじめにAthenaで参照するNginx アクセスログ転送先のS3バケットを用意していきます。

aws s3 mb s3://<バケット名> --region ap-northeast-1

ローカル環境の作成

Nginx アクセスログを転送するためにFluentdを利用していきます。

FluentdにてS3へログ転送する為に独自のコンテナイメージを作成していきます。

作業ディレクトリを作成します。

mkdir athena-fluentd-nginx
cd athena-fluentd-nginx

Fluentd 用のディレクトリを作成します。

mkdir -p fluentd/etc

Dockerfileをfluend配下へ作成します。

FROM fluent/fluentd:v1.12.0-debian-1.0
USER root
RUN ["gem", "install", "fluent-plugin-s3", "--no-document"]
RUN ["gem", "install", "fluent-plugin-rewrite-tag-filter", "--no-document"]

ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG AWS_DEFAULT_REGION
ARG S3_BUCKET_NAME
ARG S3_PREFIX

ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
ENV AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
ENV S3_BUCKET_NAME=${S3_BUCKET_NAME}
ENV S3_PREFIX=${S3_PREFIX}

USER fluent

Fluentdの設定ファイルをfluentd/etc/fluent.confへ作成します。

<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match nginx>
  @type rewrite_tag_filter
  <rule>
    key source
    pattern /^stdout$/
    tag nginx.access_log
  </rule>
  <rule>
    key source
    pattern /^stderr$/
    tag nginx.error_log
  </rule>
</match>

<match nginx.access_log>
  @type copy
  # S3
  <store>
  @type s3
  s3_region "#{ENV['AWS_DEFAULT_REGION']}"
  s3_bucket "#{ENV['S3_BUCKET_NAME']}"
  path "#{ENV['S3_PREFIX']}/nginx/access_log/dt=%Y%m%d/"
  <format>
  @type single_value
  message_key log
  </format>
  <buffer tag,time>
    @type file
    flush_interval 60s
    path /tmp/s3/access_log
    timekey 1m
    timekey_wait 1m
    timekey_zone Asia/Tokyo
  </buffer>
  </store>

  # Debug
  <store>
  @type stdout
  <format>
  @type single_value
  message_key log
  </format>
  </store>
</match>

<match nginx.error_log>
  @type copy
  # S3
  <store>
  @type s3
  s3_region "#{ENV['AWS_DEFAULT_REGION']}"
  s3_bucket "#{ENV['S3_BUCKET_NAME']}"
  path "#{ENV['S3_PREFIX']}/nginx/error_log/dt=%Y%m%d/"
  <format>
  @type single_value
  message_key log
  </format>
  <buffer tag,time>
    @type file
    flush_interval 60s
    path /tmp/s3/error_log
    timekey 1m
    timekey_wait 1m
    timekey_zone Asia/Tokyo
  </buffer>
  </store>

  # Debug
  <store>
  @type stdout
  <format>
  @type single_value
  message_key log
  </format>
  </store>
</match>

下記のdocker-compose.ymlを作成します。

version: "3"
services:
  fluentd:
    build: ./fluentd
    image: fluentd-custom:v1.12.0
    container_name: fluentd
    environment:
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
      - S3_PREFIX=${S3_PREFIX}
      - S3_BUCKET_NAME=${S3_BUCKET_NAME}
    volumes:
      - ./fluentd/etc:/fluentd/etc
    ports:
      - "24224:24224"
      - "24224:24224/udp"

  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
        - '8080:80'
    logging:
      driver: fluentd
      options:
        fluentd-address: "localhost:24224"
        tag: nginx
    depends_on:
        - fluentd

各ファイル作成後、下記のような構成になっているはずです。

.
├── docker-compose.yml
└── fluentd
    ├── Dockerfile
    └── etc
        └── fluent.conf

Nginx アクセスログの転送確認

Compose ファイルで環境変数を参照させる為に、.envファイルを用意します。

AWS_ACCESS_KEY_ID=<AWS アクセスキー>
AWS_SECRET_ACCESS_KEY=<AWS シークレットキー>
AWS_DEFAULT_REGION=ap-northeast-1
S3_BUCKET_NAME=<S3 バケット名>
S3_PREFIX=logs

コンテナを起動します。

docker-compose up -d

Nginxへアクセスします。

for i in `seq 1 10`;
do
curl -s http://localhost:8080/index.html -o /dev/null -w '%{http_code}\n'
sleep 1
done

作成したS3バケットへログが転送されていることを確認します。

aws s3 ls s3://<S3 バケット名>/logs/nginx/access_log/ --recursive

下記のようにdt=yyyymmdd形式でパーティション分割されていることが確認できるはずです。

2021-09-05 23:37:02        114 logs/nginx/access_log/dt=20210905/202109052335_0.gz
2021-09-05 23:38:02        125 logs/nginx/access_log/dt=20210905/202109052336_0.gz
2021-09-05 23:40:03        176 logs/nginx/access_log/dt=20210905/202109052338_0.gz

Partition Projectionを利用したテーブル作成

Athena コンソールへアクセスし、クエリエディタを使用しデータベースを作成します。

クエリエディタに下記を入力し、[Run Query]をクリックします。

CREATE DATABASE IF NOT EXISTS nginx;

次にPartition Projectionを利用したテーブルを作成していきます。

Partition Projectionの設定はTBLPROPERTIES句に設定していきます。

クエリエディタに下記を入力し、[Run Query]をクリックします。

LOCATION句で指定するS3バケット名は適宜指定してください。

CREATE EXTERNAL TABLE IF NOT EXISTS nginx.access_log (
  `clientip` string COMMENT 'from deserializer',
  `ident` string COMMENT 'from deserializer',
  `auth` string COMMENT 'from deserializer',
  `timestamp` string COMMENT 'from deserializer',
  `verb` string COMMENT 'from deserializer',
  `request` string COMMENT 'from deserializer',
  `httpversion` string COMMENT 'from deserializer',
  `response` string COMMENT 'from deserializer',
  `bytes` string COMMENT 'from deserializer',
  `referrer` string COMMENT 'from deserializer',
  `agent` string COMMENT 'from deserializer',
  `rawrequest` string COMMENT 'from deserializer')
PARTITIONED BY (
  `dt` string)
ROW FORMAT SERDE
  'com.amazonaws.glue.serde.GrokSerDe'
WITH SERDEPROPERTIES (
  'input.format'='%{COMBINEDAPACHELOG}')
STORED AS INPUTFORMAT
  'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://<S3 バケット名>/logs/nginx/access_log'
TBLPROPERTIES (
  'compressionType'='gzip',
  'projection.dt.format'='yyyyMMdd',
  'projection.dt.range'='NOW-5YEARS,NOW',
  'projection.dt.type'='date',
  'projection.enabled'='true')

下記のようなテーブルが作成されていれば成功です。

テーブル作成後、パーティション追加する必要なくテーブルに対してクエリ実行できるはずです。

SELECT * FROM nginx.access_log
WHERE dt > '20210901'

さいごに

Athena Partition Projectionを使用しNginx アクセスログをクエリする方法いかがでしたでしょうか?

Partition Projectionを利用することで、Athenaに対するクエリ実行時パーティション追加等を意識する必要がなくなるのは大きなメリットではないでしょうか。

Athena Partition Projectionを利用する際、本記事が参考になれば幸いでございます。

最後までご覧いただきありがとうございます。

SNSでもご購読できます。