この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
とあるLambda関数をserverless frameworkベースからcdkベースへの切り替えが必要になりました。問題は私自身がserverless frameworkを初めて触るという点です。
serverless frameworkのドキュメントを読むことも考えましたが、Lambda関数の実装に用いていないかもしれない機能説明を只管読むのも時間の無駄と感じ、動いているものを読み解きながらの切り替えをやってみました。
serverless frameworkによる設計をなぞってみる
以下、実際のコードから幾つか省いた状態での引用となります。
provider:
name: aws
runtime: python3.7
region: ap-northeast-1
functions:
auth:
handler: auth.invoke
events:
- http:
path: auth
method: post
callback:
handler: callback.invoke
events:
- http:
path: callback
method: get
environment:
TASK_QUEUE_ENDPOINT: { "Ref": "TaskQueue" }
task:
handler: task.invoke
events:
- sqs:
arn:
Fn::GetAtt:
- TaskQueue
- Arn
timeout: 900
resources:
Resources:
TaskQueue:
Type: AWS::SQS::Queue
Properties:
RedrivePolicy:
deadLetterTargetArn:
Fn::GetAtt:
- TaskDLQ
- Arn
maxReceiveCount: 1
TaskDLQ:
Type: AWS::SQS::Queue
実際に動作しているLambda関数に紐付いているサービスと設定ファイルを比較しながら確認しました。上記の構成に沿ったものを挙げると以下の通りになります。
- 3つのLambda関数
- auth
- callback
- task
- イベントソースがSQS
- 900秒でタイムアウト
- 1つのAPI Gateway
- path: /callback
- GETリクエストを受け付ける
- Lambda関数 callbackを呼び出す
- path: /auth
- POSTリクエストを受け付ける
- Lambda関数 authを呼び出す
- path: /callback
- 1つのSQS
- DeadLetterQueueの取り扱い
- 最大受信数は1
serverless flameworkを使った実装はサービスの連携に必要な手続きが大幅に簡略化されます。設定ファイルに指定されているパラメータの種類がとても少ない割に、実際に出来上がったものをみると「どこからこれは出てきた?」と思うことばかりです。
API Gatewayについては、Lambda関数を主体にした定義で且つhttp
のみの記述のため認識し辛いかもしれません。
cdkで書き起こす
挙げた構成を元に、cdkで書き起こしてみます。
import { Construct, Duration, Stack, StackProps } from '@aws-cdk/core';
import { Queue, DeadLetterQueue } from '@aws-cdk/aws-sqs';
import { LambdaIntegration, RestApi, Integration, } from '@aws-cdk/aws-apigateway';
import { Function, Runtime, Code, LayerVersion, AssetCode } from '@aws-cdk/aws-lambda';
import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources';
export class TridoronBotStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
const dlQueue = new Queue(this, 'DeadLetterQueue', {});
const taskQueue = new Queue(this, 'taskQueue', {
visibilityTimeout: Duration.seconds(900),
deadLetterQueue: { queue: dlQueue, maxReceiveCount: 1}
});
const task = new Function(this, 'Task', {
runtime: Runtime.PYTHON_3_7,
handler: 'task.invoke',
code: AssetCode.fromAsset('/path/to'),
events: [new SqsEventSource(taskQueue)],
timeout: Duration.seconds(900)
});
const auth = new Function(this, 'Auth', {
runtime: Runtime.PYTHON_3_7,
handler: 'auth.invoke',
code: AssetCode.fromAsset('/path/to')
});
const callback = new Function(this, 'Callback', {
runtime: Runtime.PYTHON_3_7,
handler: 'callback.invoke',
code: AssetCode.fromAsset('/path/to'),
environment: {
TASK_QUEUE_ENDPOINT: taskQueue.queueUrl
}
});
taskQueue.grantSendMessages(callback);
const sampleAPI = new RestApi(this, 'SampleAPI', {
restApiName: 'sample',
description: 'sample'
});
const authIntegration = new LambdaIntegration(auth);
const authResource = sampleAPI.root.addResource('auth');
authResource.addMethod('POST', authIntegration);
const callbackIntegration = new LambdaIntegration(callback);
const callbackResource = sampleAPI.root.addResource('callback');
callbackResource.addMethod('GET', callbackIntegration);
}
}
途中であったしくじりはAPI Gatewayの構成です。今回は一つのAPI Gatewayに複数のパスを追加し、夫々からLambda関数を実行するべきものでした。その場合は上記のRestApi
を使って構成する必要があります。
しかし、最初はLambdaRestApi
を使って実装していました。この場合Lambda関数ごとにAPI Gatewayを用意されることになり、夫々ホスト名も変わります。同ホスト名下にあるはずのパスが存在しないことになり、結果エラーとなっていました。
あとがき
serverless frameworkのドキュメントを全く読まずに、動いているLambda関数と紐付いたサービスを元にcdkを書き起こしてみましたが、案外何とかなるんだなと思いました。
一通り設定されているパラメータの見落としが全くなかったため問題ありませんでしたが、意図不明なパラメータがある場合はドキュメントの参照をおすすめします。