Help us understand the problem. What is going on with this article?

AWS SAM で Hello World する

More than 1 year has passed since last update.

目的

AWS Lambda を使ってサーバーレスアプリケーションを開発するために、まずは AWS SAM で Hello World してみます。

具体的には、Lambda のテストを実行して、ローカルで動かして、デプロイまで行います。

前提知識

前提となる知識について、簡単に書いておきます。

AWS Lambda とは

いわゆる FaaS(Function as a Service) というやつです。関数を書いて置いてあげるだけで、サーバーを意識することなく実行してくれます。

AWS Lambda は AWS のサービスですが、Google がやっている Google Cloud Functions や Microsoft の Azure Functions などの FaaS もあります。

AWS SAM とは

AWS Lambda は AWSマネジメントコンソール からポチポチ作成できます。コードも管理画面から書けるのですが、それだと Git 管理ができません。そこで、 Serverless Framework や AWS SAM などのツールを使うと、ローカルでコードを書き、コマンド一発でデプロイまでできるようになります。Serverless Famework は AWS 専用ではなく、他のクラウドサービスにも対応しています。前職ではこちらを使っていました。

今回は、AWS が公式で出している AWS SAM を使ってみようと思います。

ちなみに AWS サーバーレスアプリケーションモデル (AWS SAM) の使用 - AWS Lambda によると、

AWS サーバーレスアプリケーションモデル (AWS SAM) はサーバーレスアプリケーションを定義するモデルです。

と書いてあります。

AWS SAM CLI とは

元々は AWS SAM Local と呼ばていたやつです。ローカルで Lambda 関数を実行して動作確認ができます。
コードを修正する度にデプロイして動作確認をするのは大変なので、重宝します。

この記事でやること

  • AWS SAM CLI で Hello, World する
  • テストを実行する
  • AWS にデプロイして動かす

環境

  • macOS High Sierra 10.13.6
  • AWS SAM CLI
  • Node.js 8.10
  • yarn 1.9.4

Hello World までの手順

AWS SAM CLI をインストールする

Installing the AWS SAM CLI - AWS Serverless Application Model によると

SAM CLI を最も簡単にインストールするには pip を使用します。

とのことです。

pip は Python のパッケージマネージャです。Python がインストールされていて pip が使えれば良いと思いますが、一応 Python の最新安定版を入れます。
Python Release Python 3.7.0 | Python.org によると 3.7.0 みたいです。

※ 私は Python のバージョン切り替えに pyenv を使っています。Ruby の rbenv のように使えるので、Rubyist にはオススメです。

# pyenv の upgrade
$ pyenv --version
pyenv 1.2.5
$ brew upgrade pyenv
$ pyenv --version
pyenv 1.2.7

# Python 3.7.0 のインストール
$ pyenv install -l | grep 3.7.0 # インストールできるバージョンの確認
  3.7.0
  miniconda-3.7.0
  miniconda3-3.7.0
$ pyenv install 3.7.0

pyenv install 3.7.0 でエラーになった

$ pyenv install 3.7.0
python-build: use openssl from homebrew
python-build: use readline from homebrew
Downloading Python-3.7.0.tar.xz...
-> https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz
Installing Python-3.7.0...
python-build: use readline from homebrew

BUILD FAILED (OS X 10.13.6 using python-build 20180424)

Inspect or clean up the working tree at /var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578
Results logged to /var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578.log

Last 10 log lines:
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__main__.py", line 5, in <module>
    sys.exit(ensurepip._main())
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__init__.py", line 204, in _main
    default_pip=args.default_pip,
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__init__.py", line 117, in _bootstrap
    return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__init__.py", line 27, in _run_pip
    import pip._internal
zipimport.ZipImportError: can't decompress data; zlib not available
make: *** [install] Error 1

zipimport.ZipImportError: can't decompress data; zlib not available が怪しいのでググってみる。

Common build problems · pyenv/pyenv Wiki · GitHub によると

try reinstalling XCode command line tools for your OS (especially if you just upgraded your OS)

のために xcode-select --install を実行してくれとのこと。
たしかに、最近 OS アップデートした気がする。

$ xcode-select --install

改めて

$ pyenv install 3.7.0
$ pyenv rehash
$ pyenv versions
  system
  3.6.3
* 3.7.0 (set by /Users/yuichiro/.pyenv/version)
$ python --version
Python 3.7.0

無事 Python 3.7.0 が入った

AWS SAM CLI をインストールします。

$ pip install aws-sam-cli
$ sam --version
SAM CLI, version 0.6.0

sam init

$ sam init --runtime nodejs8.10
[+] Initializing project structure...
[SUCCESS] - Read sam-app/README.md for further instructions on how to proceed
[*] Project initialization is now complete

Read sam-app/README.md for further instructions on how to proceed と書いてあるので、以降は README を読んで進めてみる。

テストを実行してみる

$ tree sam-app/
sam-app/
├── README.md
├── hello_world
│   ├── app.js
│   ├── package.json
│   └── tests
│       └── unit
│           └── test_handler.js
└── template.yaml

依存関係管理に npm だけでなく yarn も使えるらしいので、今回は yarn を使う。
一応、upgrade してから yarn install する。

$ yarn --version
1.7.0
$ brew upgrade yarn
$ yarn --version
1.9.4
$ cd hello_world/
$ yarn install

テスト実行

$ yarn run test
yarn run v1.9.4
$ mocha tests/unit/


  Tests index
    1) verifies successful response


  0 passing (7ms)
  1 failing

  1) Tests index
       verifies successful response:
     TypeError: app.lambda_handler is not a function
      at Context.it (tests/unit/test_handler.js:11:34)



error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

落ちた・・・。

$ git diff
diff --git a/hello_world/tests/unit/test_handler.js b/hello_world/tests/unit/test_handler.js
index 363c192..cf4dcd2 100644
--- a/hello_world/tests/unit/test_handler.js
+++ b/hello_world/tests/unit/test_handler.js
@@ -8,7 +8,7 @@ var event, context;

 describe('Tests index', function () {
     it('verifies successful response', async () => {
-        const result = await app.lambda_handler(event, context, (err, result) => {
+        const result = await app.lambdaHandler(event, context, (err, result) => {
             expect(result).to.be.an('object');
             expect(result.statusCode).to.equal(200);
             expect(result.body).to.be.an('string');
@@ -21,4 +21,3 @@ describe('Tests index', function () {

         });
     });
 });

なんと関数名が間違っていたので、修正。

x lambda_handler
o lambdaHandler

$ yarn run test
yarn run v1.9.4
$ mocha tests/unit/


  Tests index
    ✓ verifies successful response (523ms)



  1 passing (530ms)

✨  Done in 0.93s.

通った!

ローカルで API Gateway 経由で Lambda 関数を呼び出す

# プロジェクトルートで
$ sam local start-api
Error: Running AWS SAM projects locally requires Docker. Have you got it installed?

おっと、 Docker が必要だった。インストールはしているので、起動してあげる。

$ sam local start-api
2018-09-15 23:16:55 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-09-15 23:16:55 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
2018-09-15 23:16:55  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

ブラウザから http://127.0.0.1:3000/hello にアクセスする。

2018-09-15 23:17:30 Invoking app.lambdaHandler (nodejs8.10)

Fetching lambci/lambda:nodejs8.10 Docker container image...............................

最初は時間かかるのかな?
ブラウザに以下が表示されたので、OK!!

{"message":"hello world","location":"xxx.xxx.xxx.xxx"}

定義はここでされているみたい。

template.yaml
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.lambdaHandler
            Runtime: nodejs8.10
            Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
                Variables:
                    PARAM1: VALUE
            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

デプロイして動作確認

Zip化された Lambda 関数を置くための S3 パケットが必要なので、予め用意しておく。

※ デプロイするには、AWS CLI の設定が必要です。今回は割愛させていただきますが、以下をご参照ください。
AWS CLI をインストールする - Qiita

# プロジェクトルートで
$ sam package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket YOUR_S3_BUCKET_NAME
$ sam deploy \
    --template-file packaged.yaml \
    --stack-name sam-app \
    --capabilities CAPABILITY_IAM

以下を実行すると、API Gateway のエンドポイントがわかる

$ aws cloudformation describe-stacks \
    --stack-name sam-app \
    --query 'Stacks[].Outputs'
[
    [
        {
            "OutputKey": "HelloWorldFunctionIamRole",
            "OutputValue": "xxx",
            "Description": "Implicit IAM Role created for Hello World function"
        },
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://xxx.execute-api.xxx.amazonaws.com/Prod/hello/",
            "Description": "API Gateway endpoint URL for Prod stage for Hello World function"
        },
        {
            "OutputKey": "HelloWorldFunction",
            "OutputValue": "xxx",
            "Description": "Hello World Lambda Function ARN"
        }
    ]
]

https://xxx.execute-api.xxx.amazonaws.com/Prod/hello/ にブラウザからアクセスすると、以下が表示されたのでOK!!

{"message":"hello world","location":"xxx.xxx.xxx.xxx"}

終わりに

前職で初めて AWS Lambda を触ったとき、正直訳がわからなかったです。
その上 Serverless Framework とか・・・
は?って感じでした。

この記事は初心者向けとは言えないかもしれませんが、少しでもお役に立てれば幸いです。

mokuo
Webエンジニアをやっています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away