サーバーレスでメール検索システムを作ってみる
Author : 福嶋 一史
こんにちは。プロトタイピングソリューションアーキテクトの福嶋です。
私には小学生の娘がいまして、最近小学校が再開されたその娘から、ずっと家にいられていいなーと言われてたりします。仕事しているんですけどね。
さて、その娘が通っている学校からは、ほぼすべてメールで連絡が送られてきます。例えば、そのメールには登校日の持ち物が書かれています。メールを読んだときは分かったつもりになっているのですが、登校日の前日には忘れていて、そのメールを探すのにいつも苦労しています。困ったことにメールには PDF が添付されているので、一つ一つ開いて確認していくしかありません。
そんなメールを検索できたら楽なのにということで、AWS で作ってみました。
このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »
毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。
アーキテクチャ
このシステムのアーキテクチャは、このようになっています。
Amazon SES でメールを受信して検索インデックスを作ることで、自動的に検索データを溜めることができます。
メールを受信して検索インデックスを作るまでの流れ
- Amazon SES がメールを受信する (上図 1)
- Amazon S3 バケットにメールデータを書き出す (上図 2)
- Amazon S3 にメールデータが格納されると、それをトリガーにして AWS Lambda が実行される (上図 3)
- AWS Lambda でメールデータを解析し、Amazon Elasticsearch Service にインデックスを作成する (上図 4)
検索処理の流れ
- クライアントが Amazon API Gateway に検索リクエストを送信する (上図 5)
- Amazon API Gateway が AWS Lambda を実行する (上図 6)
- AWS Lambda が Amazon Elasticsearch Service で検索して結果を返す (上図 7, 8, 9)
- Amazon API Gateway がクライアントに結果を返す (上図 10)
各サービスの概要
Amazon SES
Amazon SES (Amazon Simple Email Service) は、コスト効率の高いクラウドベースの E メール送受信サービスです。詳しくは、Amazon SES 紹介ページをご覧ください。
Amazon S3
Amazon S3 (Amazon Simple Storage Service) は、ユーザがデータを安全に、容量制限なく、データ保存が可能なクラウドオブジェクトストレージです。詳しくは、Amazon S3 紹介ページをご覧ください。
AWS Lambda
AWS Lambda は、サーバーのプロビジョニングしたり管理したりする必要なく、コードを実行できるコンピューティングサービスです。詳しくは、AWS Lambda 紹介ページをご覧ください。
Amazon Elasticsearch Service
Amazon Elasticsearch Service は、Elasticsearch を大規模かつ簡単でコスト効率の良い方法を使用してデプロイ、保護、実行する完全マネージド型サービスです。詳しくは、Amazon Elasticsearch Service 紹介ページをご覧ください。
Amazon API Gateway
Amazon API Gateway は、あらゆる規模の REST、HTTP、WebSocket API を作成、公開、管理、モニタリング、保護するためのサービスです。詳しくは、Amazon API Gateway 紹介ページをご覧ください。
前提
- 受信用メールアドレス
- SES が受信するメールアドレスとして使用します。
- そのドメインの DNS レコードを追加できる権限が必要です。
- メールアドレスがない方でも、SES の設定はスキップして、S3 バケットに MIME 形式のメールデータをアップロードすることでも、検索インデックスの作成やその検索は試していただけます。
- Maven (3.6 以上)、Java SDK (8 以上)
- Lambda 用の Java コードをビルドするために必要です。
ガイド
それでは、各サービスについて開発を進めていきましょう。
あらかじめサンプルコードをこちらからダウンロードしておいてください。
メール受信用の Amazon S3 バケットの作成
最初に、Amazon SES が受信したメールを保管する、S3 バケットを作成します。Amazon S3 コンソールを開き、『バケットを作成する』を押します。
クリックすると拡大します
他のバケットと重複しないバケット名を入力し、『作成』を押します。
クリックすると拡大します
バケットが作成されたら、Amazon SES から書き込めるように、バケットポリシーを変更します。
クリックすると拡大します
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<バケット名>/*",
"Condition": {
"StringEquals": {
"aws:Referer": "<アカウント ID>"
}
}
}
]
}
<バケット名> と <アカウント ID> をそれぞれ、上記で作成した S3 バケットの名前と、使用する AWS アカウント ID に置き換えてください。
Amazon SES でメール受信設定
次に、Amazon SES で、メールを受信し、上記で作成した S3 バケットに書き込む設定をしていきます。
Amazon SES で、メールを受信できるリージョンは、現時点で、米国東部 (バージニア北部)、米国西部 (オレゴン)、欧州 (アイルランド) となっていますので、このいずれかのリージョンを選択してください。なお、作成した S3 バケットとリージョンが違っていても大丈夫です。
まず、Amazon SES のメール受信で扱うドメインを登録します。Amazon SES コンソールを開き、Domains メニューを開きましょう。次に、表示される『Verify a New Domain』を押します。
クリックすると拡大します
対象のドメインを入力して、『Verify This Domain』を押します。例えば、メールアドレスが buildersflash@example.com の場合は、example.com のドメインを入力することになります。
クリックすると拡大します
『Verify This Domain』を押すと、このドメインの DNS 設定で TXT レコードと MX レコードの案内が表示されます。
このドメインの DNS 設定にて、指定されている値の TXT レコードと MX レコードを追加してください。この設定方法は、ドメインを管理しているサービスによって異なります。
なお、このドメインが Route 53 で管理されているドメインの場合は、右下の『Use Route 53』を押すことで、自動的に TXT レコードと MX レコードが追加することができます。
クリックすると拡大します
Route 53 を使用する場合、『Use Route 53』を押すと、次のダイアログが表示されます。ここで『Email Receiving Record』にチェックを入れ、『Create Record Sets』を押します。これにより、必要な TXT レコードと MX レコードが追加されます。
クリックすると拡大します
TXT レコードと MX レコードを追加したら、Amazon SES によるドメインの検証が終わるまで待ちます。
『Vefification Status』が『pending verification』から『verified』に変われば OK です。
クリックすると拡大します
これで、ドメインの登録が終わりました。次は、受信したメールを S3 バケットに保存する設定を入れていきましょう。
同じく SES コンソールの Rule Sets メニューを開き、『Create a Receipt Rule』を押します。
クリックすると拡大します
メールを受信するメールアドレスを入力して、『Add Receipient』を押し、一覧に追加されることを確認します。問題なければ、『Next Step』を押します。
クリックすると拡大します
メールを受信した際のアクションを設定していきます。Add action で『S3』を選択します。
クリックすると拡大します
すると、S3 バケットを入力する画面が表示されるので、上記で作成した S3 バケット名を入力し、『Next Step』を押します。
クリックすると拡大します
次の画面で、ルール名を入力し、『Next Step』を押します。
クリックすると拡大します
最後に、これまでの入力を確認し、『Create Rule』を押します。
クリックすると拡大します
ここまでの設定について、動作確認してみます。SES に設定したメールアドレスに対して、メールを送ってみましょう。設定に問題がなければ、S3 バケットにメールデータが入っているはずです。このファイルをダウンロードして開いてみてください。送信したメールが受信されていることを確認できると思います。
クリックすると拡大します
Amazon Elasticsearch Service ドメインの作成
次に、検索サービスとして、Amazon Elasticsearch Service のドメインを作っていきます。
Amazon Elasticsearch Service コンソールのダッシュボードを開き、『新しいドメインの作成』を押します。
クリックすると拡大します
今回はパフォーマンスや可用性は重要ではないため、デプロイタイプは『開発およびテスト』を選択し、『次へ』を押します。
クリックすると拡大します
Elasticsearch ドメイン名に適当な名前を入力します。また、パフォーマンスや可用性は重要ではないため、インスタンスタイプも『t2.small.elasticsearch』にします。
クリックすると拡大します
次の画面では、ドメインにアクセスできる範囲を VPC 内に制限するため、ネットワーク構成を『VPC アクセス』を選択します。合わせて、ドメインを配置する VPC、サブネットを指定します。
また、ドメインアクセスポリシーでは『ドメインへのオープンアクセスを許可』を選択します。オープンアクセスを許可した場合でも、ドメインへのアクセスは、VPC からのみに制限されています。なお、VPC からのアクセスはセキュリティグループでコントロールできます。
クリックすると拡大します
設定するセキュリティグループのインバウンドルールでは、以下のように、対象 VPC の CIDR からの HTTPS 通信を許可します。
クリックすると拡大します
最後に、設定内容に問題なければ、『確認』を押します。
クリックすると拡大します
これで、ドメインの作成が始まります。ドメインのステータスが『アクティブ』になるまで待ちましょう。また、この画面で表示されている VPC エンドポイントは、後ほどこのドメインに接続するために必要になるので、メモしておきます。
クリックすると拡大します
これで Amazon Elasticsearch Service のドメインが作成されました。
Amazon Elasticsearch Service ドメインへのアクセス用 EC2 インスタンスの作成
次に、Amazon Elasticsearch Service に対して、インデックスの設定をしていきます。そのためには、ドメインの VPC エンドポイントにアクセスする必要があります。しかし、ドメインをネットワーク構成を VPC で作成しているため、インターネット経由でのアクセスができません。このため、VPC 内にドメイン操作用の EC2 インスタンスを立てて、そこからコマンドを実行することになります。
では、EC2 インスタンスを起動しましょう。EC2 コンソールを起動し、インスタンスを選択し、『インスタンスの作成』を押します。
クリックすると拡大します
AMI は、Amazon Linux 2 を選択します。
クリックすると拡大します
インスタンスタイプは t2.micro を選択し、『次のステップ: インスタンスの詳細の設定』を押します。
クリックすると拡大します
ネットワークに Amazon Elasticsearch Service のドメイン作成で指定した VPC を選択します。サブネットは、その VPC 内のパブリックなサブネットを選択してください。自動割り当てパブリック IP は有効にします。最後に『次のステップ: ストレージの追加』を押します。
クリックすると拡大します
ストレージの設定はそのままで『次のステップ: タグの追加』を押します。
クリックすると拡大します
タグの設定もそのままで『次のステップ: セキュリティグループの設定』を押します。
クリックすると拡大します
セキュリティグループのインバウンドルールでは、アクセス元からの SSH を許可します。ソースで『マイ IP』を選ぶと、現在このコンソールにアクセスしている環境の IP アドレスが自動で入力されます。このルールを追加したら、『確認と作成』を押します。
クリックすると拡大します
これまでの設定を確認し、問題なければ、『起動』を押してください。
クリックすると拡大します
表示されたダイアログで、キーペアを新規作成するか、既存のものを選択し、『インスタンスの作成』を押します。
クリックすると拡大します
インスタンスの一覧画面で、起動したインスタンスのステータスが『running』に、ステータスチェックが『2/2 のチェックに合格しました』になれば、起動完了です。このインスタンスに対して SSH で接続するので、説明タブ内の IPv4 パブリック IP をメモしておきます。
クリックすると拡大します
Amazon Elasticsearch Service ドメインでインデックス作成
上記で作成した EC2 インスタンスに SSH 接続します。接続方法はこちらをご参考ください。
SSH 接続できたら、cURL コマンドで、メモしておいた、Amazon Elasticsearch Service ドメインの VPC エンドポイントにリクエストを投げてみます。
$ curl -X GET https://<ドメインの VPC エンドポイント>
このようなレスポンスが返って来れば、ドメインへの接続ができています。
{
"name" : "2ad6b898d7a65a232d2c0de3d05388b3",
"cluster_name" : "<アカウントID>:<ドメイン名>",
"cluster_uuid" : "qeqgYiNGTh6oBPEDkmfP2g",
"version" : {
"number" : "7.4.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "unknown",
"build_date" : "2020-05-05T04:47:22.951128Z",
"build_snapshot" : false,
"lucene_version" : "8.2.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
ここまで確認できたら、受信したメール用のインデックスを作成します。日本語を扱うため、kuromoji の analyzer を作成しています。<ドメインの VPC エンドポイント> の部分は、VPC エンドポイントに置き換えてください。
$ curl -X PUT -H "Content-Type: application/json" https://<ドメインの VPC エンドポイント>/mail -d'
{
"settings":{
"analysis":{
"tokenizer" : {
"kuromoji" : {
"type" : "kuromoji_tokenizer"
}
},
"analyzer" : {
"kuromoji" : {
"type" : "custom",
"tokenizer" : "kuromoji"
}
}
}
}
}'
成功すると、以下のようなレスポンスが返ります。
{"acknowledged":true,"shards_acknowledged":true,"index":"mail"}
次にインデックスのマッピングを設定します。今回のインデックスに含まれる property は、この 3 つです。
Property | 説明 | Data Type |
---|---|---|
subject | 件名 | text |
content | 本文 | text |
sentDate | 送信日時 | long |
$ curl -X PUT -H "Content-Type: application/json" https://<ドメインの VPC エンドポイント>/mail/_mapping -d'
{
"properties": {
"subject": {
"type": "text",
"analyzer": "kuromoji"
},
"content": {
"type": "text",
"analyzer": "kuromoji"
},
"sentDate": {
"type": "long"
}
}
}'
成功すると、以下のようなレスポンスが返ります。
{"acknowledged":true}
AWS Lambda 関数 (Document 追加用) の作成
次に、S3 にメールデータが保存された際に実行される Lambda 関数を作成していきます。この Lambda 関数では、メールデータと添付されている PDF ファイルを解析して、その内容を Elasticsearch Service に Document として追加する処理を行います。
まずは、Lambda 関数で使用する IAM ロールを作成します。IAM コンソールのロールを開き、『ロールの作成』を押してください。
クリックすると拡大します
ユースケースの選択で、『Lambda』を選択し、『次のステップ: アクセス権限』を押します。
クリックすると拡大します
ポリシーのフィルタに『AWSLambdaVPCAccessExecutionRole』と入力し、表示されたポリシーのチェックボックスにチェックを入れます。
クリックすると拡大します
ポリシーのフィルタに『AmazonS3ReadOnlyAccess』と入力し、表示されたポリシーのチェックボックスにチェックを入れます。最後に、『次のステップ: タグ』を押してください。
クリックすると拡大します
タグの追加画面では、そのまま『次のステップ: 確認』を押します。
クリックすると拡大します
確認画面で、ポリシーが正しく 2 つ選択されているか確認してください。OK ならロール名を入力し、『ロールの作成』を押します。
クリックすると拡大します
さて、いよいよ Lambda 関数の作成です。Lambda コンソールを開き、『関数の作成』を押します。
クリックすると拡大します
関数名を入力し、ランタイムに『Java 8』を選択します。次に、実行ロールには『既存のロールを使用する』を選んだ上で、上記で作成した Lambda 用のロールを選択します。最後に『関数の作成』を押してください。
クリックすると拡大します
Lamdba 関数が作成されると、このような画面が表示されます。この画面でさらに設定を行なっていきます。
クリックすると拡大します
まずは、関数コードをビルドしてアップロードします。サンプルコードの ZIP ファイルをダウンロードします。その後以下のようにコマンドを実行し、JAR ファイルを作成します。
unzip school-mail-search-lambda.zip
cd school-mail-search-lambda
mvn clean package
target ディレクトリの下に school-mail-search-1.0.jar というファイルができていれば成功です。
このコードでは、MIME 形式のメールデータや PDF を解析して、テキストを抜き出し、Elasticsearch Service ドメインにドキュメントとして追加しています。興味がある方はぜひコードをご覧ください。
このファイルを Lambda 関数にアップロードします。関数コードのプルダウン『アクション』を開き、『zip または jar ファイルをアップロード』をクリックします。
クリックすると拡大します
表示されるダイアログで、先ほど生成した school-mail-search-1.0.jar を選択し、『保存』を押します。
クリックすると拡大します
次に、関数コードの下にある、環境変数の設定を行います。Lambda 関数のコードでは、Elasticsearch Service ドメインの VPC エンドポイントなどを環境変数として渡すようにしています。環境変数設定の『編集』か『環境変数を管理』を押してください。
クリックすると拡大します
環境変数は、以下のように設定してください。
キー | 値 |
---|---|
AES_ENDPOINT | Elasticsearch Service ドメインの VPC エンドポイント 例) vpc-xxxxxx-xxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com |
INDEX_NAME | |
REGION | Elasticsearch Service ドメインのリージョン 例) ap-northeast-1 |
環境変数が追加できたら、『保存』を押します。
クリックすると拡大します
次に、基本設定で、Lambda 関数として呼び出すハンドラを設定します。基本設定の『編集』を押します。
クリックすると拡大します
ハンドラに『com.amazon.aws.buildersflash.schoolmailsearch.AddDocumentFunction::handle』を入力し、『保存』を押します。
クリックすると拡大します
最後に、この Lambda 関数から Elasticsearch Service ドメインが紐づく VPC にアクセスできるように、VPC の設定を行います。
EC2 コンソールを開き、Lambda 関数用のセキュリティグループを作成します。
クリックすると拡大します
セキュリティグループ名を入力し、Elasticsearch Service ドメインで設定した VPC を選択します。インバウンドルール、アウトバウンドルールはそのままで『セキュリティグループを作成』を押します。
クリックすると拡大します
VPC 設定の『編集』を押します。
クリックすると拡大します
VPC 接続で『カスタム VPC』を選択し、VPC には Elasticsearch Service ドメインで設定した VPC を選択します。サブネットには同じ VPC のプライベートな Subnet を選択してください。最後に、さきほど作成したセキュリティグループを選び、『保存』を押します。
クリックすると拡大します
これで Lambda 関数自体の設定は終わりです。では次に、メールを受信して S3 にメールデータが書き込まれた時に、この Lambda 関数が呼び出されるようにトリガーを設定します。
Lambda 関数の『トリガーを追加』を押してください。
クリックすると拡大します
S3 を選択し、バケット名を入力します。『追加』を押すと、トリガーが作成されます。
クリックすると拡大します
AWS Lambda 関数 (Document 追加用) の作成
さきほど作成した Document 追加用の Lambda 関数と同様に、もう一つ検索用の Lambda 関数を作成します。IAM ロールは、Document 追加用に作成したものを使います。
クリックすると拡大します
関数コード、環境変数、VPC には、追加用の Lambda 関数と同じものを設定してください。
異なるものとしては、基本設定のハンドラがあります。ハンドラには、『com.amazon.aws.buildersflash.schoolmailsearch.SearchDocumentFunction::handle』を指定します。
クリックすると拡大します
もう一つ異なるものとして、トリガーがあります。この Lambda 関数をブラウザから実行できるように、API Gateway のトリガーを作りましょう。
クリックすると拡大します
トリガーに API Gateway を選択し、『Create an API』で新たに作成するようにします。API type は『REST API』を選択します。セキュリティについては『オープン』にしていますが、メールを扱う API であることから、本来は何らかの認証を追加することをおすすめします。REST API のセキュリティについては、こちらを参考にしてください。
クリックすると拡大します
動作確認
では、いよいよ、メールを検索してみましょう。
再度、SES に設定したメールアドレスに対して、メールを送ってみてください。
今度は、S3 バケットにメールデータが書き込まれた後、Lambda 関数が実行され、Elasticsearch Service のインデックスが作成されます。
これを検索するためには、検索用 Lambda 関数のトリガーに表示されている URL を開きます。
クリックすると拡大します
この URL を開いただけでは、クエリを指定していないので、何も表示されないはずです。ブラウザの URL に『*?query=検索文字列』といったパラメータを追加してみてください。検索文字列には、上記で送信したメールに含まれている文字列を指定してください。うまくヒットすると次のように JSON が返ってきます。
クリックすると拡大します
まとめ
うまく検索できましたでしょうか ? Elasticsearch Service の設定のために使用した EC2 を除き、AWS サービスを連携させることでサーバーレスなシステムを構築できることがお分りいただけたと思います。
使用しているサービスが多いため、手順が長くなっていますが、ぜひチャレンジしていただけるとうれしいです。
筆者プロフィール
福嶋 一史 (ふくしま かずふみ)
アマゾン ウェブ サービス ジャパン合同会社
プロトタイピング ソリューションアーキテクト
Web 系のソフトウェアエンジニアを経て、クラウドサポートエンジニアとして、2016 年に AWS 入社しました。その後、2019 年にプロトタイピングソリューションアーキテクトとなり、お客様のプロトタイピング開発を支援しています。好きな AWS サービスは、Amazon API Gateway と AWS Lambda です。
AWS を無料でお試しいただけます