概要
AWS SAM(Lambda) + Step Functions を利用してウェブサイトの外形監視を行います。
やること
- サイトの外形監視をする。(lambdaで実行)
- アクセスした際にステータスコードが200 or 302の場合、メールで通知(lambda + SES)
- アクセスアクセスした際に上記のステータスコード以外の場合は、時間をおいて再アクセスをする。
StepFunctionsでループの処理を実装し、最大5回実行する。 - 正常なステータスコードが取得できなかった場合は実行を失敗として終了する。
失敗時の通知はCloudWatch Eventで通知予定ですが、今回は実装しません。
注意)
通常は成功時は通知をせず、失敗時に通知を行うと思いますが、今回は成功した場合に通知をする設定にしています。(学習目的のため)
実行環境
- AWS CLIを設定済であること(バージョン1)
- AWS SAMをインストール済であること
- SESを設定済であること
環境設定の参考サイト
AWS CLI インストール
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html
AWS CLI の設定
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html
AWS SAM CLI インストール
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
Amazon SES とは
https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/Welcome.html
Amazon SESによるメール送信環境の構築と実践
https://dev.classmethod.jp/articles/amazon-ses-build-and-practice/
初期設定
AWS SAM の初期化
$ sam init --runtime ruby2.5
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Project name [sam-app]: ruby-stepfunc
Quick start templates may have been updated. Do you want to re-download the latest [Y/n]: Y
-----------------------
Generating application:
-----------------------
Name: ruby-stepfunc
Runtime: ruby2.5
Dependency Manager: bundler
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./ruby-stepfunc/README.md
ディレクトリを移動する
$ cd ruby-stepfunc
Lambda関数作成
今回はWEBサイトのレスポンスチェックと結果をメールで通知するスクリプトを作成します。
アクセスチェックのスクリプト
$ mkdir -p function/check_response/
$ vi function/check_response/main.rb
require 'aws-sdk'
############################################
# アクセスのレスポンスチェック
############################################
def lambda_handler(event:, context:)
count = event['checkresponse']['result']['count'].to_i rescue 0
url = event['url']
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"
headers = { "Content-Type" => "text/html" }
begin
response = http.get(uri.path, headers)
available = response.code == ('200' || '302') ? true : false
count += 1
result = {
available: available,
response_code: response.code,
uri: uri.to_s,
msg: response.msg,
header: response.header,
count: count
}
rescue => e
count += 1
result = {
available: false,
uri: uri.to_s,
msg: e.message,
count: count
}
end
{
statusCode: 200,
body: {
message: result[:msg],
}.to_json,
result: result
}
end
メール送信機能
SESの設定は以下のサイトを参考に行ってください。
実際に外部へ送信される際は、サンドボックスの解除申請も忘れずに行ってください。
$ mkdir -p function/send-email/
$ vi function/send-email/main.rb
require 'aws-sdk'
require "net/http"
############################################
# メール送信
############################################
def lambda_handler(event:, context:)
sender = ENV['SENDER']
ses_region = ENV['SES_REAGION'] || "us-west-1"
encoding = ENV['ENCODING'] || "UTF-8"
recipient = event['email']
subject = "アクセス監視の通知"
textbody = event['checkresponse']['result']['msg'] rescue "no text"
ses = Aws::SES::Client.new(region: ses_region)
begin
# Provide the contents of the email.
resp = ses.send_email({
destination: {
to_addresses: [ recipient ],
},
message: {
body: { text: { charset: encoding, data: textbody } },
subject: { charset: encoding, data: subject }
},
source: sender,
})
result = {
msg: "Email sent!"
}
# If something goes wrong, display an error message.
rescue Aws::SES::Errors::ServiceError => error
result = {
msg: "Email not sent. Error message: #{error}"
}
end
{
statusCode: 200,
body: {
message: result[:msg],
}.to_json,
result: result
}
end
Step Functionの設定
以下、AWS SAMのyamlになります。(StepFunctionsの設定を含む)
$ vi template.yaml
your-email
, your-ses-region
にはそれぞれSESで設定した適した値を入れてください。
(sesでdomain認証をした場合は@前の文字列は任意に決定できます。)
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: |
ruby-stepfunc
SAM Template for ruby-stepfunc
Mappings:
prd:
Ses:
sender: [your-email]
region: [your-ses-region]
stg:
Ses:
sender: [your-email]
region: [your-ses-region]
Parameters:
Environment:
Description: Type of this environment.
Type: String
Default: prd
AllowedValues:
- prd
- stg
Globals:
Function:
Timeout: 120
Handler: main.lambda_handler
Runtime: ruby2.5
MemorySize: 128
Resources:
LambdaRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub '${AWS::StackName}-check-response-role'
Path: /
AssumeRolePolicyDocument:
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
- arn:aws:iam::aws:policy/AmazonEC2ContainerServiceFullAccess
- arn:aws:iam::aws:policy/AmazonSESFullAccess
StatesExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- !Sub states.${AWS::Region}.amazonaws.com
Action: "sts:AssumeRole"
Path: "/"
Policies:
- PolicyName: StatesExecutionPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "lambda:InvokeFunction"
Resource: "*"
CheckResponse:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub 'CheckResponse-${AWS::StackName}'
CodeUri: function/check-response/
Role: !GetAtt LambdaRole.Arn
SendEmail:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub 'SendEmail-${AWS::StackName}'
CodeUri: function/send-email/
Role: !GetAtt LambdaRole.Arn
Environment:
Variables:
SENDER: !FindInMap [ !Ref Environment, Ses, sender ]
SES_REAGION: !FindInMap [ !Ref Environment, Ses, region ]
ENCODING: UTF-8
ServiceCheck:
Type: "AWS::StepFunctions::StateMachine"
Properties:
DefinitionString: !Sub |-
{
"Comment": "Check WEBSITE resoponse",
"StartAt": "CheckResponse",
"States": {
"CheckResponse": {
"Type": "Task",
"Resource": "${CheckResponse.Arn}",
"ResultPath": "$.checkresponse",
"Next": "ChoiceState"
},
"ChoiceState": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.checkresponse.result.available",
"BooleanEquals": true,
"Next": "SendEmail"
},
{
"And": [
{
"Variable": "$.checkresponse.result.available",
"BooleanEquals": false
},
{
"Variable": "$.checkresponse.result.count",
"NumericGreaterThanEquals": 5
}
],
"Next": "FunctionEnd"
}
],
"Default": "wait_seconds1"
},
"wait_seconds1": {
"Type": "Wait",
"Seconds": 10,
"Next": "CheckResponse"
},
"SendEmail": {
"Type": "Task",
"Resource": "${SendEmail.Arn}",
"End": true
},
"FunctionEnd": {
"Type": "Fail",
"Cause": "Email not sent.",
"Error": "Error"
}
}
}
RoleArn: !GetAtt [ StatesExecutionRole, Arn ]
Outputs:
ServiceCheck:
Description: Check Function ARN
Value: !Ref: ServiceCheck
注意
ロールの権限はモリモリになっています。
本番運用の際にはしっかり権限を絞ってください。
StepFunctionsの補足説明
構成図
Step | 説明 |
---|---|
CheckResponse | 指定したURLのレスポンスを確認します。 |
ChoiseState | CheckResponseのレスポンスによって、SendEmail,wait_second1に分岐します。 |
SendEmail | 指定したURLへアクセス結果を送信します。 |
wait_seconds1 | 10秒時間をあけてからCheckResponseへ際アクセスします。 |
FunctionEnd | 指定したループの上限を超えた場合、処理は失敗として終了します。 |
End | 終了 |
フローサンプル1(成功)
- CheckResponseで指定したURLのレスポンスを確認します。
- ChoiseStateでCheckResponseのレスポンスが200 or 302 であることを確認できましたのでSendEmailに分岐します。
- SendEmailで指定したURLへアクセス結果を送信します。
フローサンプル2(失敗)
- CheckResponseで指定したURLのレスポンスを確認します。
- ChoiseStateでCheckResponseのレスポンスが200 or 302 以外であることが確認できましたのでwait_seconds1に分岐します。
- wait_seconds1で10秒時間をあけてからCheckResponseへ際アクセスします。
- 2,3の処理を繰り返して、200 or 302のステータスコードを取得できなかったので、FunctionEndへ分岐します。
- FunctionEndで意図的に処理を失敗にして終了します。
条件分岐・ループ処理処理
デプロイ
cloudformationで使用する、template.yamlの生成とlambdaのファイルをS3へ固めてアップロードする。
bucket_name
は事前に準備をしたバケット名を指定してください。
$ sam package --template-file template.yaml \
--output-template-file packaged.yaml \
--s3-bucket [bucket_name]
template.ymlを実行する(CloudFormation)
stack_name
はCloudFormationのスタック名になります。任意の名前を決めて実行してください。
$ sam deploy --template-file packaged.yaml \
--parameter-overrides Environment=stg \
--stack-name [stack_name] \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
{
"email": "yoshioka@farend.jp",
"url": "https://mica1111.g2staging.farend.ne.jp/"
}
実行
コマンドラインから実行する
ステートマシンの実行時に入力する値のjsonファイルを作成します。
$ vi input_value.json
{
"email": "[your_notification_emal]",
"url": "https://[check_url]/"
}
[statemachine_arn] は先ほど作成した、Step FunctionsのステートマシンのARNを入力します。
ARNはCloudFormationの出力から確認もできます。
$ aws stepfunctions start-execution --state-machine-arn [statemachine_arn] --name `uuid` --input file://input_value.json
AWSコンソールから実行する
先ほど作成したStep Functionsのステートマシンを選択し、「実行の開始」をクリックし、以下の値を入力して「実行の開始」をしてください。
{
"email": "[your_notification_emal]",
"url": "https://[check_url]/"
}
まとめ(所感)
今回実行した処理程度でしたらLambda一つで実行しても良いかと思います。
ただ、Lambdaは実行時間で課金されるので、Sleepを使用する際にはStepFunctionsでループして間にwaitを挟んで実行した方が良さそうです。または今回は試していませんが、並列実行が必要な処理の場合などに威力を発揮しそうです。