CloudWatchLogsのサブスクリプションフィルターで、エラーログをフィルタリングして、Lambda関数経由でSNSメール通知する仕組みを構築してみた!

AWS

はじめに

今回は、CloudWatchLogsで収集したログからエラーログをサブスクリプションフィルターでフィルタリングして、 Lambda関数を起動しSNSでメール発報する仕組みを構築していきます。

CloudWatchは、AWSサービスに限らず様々なリソースのログを収集できます。ですので、作成したサービスに合わせて、ログ監視をこの仕組みに切り出して使用することもできるかと思います。

また、Lambdaを介しているため、必要に応じてLambdaのコード内でエラーログを詳細にフィルタリングすることができます。

構成図

何らかのシステムのログをCloudWatchLogsのロググループに出力し、ロググループにサブスクリプションフィルターを設定します。ERRORなどの文字列でフィルタリングされたログイベントがLambdaに送信されてSNSトピックにメール送信をさせます。

事前準備

必要なリソースを準備しておきます。

擬似エラーログを発生させるCloudWatchロググループの作成

CloudWatchロググループは、CloudWatchが収集したログをグルーピングしておける機能です。ロググループという単位のフォルダを作成して、ログを溜め込んでおけるようなイメージです。

  • ロググループの作成を選択
  • ロググループ名は、sample-loggroup-001としておきます。
    • 別名で作成される際は、CloudFormationスタック作成時に、Defaultの値を書き換えるようにしてください。
  • 保持期間は、7日にします。
  • KMSkeyの指定は空白のまま作成を選択します。

SNSを作成してメールアドレスを設定

SNSはメール通知を初めとした様々な通知機能をサポートするサービスです。トピックとサブスクリプションという単位で通知対象を設定していきます。今回は、Eメール通知用のアドレスを設定します。

  • トピックの作成を選択
  • タイプは、スタンダードを選択
  • その他の設定は変更せずトピックの作成を選択
  • サブスクリプションの作成を選択
  • プロトコルはEメールを選択
  • エンドポイントにメールを受け取りたいEメールアドレスを入力してサブスクリプションの作成を選択
  • 下記のようなメールがSNSから届くのでConfirm subscriptionを選択することでメールアドレスの設定が完了する

CloudFormationで構築

CloudFormationテンプレートは以下にございます。
ダウンロードしてデスクトップなど任意のフォルダに保存します。
※その他のファイルも後述の手順で使用するためダウンロードしておきます

GitHub - kosments/lambda-by-subscriptionfilter-for-log-alert
Contribute to kosments/lambda-by-subscriptionfilter-for-log-alert development by creating an account on GitHub.

CloudFormation:YAMLテンプレートのポイント

YAMLテンプレートのポイントを記述します。

  • Lambda
    • SNS通知を実行する Lambda関数を作成しています。
    • Propertiesで必要なパラメータを指定しています。
      • 特に、RuntimeやHandlerは必須項目となるため、必ず指定します。
      • また、Hndlerは、コード内で定義する関数名に一致していないと正しく動作しませんので注意が必要です。
    • Environmentで環境変数を設定しています。
      • 環境やアカウント、リージョンで固有の値は、ハードコーディングせず、OutputsParameters、あるいは、別途jsonを用意して、参照する形にします。
  • Lambda IAM Role
    • Lambdaに付与するロールとそのポリシーの作成と紐付けをしています。
      • GUI作成時には、自動作成されますが、CloudWatchLogsロググループへのログストリームとログイベント作成権限を与えています。
        • リソースも対象のロググループに絞っています。
      • boto3でSNS通知したいためSNSへのアクセス権限も付与しています。
  • CloudWatchLogs subscriptionfilter
    • ログ監視対象となるロググループに対して設定しています。
    • FilterPatternにてParametersから受け取ったフィルタリングしたい文字列を指定しています。
Resources:
  # ------------------------------ #
  # Lambda
  # ------------------------------ #
  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${Env}-lambda-${ResourceName}"
      Architectures: 
      - "arm64"
      MemorySize: 128
      Role: !GetAtt LambdaRole.Arn
      Runtime: !Ref Runtime
      Handler: !Sub "${Env}-lambda-${ResourceName}.lambda_handler"
      Timeout: 60
      Environment:
        Variables:
          SNS_TOPIC_ARN: !Ref SnsTopicArn
      Code:
        S3Bucket: !ImportValue S3BucketNameForLambdaCode
        S3Key: !Sub "${Env}-lambda-${ResourceName}.zip"
  # ------------------------------ #
  # Lambda IAM Role
  # ------------------------------ #
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${Env}-role-lambda-${ResourceName}"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
            - sts:AssumeRole
      Policies:
        - PolicyName: !Sub "${Env}-policy-LambdaBasicExecutionRole-${ResourceName}"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Sid: CreateLogGroup
              Effect: Allow
              Action: logs:CreateLogGroup
              Resource: !Sub "arn:aws:logs:ap-northeast-1:${AccountId}:*"
            - Sid: PutLogEvents
              Effect: Allow
              Action: 
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource: !Sub "arn:aws:logs:ap-northeast-1:${AccountId}:log-group:/aws/lambda/${Env}-lambda-${ResourceName}:*"
        - PolicyName: !Sub "${Env}-policy-snspublish-${ResourceName}"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Sid: Publish
              Effect: Allow
              Action: sns:Publish
              Resource: !Ref SnsTopicArn
  # ------------------------------ #
  # CloudWatchLogs SubscriptionFilter
  # ------------------------------ #
  CloudWatchLogsSubscriptionFilter:
    Type: AWS::Logs::SubscriptionFilter
    Properties:
      FilterName: !Sub "${Env}-subscriptionfilter-${ResourceName}"
      LogGroupName: !Ref LogGroupName
      FilterPattern: !Ref FilterPattern
      DestinationArn: !GetAtt Lambda.Arn
  # ----- Permission to Lambda ----- #
  PermissionToLambda:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt Lambda.Arn
      SourceArn: !Sub "arn:aws:logs:ap-northeast-1:${AccountId}:log-group:${LogGroupName}:*"
      Principal: "logs.ap-northeast-1.amazonaws.com"
      Action: "lambda:InvokeFunction"

PythonファイルをS3にアップロード

zip化したpythonコードをS3にアップロードしておきましょう。
詳細は、以下を参考にしてください。

スタックの作成

早速、CloudFormationを使用してリソースを作成していきます。
手順は、以下の通りで、リンクにも詳細がございます。

  1. CloudFormation画面を開きスタックの作成>新しいソースを使用(標準)を選択
  2. スタックに任意の名称を入力して、パラメータは環境に合わせて変更>次へを選択。※後述の手順に従う場合、デフォルト値でOK
  3. スタックオプションは特に変更せず、次へを選択
  4. テンプレートの準備完了>テンプレートファイルのアップロードを選択して、保存したymlを選択
  5. レビュー画面を確認して送信を選択

動作検証

では、エラーログを発生させて、メール通知されるか確かめていきましょう。

CloudWatchロググループでエラーログを発生させる

CloudWatchのロググループ画面を開きます。

ログストリームの作成を選択してtest-logstreamと入力し作成を選択します。

アクション>ログイベントの作成>ERRORと入力し作成を選択します。

これで、擬似的にERROR文字列の含むエラーログが出力されました。
サブスクリプションフィルターが検知して、SNS送信Lambdaを起動して、メール通知されているはずですので確認してみましょう。

まず、Lambdaのロググループを見てみると、確かに同時間帯に処理が動いていることがわかります。

メールの方も以下の通り届いていることが確認できました。

まとめ

今回は、CloudWatchLogsロググループのサブスクリプションフィルターとLambda、SNSを用いて、ログ検知と通知の仕組みをCloudFormationで作成しました。
実際の実装時には、Lambda側で詳細なフィルタリングをして適切に通知対象を絞るなど運用を考慮した作り込みも必要になるかと思います。
ロググループを使用しているため、使用したいシステムのログ出力先ををCloudWatchLogsにすることで今回の仕組みを簡単に使用できる点の手軽さが良いと思います、機会があれば使用してみます!
最後までご覧いただきありがとうございました。

コメント

タイトルとURLをコピーしました