コンニチハ。川野です。
Fusic Advent Calendar 2019 - Qiita 7日目の記事です。
昨日は @ya_ma23 による Rustでcsvを読み込みDBにインポートする でした。
最近よく目にするRust、気になる存在です。
さて本日は、簡単に導入できるように設計・実装したバッチ処理の監視機構について紹介します。
本記事は「一日一回 決まった時刻に 動く処理が、何らかの理由で失敗していたことを検知したい」という方向けです。
概要
アプリケーションサーバ上で、毎朝1回決まった時刻にcronによって実行されるphpファイルがあります。
当然、毎朝このファイルが実行されないと困りますね。
そこで、ファイルの実行に失敗したことを検知し、システム管理者にSlack通知するような機構を実装しました。
「ファイルの実行に失敗した
」といっても、その原因は色々と考えられるでしょう。
(phpファイルの実行自体が失敗していた、そもそもcrondが起動してなかった、etc...)
今回の機構では、 「何らかの原因で、バッチ処理が動いていなかった」 ということを監視できるようにします。
システム構成
下図のような構成です。
普段AWSを使ってアプリケーションを開発されている方にとってはカンタンでしょう。
AWS各種サービスの設定 / 実装
以下の目次で進みます。
- S3へのテキストファイルアップロード(php)
- S3バケットの設定
- Lambdaの設定・S3のファイル存在チェック
- 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関数を指定します。
S3さんのご様子
わざわざ上げる必要もないかもしれませんが、
まとめ
これで監視機構の導入は完了です。
AWS利用料も0.1(円/月)程度でした。
監視により平穏な日々を過ごすことができますね。
さて、明日のアドベントカレンダーは @daiki7nohe です。
どうぞよろしくおねがいします!