serverless frameworkで設計されたLambda関数を実運用状況と比較しつつcdkで書き起こしてみた

serverless frameworkで構成されたLambda関数をcdkで書き起こしました。設定ファイルのみでは察知し辛く、実際に動作しているサービスと比較しながらの作業となりました。
2020.09.28

はじめに

とある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を呼び出す
  • 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を書き起こしてみましたが、案外何とかなるんだなと思いました。

一通り設定されているパラメータの見落としが全くなかったため問題ありませんでしたが、意図不明なパラメータがある場合はドキュメントの参照をおすすめします。

参考リンク