Help us understand the problem. What is going on with this article?

S3 × Lambda × Cloudwatch Eventsで、簡単にバッチ処理の監視機構を導入する

コンニチハ。川野です。

Fusic Advent Calendar 2019 - Qiita 7日目の記事です。
昨日は @ya_ma23 による Rustでcsvを読み込みDBにインポートする でした。
最近よく目にするRust、気になる存在です。


さて本日は、簡単に導入できるように設計・実装したバッチ処理の監視機構について紹介します。

本記事は「一日一回 決まった時刻に 動く処理が、何らかの理由で失敗していたことを検知したい」という方向けです。

概要

アプリケーションサーバ上で、毎朝1回決まった時刻にcronによって実行されるphpファイルがあります。
当然、毎朝このファイルが実行されないと困りますね。
そこで、ファイルの実行に失敗したことを検知し、システム管理者にSlack通知するような機構を実装しました。

ファイルの実行に失敗した」といっても、その原因は色々と考えられるでしょう。
(phpファイルの実行自体が失敗していた、そもそもcrondが起動してなかった、etc...)
今回の機構では、 「何らかの原因で、バッチ処理が動いていなかった」 ということを監視できるようにします。

システム構成

下図のような構成です。

普段AWSを使ってアプリケーションを開発されている方にとってはカンタンでしょう。
monitoring-architecture.png

AWS各種サービスの設定 / 実装

以下の目次で進みます。

  1. S3へのテキストファイルアップロード(php)
  2. S3バケットの設定
  3. Lambdaの設定・S3のファイル存在チェック
  4. CloudWatch EventsによるLambda関数の定期実行

1. S3へのテキストファイルアップロード(php)

aws-sdkを使用します。 aws-sdk-php をインストールしましょう。

composer require aws/aws-sdk-php

テキストファイルは、「phpファイルが実行された」ことを担保するためにアップロードします。
アップロード処理は以下です。

ちなみに、アプリケーションはCakePHP3で動いています。

     /**
      * 処理が実行された証拠となるファイルをS3へアップロード
      *
      * @return bool
      */
     private function evidenceFileUpload()
     {
         $filePath = 'path/to/file'; // アップロードするファイルのパス
         $date = (new FrozenTime())->format('Y-m-d'); // '2019-12-07' のように、アップロードされた日付をフォルダ名とする
         $newFileName = 'evidence.txt';
         $result = S3Manager::putObject($filePath, $date, $newFileName);  // `Aws\S3\S3Client の putObject()` を使用
         if (!$result) {
             // S3へのアップロード処理に失敗したらSlack通知(=phpファイルが実行されたということは担保されます)
             Slack::sendSlackNotification('ファイルのS3へのアップロードに失敗しました');
         }

         return $result;
     }

cronによってこのphpファイルが実行されたとしても、S3へのアップロードに失敗した場合は、一応Slack通知するようにしています。

IAMユーザーの設定

アプリケーションが使用するIAMユーザーにアタッチするポリシーには、S3への putObject() のみを許可するようにしておくのがセキュアですね。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::hoge-bucket/*"
        }
    ]
}

2. S3バケットの用意・設定

なにはともあれバケットを用意します。

そして、バケットにはライフサイクルルールを設定しておいた方が良いでしょう。
Lambdaによるテキストファイルの存在チェック後には、そのファイルはもう不要ですので。

3. Lambdaの設定・S3のファイル存在チェック

S3にファイルが存在しているかチェックするLambda関数を用意します。

コード(Python3.7)

import json
import time
import boto3
import urllib.request
from botocore.errorfactory import ClientError
from datetime import datetime, timedelta, timezone


def lambda_handler(event, _context):
    lambda_client = boto3.client("lambda")

    # 指定ファイルが存在しているかどうかのステータス取得
    status = check_file_exists()
    if not status:
        response = execute_notification(event, lambda_client)

        return response


# ファイルの存在チェック
def check_file_exists():
    s3_client = boto3.client("s3")
    # key名を指定
    jst = timezone(timedelta(hours=+9), "JST")
    jst_now = datetime.now(jst)
    date = datetime.strftime(jst_now, "%Y-%m-%d")
    key_name = date + '/' + 'evidence.txt'

    # ファイル存在チェックを実行
    try:
        s3_client.head_object(
            Bucket='bucket-name',
            Key=key_name
        )
        status = True
    except ClientError:
        status = False

    return status


# slack通知
def execute_notification(event, lambda_client):
    send_data = {
        "text": "処理の実行を確認できませんでした",
        "channel": "#slack-chennel-name"
    }
    send_text = ("payload=" + json.dumps(send_data)).encode('utf-8')
    request = urllib.request.Request(
        "", #slack webhook url指定
        data=send_text,
        method="POST"
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode('utf-8')

    return response_body

コチラを参考にさせていただきました。ありがとうございます。

S3のファイルの存在を s3_client.head_object() でチェックしています。
boto3のDocument によると s3:GetObject を許可する必要があるとのことです。

You need the s3:GetObject permission for this operation

IAMロールの設定

LamdbaにIAMロールを設定します。
ロールにアタッチするポリシーには、S3の指定のバケットに対して getObject() を許可するようにしておきましょう。
(ここでは全バケットを許可しちゃってますが)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::*"
        }
    ]
}

4. CloudWatch EventsによるLambda関数の定期実行

ルールを作成しましょう。
「スケジュール」から「Cron式」を選択します。
一つ注意点としては、スケジュールイベントが使用するタイムゾーンはUTCであるということです。
私が使用しているのは東京リージョンなので、指定したい時刻から -9 時間 した値にしないといけません。
(CloudWatch Events Document: Schedule Expressions for Rules より)

ターゲットには、3. で作成したLambda関数を指定します。

スクリーンショット 2019-12-07 19.06.44.png

S3さんのご様子

わざわざ上げる必要もないかもしれませんが、

監視により安心を得られます。
スクリーンショット 2019-12-07 19.19.36.png

まとめ

これで監視機構の導入は完了です。
AWS利用料も0.1(円/月)程度でした。

監視により平穏な日々を過ごすことができますね。

さて、明日のアドベントカレンダーは @daiki7nohe です。
どうぞよろしくおねがいします!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした