blog

awslogsでEC2のログをCloudWatch連携する方法

yokoyama
yokoyama
2018年10月15日

こんにちは、エンジニアのよこやまです。

EC2インスタンス内のログをawslogsでCloudWatch連携する方法について紹介します。

<やりたいこと>

  • ログをCloudWatchに送信する
  • CloudWatchでログを監視してSlackやメールでアラートする
  • 日々溜まっていくCloudWatchログを定期的にS3にエクスポートする

awslogs_image.png

ログをCloudWatchに送信する

Amazon LinuxのログのCloudWatch連携は、「awslogs」を使うと簡単に実現できます。

以下のサイトを参考に、非常に簡単に主要なログをCloudWatch連携できました。

https://qiita.com/zaburo/items/57bf357065b7391e1a9d

まず、EC2にCloudWatchログ出力可能なロールを関連付けします。リファレンスを参考に、必要なポリシーを設定します。

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": [
"arn:aws:logs:*:*:*"
]
}
]
}
view raw gistfile1.txt hosted with ❤ by GitHub

ロールの関連付けをしたあとは、awslogsのインストール、confファイル修正、ログ出力の定義を設定してサービス起動して終了です。設定手順は以下の通りです。

$ sudo yum install awslogs
$ cd /etc/awslogs/
$ sudo vi awscli.conf
$ sudo vi awslogs.conf
$ sudo service awslogs start
$ sudo chkconfig awslogs on
view raw gistfile1.txt hosted with ❤ by GitHub

※ 3行目の「awscli.conf」は、"region"を

region = us-east-1
↓
region = ap-northeast-1

に変更します。

※ 4行目の「awslogs.conf」は、以下のように取得したいログファイルの定義を追加します。

[HttpdAccessLog]
datetime_format = %d/%b/%Y:%H/%M/%S
file = /path/to/access_log.*
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /HttpdAccessLog

[/var/log/messages]
datetime_format = %b %d %H:%M:%S
file = /var/log/messages
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /var/log/messages

[/var/log/mysqld.log]
datetime_format = %Y-%m-%dT%H:%M:%S%z
file = /var/log/mysqld.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /var/log/mysqld.log

[/var/log/supervisord.log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/supervisord.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /var/log/supervisord.log

[/var/log/audit/audit.log]
file = /var/log/audit/audit.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /var/log/audit/audit.log

※awslogs.confの"file"にはログファイルのパスを記述します。

※awslogs.confの"datetime_format"は、対象ログから時刻を抽出する際に設定します。

日付書式設定については、リファレンスに記載されています。

正しく設定ができていれば、CloudWatch上にログが出力されます。

awslogs1.png

CloudWatchでログを監視してSlackやメールでアラートする

CloudWatchログでは、指定されたものに一致する語句や値をログから検索できる「メトリクスフィルタ」と、フィルタ結果を基にアラート通知させる「アラーム」を設定することができます。

フィルタ設定内容はログによって様々なので、本記事では具体的な手順は記載しませんが、参考にさせていただいた記事を載せておきます。

・Nginxの500系エラー検知:https://remotestance.com/blog/2433/

・フィルタとパターン構文:https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html

アラーム設定時に"アクション"で通知先のSNSトピックを設定することで、アラートのメール通知ができます。

通知は、アラームの状態が変化する毎に通知されます。「警告(Alert)」「OK(OK)」それぞれ通知を設定しておくと、警告〜警告の解消が把握できるので便利です。

アラートのSlack通知は、Lambdaに標準で提供されているSlack通知用のBlueprint(cloudwatch-alarm-to-slack-python3)を使うと簡単に実現できます。

・参考:http://blog.serverworks.co.jp/tech/2016/02/16/lambda-cloudwatch-alarm-to-slack/

このような感じでアラートがSlack通知されます(文面はBlueprintの"slack_message"をカスタマイズします)。

awslogs_slack.png

CloudWatchログを定期的にS3にエクスポートする

Amazon LinuxのログをCloudWatchに出力して監視もできましたが、このままではCloudWatch上に延々とログが蓄積されてしまうので、自動でS3にエクスポートしてCloudWatchログは一定期間のみ保持するようにします。

S3へのエクスポートはLambdaを使って自動化できますが、CloudWatchのCreateExportTaskは複数同時に実行できないため、1つエクスポートが終わってから次...と実行していく必要があります。

そのため、今回はStepFunctionsLambdaを組み合わせて実現します。

まずは、ログをエクスポートするS3バケットを作成します。

また、作成したS3バケットに以下のようにバケットボリシーを設定しておきます。

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "logs.ap-northeast-1.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::(バケット名)"
},
{
"Effect": "Allow",
"Principal": {
"Service": "logs.ap-northeast-1.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::(バケット名)/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
view raw gistfile1.txt hosted with ❤ by GitHub

次に、作成したS3バケットにCloudWatchログをエクスポートするLambda関数を作成します。

Lambdaは以下の通り作成しました(python3.6)。

import logging
import boto3
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from dateutil.parser import parse
import time
import os
def _is_executing_export_tasks():
'''
CloudWatch LogsのS3エクスポートタスク実行状態を判定する.
'''
client = boto3.client('logs')
for status in ['PENDING', 'PENDING_CANCEL', 'RUNNING']:
response = client.describe_export_tasks(
limit = 50,
statusCode=status
)
if 'exportTasks' in response and len(response['exportTasks']) > 0:
return True
return False
def _get_target_date(event):
'''
CloudWatch Eventsの(実行日時 - 1)日をエクスポート対象にする
'''
target = None
JST = timezone(timedelta(hours=+9), 'JST')
utc_dt = datetime.strptime(event['time'], '%Y-%m-%dT%H:%M:%SZ')
jst_time = utc_dt.astimezone(JST)
target = jst_time - timedelta(days=1)
t = target.replace(hour=0, minute=0, second=0, microsecond=0)
target_date = t.strftime('%Y%m%d')
from_time = int(t.timestamp() * 1000)
to_time = int((t + timedelta(days=1) - timedelta(milliseconds=1)).timestamp() * 1000)
return from_time, to_time, target_date
def _get_log_group(next_token):
client = boto3.client('logs')
if next_token is not None and next_token != '':
response = client.describe_log_groups(
limit = 50,
nextToken = next_token
)
else:
# nextTokenは空文字を受け付けない
response = client.describe_log_groups(limit = 50)
if 'logGroups' in response:
yield from response['logGroups']
# ロググループが多くて50件(最大)を超えるようなら再帰呼出し
if 'nextToken' in response:
yield from _get_log_group(next_token = response['nextToken'])
def _is_bucket_object_exists(bucket_name, bucket_dir):
client = boto3.client('s3')
response = client.list_objects_v2(
Bucket = bucket_name,
Prefix = (bucket_dir)
)
return 'Contents' in response and len(response['Contents']) > 0
def _export_logs_to_s3(bucket_name, bucket_dir, from_time, to_time, log_group_name):
client = boto3.client('logs')
response = client.create_export_task(
taskName = bucket_dir,
logGroupName = log_group_name,
fromTime = from_time,
to = to_time,
destination = bucket_name,
destinationPrefix = bucket_dir
)
def lambda_handler(event, context):
bucket_name = os.environ['BUCKET_NAME']
from_time, to_time, target_date = _get_target_date(event=event)
# export_taskは複数同時実行できないため、他のタスクが実行中の場合はreturnする
if _is_executing_export_tasks():
return {
"status": "running",
"time": event['time']
}
for log_group in _get_log_group(next_token=None):
bucket_dir_prefix = target_date + '/'
bucket_dir = bucket_dir_prefix + log_group['logGroupName']
if log_group['logGroupName'].find('/') == 0:
bucket_dir = bucket_dir_prefix + log_group['logGroupName'][1:]
if _is_bucket_object_exists(bucket_name = bucket_name, bucket_dir = bucket_dir):
continue
# ログエクスポート処理を実行
_export_logs_to_s3(
bucket_name = bucket_name,
log_group_name = log_group['logGroupName'],
from_time= from_time,
to_time = to_time,
bucket_dir = bucket_dir
)
return {
"status": "running",
"time": event['time']
}
return {
"status": "completed",
"time": event['time']
}
view raw lambda_function.py hosted with ❤ by GitHub

・参考:https://www.soudegesu.com/aws/export-cloudwatchlogs-to-s3/

なお、Lambdaには以下のIAMロールのポリシーを設定しました。

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": [
"arn:aws:logs:*:*:*"
]
}
]
}
view raw gistfile1.txt hosted with ❤ by GitHub

次に、ロググループ毎に上記Lambda関数を実行するStepFunctionsステートマシンを定義します。

ステートマシンでは、ロググループ毎にcreate export taskを行い、タスクが処理中の場合は数秒待ってリトライを行います。

こちらは、上記参考サイトの手順そのままに実装して実現できました。

あとは、CloudWatchの「ルール>ルールの作成」で、

  • イベントソース:任意のパターン、スケジュール
  • ターゲット:Step Functions ステートマシン
    • ステートマシン:上記で作成したステートマシン

を設定すると、自動でCloudWatchログが定期的にS3エクスポートされます。

awslogs2.png

また、S3に過去ログを退避させることで、CloudWatchのログ保持期間やS3のライフサイクルルールを設定して、

  • CloudWatchログは1週間保持
  • S3にはログを3ヶ月保持

といった管理ができます。

以上が、CloudWatchへのログ出力からログ監視、S3へのエクスポートまでの連携方法でした。

いやぁ、ほんと便利ですね。

yokoyama
yokoyama
2018/10/15
次の記事

ページトップへ

お電話でのお問い合わせ

TEL:03-6805-3221
受付:平日9:30~18:30

インターネットでのお問い合わせ

お仕事のご依頼・ご相談など

営業・人材紹介など

© 2016 COLSIS inc.