背景
大量のメールを短時間で送信し切る為に、送信時にハイスペックな専用のbatchサーバを起動し、並列数を多くして送信処理を行なう必要がありました。
これまではサーバの起動を手動で行なっていたのですが、メールの一斉送信の頻度が増えたので、SQSとLambdaを使って一斉送信時に自動でサーバを起動し、送信後にはサーバが停止されるようにしました。
構成
- 一斉送信メールが設定されるとkickerが検知して、SQSにEC2起動のキューを投げます
- SQSにキューが投げられるとlambdaの関数が実行され、一斉送信専用batchサーバが起動します
- batchサーバは起動と同時に一斉送信メールの設定を読み込みメール送信処理を開始します
- メール送信処理が完了すると、SQSにEC2停止のキューを投げます
- SQSにキューが投げられるとlambdaの関数が実行され、一斉送信専用batchサーバが停止します
SQSの設定
新しいキューの作成から、キューを作成します。
lambdaの設定
まずlambda用の実行ロールを定義します。
ロールの作成で、Lambdaを選択します。
ポリシーにはAmazonSQSFullAccessとLambdaEC2Controlを指定して作成します。
LambdaEC2ControlはEC2の起動・停止を定義したポリシーです。
以下の設定で作成しておきます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"ec2:Start*",
"ec2:Stop*"
],
"Resource": "*"
}
]
}
次に関数の作成からEC2を起動させるlambda関数を作成します。
ランタイムにはpython3.7を選択し、実行ロールには先ほど作成したlambda-control-instanceを選択します。
lambdaの作成後、EC2起動用のSQSをトリガーに設定します。
最終的に以下のようになります。
最後にインスタンスを起動させるlambda関数を設定します。
import boto3
import os
def lambda_handler(event, context):
ec2 = boto3.client('ec2', region_name='ap-northeast-1')
ec2.start_instances(InstanceIds=[os.environ['instance_id']])
print('Instance ' + os.environ['instance_id'] + ' Started')
一斉送信用のbatchサーバのインスタンスIDを、環境変数として指定します。
これでEC2起動用のlambda関数作成は完了です。
同様の手順でEC2停止用のlambda関数も作成しておきます。
EC2停止用のlambdaでは以下の関数を設定します。
import boto3
import os
def lambda_handler(event, context):
ec2 = boto3.client('ec2', region_name='ap-northeast-1')
ec2.stop_instances(InstanceIds=[os.environ['instance_id']])
print('Instance ' + os.environ['instance_id'] + ' Started')
今後の課題
排他制御
今後メール一斉送信の頻度がさらに増えた場合、メール送信処理中にkickerが起動して意図しない動きになってしまうので、排他制御の処理を追加する必要があります。
サーバレス構成に向けて
SQS + lambda + S3 + SESでサーバレスなメール配信システムを作ることができます。
それぞれの役割は以下の通りです。
SQS ... メールの送信処理待ちキュー
Lambda ... SQSからキューを受け取って、SES経由でメールを送る
S3 ... メールのペイロードを入れておく
SES ... メール送信
この構成の課題は、送信したメールのデータを更新したい場合、別でデータを管理する必要が出てくる点です。
例えば、HTMLメールを開封したタイミングでダミーピクセスのアクセスがサーバに飛んで、送信データのテーブルを更新する様なパターンです。
ちなみに、LambdaからMySQLにアクセスするのはコネクションプールの関係からアンチパターンらしいので、切り分ける場合でもDynamoDBとかに入れる必要があります。
ただし、最近はAurora Serverlessが出てきたのでLambdaからMySQLを使えるかもしれません。



