こんにちは! ピクトリンク事業部商品技術開発部の橋本です。
ハイラルの平和のために焼きリンゴをかじる日々です。明日の希望を取り戻そうぜ!
さて今回は、AWSのAPI Gateway + Lambda + DynamoDBを使ってサーバーレスなWEBアプリを作っていきたいと思います。
常時アクセスに備えたり、大きな負荷が掛かった場合にスケールアップしたいみたいな要件であればEC2を使うのが無難ですが、 利用する人数が限られて いて、 たまに使うくらい のWEBアプリであればEC2を使わずにリーズナブルに仕上げられます。
構成
システム構成はこのような形になります。
ユーザはブラウザからhttpsでAPI Gatewayのurlにアクセスし、API Gatewayはプロキシ透過でLambdaを呼び出します。
Lambdaは必要な情報をDynamoDBから取得してHTMLを組み立ててAPI Gatewayに返し、API GatewayはLambdaからのレスポンスをそのままユーザに返します。
Lambdaではjsonを返してAPI GatewayでHTMLを組み立てることも可能ですが、ejsテンプレートエンジンを使って出し分けを行いたいので、今回はLambdaでHTMLを組み立てることにします。
今回は社内で使うWEBアプリという想定です。
アクセスできるのは限られたIPアドレスのみとし、社外からのアクセスを遮断します。
長くなったので今回の記事では図中赤点線で示した①について解説し、以降は後編の記事で解説します。
IAM role
まずはLambda関数の作成をする前に、Lambdaに必要な権限のついたroleを作っておきます。
新たにIAM roleを作る権限 が必要なので、権限がなくエラーが発生する場合は管理者に連絡してください。
DynamoDBからデータを取得したいので、 AWSLambdaBasicExecutionRole
に加えて下記の権限がついたroleを作成しておきます。
- 'dynamodb:GetItem' - 'dynamodb:Query'
リソースも必要なテーブル(スキーマ)のみに絞っておいたほうが良いでしょう。
ついつい「*」にしてしまいたくなりますが、セキュリティ的に良くないので、過度な権限は与えない癖をつけておいたほうがなにかと安全です。
Lambda
API Gatewayから呼び出すLambda関数を作成します。
今回の解説範囲では中身の作り込みはせず、シンプルなレスポンスを返すに留めます。
設計図の使用(Blueprint)でもかまいませんが、今回はNode.js 18.xを使いたいので「一から作成」を選びます。
「デフォルトの実行ロールの変更」から「既存のロールを使用する」を選択し、先ほど作ったroleを割り当てておきます。
roleの割り当てには iam:PassRole (roleを割り当てる) 権限が必要です。エラーが出る場合は管理者に連絡してください。
今回の設計では、LambdaからはDynamoDBにのみアクセスする想定ですが、S3へアクセスしたりバッチジョブを作ったりといろいろ応用が効きます。
作った関数には、ひとまず下記のようなコードを書いてデプロイしておきます。
export const handler = async function(event, context) { return { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: 'こんにちわーるど!', }; };
API Gateway
API Gatewayの設定をしていきます。
まずは[API] -> [APIを作成]から始めます。
今回の構成ではリソースポリシーによるIP制限を加えたいのでREST API(注: プライベートではない方)を使います。
IP制限など、リソースポリシーによる制御が不要であればHTTP APIで作った方が、より安価に実現できます。
無料枠もあるので試しやすくて嬉しいですね。
aws.amazon.com
お値段の差
東京リージョンの場合
- HTTP API: 100万リクエストあたり1.29ドル
- REST API: 100万リクエストあたり4.25ドル
となっています。だいたい3.3倍です(2023/6月現在)
Lambda側でベーシック認証などの制限を実現したい場合はHTTP APIでもかまいません。
メソッドの作成
作成したAPIを選択し、[リソース] -> [アクション] から [メソッドの作成] を選択します。
ひとまず今回は[GET]にしておきます。
GETメソッドを作るとメソッドのセットアップに進みますので、[統合タイプ]を[Lambda関数]にして、[Lambdaプロキシ統合の使用]にチェックを入れておきます。
プロキシ統合が有効になっていると、リクエスト内容がeventとしてLambda関数に渡されるようになります。
GETやPOSTのパラメタを取得するために必要となります。
たとえば https://URL.DOMAIN/samplepage?param=42
のようにアクセスしたとき、lambda側で下記のようなコードを書くとパラメタを取得できます。
async function(event, context) { const param1 = event.queryStringParameters?.param; console.log(param1); // 42が出力される };
Lambda関数にはさきほど作成した関数を設定します。
APIのテスト
メソッドの実行画面で[テスト]を選択すると、ステータス: 200と、レスポンス本文に「こんにちわーるど!」と返ってくるはずです。
なんだこのふざけたレスポンス本文は……。
リソースポリシーの設定で使うので、[メソッドリクエスト]に表示されている ARN をメモしておきましょう。
ブラウザ確認
ここまで確認できたら、[アクション]から[APIのデプロイ]を行います。
今回はtestという名前のステージを新しく作成しています。
デプロイを行うことで、ブラウザからアクセスできるようになります。
メソッド設定を追加・変更した場合やリソースポリシーの更新を行った場合は、変更を反映するため再度デプロイを行いましょう。
[URLの呼び出し]に記されたURLをブラウザで開いてみましょう。「こんにちわーるど!」と表示されるはずです。
なんだこのふざけた表示は……。
アクセス制限を加える
あともう一息です。
[リソースポリシー]を選択します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "さきほどメモしたAPI GatewayのARN", "Condition": { "IpAddress": { "aws:SourceIp": [ "許可アドレス1", "許可アドレス2" ] } } } ] }
この書き方だとホワイトリスト(許可されるIPを記述して、他は全部遮断)になります。
「さきほどメモしたARN」と「許可アドレス」の部分はお使いの環境のものに適宜置き換えてください。
たとえば、現在接続しているIPからのアクセスのみを許可したい場合、 ipconfig
(windowsの場合) などを用いて自身のIPアドレスを確認し、許可アドレスの部分に設定します。
許可アドレスは、IPアドレスのほかにCIDR表記も可能です。
リソースポリシーを保存したら、再度APIのデプロイをしておきましょう。
リソースポリシーは設定後、反映までに数分ほど時間がかかることがあります。ニルギリ茶でも飲んでお待ちください。
即時反映だと思っていたので試行錯誤する際に若干ハマりました。
反映された頃合いで、先ほどのURLにもう一度アクセスしてみましょう。
先ほどと同様の画面が表示されているのが確認きでましたか?
次にスマホなどを使い、別のIPアドレスからアクセスしてみましょう。
wifiだと環境によっては同じIPアドレスになることがあるので、モバイル通信に切り替えて試すのが無難です。
今度は anonymous is not authorized to perform: execute-api:Invoke on resource
という表示になっているはずです。
アクセス制限が正しく掛かっています。
おわりに
Gateway API + LambdaでIP制限を設けつつ簡単なWEBアプリのガワが作れました。
次回は②の部分の作り込みをしていこうと思います。