LambdaとCloudWatch Logsを使ったジョブの遅延監視を考えてみる

LambdaとCloudWatch Logsを使ったジョブの遅延監視を考えてみる
この記事をシェアする

こんにちは。スカイアーチHRソリューションズのきむです。

LambdaとCloudWatch Logsを使ったジョブの遅延監視についてご紹介させていただきます。

今回作成する構成は以下のようになります。

今回の監視対象となるのはEventBridegeにより日時で実行されるジョブ実行用Lambdaです。

遅延監視用Lambdaを使ってジョブ実行用LambdaのCloudWatch Logsからジョブの実行開始を示すメッセージを探し、対象のログが見つからない場合はジョブの実行遅延とし、SNSで通知を飛ばします。

今回は遅延監視の機能を確認したいため、EventBridegeについては割愛し赤枠の中のリソースを作成していきます。

コードの作成

今回の実行環境はPython3.9です。

ジョブ実行用Lambda

今回はテスト用なのでログだけを出力させる簡易的なコードにしています。

def lambda_handler(event, context):
    
    print("Hello World Job Start")
    print("Hello World")
    print("Hello World Job End")
    
    return {"statusCode": 200}

遅延監視用Lambda

import json
import os
from datetime import datetime

import boto3
import dateutil

logs = boto3.client("logs")
sns = boto3.client("sns")

# タイムゾーンの設定
UTC = dateutil.tz.gettz("UTC")
JST = dateutil.tz.gettz("Asia/Tokyo")

# 変数設定
start_hour = HH
start_minute = MM
end_hour = HH
end_minute = MM
log_group_name = os.environ.get("log_group_name")
filter_pattern = "Hello World Job Start"
topic_arn = os.environ.get("topic_arn")


def lambda_handler(event, context):
    # 監視時刻の設定
    today = datetime.now(JST)

    start = datetime(
        today.year,
        today.month,
        today.day,
        start_hour,
        start_minute,
        0,
        0,
        JST,
    )
    end = datetime(
        today.year,
        today.month,
        today.day,
        end_hour,
        end_minute,
        0,
        0,
        JST,
    )

    # UNIXタイムスタンプに変換
    start_timestamp = int(start.timestamp() * 1000)
    end_timestamp = int(end.timestamp() * 1000)

    log_stream_name = [log_stream["logStreams"][0]["logStreamName"]]

    log_event = logs.filter_log_events(
        logGroupName=log_group_name,
        logStreamNames=log_stream_name,
        startTime=start_timestamp,
        endTime=end_timestamp,
        filterPattern=filter_pattern,
    )

    # 対象のログが存在する場合は正常終了させる。
    if log_event["events"]:
        return {"statusCode": 200}

    # 対象のログが存在しない場合はSNSによる通知を行う。
    else:
        sns_body = {
            "default": f'Time: {datetime.now(JST).strftime("%Y-%m-%d %H:%M:%S")} \n'
            f"LogGroup: {log_group_name} \n"
            "Messages: ジョブが実行されていません \n"
        }

        publish = sns.publish(
            TopicArn=topic_arn,
            Message=json.dumps(sns_body, ensure_ascii=False),
            Subject="ログ遅延監視",
            MessageStructure="json",
        )

        return

以下の変数には監視時刻としたいと時間と分をそれぞれ指定してください。

start_hour = HH
start_minute = MM
end_hour = HH
end_minute = MM

SAMテンプレートの作成

SAMを使ってAWSリソースを作成していきます。

今回作成するリソースは以下の通りです。

  • ジョブ実行用LambdaのIAMロール
  • 遅延監視用LambdaのIAMロール
  • ジョブ実行用LambdaのCloudWatchロググループ
  • 通知用SNS
  • ジョブ実行用Lambda(HelloWorldJobLambda)
  • 遅延監視用Lambda(DelayMonitorLambda)
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Parameters:
  IamRoleName1:
    Type: String
    Default: JobLambdaRole

  IamRoleName2:
    Type: String
    Default: DelayMonitorLambdaRole

  IamPolicyName1:
    Type: String
    Default: DelayMonitorLambdaPolicy

  LambdaFunctionName1:
    Type: String
    Default: HelloWorldJobLambda

  LambdaFunctionName2:
    Type: String
    Default: DelayMonitorLambda

  SNSTopicName1:
    Type: String
    Default: DelayMonitorTopic

Resources:
  IamRole1:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref IamRoleName1
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  IamRole2:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref IamRoleName2
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole

  IamPolicy2:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Ref IamPolicyName1
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "logs:*"
            Resource: "*"
          - Effect: "Allow"
            Action: "sns:*"
            Resource: "*"
      Roles:
        - !Ref IamRole2

  LogGroup1:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${LambdaFunctionName1}"
      RetentionInDays: 3

  SNSTopic1:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: "通知を飛ばしたいEメールアドレス"
          Protocol: email
      TopicName: !Ref SNSTopicName1

  Lambda1:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref LambdaFunctionName1
      Description: "ジョブ実行用"
      CodeUri: ./functions/job/
      Handler: app.lambda_handler
      Runtime: python3.9
      MemorySize: 256
      Role: !GetAtt IamRole1.Arn
      Timeout: 10
      Architectures:
        - x86_64

  Lambda2:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref LambdaFunctionName2
      Description: "遅延監視用"
      CodeUri: ./functions/monitor/
      Handler: app.lambda_handler
      Runtime: python3.9
      MemorySize: 256
      Role: !GetAtt IamRole2.Arn
      Timeout: 300
      Architectures:
        - x86_64
      Environment:
        Variables:
          log_group_name: !Ref LogGroup1
          topic_arn: !GetAtt SNSTopic1.TopicArn

ジョブ実行用Lambdaにはマネージドポリシーである「AWSLambdaBasicExecutionRole」をアタッチしたロールを使用しています。

遅延監視用LambdaにはCloudWatch LogsやSNSの操作が必要になるので、両リソースのフルアクセス権限を付与しています。

遅延監視用Lambdaのコード内でロググループ名とSNSトピックのArnをOS環境変数から取得するようにしているのでSAMテンプレート内で設定をしています。

テスト実行

それではテストを行っていきます。

まずはHelloWorldJobLambdaを実行しログを出力させます。

21:00:53に監視対象のログが出力されました。

次にDelayMonitorLambdaを実行します。

ログイベントの検索期間を21:00-21:05としました。

対象期間内にログが出力されているため正常に終了しました。

次はログイベントの検索期間を21:01-21:05とし、SNS通知が飛んでくるかを確認します。

対象期間内に監視対象のログが存在しない場合は、戻り値なしとしているためレスポンスがnullとなっています。

通知先に設定したメールの受信ボックスを確認してみましょう。

遅延監視を知らせるSNS通知も届きました。

おわりに

お疲れさまでした。

メトリクスフィルターやサブスクリプションフィルターで特定のログが出力された場合の監視は行えますが、

ログが出力されていない場合の監視について頭を悩ませていました。

今回はAWSのリソースだけで完結する監視の仕組みを紹介させていただきました。

この記事が少しでも皆様のご参考になれば幸いです。

この記事をシェアする
著者:きむ
サウナとフェスに生かされているエンジニア。