Serverless Framework勉強する
Serverless Frameworkを勉強して使えるようになろう
参考 というかこちらの記事の実践記録
とりあえずhello-worldテンプレートからプロジェクトを作ってみる
❯ sls create -t hello-world -p sls-hello-world
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/kawarimidoll/ghq/github.com/kawarimidoll/sls-hello-world"
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v2.46.0
-------'
Serverless: Successfully generated boilerplate for template: "hello-world"
❯ cd sls-hello-world
❯ ls -A1
.npmignore
handler.js
serverless.yml
生成されたファイルは以下のようになっている(コメントは除く)
'use strict';
module.exports.helloWorld = (event, context, callback) => {
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
},
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
}),
};
callback(null, response);
};
service: sls-hello-world
frameworkVersion: '2'
provider:
name: aws
runtime: nodejs12.x
functions:
helloWorld:
handler: handler.helloWorld
events:
- http:
path: hello-world
method: get
cors: true
sls invoke
で実行できるので、--function helloWorld
を指定してみる
❯ sls invoke local --function helloWorld
Serverless: Deprecation warning: Resolution of lambda version hashes was improved with better algorithm, which will be used in next major release.
Switch to it now by setting "provider.lambdaHashingVersion" to "20201221"
More Info: https://www.serverless.com/framework/docs/deprecations/#LAMBDA_HASHING_VERSION_V2
{
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "*"
},
"body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":\"\"}"
}
handler.helloWorld
の内容が実行された
warningメッセージが出ているので、設定を追加する
provider:
name: aws
runtime: nodejs12.x
+ lambdaHashingVersion: 20201221
handler.helloWorld
の実行結果にはinputの部分があるので、引数を--data
オプション経由で追加して実行してみる
❯ sls invoke local --function helloWorld --data 'this is the input!'
{
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "*"
},
"body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":\"this is the input!\"}"
}
入力値が反映された
awsの情報を設定
❯ aws configure
AWS Access Key ID [None]: (入力)
AWS Secret Access Key [None]: (入力)
Default region name [None]: us-west-2
Default output format [None]:
デプロイ実行、暫し待つ
❯ sls deploy --verbose --region us-west-2
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
(略)
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service sls-hello-world.zip file to S3 (578 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
(略)
Serverless: Stack update finished...
Service Information
service: sls-hello-world
stage: dev
region: us-west-2
stack: sls-hello-world-dev
resources: 12
api keys:
None
endpoints:
GET - (略)
functions:
helloWorld: sls-hello-world-dev-helloWorld
layers:
None
Stack Outputs
(略)
1分50秒かかった
ローカルディレクトリを確認すると.serverless
ディレクトリが作成されている
この.serverless/sls-hello-world.zip
がlambdaへアップロードされているもよう
❯ tree
.
├── .npmignore
├── .serverless
│ ├── cloudformation-template-create-stack.json
│ ├── cloudformation-template-update-stack.json
│ ├── serverless-state.json
│ └── sls-hello-world.zip
├── handler.js
└── serverless.yml
実際lambdaを見るとsls deploy
の結果のfunctions
に出ていたsls-hello-world-dev-helloWorld
が設定されているのがわかる
なおregionをいちいち指定するのが面倒なのでserverless.yml
に追加しておく
provider:
name: aws
runtime: nodejs12.x
lambdaHashingVersion: 20201221
+ region: us-west-2
実行してみる
❯ sls invoke --function helloWorld --data 'deploy ok!'
{
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "*"
},
"body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":\"deploy ok!\"}"
}
実行できた
あと、lambdaのアプリケーションの方にもちゃんと上がっていた
CloudWatchのログも確認しておく
CloudWatch > Log groupsから今回のlambda functionに対応するグループを選択
ログストリームができていることが確認できる
ログの出力を確認するため、handler.js
をちょっと修正してみる
'use strict';
module.exports.helloWorld = (event, context, callback) => {
const response = {
// (略)
};
+ console.log("Hello Serverless World!")
callback(null, response);
};
これでデプロイし、再度sls invoke
でfunctionを実行する
再びCloudWatchへ戻りログを見てみる(デプロイし直したのでログストリームが変わる)
console.log()
した内容がCloudWatchへ出力されていることが確認できた
serverless.yml
でhelloWorld.events.http
が登録されているので公開APIが作られている
deployの実行結果で表示されたentrypointへアクセスしてみる
❯ curl https://xxx.execute-api.us-west-2.amazonaws.com/dev/hello-world
{"message":"Go Serverless v1.0! Your function executed successfully!","input":{"resource":"/hello-world","path":"/hello-world","httpMethod":"GET","headers":(略)
DynamoDBを扱うため、serverless.yml
に設定を追加する
service: sls-hello-world
frameworkVersion: '2'
provider:
name: aws
runtime: nodejs12.x
region: us-west-2
lambdaHashingVersion: 20201221
+ environment:
+ DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
+ iamRoleStatements:
+ - Effect: Allow
+ Action:
+ - dynamodb:Query
+ - dynamodb:Scan
+ - dynamodb:GetItem
+ - dynamodb:PutItem
+ - dynamodb:UpdateItem
+ - dynamodb:DeleteItem
+ Resource: arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}
funcsions:
(略)
+ resources:
+ Resources:
+ helloTable:
+ Type: AWS::DynamoDB::Table
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: id
+ AttributeType: S
+ KeySchema:
+ - AttributeName: id
+ KeyType: HASH
+ ProvisionedThroughput:
+ ReadCapacityUnits: 1
+ WriteCapacityUnits: 1
+ TableName: ${self:provider.environment.DYNAMODB_TABLE}
追加した点の解説
provider
service: sls-hello-world
provider:
name: aws
region: us-west-2
+ environment:
+ DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
+ iamRoleStatements:
+ - Effect: Allow
+ Action:
+ - dynamodb:Query
+ - dynamodb:Scan
+ - dynamodb:GetItem
+ - dynamodb:PutItem
+ - dynamodb:UpdateItem
+ - dynamodb:DeleteItem
+ Resource: arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}
-
environment
でテーブル名を環境変数に設定している-
${self:xxx}
はserverless.yml
の内容を、${opt:xxx}
はコマンドライン引数にアクセスできる- たとえば
sls deploy --stage prod
が実行されたら${opt:stage}
はprod
になる
- たとえば
- ${opt:xxx, self:xxx}の場合、前者が存在すればその値を、なければ後者の値を使用する
- ${self:provider.stage}のデフォルト値は'dev'
- ということで今回は引数がなければ
DYNAMODB_TABLE=sls-hello-world-dev
が環境変数に追加される
-
-
iamRoleStatements
でlambdaから他のAWSリソースを触る際に必要なIAMロールを設定する-
Action
でDynamoDBへの各種アクションを許可 -
Resource
で対象となるリソースを設定-
${opt:region, self:provider.region}
は前述の通りregion
が引数として指定されていればその値を、なければproviderの値を、それもなければデフォルト値(us-east-1)が使用される - 今回は
region:us-west-2
を指定しているのでリソース名はarn:aws:dynamodb:us-west-2:*:table/sls-hello-world-dev
となる
-
-
resources
+ resources:
+ Resources:
+ helloTable:
+ Type: AWS::DynamoDB::Table
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: id
+ AttributeType: S
+ KeySchema:
+ - AttributeName: id
+ KeyType: HASH
+ ProvisionedThroughput:
+ ReadCapacityUnits: 1
+ WriteCapacityUnits: 1
+ TableName: ${self:provider.environment.DYNAMODB_TABLE}
-
Resources
配下のキーがリソースの論理IDとなる、今回はhelloTable
(一意な値なら何でもOK) -
AttributeDefinitions
とKeySchema
でテーブルのパーティションキーを設定 - コスト削減のため
CapacityUnits
は読み書きともに1に設定 - スタックを削除した際にテーブルを削除したくない場合は(DeletionPolicy 属性)を設定する
deployしたところDeprecation warningが表示された
❯ sls deploy
Serverless: Deprecation warning: Variables resolver reports following resolution errors:
- Cannot resolve variable at "provider.environment.DYNAMODB_TABLE": Value not found at "self" source
From a next major this will be communicated with a thrown error.
Set "variablesResolutionMode: 20210326" in your service config, to adapt to new behavior now
More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_VARIABLES_RESOLVER
Serverless: Deprecation warning: Starting with version 3.0.0, following property will be replaced:
"provider.iamRoleStatements" -> "provider.iam.role.statements"
More Info: https://www.serverless.com/framework/docs/deprecations/#PROVIDER_IAM_SETTINGS
一件目はprovider.environment.DYNAMODB_TABLE
が参照できないとなっているが、sls print
するとちゃんと見えているので問題はなさそう
variablesResolutionMode: 20210326
を設定するとエラーで止まってしまう
いろいろ試してみたが間違っていることはなさそうだし修正できなかったのでこれに関する変更は無し
バージョンが上がってエラーを起こすようになったら使えなくなるかも…
二件目は記法が変わるようなので修正しておく
- iamRoleStatements:
+ iam:
+ role:
+ statements:
# 以下インデント修正…
ということでiamRoleStatements
の部分のみ修正してデプロイ実行
DynamoDBのコンソールを見るとたしかにsls-hello-world-dev
テーブルが作成されている
また、IAM > ロール > sls-hello-world-dev-us-west-2-lambdaRole(今回のデプロイに対応するポリシー)を確認すると、DynamoDBの読み書き権限が設定されている
ここからは、公式チュートリアルとサンプルリポジトリを見つつ、TODOリストを実装する
aws-sdk
とuuid
を導入
❯ yarn init -y && yarn add aws-sdk uuid && echo node_modules >> .gitignore
APIはREST形式に従う
path | method | DynamoDBのaction | 作成するfunction名 |
---|---|---|---|
/todos | GET | Scan | list |
/todos/:id | GET | GetItem | get |
/todos | POST | PutItem | create |
/todos/:id | PUT | UpdateItem | update |
/todos/:id | DELETE | DeleteItem | delete |
Scanを行うlist.js
を作成する
const { DynamoDB } = require("aws-sdk");
const dynamoDB = new DynamoDB.DocumentClient();
const params = {
TableName: process.env.DYNAMODB_TABLE,
};
module.exports.list = (event, context, callback) => {
dynamoDB.scan(params, (error, result) => {
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { "Content-Type": "text/plain" },
body: "Couldn't fetch the todos.",
});
return;
}
callback(null, {
statusCode: 200,
body: JSON.stringify(result.Items),
});
});
};
解説:
-
aws-sdk
からDynamoDB
をインポートし、クライアントを生成する - クライアントに渡すパラメータにテーブル名を入れるが、
serverless.yml
で定義したとおり、DYNAMODB_TABLE
という環境変数に保存されている - ハンドラの
module.exports.[handler name] = (event, context, callback) => { callback(null, {}) }
はおまじない - 実行結果のコールバックを
dynamoDB.scan()
の第2引数に渡す
追加したlambda/todos/todos.list.js
をserverless.yml
に追記する
functions:
+ list:
+ handler: lambda/todos/list.list
+ events:
+ - http:
+ path: todos
+ method: get
+ cors: true
ローカルでテスト実行
❯ sls invoke local --function list
{
"statusCode": 200,
"body": "[]"
}
DBは現在からっぽ
PutItemを行うcreate.js
を作成する
const uuid = require("uuid");
const { DynamoDB } = require("aws-sdk");
const dynamoDB = new DynamoDB.DocumentClient();
const errMsg = "Couldn't create the todo item.";
module.exports.create = (event, context, callback) => {
console.log(`input: ${event.body}`);
const data = event.body;
if (typeof data?.text !== "string") {
console.error("Validation failed");
callback(new Error(errMsg));
return;
}
const timestamp = new Date().getTime();
const params = {
TableName: process.env.DYNAMODB_TABLE,
Item: {
id: uuid.v4(),
text: data.text,
checked: false,
createdAt: timestamp,
updatedAt: timestamp,
},
};
dynamoDB.put(params, (error, result) => {
if (error) {
console.error(error);
callback(new Error(errMsg));
return;
}
callback(null, {
statusCode: 200,
body: JSON.stringify(params.Item),
});
});
};
解説:
-
helloWorld
でやったように、event
からデータを受け取る- 受け取ったデータが無ければエラーを返す
- スキーマで定義されているのは
id
だけだがDynamoDBではスキーマ以外の項目は任意なので追加可能 -
id
はuuid.v4()
で生成している
追加したlambda/todos/create.create.js
をserverless.yml
に追記する
functions:
+ create:
+ handler: lambda/todos/create.create
+ events:
+ - http:
+ path: todos
+ method: post
+ cors: true
ローカルでテスト実行
❯ sls invoke local --function create
undefined
Validation failed
{
"errorMessage": "Couldn't create the todo item.",
"errorType": "Error",
"stackTrace": [(略)]
}
❯ sls invoke local --function create --data '{"body":{"text":"test todo"}}'
{ text: 'test todo' }
{
"statusCode": 200,
"body": "{\"id\":\"6234b715-af2e-412f-abd8-0547367dfdc9\",\"text\":\"test todo\",\"checked\":false,\"createdAt\":1623758628964,\"updatedAt\":1623758628964}"
}
❯ sls invoke local --function list
{
"statusCode": 200,
"body": "[{\"checked\":false,\"createdAt\":1623758628964,\"text\":\"test todo\",\"id\":\"6234b715-af2e-412f-abd8-0547367dfdc9\",\"updatedAt\":1623758628964}]"
}
DBにデータを追加できた
GetItemを行うget.js
を作成する わりとlist.js
に近い
const { DynamoDB } = require("aws-sdk");
const dynamoDB = new DynamoDB.DocumentClient();
module.exports.get = (event, context, callback) => {
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
id: event.pathParameters.id,
},
};
dynamoDB.get(params, (error, result) => {
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { "Content-Type": "text/plain" },
body: "Couldn't fetch the todo item.",
});
return;
}
if (result?.Item) {
callback(null, {
statusCode: 200,
body: JSON.stringify(result.Item),
});
return;
}
console.warn(`Item not found: id: ${params.Key.id}`);
callback(null, {
statusCode: 404,
headers: { "Content-Type": "text/plain" },
body: "Item not found.",
});
});
};
解説:
- ほぼ
list.js
に近いが、scan
ではなくget
を実行している- このとき渡すパラメータに
Key:{ id: event.pathParameters.id }
を入れている
- このとき渡すパラメータに
追加したlambda/todos/get.js
をserverless.yml
に追記する
パラメータは{id}
の形で指定する
functions:
+ get:
+ handler: lambda/todos/get.get
+ events:
+ - http:
+ path: todos/{id}
+ method: get
+ cors: true
ローカルでテスト実行、createのときにできたidを使う
❯ sls invoke local --function get --data '{"pathParameters":{"id":"6234b715-af2e-412f-abd8-0547367dfdc9"}}'
{
"statusCode": 200,
"body": "{\"checked\":false,\"createdAt\":1623758628964,\"text\":\"test todo\",\"id\":\"6234b715-af2e-412f-abd8-0547367dfdc9\",\"updatedAt\":1623758628964}"
}
❯ sls invoke local --function get --data '{"pathParameters":{"id":"hogehoge"}}'
Item not found: id: hogehoge
{
"statusCode": 404,
"headers": {
"Content-Type": "text/plain"
},
"body": "Item not found."
}
データを取得できた
UpdateItemを行うupdate.js
を作成する
const { DynamoDB } = require("aws-sdk");
const dynamoDB = new DynamoDB.DocumentClient();
module.exports.update = (event, context, callback) => {
const data = event.body;
console.log(`input: ${data?.text} ${data?.checked}`);
// validation
// data is not exist || data.text is not string || data.checked is not boolean
if (typeof data?.text !== "string" && typeof data.checked !== "boolean") {
console.error("Validation Failed");
callback(null, {
statusCode: 400,
headers: { "Content-Type": "text/plain" },
body: "Couldn't update the todo item.",
});
return;
}
const timestamp = new Date().getTime();
const expressionAttributeNames = {};
const expressionAttributeValues = {
":updatedAt": timestamp,
};
const updateExpressionArray = ["updatedAt = :updatedAt"];
if (data.text) {
expressionAttributeNames["#todo_text"] = "text";
expressionAttributeValues[":text"] = data.text;
updateExpressionArray.push("#todo_text = :text");
}
if (data.checked != null) {
expressionAttributeValues[":checked"] = data.checked;
updateExpressionArray.push("checked = :checked");
}
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeValues: expressionAttributeValues,
UpdateExpression: `SET ${updateExpressionArray.join(",")}`,
ReturnValues: "ALL_NEW",
};
// add ExpressionAttributeNames when it is not empty
if (Object.keys(expressionAttributeNames).length > 0) {
params.ExpressionAttributeNames = expressionAttributeNames;
}
dynamoDB.update(params, (error, result) => {
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { "Content-Type": "text/plain" },
body: "Couldn't fetch the todo item.",
});
return;
}
callback(null, {
statusCode: 200,
body: JSON.stringify(result.Attributes),
});
});
};
解説:
-
update
にわたすパラメータが重要-
ExpressionAttributeValues
に更新する具体的な値を入れる -
UpdateExpression
に更新する式を入れる- 例えばcheckedを更新する場合は
'SET checked = :checked, updatedAt = :updatedAt'
となる
- 例えばcheckedを更新する場合は
- 更新するカラムを別名で指定する場合は
ExpressionAttributeNames
に指定する- 今回は
text
カラムをそのまま更新しようとするとAttribute name is a reserved keyword; reserved keyword: text
が出るので名前を変更している - この項目が空だったり使わないのに指定した場合はエラーが出るので
text
を更新する場合のみ追加する
- 今回は
-
ReturnValues
はALL_NEW
を指定することで更新後のレコード全体を返してくれる- その他の値に関しては 公式ドキュメント を参照
-
追加したlambda/todos/update.js
をserverless.yml
に追記する
functions:
+ update:
+ handler: lambda/todos/update.update
+ events:
+ - http:
+ path: todos/{id}
+ method: put
+ cors: true
ローカルでテスト実行
❯ sls invoke local --function update --data '{"pathParameters":{"id":"6234b715-af2e-412f-abd8-0547367dfdc9"},"body":{"checked":true}}'
input: undefined true
{
"statusCode": 200,
"body": "{\"checked\":true,\"createdAt\":1623758628964,\"text\":\"test todo\",\"id\":\"6234b715-af2e-412f-abd8-0547367dfdc9\",\"updatedAt\":1623803748901}"
}
❯ sls invoke local --function update --data '{"pathParameters":{"id":"6234b715-af2e-412f-abd8-0547367dfdc9"},"body":{"text":"updated!"}}'
input: updated! undefined
{
"statusCode": 200,
"body": "{\"checked\":true,\"createdAt\":1623758628964,\"text\":\"updated!\",\"id\":\"6234b715-af2e-412f-abd8-0547367dfdc9\",\"updatedAt\":1623803797589}"
}
データを更新できた
DeleteItemを行うdelete.js
を作成する
const { DynamoDB } = require("aws-sdk");
const dynamoDB = new DynamoDB.DocumentClient();
module.exports.delete = (event, context, callback) => {
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
id: event.pathParameters.id,
},
ReturnValues: "ALL_OLD",
};
dynamoDB.delete(params, (error, result) => {
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { "Content-Type": "text/plain" },
body: "Couldn't fetch the todo item.",
});
return;
}
console.log(JSON.stringify(result));
if (result?.Attributes) {
callback(null, {
statusCode: 200,
body: JSON.stringify(result.Attributes),
});
return;
}
console.warn(`Item not found: id: ${params.Key.id}`);
callback(null, {
statusCode: 404,
headers: { "Content-Type": "text/plain" },
body: "Item not found.",
});
});
};
解説:
-ほぼget.js
のget
をdelete
に変えただけ
- 削除対象が存在したかどうかわからないのでパラメータに
ReturnValues: "ALL_OLD"
を設定する - これにより削除結果が
Attributes
というキーで帰ってくるのでそれを返す- 帰ってこなければ対象のデータがないということなので404を返却
追加したlambda/todos/delete.js
をserverless.yml
に追記する
functions:
+ delete:
+ handler: lambda/todos/delete.delete
+ events:
+ - http:
+ path: todos/{id}
+ method: delete
+ cors: true
ローカルでテスト実行
(ミスってデータを消してしまったので再度createから)
❯ sls invoke local --function create --data '{"body":{"text":"new one"}}'
input: [object Object]
{
"statusCode": 200,
"body": "{\"id\":\"a0145b57-e7ce-448b-8e3c-5827a3a149f2\",\"text\":\"new one\",\"checked\":false,\"createdAt\":1623805583487,\"updatedAt\":1623805583487}"
}
❯ sls invoke local --function list
{
"statusCode": 200,
"body": "[{\"checked\":false,\"createdAt\":1623805583487,\"text\":\"new one\",\"id\":\"a0145b57-e7ce-448b-8e3c-5827a3a149f2\",\"updatedAt\":1623805583487}]"
}
❯ sls invoke local --function delete --data '{"pathParameters":{"id":"a0145b57-e7ce-448b-8e3c-5827a3a149f2"}}'
{
"statusCode": 200,
"body": "{\"checked\":false,\"createdAt\":1623805583487,\"text\":\"new one\",\"id\":\"a0145b57-e7ce-448b-8e3c-5827a3a149f2\",\"updatedAt\":1623805583487}"
}
❯ sls invoke local --function list
{
"statusCode": 200,
"body": "[]"
}
データを消去できた
serverless.ymlがでかくなってきたので分割する
functions
とresources
以下を別ファイルに出して${file()}
構文で読み込む
service: sls-hello-world
frameworkVersion: "2"
provider:
name: aws
runtime: nodejs12.x
region: us-west-2
lambdaHashingVersion: 20201221
environment:
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}
functions: ${file(./resources/functions.yml)}
resources:
- ${file(./resources/dynamodb.yml)}
helloWorld:
handler: lambda/handler.helloWorld
events:
- http:
path: hello-world
method: get
cors: true
list:
handler: lambda/todos/list.list
events:
- http:
path: todos
method: get
cors: true
create:
handler: lambda/todos/create.create
events:
- http:
path: todos
method: post
cors: true
get:
handler: lambda/todos/get.get
events:
- http:
path: todos/{id}
method: get
cors: true
update:
handler: lambda/todos/update.update
events:
- http:
path: todos/{id}
method: put
cors: true
delete:
handler: lambda/todos/delete.delete
events:
- http:
path: todos/{id}
method: delete
cors: true
Resources:
helloTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:provider.environment.DYNAMODB_TABLE}
デプロイ実行
❯ sls deploy -v
(略)
Serverless: Stack update finished...
Service Information
service: sls-hello-world
stage: dev
region: us-west-2
stack: sls-hello-world-dev
resources: 42
api keys:
None
endpoints:
GET - https://xxx.execute-api.us-west-2.amazonaws.com/dev/hello-world
GET - https://xxx.execute-api.us-west-2.amazonaws.com/dev/todos
POST - https://xxx.execute-api.us-west-2.amazonaws.com/dev/todos
GET - https://xxx.execute-api.us-west-2.amazonaws.com/dev/todos/{id}
PUT - https://xxx.execute-api.us-west-2.amazonaws.com/dev/todos/{id}
DELETE - https://xxx.execute-api.us-west-2.amazonaws.com/dev/todos/{id}
functions:
helloWorld: sls-hello-world-dev-helloWorld
list: sls-hello-world-dev-list
create: sls-hello-world-dev-create
get: sls-hello-world-dev-get
update: sls-hello-world-dev-update
delete: sls-hello-world-dev-delete
layers:
None
(略)
sls invoke
でデプロイしたlambdaにアクセスしてみる
❯ sls invoke --function list
{
"statusCode": 200,
"body": "[]"
}
❯ sls invoke --function create --data '{"body":{"text":"this is a task"}}'
{
"errorType": "Runtime.UserCodeSyntaxError",
"errorMessage": "SyntaxError: Unexpected token '.'",
"trace": [
"Runtime.UserCodeSyntaxError: SyntaxError: Unexpected token '.'",
" at _loadUserApp (/var/runtime/UserFunction.js:98:13)",
" at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)",
" at Object.<anonymous> (/var/runtime/index.js:43:30)",
" at Module._compile (internal/modules/cjs/loader.js:999:30)",
" at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)",
" at Module.load (internal/modules/cjs/loader.js:863:32)",
" at Function.Module._load (internal/modules/cjs/loader.js:708:14)",
" at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)",
" at internal/main/run_main_module.js:17:47"
]
}
(略)
create
でエラーが出てしまった
ランタイムがNode.js 12.x
だったため、?.
構文を使えないことが原因だった
lambda
のページからランタイムを修正することで解消した
❯ sls invoke --function create --data '{"body":{"text":"this is a task"}}'
{
"statusCode": 200,
"body": "{\"id\":\"b5250f4e-b22c-4704-a959-711066873188\",\"text\":\"this is a task\",\"checked\":false,\"createdAt\":1623842464484,\"updatedAt\":1623842464484}"
}
ということでこれをserverless.yml
に反映し、再度デプロイ
provider:
name: aws
- runtime: nodejs12.x
+ runtime: nodejs14.x
region: us-west-2
(略)
update
など他のハンドラもNode.js 14.x
になっていることを確認した
sls remove
して削除しておく
❯ sls remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack delete progress...
...........................................................................
Serverless: Stack delete finished...
Lambdaからアプリケーションが削除され、DynamoDBからテーブルが削除された
現状Postmanからリクエストすると403になってしまうが、証明書を設定すると解決しそうなのでこのまま進む
プラグインを導入
❯ yarn add -D serverless-domain-manager
plugins:
- serverless-domain-manager
証明書の設定が必要っぽいのでACMから作業する
こちらの記事を参考にする
ACMから「証明書のプロビジョニング」の「今すぐ始める」を選択
パブリック証明書をリクエスト
ドメイン名を追加して「次へ」
検証方法の選択はそのまま(DNSの検証)、タグ設定も特に追加せず次へ進み、確認画面で「確定とリクエスト」を押す
検証状態が「検証保留中」の画面が出てきたらひとまずOK、続行
Route53のHostedZoneの設定が出来ていなかったので設定する
Route53のダッシュボードから「ホストゾーンの作成」を選択
ドメインを入力して「ホストゾーンの作成」
これを行うとACSの独自ドメインの部分に「Route53でのレコードの作成」が出現する
ホストゾーンに設定して暫く待つ
CloudFrontのCreate Distribution→Get StartedとクリックしてCreate Distribution画面へ進む
Origin SettingsではOrigin Domain Nameにserverlessで作成されたバケット名を選択、Origin IDも自動で入力される
Enable Origin ShieldをYesにし、Origin Shield Regionは設定したRegion(今回はus-west-2)に設定
Restrict Backet AccessをYesにし、Origin Access IdentityはCreate a New Identityを選択、Commentに適当なメッセージを入力、Grant Read Permissions on BucketもYesにしておく
Default Cache Behavior SettingsではViewer Protocol PolicyをRedirect HTTP to HTTPSに、Allowed HTTP MEthodsを全部入りに設定
Route53の設定が完了すると証明書の状況が「発行済み」に、ドメインの検証状態が「成功」になる
90分近くかかった…
おっと…CloudFrontで証明書を使うにはus-east-1で証明書を作成しないといけなかったのでやりなおし
リージョン変えてやりなおしたら失敗した…
一旦消して再度実行したら今度は17秒で完了した、よくわからん
これでCloudFrontのDistribution Settingsから独自ドメインを選択できるようになったので設定する
AWSの設定ができたということでserverless.yml
に設定を追加
+ custom:
+ customDomain:
+ domainName: api.kawarimidoll.com
+ basePath: lambda
+ certificateName: '*.kawarimidoll.com'
+ createRoute53Record: true
+ endpointType: 'regional'
+ securityPolicy: tls_1_2
ドメイン追加を実行 regionを修正していなかった場合は失敗したのでus-east-1にしておく
❯ sls create_domain
Serverless Domain Manager: Info: Custom domain api.kawarimidoll.com was created.
New domains may take up to 40 minutes to be initialized.
「設定に40分くらいかかるよ」といのことなので暫し待つ
serverless.yml
で設定したdomainName
に対応したAレコードとAAAAレコードがRoute53に追加される
sls deploy
することでAPI Gatewayにカスタムドメインが追加される
ここまでは出来たがapi.kawarimidoll.com
にアクセスしてもVercel管理下だったのでLambda実行はできなかった、残念…
今回はある程度確認できたので良しとする
なおこのデプロイ内容を取り下げる際はLambdaを消してからカスタムドメインを消す
順番を間違えると削除に失敗する可能性があるとのこと
❯ sls remove
Serverless Domain Manager: Info: Found apiId: xxxx for api.kawarimidoll.com
Serverless Domain Manager: Info: Removed basepath mapping.
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack delete progress...
..............................................................................
Serverless: Stack delete finished...
❯ sls delete_domain
Serverless Domain Manager: Info: Custom domain api.kawarimidoll.com was deleted.
いずれfreenomとか使ってリベンジするかも
ホストゾーンの維持で料金が発生していたので削除や
先にホストゾーンの詳細にアクセスしてCNAMEレコードを削除(NSとかSOAは削除できない)
SAYONARA!
ふう きれいになりました