7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ELB(ALB,NLB,CLB)アクセスログを、CloudWatch Logsに出力させてみた(Lambda)

Posted at

はじめに

こんにちは、はやぴー(@HayaP)です。
日々のログ監視、皆さんはどうされていますか?

  • とりあえず、ログをS3に出力している
  • CloudWatchで見たいな、、と思いつつ諦めている

なんて人は、少なくないはずです。

そこで、今回は
ELBアクセスログを、CloudWatch Logsに出力した
経験を記したいと思います。

対象読者

  • ログ監視設計者
  • CloudWatch Logsに、ログを集めたい
  • 毎回S3に、ログを見に行くのがしんどい

この記事が、クライドネイティブな運用の参考になれればと願っています。

概要

ELB(ALB,NLB,CLB)アクセスログとは

ロードバランサーに送信されるリクエストの、詳細情報が記されているログです。

  • リクエストを受け取った時刻
  • クライアントの IP アドレス
  • サーバーレスポンス
    などの情報が含まれます。

要件

ELB(ALB,NLB,CLB)アクセスログを、CloudWatch Logsに出力することです。

問題

残念ながら、ELB(ALB,NLB,CLB)アクセスログは
S3にしか出力することができません。

Amazon Athenaなどを使う事で、ある程度検索はできます。
しかし、使い慣れたCloudWatch Logsのコンソールを使いたいところです。

解決策

そのため、Amazon S3 イベント通知 + Lambda(CloudWatch API)を用いる事で
解決をしました。
ELB_Access_log_to_CWL.drawio.png

では、具体的な方法を記していきます。

詳細

リソース毎に説明をします。順序ではないので、注意してください。

ELB(ALB,NLB,CLB)

今回は、ALBを例に記していきます。

ALBを作成し、
Edit load balancer attributes -> Monitoringから、
Access Logsを有効化します。(事前にS3バケットを作成する必要あり)

MicrosoftTeams-image (12).png

S3

アクセスログが出力している事を確認します。
MicrosoftTeams-image (13).png
S3イベント通知を設定します。(事前にLambda関数を作成する必要あり)
MicrosoftTeams-image (14).png

Lambda

言語はPythonを使用し、boto3を利用しています。
下記のようなコードになりました。(動作は保証しません)

ロググループを、先に作成する必要があるので注意です。
(IaCなどで、先にリソースを作る事をお勧めします)

sample.py
import boto3
import time
import json
import io
import gzip

s3_boto3 = boto3.client('s3')
cw_boto3 = boto3.client('logs')

LOG_GROUP_NAME = "hoge"

def parse_event(event):
    return {
        "bucket_name": event["Records"][0]["s3"]["bucket"]["name"],
        "bucket_key": event["Records"][0]["s3"]["object"]["key"]
    }

def convert_response(response):
    body = response['Body'].read()
    test = gzip.open(io.BytesIO(body))
    return test.read().decode('utf-8')

def put_log(log, put_log_count, sequence_token, log_stream_name):
    try:
        if put_log_count == 1 and sequence_token == None:
            response = cw_boto3.put_log_events(
                logGroupName=LOG_GROUP_NAME,
                logStreamName=log_stream_name,
                logEvents=[
                    {
                        'timestamp': int(time.time()) * 1000,
                        'message': log
                    },
                ]
            )
            return response

        elif sequence_token != None and put_log_count != 3:
            response = cw_boto3.put_log_events(
                logGroupName=LOG_GROUP_NAME,
                logStreamName=log_stream_name,
                logEvents=[
                    {
                        'timestamp': int(time.time()) * 1000,
                        'message': log
                    },
                ],
                sequenceToken=sequence_token
            )
            return response
        else:
            raise Exception
    except cw_boto3.exceptions.InvalidSequenceTokenException as e:
        sequence_token = e.response.get('expectedSequenceToken')
        put_log_count += 1
        response = put_log(log, put_log_count, sequence_token, log_stream_name)
        return response

    except cw_boto3.exceptions.ResourceNotFoundException as e:
        cw_boto3.create_log_stream(
            logGroupName=LOG_GROUP_NAME, logStreamName=log_stream_name)
        response = put_log(log, put_log_count, sequence_token, log_stream_name)
        return response
    except Exception as e:
        print("error", e)
        raise

def lambda_handler(event, context):
    bucket_info_dictionary = parse_event(event)
    response = s3_boto3.get_object(
        Bucket=bucket_info_dictionary["bucket_name"], Key=bucket_info_dictionary["bucket_key"])
    log_stream_name = bucket_info_dictionary["bucket_key"].split(".")[1]
    log_text = convert_response(response)
    log_list = log_text.splitlines()
    sequence_token = None
    for log in log_list:
        put_log_count = 1
        response = put_log(log, put_log_count,
                            sequence_token, log_stream_name)
        sequence_token = response["nextSequenceToken"]
    return {
        "statusCode": 200
    }

ポイントは、CloudWatch APIを使用する際に必要な
sequence_tokenです。概念を含め理解をしておくと、デバック時に役に立ちます。

今回のコードでは、一回目に故意にエラーを発生させることで
sequence_tokenを取得しています。

下記記事を参考にすると、理解が深まります。

CloudWatch Logs

指定したロググループ、ログストリームにALBアクセスログが
出力されています。
MicrosoftTeams-image (15).png

まとめ

いかがだったでしょうか?

CloudWatch Logsのコンソールは、見やすいので
実は需要があるのでは・・?と思い投稿してみました。

コストに関しても、数日で削除すれば気にならないと思います。

是非、活用してみてください!

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?