はじめに
AWS SAMを使ってLambda(GO)のCICDをGitHub Actionsで構築でサーバーレスアプリケーションを構築するフレームワークとしてAWS SAMは触った事があったが、serverlessは触った事がなかったので、serverlessでHello World(Node.js)をやってみたので備忘録を残す。
やってみた事としては、以下の3つ。
- cli上で
severless
コマンドからHello World的なLambda関数を作成するseverless.ymlの作成 - ローカル環境でそのLambda関数を実行し、Lambdaを呼び出し
- 【おまけ】実際にAWS環境にDeploy
ソースコード全体は以下。
はじめてのserverless
プロジェクトを作成
今回はVirtualboxのCentOS7.9上でserverlessのセットアップを行った(環境構築方法の詳細はWindows上にLinux(CentOS)のWebアプリ開発環境をvirtualboxで構築するを参照)。
[root@localhost serverless]# yarn init
yarn init v1.22.17
question name (serverless):
question version (1.0.0):
question description: serverlessFW-learning
question entry point (index.js):
question repository url:
question author (Yuta Katayama xxx@xxx.com>): yuta-katayama-23
question license (MIT):
question private:
success Saved package.json
Done in 36.32s.
serverlessをインストール
Get started with Serverless Framework Open Source & AWSにはグローバルにインストールする方法が書かれているが、グローバルだと他の人がプロジェクトを利用する際に前提条件が増えるので、素直にpackage.json
で管理する方法を取る。
yarn add --dev serverless
こうする事で、npxコマンドでnpx sls --version
のようにserverless
(省略形でsls
)コマンドが使えるようになる。
※npxはローカルインストールしたコマンドを実行するために使うもの(この辺りはnpxコマンドとは? 何ができるのか?などを参照)
npx serverless
でserverlessプロジェクトを作成
公式に書かれている通り、serverless
コマンドを実行(ローカルパッケージの場合にはnpx serverless
)する事でCLI上で対話をしながらプロジェクトを作成できる。
[root@localhost serverless]# npx serverless
Creating a new serverless project
? What do you want to make? AWS - Node.js - Starter
? What do you want to call this project? aws-node-project
✔ Project successfully created in aws-node-project folder
? Do you want to login/register to Serverless Dashboard? No
? No AWS credentials found, what credentials do you want to use? Skip
You can setup your AWS account later. More details available here: http://slss.io/aws-creds-setup
上記のようにする事でserverless.ymlやhandler.js
が作成され、後はdeployすればLambda関数が作成できる状態が作られる。
※上記においてAWSのアカウント設定をskipしているが、これはAWS環境に実際にDeployする時になってから改めて設定すれば問題ない(serverlessはUsing AWS Access KeysのSetup with the aws-cliに書かれている通り、aws-cliの設定内容(実態は~/.aws
以下のconfigとcredential)からユーザ情報を読み取ってくれる)
※今回のように既にプロジェクトを作成済みの場合、create
コマンドを用いてserverlessプロジェクトを作成する事もできる。createコマンドの場合、serverless.yamlやhandler.jsの出力先を--path
オプションで指定できるので、既にプロジェクトがある時には便利だろう。
[root@localhost serverless]# npx serverless create --template aws-nodejs --path myService
✔ Project successfully created in "myService" from "aws-nodejs" template (6s)
[root@localhost serverless]# cd myService/
[root@localhost myService]# tree -a
.
├── .gitignore
├── handler.js
└── serverless.yml
ローカルでserverlessアプリケーションを実行できるようにする
今回はserverlessアプリケーションと言ってもLambda単体しかないのでLambdaをローカルで実行できるようにする。
方法は簡単で公式のPluginsにも書かれているserverless-offlineをインストールし、serverless.yamlに以下のように追記するだけ。
service: aws-node-project
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs14.x
# 以下を追記
plugins:
- serverless-offline
functions:
hello:
handler: handler.hello
後は、npx sls offline
コマンドを叩けばLambdaをローカルで起動できる。
[root@localhost serverless]# npx sls offline
Starting Offline at stage dev (us-east-1)
Offline [http for lambda] listening on http://localhost:3002
Function names exposed for local invocation by aws-sdk:
* hello: aws-node-project-dev-hello
(λ: hello) RequestId: cl1suu1g00000f0pvg2b8cg52 Duration: 4.05 ms Billed Duration: 5 ms
※上記では実際にLambda関数を実行した際のログも出でいる((λ: hello) RequestId: cl1suu1g00000f0pvg2b8cg52 Duration: 4.05 ms Billed Duration: 5 ms
)が、それは次の章で見ていく。
※serverless-offlineを入れずとも、AWS - Invoke Localに書かれているように、以下のようにローカルでLambdaを実行する事は可能。API Gatewayなど他のAWSサービスと連携するサーバーレスアプリケーションをローカルでエミュレーションするにはserverless-offlineが必要になる。
study@localhost:~/workspace/serverless (main *)
$ yarn invoke:local
yarn run v1.22.19
$ npx sls invoke local --function hello
{
"statusCode": 200,
"body": "{\n \"message\": \"Go Serverless v3.0! Your function executed successfully!\",\n \"input\": \"\"\n}"
}
Done in 3.00s.
aws-cliでLambdaを実行する(呼び出す)
まずはaws-cliのインストールが必要なのでAWS CLI の最新バージョンをインストールまたは更新します。を参照しインストールする。
あとはinvokeに書かれているようにコマンドを実行するだけ。
[root@localhost serverless]# aws lambda invoke --payload '{ "name": "Bob" }' --endpoint-url http://localhost:3002 --function-name aws-node-project-dev-hello --cli-binary-format raw-in-base64-out response.json
{
"StatusCode": 200
}
{
"statusCode": 200,
"body": "{\n \"message\": \"Go Serverless v3.0! Your function executed successfully!\",\n \"input\": {\n \"name\": \"Bob\"\n }\n}"
}
※2点、注意点があるのでそれについて補足する。
aws-cliを利用する際には、ダミーでもAWSのクレデンシャル情報が必要
AWSクレデンシャルがない状態でコマンドを実行すると、以下のようにエラーになる。
[root@localhost serverless]# aws lambda invoke --payload '{ "name": "Bob" }' --endpoint-url http://localhost:3002 --function-name aws-node-project-dev-hello --cli-binary-format raw-in-base64-out response.json
You must specify a region. You can also configure your region by running "aws configure".
という事でダミーでもいいので以下のようにしてAWSクレデンシャルを設定する。
[root@localhost serverless]# aws configure
AWS Access Key ID [None]: dumy
AWS Secret Access Key [None]: dumy
Default region name [None]: ap-northeast-1
Default output format [None]: json
※後々AWS環境にDeployする(serverless.ymlに基づいてAWS環境にLambdaを作成する)際には、aws-cliのprofileを切り替えて本物のクレデンシャルを利用する必要があるので注意(詳細はAWS CLI の名前付きプロファイルを参照)
aws-cli v2 から、cliの引数のパラメータはbase64エンコードされた文字列を渡す必要がある
詳細はAWS CLI バージョン 2 では、デフォルトでバイナリパラメータが base64 でエンコードされた文字列として渡されるようになりましたを参照。
やる事としては、cliのコマンド実行時に--cli-binary-format raw-in-base64-out
オプションを追加する or ~/.aws/config
にcli_binary_format=raw-in-base64-out
を追記すればいい。
ちなみに、何もしないで実行すると以下のようにエラーになる。
[root@localhost serverless]# aws lambda invoke --payload '{ "name": "Bob" }' --endpoint-url http://localhost:3002 --function-name aws-node-project-dev-hello response.json
Invalid base64: "{ "name": "Bob" }"
まとめとして
今回はserverlessコマンドで初期プロジェクトを作成して、それをローカルで起動・実行するだけをやってみた。今後はNode.jsでもES6で実装したり、ESLintを導入したり、色々やってみたいと思う。
おまけ
AWS環境にDeployしてみる
aws-cliのクレデンシャルを追加で設定する
上記のaws-cliを利用する際には、ダミーでもAWSのクレデンシャル情報が必要ではダミーの実際のAWSアカウントで払い出したものではないクレデンシャル情報を設定していたので、今度は本物のAWSアカウントのクレデンシャル情報を設定する。
やり方は簡単で、公式の名前付きプロファイルを使用するに書かれている通り、aws configure --profile {新しく作成するprofile名}
というコマンドで設定できる。
追加のプロファイルを設定するには、--profile オプションで aws configure を使用する
[root@localhost serverless]# aws configure --profile yuta_katayama
AWS Access Key ID [None]: ******************** ← マスクのために*で表記している
AWS Secret Access Key [None]: **************************************** ← マスクのために*で表記している
Default region name [None]: ap-northeast-1
Default output format [None]: json
設定すると以下のように新しくprofileが作成できている事が確認できる。
[root@localhost serverless]# aws configure list-profiles
default
yuta_katayama
profileを切り替えるには名前を指定されたプロファイルを使用するに書かれているようにLinuxであれば環境変数を設定すればいい。設定後にaws configure list
を実行すれば、現在aws-cliで使用されているクレデンシャル情報が確認できる。
[root@localhost serverless]# export AWS_PROFILE=yuta_katayama
[root@localhost serverless]# aws configure list
Name Value Type Location
---- ----- ---- --------
profile yuta_katayama env ['AWS_PROFILE', 'AWS_DEFAULT_PROFILE']
access_key ****************CQID shared-credentials-file
secret_key ****************NJo8 shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
npx sls deploy
でAWS環境にDeployしてみる
公式のAWS - deployに書かれている通り、npx sls deploy --region ap-northeast-1
コマンドを実行するだけ(--region
オプションがないとデフォルトのus-east-1
にDeployされる)。実行すると以下のようになる。
[root@localhost serverless]# yarn deploy
yarn run v1.22.17
$ npx sls deploy --region ap-northeast-1
Deploying aws-node-project to stage dev (ap-northeast-1)
✔ Service deployed to stack aws-node-project-dev (185s)
functions:
hello: aws-node-project-dev-hello (108 kB)
Toggle on monitoring with the Serverless Dashboard: run "serverless"
Done in 189.37s.
この状態になると実際にAWS環境のLambdaを呼び出す事もできる。
※注意として--function
オプションに渡すLambda関数名はservrless.ymlに定義している関数名を指定する必要がある
[root@localhost serverless]# npx serverless invoke --function hello --data "hello world" --stage dev --region ap-northeast-1
{
"statusCode": 200,
"body": "{\n \"message\": \"Go Serverless v3.0! Your function executed successfully!\",\n \"input\": \"hello world\"\n}"
}
npx sls deploy
でやっている事
The sls deploy command deploys your entire service via CloudFormation.(sls deployコマンドは、CloudFormation経由でサービス全体をデプロイします。)
と書かれているように、裏で実行されている事としては、CloudFormationのスタックを作成しAWSサービスの各リソースを構築するという事が行われている。
実際にマネジメントコンソールからCloudFormationのスタックを確認してみると、以下のようにserverlessによって作成されたスタックが確認できる。
また、スタックの中を見てみると以下の画像のようにLambda関数以外にも複数のリソースが自動で作成されている事が分かる。
これがserverlessフレームワークがサーバレスアプリケーションの構築のためのフレームワークと言われる所以。つまり、アプリケーションとして成り立つために必要になるログ出力など最低限のものを自動で構築してくれ、一般的にLambdaのDeployというとS3にzipを格納してそれをLambda関数に適用させるがそれに必要になるS3も自動で作成してくれる。
この辺りが便利になっている部分だろう。
ちなみに、自分でLambda関数のDeployをするためにCloudFormationを書くとCloud Formationを使ってGolang製LambdaのCICDをGitHub Actionsで構築でやってみたように色々AWSリソースの定義を書く必要があり大変。
※今回serverlessの方で自動的に作成してくれたCloudFormationのtemplate.yamlの全体としては以下(これだけの定義をserverless.yml
に書いたたった数行でできる)。
template.yaml(クリックで開きます)
AWSTemplateFormatVersion: 2010-09-09
Description: The AWS CloudFormation template for this Serverless application
Resources:
ServerlessDeploymentBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
ServerlessDeploymentBucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref ServerlessDeploymentBucket
PolicyDocument:
Statement:
- Action: 's3:*'
Effect: Deny
Principal: '*'
Resource:
- !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':s3:::'
- !Ref ServerlessDeploymentBucket
- /*
- !Join
- ''
- - 'arn:'
- !Ref 'AWS::Partition'
- ':s3:::'
- !Ref ServerlessDeploymentBucket
Condition:
Bool:
'aws:SecureTransport': false
HelloLogGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
LogGroupName: /aws/lambda/aws-node-project-dev-hello
IamRoleLambdaExecution:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: !Join
- '-'
- - aws-node-project
- dev
- lambda
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogStream'
- 'logs:CreateLogGroup'
Resource:
- !Sub >-
arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/aws-node-project-dev*:*
- Effect: Allow
Action:
- 'logs:PutLogEvents'
Resource:
- !Sub >-
arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/aws-node-project-dev*:*:*
Path: /
RoleName: !Join
- '-'
- - aws-node-project
- dev
- !Ref 'AWS::Region'
- lambdaRole
HelloLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Ref ServerlessDeploymentBucket
S3Key: >-
serverless/aws-node-project/dev/1649769206618-2022-04-12T13:13:26.618Z/aws-node-project.zip
Handler: handler.hello
Runtime: nodejs14.x
FunctionName: aws-node-project-dev-hello
MemorySize: 1024
Timeout: 6
Role: !GetAtt
- IamRoleLambdaExecution
- Arn
DependsOn:
- HelloLogGroup
HelloLambdaVersioncUWuCRlBIs9VT7ZlYopUEw3ADBvd3GjqQ5P9jF0YN8:
Type: 'AWS::Lambda::Version'
DeletionPolicy: Retain
Properties:
FunctionName: !Ref HelloLambdaFunction
CodeSha256: ragOnPdr8ZNOSxsHRpByhvtsKyMt7qYT6H3gJcsuBoM=
Outputs:
ServerlessDeploymentBucketName:
Value: !Ref ServerlessDeploymentBucket
Export:
Name: sls-aws-node-project-dev-ServerlessDeploymentBucketName
HelloLambdaFunctionQualifiedArn:
Description: Current Lambda function version
Value: !Ref HelloLambdaVersioncUWuCRlBIs9VT7ZlYopUEw3ADBvd3GjqQ5P9jF0YN8
Export:
Name: sls-aws-node-project-dev-HelloLambdaFunctionQualifiedArn