AWS SAMを利用してAPI GatewayとLambda Authorizer(Golang)の構築をやってみた

f:id:kaminashi-developer:20210330105218j:plain

こんにちは、株式会社カミナシのエンジニア @Taku です。

前回、GolangでLambda Authorizer用関数をやってみましたが、 Golangはコードエディタが使えず、テストするには毎回zipで固めてアップするという一手間がありました。 kaminashi-developer.hatenablog.jp

そんな中、先日「AWS SAMを使うとAPI Gateway等とまとめて作成できるので便利だよ!」という話を伺ったので今回使ってみることとしました。

AWS SAMとは

aws.amazon.com

YAMLで記載したテンプレートを利用して、CLIにてサーバーレスのアプリケーションを構築できるオープンソースフレームワークです。

例として、以下のようなYAMLを作成することでサーバーレスのアプリケーションを構築できます。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

(チュートリアルsam initした際に作成されるもの)

利用するメリットとして、

  • サーバーレスのアプリケーションの一元管理
  • Lambdaに似た実行環境をローカルに構築することができるので、テストのために逐一アップロードするという手間が省ける

ということがあると思います。

CloudFormation の拡張機能であるとのことで、同じように記載ができるので、そちらに馴染みがある方は比較的簡単に利用できるのではないかと思われます(ただし、AWS SAMはJSONでの記載は不可でYAMLのみ)

詳細なテンプレートの記載方法については以下をご参照ください。

docs.aws.amazon.com

AWS SAM CLIのインストール

AWS SAMを利用するにはまずCLIをインストールする必要があります。

前提としてAWSアカウントや、ローカルで起動するためにはDockerのインストールが必要となりますので、以下を参照してご自身の環境に合わせてセットアップください。

docs.aws.amazon.com

前回やったサーバーレスアプリケーションをSAMで構築

続いて、早速ですが前回作成したものをSAMテンプレートを利用して作成していきます。

軽いおさらいとして、前回の構成は以下です。

f:id:kaminashi-developer:20210224131025p:plain

今回はこの中から、

AWS SAMで構築していきます。

詳細は省きますが、チュートリアルや以下の公式ドキュメントを参考に設定しました、

※日本語版のドキュメントは作成されておらず自動翻訳となっているため、読んでてわかりづらい箇所は英語のまま読む方が良いと感じました。

今回実際に作成したテンプレートは以下です。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description:  Sample API Gateway Lambda Authorizer by Golang.

Resources:
  # API Gatewayの作成
  SampleRestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: dev
      Description: "This is Sample Rest API"
      Cors:
        AllowMethods: "'GET, OPTIONS'"
        AllowHeaders: "'*'"
        AllowOrigin: "'*'"
      Auth:
        DefaultAuthorizer: MyLambdaAuthorizerFunction
        Authorizers:
          MyLambdaAuthorizerFunction:
            FunctionArn: !GetAtt LambdaAuthorizerFunction.Arn
  # API Gatewayのレスポンス作成
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /
            Method: get
            RestApiId:
              Ref: SampleRestApi
      Runtime: python3.7
      Handler: index.handler
      InlineCode: |
        def handler(event, context):
            return {'body': 'Hello World!', 'statusCode': 200}
  # LambdaAuthorizer用の関数の作成
  LambdaAuthorizerFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: samAuth
      CodeUri: ./lambda_auth # buildされるが、configがアップされなかったので、`/build/関数名`のディレクトリに手動で配置
      Handler: app.lambda_handler
      Runtime: go1.x

# SAMでdeployした後、コンソールに表示するもの(URL表示したり)
Outputs:
  SampleRestApi:
    Description: "Sample API Gateway Lambda Authorizer by Golang."
    Value: !Sub "https://${SampleRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/"

前回とちょっと違う点として、API GatewayのエンドポイントにMockを指定する方法を見つけられなかったのでLambda Fanctionを利用して簡単なレスポンスを返す関数を置いてみました。

  # API Gatewayのレスポンス作成
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /
            Method: get
            RestApiId:
              Ref: SampleRestApi
      Runtime: python3.7
      Handler: index.handler
      InlineCode: |
        def handler(event, context):
            return {'body': 'Hello World!', 'statusCode': 200}

チュートリアルのサンプルを元にしたものですが、SAMであればこれもサクッと追加できるのが良いですね。

ビルド & デプロイ

テンプレートの作成が完了しましたら、ビルド及びデプロイをやっていきます。

ビルド

ビルドは作成したテンプレートが存在するディレクトリで sam build コマンドを実行します。

-> % sam build
Building codeuri: /Users/takuya/dev/new_technology_learning/lambda/sam/api-gateway-authorizer-golang/lambda_auth runtime: go1.x metadata: {} functions: ['LambdaAuthorizerFunction']
Running GoModulesBuilder:Build

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

ハマったポイントとして、前回はLambdaAuthorizer用のファイルはzipでまとめてアップすれば良かったのですが、SAMですと設定が悪いのかビルドするディレクトリを指定してもその中のconfigがアップされませんでした。 (ビルド&デプロイはできるが、Lambda関数実行する際にconfigが指定箇所に存在しないエラーとなる)

  # LambdaAuthorizer用の関数の作成
  LambdaAuthorizerFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: samAuth
      CodeUri: ./lambda_auth # buildされるが、configがアップされなかったので、`/build/関数名`のディレクトリに手動で配置
      Handler: app.lambda_handler
      Runtime: go1.x

そのため今回は、デプロイ前にビルド後作成された .aws-sam/build ディレクトリ配下の、生成されたLambda関数のディレクトリに手動でconfigを配置することで対応しました。

-> % tree -a .
.
├── .aws-sam
│   ├── build
│   │   ├── LambdaAuthorizerFunction
│   │   │   ├── app.lambda_handler
│   │   │   └── config
│   │   │       └── jwt-lambda-auth-firebase-adminsdk-3sa6r-c2e0d97f93.json // ここに手動で配置
│   │   └── template.yaml
│   └── build.toml
├── lambda_auth
│   ├── config
│   │   └── jwt-lambda-auth-firebase-adminsdk-3sa6r-c2e0d97f93.json
│   ├── go.mod
│   ├── go.sum
│   └── main.go
├── samconfig.toml
├── template.yaml
└── web
    ├── js
    │   └── config.js
    ├── login.html
    └── pets.html

デプロイ

ビルドが完了しましたら同じディレクトリで sam deploy --guided コマンドを実行します。

 -> % sam deploy --guided


Configuring SAM deploy
======================

    Looking for config file [samconfig.toml] :  Found
    Reading default arguments  :  Success

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sample-auth]:
    AWS Region [ap-northeast-1]:
    #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
    Confirm changes before deploy [Y/n]: Y
    #SAM needs permission to be able to create roles to connect to the resources in your template
    Allow SAM CLI IAM role creation [Y/n]: Y
    Save arguments to configuration file [Y/n]: Y
    SAM configuration file [samconfig.toml]:
    SAM configuration environment [default]:

    Looking for resources needed for deployment: Found!
...

CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key                 SampleRestApi
Description         Sample API Gateway Lambda Authorizer by Golang.
Value               https://x7ow8vkh09.execute-api.ap-northeast-1.amazonaws.com/dev/
-------------------------------------------------------------------------------------------------

Successfully created/updated stack - sample-auth in ap-northeast-1

正常に終了した場合、AWS(API Gateway、CloudFormation、Lambda)を確認するとリソースが追加されているかと思います。

ローカルで起動

ローカルでテストしたい場合、ビルド完了後 sam local start-api コマンドでDockerを利用したシミュレーターを起動してテストすることができます。

 -> % sam local start-api
Mounting ApiFunction at http://127.0.0.1:3000/ [GET, OPTIONS]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2021-03-30 08:33:52  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

テストをしながらLambda関数を作成する場合これは便利かと思います。

おわりに

AWS SAMを使ってみて、私自身まだLambda関数やCloudFormationに慣れていないこともあり、最初はテンプレート作成に時間がかかりましたが、慣れてしまうとまとめての構築やローカルでテストができるため便利なものかと思いました。

また、サーバーレスアプリケーションを構築するには、 Serverless Framework というツールもあるとこことのため、こちらも時間がある際に試してみたいと考えてます。

www.serverless.com

(Freeのオープンソース版と有料のPro版があり)

最後までご覧いただきありがとうございました。

不備やこうした方が良いよ!というのがあればコメント等いただけると幸いです。