はじめに
こんにちは、はやぴー(@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(ALB,NLB,CLB)
今回は、ALBを例に記していきます。
ALBを作成し、
Edit load balancer attributes -> Monitoringから、
Access Logsを有効化します。(事前にS3バケットを作成する必要あり)
S3
アクセスログが出力している事を確認します。
S3イベント通知を設定します。(事前にLambda関数を作成する必要あり)
Lambda
言語はPythonを使用し、boto3を利用しています。
下記のようなコードになりました。(動作は保証しません)
ロググループを、先に作成する必要があるので注意です。
(IaCなどで、先にリソースを作る事をお勧めします)
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アクセスログが
出力されています。
まとめ
いかがだったでしょうか?
CloudWatch Logsのコンソールは、見やすいので
実は需要があるのでは・・?と思い投稿してみました。
コストに関しても、数日で削除すれば気にならないと思います。
是非、活用してみてください!