データアナリティクス事業本部のueharaです。
今回は、API Gateway (REST) + LamndaをServerless Frameworkで構築し、pytestを使ってテストしてみたいと思います。
前提条件
今回はServerless Frameworkを利用してAWS環境にデプロイを行います。
まだ準備がおすみでない方は、以下を参考にインストールください。
また、Serverless Frameworkのserverless-python-requirements
プラグインも利用します。
Serverless Frameworkのインストール後、以下でインストールしてください。
$ sls plugin install -n serverless-python-requirements
構成図
今回作成する構成は、簡単ですが次の通りです。
実装
今回、デプロイのために用意するフォルダ構成は以下の通りです。
.
├ handler
│ └ sample.py
├ requirements.txt
└ serverless.yaml
Lamnda (Python) の実装
API GWの後ろにあるLamnda関数の実装を行います。
今回は非常にシンプルに、ステータスコード200
で"Test Response"
というレスポンスを返す処理を記載します。
また、今回は使用しませんが、requirements.txtによるLambda Layer構築のテストもしたいので、redshift_connector
をインポートする処理を記載しておきます。
sample.py
import json
import logging
# テスト用
import redshift_connector
logger = logging.getLogger()
level = logging.getLevelName("INFO")
logger.setLevel(level)
def lambda_handler(event, context):
logger.info("event: {}".format(json.dumps(event)))
logger.info("redshift_connector version: {}".format(redshift_connector.__version__))
response = {
"statusCode": 200,
"body": json.dumps("Test Response")
}
return response
requirements.txt
先にも記載した通り、今回使用する想定はありませんがテスト用にredshift_connector
を記載します。
requirements.txt
redshift_connector
serverless.yaml
今回本題となるserverless.yamlです。
公式ドキュメントやこちらのブログを参考に、ざっと以下のように記載してみました。
serverless.yaml
service: my-apigw-test # Cloudformationのstack nameを設定
frameworkVersion: '3'
provider:
name: aws
stage: dev
runtime: python3.9
lambdaHashingVersion: 20201221
region: ap-northeast-1
endpointType: REGIONAL # API Gateway REST APIのエンドポイントタイプ: edgeまたはregional (デフォルト: edge)
apiGateway:
resourcePolicy:
- Effect: Allow
Principal: '*'
Action: execute-api:Invoke
Resource:
- execute-api:/*/*/*
apiKeys:
- free: # 使用プラン
- name: ${self:service}-key # key名
value: (YOUR_API_KEY) # 30-128文字の任意の英数字
usagePlan:
- free:
quota:
limit: 100 # APIの呼び出しを行える最大回数
offset: 0 # APIの呼び出し回数の初期値(通常は0回を指定する)
period: DAY # DAY or WEEK or MONTH
throttle:
rateLimit: 2 # 1秒あたりに処理できる API リクエスト数
burstLimit: 3 # 同時に処理できる最大リクエスト数
functions:
redshift_select:
name: ${self:service}-handler # lambda関数名
handler: handler/sample.lambda_handler # 実行される関数を指定
role: LambdaRole # lambdaに紐づけられるロール
memorySize: 128 # lambdaのメモリサイズ
timeout: 30 # lambdaのタイムアウト時間
layers: # lambdaに紐づくレイヤーを指定
- Ref: PythonRequirementsLambdaLayer # Layerを参照
events: # lambda関数のトリガーを指定
- http:
path: /sample # このendpointのパス
method: post
private: true # リクエストの`x-api-key`ヘッダーにAPIキー値を追加することを要求
custom:
accountid: ${AWS::AccountId}
pythonRequirements:
dockerizePip: false # python以外で作られているライブラリを使用する時はtrueに
usePipenv: false # Pipenvを使用する場合にtrueに
layer: true # ライブラリからLambda Layerを作成するオプション
useDownloadCache: true # pipがパッケージをコンパイルするために必要なダウンロードをキャッシュするダウンロードキャッシュ
useStaticCache: true # requirements.txtのすべてをコンパイルした後にpipの出力をキャッシュする静的キャッシュ
plugins:
- serverless-python-requirements
resources:
Description: Test role for Lamnda
Resources:
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- "lambda.amazonaws.com"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
package:
patterns:
- "!.vscode/**"
- "!.git/**"
- "!.gitignore"
- "!.serverless"
- "!.serverless/**"
- "!README.md"
- "!package*.json"
- "!requirements.txt"
- "!node_modules/**"
- "!__pycache__"
- "!yarn.lock"
上記で設定していることを簡単に説明すると、/sample
のエンドポイントに対してpostリクエストがあると、handler/sample.py
で設定されるLambda関数を起動するようになっています。
なお、private: true
に設定していることにより、APIキーの指定が無いリクエストはステータスコード403
を返すようになります。
(YOUR_API_KEY)
という部分には、ご自身がAPIキーとして利用したい30-128文字の任意の英数字を記載してください。
その他、functionsプロパティでは作成するLamnda関数の設定、customeプロパティでは作成するLambda Layerに関する設定を行っています。
デプロイ
serverless.yaml
があるフォルダ上で以下のコマンドを実行します。
$ AWS_SDK_LOAD_CONFIG=true AWS_PROFILE=(AWS環境にアクセスするProfile) sls deploy
上記コマンド実行後、AWSマネジメントコンソールからCloudFormationにアクセスし、serverless.yamlで指定したサービス名のスタックが作成されていれば成功です。
同じくAWSマネジメントコンソールからAPI Gatewayにアクセスし、作成したAPIのステージを確認すると、エンドポイントポイントを確認することができます。
pytestによるテスト
まず、今回テストに必要なPythonモジュールをインストールします。
$pip install requests pytest
インストールが完了したら、次のテストファイルを作成します。
test_apigw.py
import requests
# 正常ケース
def test_endpoint_accept():
endpoint = "https://(restapi_id).execute-api.ap-northeast-1.amazonaws.com/dev/sample"
headers = {"x-api-key": "(YOUR_API_KEY)"}
res = requests.post(endpoint, headers=headers)
assert res.status_code == 200
assert res.text == '"Test Response"'
# ヘッダでAPIキーを指定しないケース
def test_endpoint_reject():
endpoint = "https://(restapi_id).execute-api.ap-northeast-1.amazonaws.com/dev/sample"
res = requests.post(endpoint)
assert res.status_code == 403
上記では、正常ケースとヘッダでAPIキーが指定されていないケースをテストします。
具体的には、正常ケースではステータスコード200
とレスポンスBodyが想定したものになっていること、ヘッダでAPIキーを指定しないケースではステータスコード403
が返るか確認しています。
endpoint
には、先程マネジメントコンソール上で確認したご自身のエンドポイントを入力して下さい。
また、ヘッダのx-api-key
にはご自身で指定したAPIキーを指定して下さい。(マネジメントコンソールからも確認可能です。)
テスト実行
テストの実行は以下コマンドで可能です。
$ pytest -v test_apigw.py
以下の通り、2つのテスト共にPASSしていれば期待通り動作しています。
Lambdaの実行ログを確認すると、今回テスト目的で実施したredshift_connector
のインポートもきちんとできていることが分かります。
最後に
今回は、API Gateway (REST) + LamndaをServerless Frameworkで構築し、pytestを使ってテストしてみました。
参考になりましたら幸いです。