困っていたこと
ログの収集と分析のためにAmazon Kinesis Data Firehoseを使用してS3バケットにストリーム形式で流し込むというデザインパターンは半ば定番のようになっていて、お仕事で普通に使用しています。
EC2インスタンス上でKinesis Data Firehoseを使用するためには、Kinesis Agentをインストールして必要なログをKinesis Data Firehose経由でS3バケットに流し込んでいるんですが、どうもこのKinesis Agentのプロセスが、流そうとしているログファイルを掴みっぱなしになって、その結果、EBSボリュームがストレージフルになり、EC2インスタンスをいちいち停止 & 起動しなければいけないという課題に遭遇していました。
ワークアラウンドとして、Amazon CloudWatchのメトリクス監視の中に、EBSボリュームの状態を監視するメトリクスを追加して、閾値を超えたらEC2インスタンスを手動で停止 & 起動するというワークアラウンドまでは確立したのですが、それだとちょっとなぁということで、何かいい方法はないかと考えていたところ、「AWS Lambda で CloudWatch アラームからの障害対応自動化」という記事を発見して、これを少しカスタマイズすれば、いちいちEC2インスタンスを停止 & 起動しなくても、Akinesis Agentのプロセス再起動だけで回避できるんじゃないの? という結論に達し、Lambda関数を少しカスタマイズさせてもらいました。@quickguardさん、ありがとうございます。
事前準備とLambda関数
事前準備は基本的に、引用元の記事とほとんど同じです。もちろんEC2インスタンスにはKinesis Agentがインストールされていることが前提です。もしインストールされていなければ、以下のコマンドでインストールします。ちなみに環境はAmazon Linux2です。
sudo yum install –y aws-kinesis-agent
EC2インスタンスはGravitonを使用しているので、SSM Agentをインストールするコマンドはこちら。
sudo yum install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_arm64/amazon-ssm-agent.rpm
ここまでできたら、EC2インスタンスに対してSSM Agentが使用できるようにIAMロールを設定してあげて、Lambda関数を実装します。Lambda関数の実装に関しては前掲の記事通りです。
カスタマイズしたLambda関数はこんな感じです。っていうか結果を返却する処理、例外処理と、実行するコマンドが違う以外はほとんど実装内容変わっていません。ごめんなさい。。。
import boto3
import json
import logging
import traceback
import datetime
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
ssm = boto3.client('ssm')
logger.info("Event: " + str(event))
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Event: " + event['Records'][0]['Sns']['Message'])
instanceId = message['Trigger']['Dimentions'][0]['value']
alarmName = message['AlarmName']
newStateValue = message['NewStateValue']
newStateReason = message['NewStateReason']
logger.info("%s - %s state is now %s: %s" & (instanceId, alarmName, newStateValue, newStateReason))
try:
ssm.send_command(
instanceIds = [
instanceId
],
DocumentName = "AWS-RunShellScript",
Parameters = {
"commands": [
"service aws-kinesis-agent restart"
],
"executionTimeout": [
"3600"
]
}
)
return {
"statusCode": 200,
"message": 'restarting Kinesis Agent: %s' & datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9)))
}
except Exception as e:
logger.error(e)
logger.error(traceback.format_exc())
return {
"statusCode": 500,
"message": 'An error occured at restarting Kinesis Agent process.'
}
根本的な解決のためには
これでワークアラウンドから一歩進んで、もしもCloudWatchでEBSボリュームのストレージ使用率の閾値が上がった場合は自動的にKinesis Agentのプロセスを再起動するという自動化は達成できた感じなんですけれども、そもそもKinesis Agentがファイルを掴みっぱなしにするという根本的な問題が解決されていないというところがなんとも。
Apache Log4j2の問題が収まった段階で、このLambda関数のお役目も終了になってくれるといいなぁと願っています。