はじめに
こんにちは!
最近、仕事で初めてサーバーレスアプリを作る機会がありました。その際Serverless Frameworkを使ったのですが、結構お手軽にアプリを作ることができたので、初めてサーバーレスをする方にお勧めだと思いました。今回は自分の整理も兼ねてその手順を記事として残したいと思います。
今回はSwaggerをAPI Gatewayのテンプレートの一部として組み込む方法もご紹介したいと思います。
全部で記事が三つと長いですが、サーバーレスが初めての方でもAPIを構築できるように書きましたので、最後までご覧いただけたら嬉しいです。
第二回、第三回の記事はこちら
- 【サーバーレス初心者向け】Serverless Framework + SwaggerでWeb APIを作る!第2回 WAF適用編(全3回)
- 【サーバーレス初心者向け】Serverless Framework + SwaggerでWeb APIを作る!第3回 Dynamo編(全3回)
今回作るもの
今回は以下のアーキテクト図のようなWeb APIバックエンドを作っていきます。
API GatewayでクライアントからのAPIリクエストを受信し、該当するLambda関数を呼び出し、必要に応じてDynamoDBからのデータ読み出しおよび書き込みを行います。さらに、WAFを適用することでセキュアにします。
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が必要となります。以下のリンク先から皆さんの環境にあうインストーラーをダウンロードし実行してください。
##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が見つかると思いますので、インストールボタンをクリックします。
これで、必要な環境は準備できました!
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
今は以下のようになっていると思います。
'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とともにシンプルにメッセージをリターンするだけの実装に変えます。
"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
今は以下のようになっていると思います。
# 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
コマンドを実行したときに指定したプロジェクト名がそのまま入っていると思います。
service: slsTestApp
最終的なスタック名はservie:
にデプロイ対象のstageがハイフンで連結されたものになります。なので上記の例だとdev stageにデプロイした場合はスタック名はslsTestApp-dev
となります。
次にprovider:
ですが、こちらはデプロイする対象のクラウドプラットフォーム名とLambda関数を実装する言語の設定が記載されています。今回の場合はAWSとnode.jsなので、そのようになっていますね。
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
と記載さています。
functions:
hello:
handler: handler.hello
では、serverless.ymlを少し修正していきます。
初めに、Serverless Frameworkでデプロイするstage名とリージョンのデフォルト値を設定します。provider:
下の# you can overwrite defaults here
直下二行のコメントアウトを外し、以下のように記載します。これで、デフォルトでap-northeast-1リージョンにdevステージとしてデプロイされるようになります。
provider:
name: aws
runtime: nodejs12.x
# you can overwrite defaults here
stage: dev
region: ap-northeast-1
次にhello関数の定義に少し追加します。name:
とdescription:
を追加し、hello関数の名前と説明を記載してみてください。
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関数が表示されていると思います。
関数をクリックし、Lambda関数の詳細画面を表示します。この画面でLambda関数のテストをすることができますので、やってみます。
右上の「テストイベントの選択」をクリックし、「テストイベントの設定」をクリックします。
すると、テストで流し込むテストデータの設定画面が表示されます。
任意のイベント名を入力し、それ以外はデフォルトで保存します。
保存したら、「テスト」ボタンをクリックし実行します。
以下のような結果になれば成功です!
Swaggerテンプレートを作る
Lambda関数をServerless Frameworkを使ってデプロイするところまでできました。
次はAPIの実装を行っていくのですが、まずはSwaggerでAPIの仕様を書いてみましょう。
まずは、名前はなんでもよいのでAPI定義を記載するyamlファイルを作成してください。
私はtemplatesというフォルダを作りその下に「swagger.yaml」として作りました。こちらに定義を書いていきます。
swaggerの書き方を解説しようと思いましたが、すでにわかりやすい記事がありましたので、こちらをご参照ください。
今回つくるもので記載したAPIのうち、GET APIの定義を書いたものが以下になります。
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定義を取り込んだ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が返却されるようになります。
"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も修正していきましょう。私と同じファイルパス、関数名にした場合は以下のようになります。違う名前にした場合はそちらに変更してください。
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:
を記載し、必要なプロパティを設定していきます。
/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/
に以下のテンプレートファイルを作成します。
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
に配列形式でテンプレートファイルへのパスを記載します。
# 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があると思いますので、そちらをクリックします。
左のメニューから「ステージ」を選択し、中央に表示される「dev」をクリックすると、右にURLが表示されます。これにswaggerで定義したURIを連結したものが、最終的なAPIのURIとなります。
この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関数の実行には本来不要なほかのコードも含まれてしまっているからです。
これを防ぐには、serverless.ymlにexclude設定を追加し、不要なファイルがパッケージされないようにします。
# you can add packaging information here
package:
exclude:
- docs/**
- node_modules/**
- templates/**
- .eslintrc.json
- LICENSE
- README.md
- package-lock.json
もう一回デプロイしてみると、以下のように必要なものだけパッケージされてるのが分かります。
デプロイしたリソースの削除
デプロイした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構築編