0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ServerlessFrameworkからAWS SAM移行で詰まった話

0
Posted at

はじめに

社内システムで最初 ServerlessFramework を使用しておりましたが、AWS SAMに移行しようという話になりました。AWS SAMへ移行するにあたり幾つか詰まった箇所を紹介します。

  1. テンプレートの分割方法
  2. テンプレートを跨いでRestApiIdが使用できない
  3. 他スタックの出力値をテンプレート内で参照すると、参照先のスタックが更新できない

1. テンプレートの分割方法について

ServerlessFrameworkでLambdaのAPIを作成する際に、大まかにLambda+APIGateway、VPCのリソースを作成していました。ServerlessFrameworkの時はCloudFormationのリソース上限に触れないようにserverless-plugin-split-stacksというプラグインを使い、分割数だけ決めて自動で分割していました。
今回の移行では、自動分割はないのでテンプレートファイルを3つの構成に分けることにしました。それぞれで作成するリソースとフォルダ構成は下記になります。

ファイル名 役割
template.yaml 各テンプレートを纏める
template-vpc.yaml ルートテーブルやエンドポイントなどのVPCリソース作成
template-lambda.yaml Lambdaとロググループの作成
template-api.yaml APIGatewayの作成とLambda紐づけ、LambdaのPermission作成
フォルダ構成
src
├─ handlers/
│  ├─ login/
│  │    └─ src/
│  │        └─ login.py
│  ├─ purchase
│  └─ calculation
├─ .samignore
├─ samconfig.toml
├─ template.yaml
├─ template-vpc.yaml
├─ template-lambda.yaml
└─ template-api.yaml

2. テンプレートを跨いでRestApiIdが使用できない

AWS SAMの記述でLambdaを作成する場合、TypeでAWS::Serverless::Functionを設定し、RestApiIdを指定すればApiGatewayとの紐づけが可能です。
しかし、公式ドキュメントのRestApiIdのパラメータ説明に下記記載があり、別テンプレートで作成したAPIGatewayのIDを設定できないことを知りました。

RestApiId

This cannot reference an AWS::Serverless::Api resource defined in another template.
(これは、別のテンプレートで定義された AWS::Serverless::Api リソースを参照できません。)

そのため、template-lambda.yamlで作成したLambdaのArnをOutPutに出力し、template-api.yamlでAPIGatewayの作成と紐づけを行うようにしました。※下記ソースは一部記載を省いております

template.yaml
Resources:
  LambdaStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: template-lambda.yaml
      Parameters:
        Service: service
        Stage: develop
        Region: ap-northeast-1
  ApiStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: template-api.yaml
      Parameters:
        Service: service
        Stage: develop
        Region: ap-northeast-1
        Origin: "*"
        LoginLambdaArn: !GetAtt LambdaStack.Outputs.LoginLambdaArn
template-lambda.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: lambda-stack

Parameters:
  Service:
    Type: String
    Description: サービス名
  Stage:
    Type: String
    Description: 環境名
  Region:
    Type: String
    Description: AWSリージョン
  LogRetentionInDays:
    Type: Number
    Description: CloudWatchにおけるログの保持日数
  LogGroupPrefix:
    Type: String
    Default: /aws/lambda
    Description: CloudWatchロググループのプレフィックス

Globals:
  Function:
    Timeout: 30
    Runtime: python3.14
    MemorySize: 512
    Architectures:
      - x86_64
    Tracing: Active
    Environment:
      Variables:
        STAGE: !Ref Stage

Resources:
  Login:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: handlers/
      Handler: login/src/login.handler
      FunctionName: !Sub ${Stage}-common-login
      Role: !GetAtt defaultRole.Arn
      LoggingConfig:
        LogGroup: !Sub ${LogGroupPrefix}/${Stage}-login
  
  LoginLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${LogGroupPrefix}/${Stage}-login
      RetentionInDays: !Ref LogRetentionInDays

Outputs:
  LoginLambdaArn:
    Value: !GetAtt Login.Arn
    Export:
      Name: !Sub ${Stage}-LoginLambdaArn
template-api.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: api-stack

Parameters:
  Service:
    Type: String
    Description: The name of the service being deployed
  Stage:
    Type: String
    Description: 環境名
  Region:
    Type: String
    Description: AWSリージョン
  Origin:
    Type: String
    Description: CORSのAllow-Origin設定値
  LoginLambdaArn:
    Type: String

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub ${Stage}-${Service}
      StageName: !Ref Stage
      EndpointConfiguration:
        Type: EDGE
      OpenApiVersion: 3.0.2
      Cors:
        AllowMethods: "'GET,POST,PUT,OPTIONS'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'"
        AllowOrigin: !Sub "'${Origin}'"
      DefinitionBody:
        openapi: '3.0'
        info: 
          title: !Sub ${Stage}-${Service}
          version: '1.0'
        paths:
          /login:
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LoginLambdaArn}/invocations
  LoginPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Sub ${LoginLambdaArn}
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGateway}/*/*"

template-api.yamlでAPIGatewayをOpenAPIの形式で作成しており、paths配下に/loginというURIを定義しAPIとLambdaの紐づけをしています。上記サンプルでは1つしか記載していないですが、URIを追加する際はpathsに記載していけば問題ありませんでした。

また、APIGatewayとLambdaの紐づけを自力で行っている為、APIGatewayからLambdaを実行する権限がありません。そのため、Permissionの設定も追加が必要です。

3. 他スタックの出力値をテンプレート内で参照すると、参照先のスタックが更新できない

この社内システムではLambda Layerを作成するスタックと、Lambdaを作成するスタックを分離させています。

  • service-api-layer-stack: Lambda Layerを作成
  • service-api-stack: Lambda関数を作成(上記Layerを使用)

そのため、template.yaml内で!ImportValue を用いて、service-api-layer-stackで作成したレイヤーのARNを参照するようにしていました。

template.yaml
  LambdaStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: template-lambda.yaml
      Parameters:
        ApiLayerArn: !ImportValue 
          Fn::Sub: develop-ApiLayerLambdaLayerArn

しかし、この記載方法でデプロイ後、service-api-layer-stackの更新を行おうとしたら以下のエラーが発生しました。

Update canceled. Cannot update export develop-ApiLayerLambdaLayerArn as it is in use by service-api-stack.

CloudFormationの仕様により、Export値を使用している間は、Export元のスタックを更新できません。レイヤー自体は定期的に更新があるため、更新できなくなることは問題です。
そのため、やり方を変更しました。samコマンド実行前にレイヤーのARNを取得し、sam deployコマンドの実行引数で渡すようにして解決しました。template.yaml側では!Ref ApiLayerArn と記載して参照できました。

API_LAYER_ARN=$(aws cloudformation describe-stacks \
  --stack-name "service-api-layer-stack" \
  --query 'Stacks[0].Outputs[?OutputKey==`develop-ApiLayerLambdaLayerArn`].OutputValue' \
  --output text)

sam build
sam deploy --config-env develop \
  --no-confirm-changeset \
  --no-fail-on-empty-changeset \
  --s3-bucket ${API_BUCKET} \
  --parameter-overrides \
    "ParameterKey=Stage,ParameterValue=develop" \
    "ParameterKey=ApiBucket,ParameterValue=${API_BUCKET}" \
    "ParameterKey=ApiLayerArn,ParameterValue=${API_LAYER_ARN}"

参考サイト

テンプレートの分割:

OpenAPIについて:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?