AWS SAMで構築したアプリケーションをデプロイする


こんにちは。T.A.です。

前回AWS SAMを用いてLambda + API Gatewayのローカルテストを行いました。
xp-cloud.jp
今回はその続きで、SAMで構築したサーバレスアプリケーションのデプロイを試してみます。

前回のおさらい

  • AWS SAMの概要紹介
  • テンプレートをローカルで動作確認
  • APIを作成し、ローカルで動作確認

今回やること


今回は前回と同様のAPIをnode.jsで作成し、SAM CLIからAWS上にデプロイしてみます。

※Go言語でない理由は巻末に記載します

実践


前回と同様にテンプレートにlambda関数とAPIを追加したものを作成します。
重複する手順がありますので一部省略します。

アプリケーション作成

$ sam init -r nodejs12.x -n sam-node-app #ランタイムをnode.jsにします。
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
        1 - Hello World Example
        2 - Step Functions Sample App (Stock Trader)
        3 - Quick Start: From Scratch
        4 - Quick Start: Scheduled Events
        5 - Quick Start: S3
        6 - Quick Start: SNS
        7 - Quick Start: SQS
        8 - Quick Start: Web Backend
Template selection: 1

# 以後の出力内容は省略

Lambda関数作成


以下のファイルを新規作成・編集します。


新規作成するファイル

  • sam-node-app/
    • hello-req/ (新規作成)
      • app.js (新規作成)
      • package.json (hello-world/package.jsonをコピー)
    • hello-str/ (新規作成)
      • app.js (新規作成)
      • package.json (hello-world/package.jsonをコピー)


hello-req/app.js

let response;

exports.lambdaHandler = async (event, context) => {
    try {
        // const ret = await axios(url);
        response = {
            'statusCode': 200,
            'body': 'hello ' + event.pathParameters.req,
        }
    } catch (err) {
        console.log(err);
        return err;
    }
    return response
};


hello-str/app.js

let response;

exports.lambdaHandler = async (event, context) => {
    try {
        // const ret = await axios(url);
        response = {
            'statusCode': 200,
            'body': 'hello ' + event.queryStringParameters.str,
        }
    } catch (err) {
        console.log(err);
        return err;
    }
    return response
};

template.yamlの編集


template.yamlを以下のように編集します。

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

  Sample SAM Template for sam-node-app-asami
  
application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    #変更なしのため省略

  # 追加
  HelloStrFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-str/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloStr:
          Type: Api
          Properties:
            Path: /hello/str
            Method: get
            RequestParameters: 
              "method.request.path.str":
                Required: true
                Caching: false

  # 追加
  HelloReqFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-req/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloReq:
          Type: Api
          Properties:
            Path: /hello/req/{req}
            Method: get
            RequestParameters: 
              "method.request.path.req" : "integration.request.path.req"

Outputs:
  HelloWorldApi:
    # 変更なしのため省略
  # 追加
  HelloStrApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello Str function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/str/"
  HelloStrFunction:
    Description: "Hello Str Lambda Function ARN"
    Value: !GetAtt HelloStrFunction.Arn
  HelloStrFunctionIamRole:
    Description: "Implicit IAM Role created for Hello Str function"
    Value: !GetAtt HelloStrFunctionRole.Arn

  # 追加
  HelloReqApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello Req function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/req/{req}/"
  HelloReqFunction:
    Description: "Hello Req Lambda Function ARN"
    Value: !GetAtt HelloReqFunction.Arn
  HelloReqFunctionIamRole:
    Description: "Implicit IAM Role created for Hello Req function"
    Value: !GetAtt HelloReqFunctionRole.Arn

ローカルで動作確認


デプロイ前にローカルで動作確認します。

$ sam build
$ sam local start-api # 先にDockerを起動する


前回と同様にGETリクエストを投げて正常なレスポンスが返ってくれば問題ありません。

デプロイ


本題のデプロイを実行します。

sam deployAWS CLIのプロファイルを参照してデプロイを行います。
プロファイルの設定をしていない場合、設定を行います。

$ aws configure --profile



以下のように、sam deployを実行します。

$ sam deploy --guided

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

        Looking for config file [samconfig.toml] :  Not found

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: sam-node-app-test ## デプロイするスタック名
        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
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y ## 認証なしAPIで問題ないか確認してくる
        HelloStrFunction may not have authorization defined, Is this okay? [y/N]: y
        HelloReqFunction may not have authorization defined, Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: y
        SAM configuration file [samconfig.toml]: y
        SAM configuration environment [default]:

## 中略

Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

## 中略

## デプロイされたリソースの情報が出力される。
CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------
Key                 HelloReqApi
Description         API Gateway endpoint URL for Prod stage for Hello Req function
Value               https://**********.execute-api.[指定したリージョン].amazonaws.com/Prod/hello/req/{req}/

Key                 HelloStrFunction
Description         Hello Str Lambda Function ARN
Value               arn:aws:lambda:[指定したリージョン]:*************:function:sam-node-app-test-HelloStrFunction-*************

Key                 HelloReqFunctionIamRole
Description         Implicit IAM Role created for Hello Req function
Value               arn:aws:iam::*************:role/sam-node-app-test-HelloReqFunctionRole-*************

Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::*************:role/sam-node-app-test-HelloWorldFunctionRole-*************

Key                 HelloReqFunction
Description         Hello Req Lambda Function ARN
Value               arn:aws:lambda:[指定したリージョン]:*************:function:sam-node-app-test-HelloReqFunction-*************

Key                 HelloWorldApi
Description         API Gateway endpoint URL for Prod stage for Hello World function
Value               https://**********.execute-api.[指定したリージョン].amazonaws.com/Prod/hello/

Key                 HelloStrApi
Description         API Gateway endpoint URL for Prod stage for Hello Str function
Value               https://**********.execute-api.[指定したリージョン].amazonaws.com/Prod/hello/str/

Key                 HelloStrFunctionIamRole
Description         Implicit IAM Role created for Hello Str function
Value               arn:aws:iam::*************:role/sam-node-app-test-HelloStrFunctionRole-*************

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:[指定したリージョン]:*************:function:sam-node-app-test-HelloWorldFunction-*************
---------------------------------------------------------------------------------------------------------------------

CloudFormationの確認


CloudFormationのコンソールから
設定した名前のスタックが存在することを確認します。


※同時にaws-sam-cli-managed-defaultというスタックも作成されます。
 このスタックはsam deployの過程で作成され、以下が含まれます。

動作確認


PostmanでAPIにGETリクエストを投げてみます。
URLはsam deployでデプロイ後に標準出力されています。


reqとstrは任意の文字列をパスパラメータとクエリストリングに設定してリクエストを投げます。


https://**********.execute-api.[指定したリージョン].amazonaws.com/Prod/hello/
※Go言語のテンプレートと異なり、jsonが返ってきます。



https://**********.execute-api.[指定したリージョン].amazonaws.com/Prod/hello/req/{req}/



https://**********.execute-api.[指定したリージョン].amazonaws.com/Prod/hello/str/


作成したAPIが正常に動作していることが確認できました。




正常にサーバレスアプリケーションのデプロイができました!

Go言語とsam deployに関する問題


前回のGo言語のサーバレスアプリケーションもデプロイして動作確認しましたが、
デプロイしたGo言語のLambda関数が実行ができませんでした。

原因が特定できなかったため、起きた問題・調べたこと等を以下に書いていきます。

やったこと


※前回行った手順は省略

hellosam/template.yamlの編集


前回作成したhellosamアプリのテンプレートを編集します。
Outputsの部分を追記するだけですので省略します。

デプロイ


sam buildsam deployを実行します。
手順は本編と同様ですので省略します。


動作確認


本編と同様にGETリクエストを投げます。


いずれのAPIからも502が返ってきます。

コンソールでLambdaテストイベント作成


以下の手順でLambdaのテストイベントを作成します。

  1. AWSのコンソールからLambdaを開く
  2. 今回デプロイされたLambda関数を開く
    [スタック名]-HelloWorldFunction-[ランダムに設定された文字列] という名前の関数です。
  3. HelloWorldFunctionのテストイベントを作成する。
    HelloWorldはイベントからパラメータを受け取らないのでイベント内容は適当でいいです。
  4. テストを押下


"fork/exec /var/task/hello-world: permission denied"
というエラーメッセージが表示されました。

作成したHelloStr,HelloReqも同様の結果になりました。

調べてわかったこと


Go言語のLambda関数は、Lambdaにアップロードしたバイナリファイルの実行権限がないとこのエラーが発生するようです。

以下の手順でデプロイした場合、Lambda関数を正常に実行することができました。

  1. set GOOS=linux
  2. set GOARCH=amd64
  3. go build [goファイル名]
  4. go get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip
  5. build-lambda-zip -o [任意のファイル名].zip [go buildで出力されたバイナリ名]
  6. 作成されたzipファイルをコンソールからLambdaにアップロード



参考文献:https://blog.narumium.net/2019/03/02/【go】aws-lambdaでgoで作ったバイナリを実行する/
https://github.com/aws/aws-sam-cli/issues/389

原因の推測


sam deployの過程で作成されたGo言語のバイナリに実行権限がないままデプロイされてしまうのではないかと考えています。
その結果、Lambda関数実行時に"permission denied"が出力されてしまったのではないかと思います。

終わりに


今回はSAM CLIからスタックのデプロイを試しました。

SAMを用いれば設計・構築・デプロイまでの作業をAWSコンソールを使わずに行うことができ、サーバレスアプリケーションの開発効率が向上します。

前回試したローカルテストも合わせると、SAMを用いて開発を行うメリットは大きいです。

しかし、Go言語によるLambda関数のデプロイは単にsam deployを実行するのみではうまくできませんでした。

Go言語のデプロイについて解決策が見つかりましたら改めて記事にしたいと思います。


著者紹介

アプリケーション開発バナー

AWS相談会バナー