14
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【サーバーレス初心者向け】Serverless Framework + SwaggerでWeb APIを作る!第1回(全3回)

Last updated at Posted at 2020-07-29

はじめに

こんにちは!
最近、仕事で初めてサーバーレスアプリを作る機会がありました。その際Serverless Frameworkを使ったのですが、結構お手軽にアプリを作ることができたので、初めてサーバーレスをする方にお勧めだと思いました。今回は自分の整理も兼ねてその手順を記事として残したいと思います。
今回はSwaggerをAPI Gatewayのテンプレートの一部として組み込む方法もご紹介したいと思います。
全部で記事が三つと長いですが、サーバーレスが初めての方でもAPIを構築できるように書きましたので、最後までご覧いただけたら嬉しいです。

第二回、第三回の記事はこちら

今回作るもの

今回は以下のアーキテクト図のようなWeb APIバックエンドを作っていきます。
API GatewayでクライアントからのAPIリクエストを受信し、該当するLambda関数を呼び出し、必要に応じてDynamoDBからのデータ読み出しおよび書き込みを行います。さらに、WAFを適用することでセキュアにします。

slsTestAppArchitect.png

APIとしては、ID・名前・身長・体重・年齢の情報を持つPersonモデルを登録・取得・更新・削除するAPIを作りたいと思います。

  • GET dev/slsTestApp/v1/api/person/{personId}
  • POST dev/slsTestApp/v1/api/person
  • PUT dev/slsTestApp/v1/api/person/{personId}
  • DELETE dev/slsTestApp/v1/api/person/{personId}

第1回目の本記事では、具体的なAPIロジックは実装せず、簡単なメッセージを返すだけのLambda関数をバックエンドとするAPI Gatewayを作ります。WAFは第2回、DynamoDBを交えたロジック実装は第3回を予定しています。

Serverless FrameworkとSwagger

Serverless Frameworkとはサーバーレスアプリケーションの構成管理およびデプロイをするためのツールです。コマンド一つでデプロイできるため、とても使いやすいです。

SwaggerとはWeb APIの仕様を記述しドキュメント化するツールです。

本記事はSwaggerでAPI仕様を記載し、それをそのままAPI Gatewayのテンプレートとして取り込み、さらにそれをServerless Frameworkを使ってデプロイする一連の流れを解説したものです。Swaggerを使うことでAPIドキュメントがそのままインフラコードとして流用できるため、作業を効率化できます。

#事前準備

今回はAWSにデプロイしますので、IAMユーザーの作成をお願いします。アカウントIDを後ほど使います。

環境構築

それではまずはクライアント側の環境構築を行っていきます。
使用する環境・ツールは以下の通りです。

  • 環境

    • Windows10
  • ツール

    • Visual Studio Code v1.47.0
    • Node.js v12.18.2
    • AWS CLI v2
    • Serverless framework v1.74.1
    • Swagger Viewer v3.0.1

VSCode(Visual Studio Code)のインストール

今回はエディターとしてVisual Studio Codeを使用します。以下のリンクからインストールします。
Visual Studio Codeダウンロード

##Node.jsのインストール
Serverless Frameworkはnpmパッケージとして公開されていますので、インストールするためにはNode.jsが必要となります。以下のリンク先から皆さんの環境にあうインストーラーをダウンロードし実行してください。

Node.jsのインストール

##AWS CLIのインストール
以下からインストールします。

AWS CLIのインストール

インストール後、どこでもよいので適当な場所でコマンドプロンプトを起動し、以下のコマンドを実行して皆さんがお持ちのIAMユーザーの情報を登録してください。

> aws configure
AWS Access Key ID [None]: [自身のアクセスキーID]
AWS Secret Access Key [None]: [自身のアクセスキー]
Default region name [None]: ap-northeast-1
Default output format [None]:

Serverless Frameworkのインストール

どこでもよいので適当な場所でコマンドプロンプトを開き、以下のコマンドを実行してインストールします。
ちなみに、以下のコマンドの中で -g オプションを指定しているのは、グローバルに(どこでも使えるように)インストールするためです。

> npm install -g serverless

インストールが完了したら、以下のコマンドを実行します。

> serverless

すると、アプリを作る言語を何にするか聞かれます。今回はNode.jsを選択します。

> Serverless: What do you want to make? AWS Node.js

そのあと、作成するプロジェクトフォルダ名を何にするか聞かれますので、お好きな名前を設定してください。

> What do you want to call this project? test

最後に「serverless frameworkのアカウントを作るか?」と聞かれます。今回はアカウントを作らないのでNoを選択します。

You can monitor, troubleshoot, and test your new service with a free Serverless account.
Serverless: Would you like to enable this? No

これでServerless frameworkのインストールは完了です。

##Swagger Viewerのインストール
最後に、Swagger Viewerをインストールします。こちらはVSCodeのプラグインとして用意されています。
まず、VSCodeを起動し、Ctrl+Shift+Xでプラグインの検索窓を起動します。「Swagger Viewwer」と入力するとSwagger Viewerが見つかると思いますので、インストールボタンをクリックします。

Swagger_Viewer.PNG

これで、必要な環境は準備できました!

Serverless Frameworkの仕組み

本題に入る前にServerless Frameworkのデプロイの仕組みを簡単に説明します。
Serverless Frameworkはyml形式のテンプレートファイルに記載した内容をCloudFormationテンプレートに変換し、CloudFormationを実行することでAWSへサービスをデプロイします。
デプロイコマンドを実行すると、ローカルにある必要なファイルをCloudFormationテンプレートに変換後、zip化しS3のバケットに配置します。それをソースとしてCloudFormationを実行します。この時、AWS CLIに設定されたシークレット情報を参照しデプロイを行います。これがあるため、事前準備でAWS CLIをインストールしました。

Lambda関数をデプロイしてみる

では早速本題に入っていきます!
まずはHTTPリクエストなどは考えない簡単なLambda関数を実装しデプロイするところまでやってみます。
Serverlessアプリインストール時に指定したディレクトリに移動してみると、以下のファイルが作成されていると思います。

  • .gitignore → Gitの管理対象としないディレクトリ・ファイルを指定
  • handler.js → Lambda関数の記述したファイル
  • serverless.yml → Serverless Frameworkの定義ファイル

.gitignoreは今はそのままで結構です。ここではhandler.jsとserverless.ymlを修正していきます。

handler.js

今は以下のようになっていると思います。

handler.js(変更前)
'use strict';

module.exports.hello = async event => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event,
      },
      null,
      2
    ),
  };

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

上記はHttpリクエストを受け取ることを前提したコードになっています。しかし前述の通りまずはHTTPリクエストなどは考えないため、既存の部分はコメントアウトして以下のように受け取ったeventとともにシンプルにメッセージをリターンするだけの実装に変えます。

handler.js(変更後)
"use strict";

module.exports.hello = async (event) => {
  return {
    message: "Go Serverless v1.0! Your function executed successfully!",
    event,
  };
  /*
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event,
      },
      null,
      2
    ),
  };*/

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

serverless.yml

今は以下のようになっていると思います。

serverless.yml(変更前)

# Welcome to Serverless!
# 
# 中略
# Happy Coding!

service: slsTestApp

# 中略

provider:
  name: aws
  runtime: nodejs12.x

# you can overwrite defaults here
#  stage: dev
#  region: us-east-1

# 中略

functions:
  hello:
    handler: handler.hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#中略

主要な個所について説明していきます。

まずservice:の部分ですが、ここにデプロイするスタック名を定義します。今はserverlessコマンドを実行したときに指定したプロジェクト名がそのまま入っていると思います。

serverless(抜粋)
service: slsTestApp

最終的なスタック名はservie:にデプロイ対象のstageがハイフンで連結されたものになります。なので上記の例だとdev stageにデプロイした場合はスタック名はslsTestApp-devとなります。

次にprovider:ですが、こちらはデプロイする対象のクラウドプラットフォーム名とLambda関数を実装する言語の設定が記載されています。今回の場合はAWSとnode.jsなので、そのようになっていますね。

serverless.yml(抜粋)
provider:
  name: aws
  runtime: nodejs12.x

  # you can overwrite defaults here
  # stage: dev
  # region: ap-northeast-1

最後にfunctions:の部分です。ここで実装したLambda関数の定義を記載しています。
一つインデントを下げて任意の属性名を記載し、handler:にはLambda関数名を記載します。今回はhander.js内のhello関数を呼び出すので、hander.helloと記載さています。

serverless.yml(抜粋)
functions:
  hello:
    handler: handler.hello

では、serverless.ymlを少し修正していきます。
初めに、Serverless Frameworkでデプロイするstage名とリージョンのデフォルト値を設定します。provider:下の# you can overwrite defaults here直下二行のコメントアウトを外し、以下のように記載します。これで、デフォルトでap-northeast-1リージョンにdevステージとしてデプロイされるようになります。

serverless.yml(抜粋、修正後)
provider:
  name: aws
  runtime: nodejs12.x

  # you can overwrite defaults here
  stage: dev
  region: ap-northeast-1

次にhello関数の定義に少し追加します。name:description:を追加し、hello関数の名前と説明を記載してみてください。

serverless.yml(抜粋、修正後)
functions:
  hello:
    handler: handler.hello
    name: "slsTestApp-hello-${self:provider.stage}"
    description: "This function is test app."

ここで、name:${self:provider.stage}という文字列がありますが、これは変数指定の書き方です。先ほどのprovider:stage:に指定した値を変数として使用する書き方になります。こうすることで、stageごとにLambda関数を出しわけることができます。

では、試しにデプロイしてみます。serverless.ymlと同じ階層で以下のコマンドを実行してみてください。-vオプションをつけることで途中経過がコンソールに表示されるようになります。

sls deploy -v

以下のようにStack Outputsが最後に表示されればデプロイ成功です!

functions:
  hello: slsTestApp-hello-dev
layers:
  None

Stack Outputs
HelloLambdaFunctionQualifiedArn: …
ServerlessDeploymentBucketName: …

では、デプロイされたLambda関数を見てみましょう。

AWSマネージメントコンソールにログインし、Lambdaのページの「関数」メニューを選択します。すると、serverless.ymlで設定したLambda関数が表示されていると思います。

Lamda_testFunc.PNG

関数をクリックし、Lambda関数の詳細画面を表示します。この画面でLambda関数のテストをすることができますので、やってみます。
右上の「テストイベントの選択」をクリックし、「テストイベントの設定」をクリックします。
set_test_event.PNG

すると、テストで流し込むテストデータの設定画面が表示されます。
任意のイベント名を入力し、それ以外はデフォルトで保存します。

test_event.PNG

保存したら、「テスト」ボタンをクリックし実行します。
以下のような結果になれば成功です!

test_result.PNG

Swaggerテンプレートを作る

Lambda関数をServerless Frameworkを使ってデプロイするところまでできました。
次はAPIの実装を行っていくのですが、まずはSwaggerでAPIの仕様を書いてみましょう。
まずは、名前はなんでもよいのでAPI定義を記載するyamlファイルを作成してください。
私はtemplatesというフォルダを作りその下に「swagger.yaml」として作りました。こちらに定義を書いていきます。
swaggerの書き方を解説しようと思いましたが、すでにわかりやすい記事がありましたので、こちらをご参照ください。

Swagger記法まとめ

今回つくるもので記載したAPIのうち、GET APIの定義を書いたものが以下になります。

swagger.yaml
swagger: "2.0"
info:
  title: slsTestApp API
  description: API description of slsTestApp.
  version: 1.0.0
host: api.example.com
basePath: /dev
schemes:
  - https
tags:
  - name: person
    description: About person API

produces:
  - application/json

consumes:
  - application/json

paths:
  /slsTestApp/v1/api/person/{personId}:
    get:
      summary: Returns a person which matches id in path.
      description: Returns a person which matches id in path.
      tags:
        - person
      parameters:
        - name: personId
          in: path
          description: person id which you want to get.
          required: true
          type: "string"
      responses:
        200:
          description: OK
          schema:
            $ref: "#/definitions/producePersonModel"
        404:
          description: Not Found
          schema:
            $ref: "#/definitions/error"

definitions:
  consumePersonModel:
    type: object
    properties:
      name:
        type: "string"
      weight:
        type: "string"
      age:
        type: "string"
  producePersonModel:
    type: object
    properties:
      personId:
        type: "string"
      name:
        type: "string"
      weight:
        type: "string"
      age:
        type: "string"
  error:
    type: object
    properties:
      errorCode:
        type: "string"
      errorMessage:
        type: "string"

これをプレビューするには、VSCode上でF1キーを押し、Preview Swaggerと入力しEnterを押します。以下のような画面が表示されると思います。
Swagger_Get.PNG

Swagger定義を取り込んだAPI Gatewayのデプロイ

先ほど作ったSwaggerをAPI Gatewayのテンプレートとして取り込みデプロイするかを解説していきます。
やることは大きく以下の四つです。

  • hello関数の修正
  • swagger.yamlにAPI Gateway統合の設定を追記する
  • API Gatewayのテンプレートファイルを作成する(Bodyにswaggerファイルを指定)
  • serverless.ymlにテンプレートファイルへのパスを記載する

hello関数の修正

先ほどデプロイしたhello関数をHttpリクエストを受け付けられるように変更していきます。
まずはsrc/lamda/getPersonフォルダを作成し、そこにhandler.jsを移動します。さらに、handler.jsからindex.jsに変更し、関数名もhelloからhandlerに変えます。個人的にわかりやすい構成に変えているだけですので、必ずしもこれに倣う必要はありません。
さらに実装を以下のように変更します。これでindex関数が呼ばれるとcallback内のstatusCodeとbodyが返却されるようになります。

src/lambda/getPerson/index.js
"use strict";

module.exports.handler = async (event, context, callback) => {
  callback(null, {
    statusCode: 200,
    body: JSON.stringify({
      message: "Go Serverless v1.0! Your function executed successfully!",
    }),
  });
};

ファイルパスと関数名を変更したので、serverless.ymlも修正していきましょう。私と同じファイルパス、関数名にした場合は以下のようになります。違う名前にした場合はそちらに変更してください。

serverless.yml(抜粋)
functions:
  getPerson:
    handler: src/lambda/getPerson/index.handler
    name: "slsTestApp-getPerson-${self:provider.stage}"
    description: "This function is test app."

ここまでできたら、一度デプロイしておきましょう。

sls deploy -v

swagger.yamlにAPI Gateway統合の設定を追記する

swagger.yamlにAPI Gateway統合の設定を記載していきます。今回は最低限の設定のみ記載することとします。

以下のようにメソッド定義の下にx-amazon-apigateway-integration:を記載し、必要なプロパティを設定していきます。

swagger.yaml(抜粋)
/slsTestApp/v1/api/person/{personId}:
    get:
      summary: Returns a person which matches id in path.
#中略
      x-amazon-apigateway-integration:
        type: aws_proxy
        responses:
          default:
            statusCode: "200"
        uri: "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:{Your Account ID}:function:{Lambda function name}/invocations"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"

簡単に解説します。

属性名 説明
type API Gatewayとの統合タイプを指定します。今回はAPI Gatewayのリクエスト処理をするのがLambda関数なので、aws_proxyを設定します
response APIレスポンスのオブジェクトを指定します。今回は細かく設定しないので、上記のコード例のようにしています
uri APIのバックエンドのエンドポイントを指定します。今回はLambda関数のuriを指定しています。上記のコード例にAWS Account IDとLambda関数名を入れてください。
httpMethod 呼び出されるHttpメソッドを指定しますが、Lambda関数をバックエンドとして使う場合は常にPOSTを指定します
contentHandling リクエストのペイロードのエンコードタイプを指定します。今回はテキストとして扱いたいのでCONVERT_TO_TEXTを指定します

これでswagger.yamlの修正は完了です。uri:のAWS Account IDと関数名はみなさんが設定したものを入力してくださいね。

API Gatewayのテンプレートファイルを作成する

API GatewayのCloudFormationのテンプレートファイルを作っていきます。
API Gatewayのデプロイには二段階あります。一つはリソース(API定義)のデプロイ、もう一つはデプロイしたリソースをステージにデプロイすることです。ステージにデプロイすることでAPIがweb経由で利用できるようになります。
今回のテンプレートではリソースのデプロイとdev ステージへのリソースデプロイを同時に行うテンプレートを作成します。
templates/に以下のテンプレートファイルを作成します。

api-gateway.yml

Resources:
  ApiGatewayRestApi:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Body: ${file(./templates/swagger.yaml)}
  GetPersonApiPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      FunctionName: "slsTestApp-getPerson-${self:provider.stage}"
      Action: "lambda:InvokeFunction"
      Principal: "apigateway.amazonaws.com"
  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: ApiGatewayRestApi
      StageName: ${self:provider.stage}
リソース名 説明
ApiGatewayRestApi API Gateway自体のリソース定義です。Bodyには先ほどのswagger.yamlへのパスを記載します
GetPersonApiPermission API GatewayからLambda関数を実行するための権限です。FunctionNameをserverless.ymlの関数名と一致させます
ApiGatewayDeployment API GatewayのAPI定義をステージへデプロイするための設定です
これでAPI Gatewayのテンプレート作成が完了しました。swagger定義を取り込んだAPI Gatewayが作成されるようになります。

serverless.ymlにテンプレートファイルへのパスを記載する

serverless.ymlに先ほど作成したAPI Gatewayのテンプレートファイルへのパスを記載していきます。
以下のようにresourcesに配列形式でテンプレートファイルへのパスを記載します。

serverless.yml(抜粋)
# you can add CloudFormation resource templates here
resources:
  - ${file(./templates/api-gateway.yml)}

これで準備は整いました!ではデプロイしてみましょう。

sls deploy -v

動作確認

これでGET APIが実装されているはずですので、実際にAPIを実行してみたいと思います。
まずは、API GatewayのURLをAWSマネージメントコンソールから調べます。
API Gatewayのメニューを開くと先ほどデプロイしたAPI Gatewayがあると思いますので、そちらをクリックします。
api-gateway-list.PNG

左のメニューから「ステージ」を選択し、中央に表示される「dev」をクリックすると、右にURLが表示されます。これにswaggerで定義したURIを連結したものが、最終的なAPIのURIとなります。
api-gateway-stg.PNG

このURLをコピーしておきます。

では、動作確認をしてみましょう。今回はcurlコマンドを使います(別途インストールが必要です)。

以下のコマンドを実行します。

curl GET https://{Your Api gateway Id}.execute-api.ap-northeast-1.amazonaws.com/dev/slsTestApp/v1/api/person/aaa

以下のようなレスポンスが返ってくれば成功です。

{"message":"Go Serverless v1.0! Your function executed successfully!"}

Lambdaのデプロイパッケージを削減する

AWSマネージメントコンソールでLambda関数を見てみると、関数の欄が以下のようになっていると思います。
Lambda_funcCode.PNG

これはLambda関数の実行には本来不要なほかのコードも含まれてしまっているからです。

これを防ぐには、serverless.ymlにexclude設定を追加し、不要なファイルがパッケージされないようにします。

serverless.yml(抜粋)
# you can add packaging information here
package:
  exclude:
    - docs/**
    - node_modules/**
    - templates/**
    - .eslintrc.json
    - LICENSE
    - README.md
    - package-lock.json

もう一回デプロイしてみると、以下のように必要なものだけパッケージされてるのが分かります。

Lambda_funcCode_modified.PNG

デプロイしたリソースの削除

デプロイしたAPI GatewayにはまだWAFを適用していないので、だれでもAPIにアクセスできる状態です。使っていないときはデプロイしたリソースを削除しておきましょう。

削除するときは以下のコマンドを実行します。

sls remove

再デプロイ時の注意点

再デプロイすると、デプロイに失敗すると思います。
これは、Lambda関数がデプロイされる前にAPI Gatewayがデプロイされるため、API Gatewayで参照されているLambda関数がないためエラーになっています。
ちゃんとした解決策はあると思いますが、ひとまず今は最初にserverless.yml内でapi-gateway.ymlを読み込んでいる箇所をコメントアウトして一度デプロイし、そのあとにコメントアウトを外してデプロイしてみてください。

おわりに

今回はごく簡単なメッセージを返すだけのLambda関数を実行するAPIを作りました。
バックエンドの大枠はできつつあるので、次回はWAFを適用し、特定のIPからしかAPIにアクセスできないようにします。

※WAF構築はこちら↓
第2回 WAF構築編

14
14
1

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
14
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?