33
19

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.

Ateam Brides Inc.Advent Calendar 2021

Day 2

Serverless Framework の使い方を初心者にも分かりやすく説明する

Last updated at Posted at 2021-12-01

2023/10/28現在、Node.jsのLTSは20.9.0になった。昔の記事でも見てくれている方がいましたので、最新の環境で動作するか確認し、記事を更新しました。

はじめに

これはAteam Brides Inc. Advent Calendar 2021(現:エイチームライフデザイン)の二日目の記事です。

さて、今回は業務でServerless Frameworkを使ったので基本的な使い方を紹介したいと思います:zap:

Serverless Frameworkってどうやって動いてるの?

全体のイメージを持って取り掛かると理解が早いと思い、概要図を描きました:pencil:

Serverless Frameworkの概要図

開発者は1〜3をするだけで、必要なAWSのリソースを作成してくれます:tada:

補足概要図では最終的にLambdaとAPI Gatewayのリソースを作成していますが、API Gatewayの部分はLambdaが動くトリガーだったり使用するリソースになるので、作りたいものによって変わります。今回は、実際に作ってみる雛形に合わせたリソースを概要図に描いています。

今までAWSコンソールでポチポチしてリソース作成したり、バラバラだったLambda等の運用方法が統一され開発速度が上がることが Serverless Framework を導入するメリットかと思います。

開発環境を準備する

それでは、早速、動かしてみましょう。私の実行環境は以下の通りです:eyes:

実行環境
OS: macOS Ventura v13.5.2
node: v18.16.0

1. Serverless Framework をインストール

まずは公式ドキュメントに従って、自分のマシンにServerless Frameworkをインストールしましょう:wrench:

ServerlessFrameworkをインストール
# インストール
$ npm install -g serverless

# バージョン確認
serverless --version

> Framework Core: 3.36.0
> Plugin: 7.1.0
> SDK: 4.4.0

2. Serverless Framework が使う IAMユーザーを作る

概要図にも書いた通り、Serverless Frameworkはデプロイコマンド一つでAWSの各リソースを作成してくれます。その際に、リソースを作成する権限をもったIAMユーザーが必要になります:santa:

それでは早速、AWSコンソールからIAMユーザーを作成してみましょう。

AWSコンソールから IAMユーザーを追加

  • ユーザー名: serverless-servicename-agent
  • AWS アクセスの種類を選択: アクセスキー - プログラムによるアクセス のみ
  • 既存のポリシーから直接アタッチ
  • ポリシーを作成後、リロードして作成したポリシーにチェックをつけてユーザを作成
    リロードして作成したポリシーを反映
  • ユーザーが作成されたら、ローカルコードから使用するためのアクセスキーを作成します。

image.png

image.png

作成したら、「アクセスキー ID」と「シークレットアクセスキー」をメモしておいてください(後で使います)

3. 作成した IAMユーザーのクレデンシャル情報を自分のマシンに設定する

手順2で作成した IAMユーザーを使って自分のマシンからデプロイするためには、クレデンシャル情報(「アクセスキーID」と「シークレットアクセスキー」)を認証情報ファイルに記載する必要があります:lock:

下記のコマンドの「アクセスキーID」と「シークレットアクセスキー」の箇所を先程メモした値に書き換えて実行してください。
profileオプションを指定することで名前付きプロファイルにしています。(今回は分かりやすいようにIAMユーザー名に合わせています。)

AWSクレデンシャル情報を認証情報ファイルに設定する
$ serverless config credentials --provider aws --key アクセスキーID --secret シークレットアクセスキー --profile serverless-servicename-agent

# 確認(コマンドqで終了)
less ~/.aws/credentials

# profileオプションに指定したプロファイル名のクレデンシャル情報が追加されていることを確認
> [default]
> aws_access_key_id=アクセスキーID
> aws_secret_access_key=シークレットアクセスキー
> 
> [serverless-servicename-agent]
> aws_access_key_id=アクセスキーID
> aws_secret_access_key=シークレットアクセスキー

プロジェクトを作成

開発環境が準備できたら、峠は超えました。
下記のコマンドを実行してプロジェクトを作成しましょう:baby:

プロジェクトを作成
$ mkdir my-special-service
$ cd my-special-service
$ serverless create --template aws-nodejs-typescript --name my-special-service
$ npm install
  • nameオプションで指定したサービスを作成します
  • サービス名にスネークケースは使わないほうがよいでしょう - 余談:私はサービス名を`my_special_service`のようにスネークケースで設定したのですが、デプロイでS3バケットが自動作成される際に、S3の命名規則(`小文字、数字、ドット (.)、およびハイフン (-) のみ`)に違反しておりエラーになってしまいました。もちろん、S3バケット名は任意の名称を設定できるので回避策もありますが、最初は公式ドキュメント通りがオススメです。

下記のフォルダ構成でひな形が作成されます。
aws-nodejs-typescriptの雛形

知っておきたいserverless.tsファイル

このファイルがServerless Frameworkのconfigファイルであり、とて〜も重要なファイルになります。
このファイルを自由に扱えたらServerless Framework中級者なのかなと思います(私は初心者)。

ざっくりと設定項目を洗ってみたいと思います:point_up:

key 内容 使い所
provider どこのクラウドサービスをどんな設定で動かすか(AWS以外もGCPとか対応してる) slsコマンドのデフォルト値とか設定しておくとオプションで指定しなくてもよくなる
functions lambdaファンクションとそのトリガーの設定
layers lambdaファンクションが使うlayerの設定 node_moduleや共通処理を切り出してくれる
resources lambdaファンクションが使うリソースを事前に定義 定義したリソースはデプロイ時に自動作成してくれる
plugins 機能拡張できるプラグインを設定します 有名なのだとserverless-offlineプラグインを入れてローカルで動かしたり

サービスをデプロイする

概要図の3.デプロイコマンドを実行に該当します。
ここで紹介するsls deployというコマンドはこれからいっぱい使うことになります。(slsservelessの略)
しかし、まだ下記のコマンドは実行しないでください:poop:

デプロイコマンド(実行しないで!)
$ sls deploy --aws-profile serverless-servicename-agent --region ap-northeast-1 --stage dev --verbose
  • aws-profileオプションの値は先に設定した名前付きプロファイルの名前を指定します
  • aws-profileregionstageオプションはserverless.tsに設定しておけばCLIオプションで指定する必要がなくなります(後述します)

serverless.ts の設定を調整

先述のとおり、デプロイのたびにCLIオプションを書くのは面倒ですね。
そこで、CLIオプションの値をserverless.tsに設定することで、毎回書く手間を省いてくれます:wrench:

serverless.tsにオプション値を設定してCLIオプションを省略する
...
  provider: {
    name: 'aws',
    runtime: 'nodejs14.x',
+   profile: 'serverless-servicename-agent',
+   region: 'ap-northeast-1',
+   stage: "${opt:stage, 'dev'}",
    apiGateway: {
      minimumCompressionSize: 1024,
      shouldStartNameWithService: true,
    },
    environment: {
      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
      NODE_OPTIONS: '--enable-source-maps --stack-trace-limit=1000',
    },
    lambdaHashingVersion: '20201221',
  },
...
  • stageに指定した${opt:stage, 'dev'}ですが、これは${可変のソース, デフォルト値}の形になっています。opt(オプション)以外にもenv(環境変数)など色んなソースを参照できます。

書いてみて分かると思うのですが、typescriptの型によってインテリセンスが効くので書きやすいですし、他にも用意されている設定を知ることもできて面白いです。

さて、これで先程のデプロイコマンドはだいぶスッキリ書くことができます。デプロイしてみましょう:rocket:

すっきりしたデプロイコマンド
$ sls deploy --verbose
  • --verboseオプションは任意です。つけておくとServerless Frameworkがどうやって動いているか理解しやすくなるので最初はオススメです
  • もし、デプロイする前に構文を検証したい場合は deploy の部分を package に変更して実行してください

エラーがでるかも?(cloudformation:DeleteChangeSet権限がない)

最新バージョンでは下記のエラーが発生するかもしれません。

User: arn:aws:iam::99999999999:user/serverless-servicename-agent is not authorized to perform: cloudformation:DeleteChangeSet on resource: arn:aws:cloudformation:ap-northeast-1:99999999999:stack/my-special-service-dev/db85a570-7572-11re-9dc9-06df524be86t because no identity-based policy allows the cloudformation:DeleteChangeSet action

serverless-servicename-agentユーザーにcloudformation:DeleteChangeSet権限が許可れれていないというエラーなので、下記のように更新してください。

serverless-servicename-policyポリシーを更新
{
    "Statement": [
        {
            "Action": [
                "apigateway:*",
+               "cloudformation:DeleteChangeSet",
                "cloudformation:CancelUpdateStack",
                "cloudformation:ContinueUpdateRollback",
                "cloudformation:CreateChangeSet",
                "cloudformation:CreateStack",
                "cloudformation:CreateUploadBucket",
                "cloudformation:DeleteStack",
                "cloudformation:Describe*",
                "cloudformation:EstimateTemplateCost",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:Get*",
                "cloudformation:List*",
                "cloudformation:UpdateStack",
                "cloudformation:UpdateTerminationProtection",
                "cloudformation:ValidateTemplate",
                "dynamodb:CreateTable",
                "dynamodb:DeleteTable",
                "dynamodb:DescribeTable",
                "dynamodb:DescribeTimeToLive",
                "dynamodb:UpdateTimeToLive",
                "ec2:AttachInternetGateway",
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:CreateInternetGateway",
                "ec2:CreateNetworkAcl",
                "ec2:CreateNetworkAclEntry",
                "ec2:CreateRouteTable",
                "ec2:CreateSecurityGroup",
                "ec2:CreateSubnet",
                "ec2:CreateTags",
                "ec2:CreateVpc",
                "ec2:DeleteInternetGateway",
                "ec2:DeleteNetworkAcl",
                "ec2:DeleteNetworkAclEntry",
                "ec2:DeleteRouteTable",
                "ec2:DeleteSecurityGroup",
                "ec2:DeleteSubnet",
                "ec2:DeleteVpc",
                "ec2:Describe*",
                "ec2:DetachInternetGateway",
                "ec2:ModifyVpcAttribute",
                "events:DeleteRule",
                "events:DescribeRule",
                "events:ListRuleNamesByTarget",
                "events:ListRules",
                "events:ListTargetsByRule",
                "events:PutRule",
                "events:PutTargets",
                "events:RemoveTargets",
                "iam:AttachRolePolicy",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:DeleteRolePolicy",
                "iam:DetachRolePolicy",
                "iam:GetRole",
                "iam:PassRole",
                "iam:PutRolePolicy",
                "iot:CreateTopicRule",
                "iot:DeleteTopicRule",
                "iot:DisableTopicRule",
                "iot:EnableTopicRule",
                "iot:ReplaceTopicRule",
                "kinesis:CreateStream",
                "kinesis:DeleteStream",
                "kinesis:DescribeStream",
                "lambda:*",
                "logs:CreateLogGroup",
                "logs:DeleteLogGroup",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams",
                "logs:FilterLogEvents",
                "logs:GetLogEvents",
                "logs:PutSubscriptionFilter",
                "s3:CreateBucket",
                "s3:DeleteBucket",
                "s3:DeleteBucketPolicy",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion",
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:ListAllMyBuckets",
                "s3:ListBucket",
                "s3:PutBucketNotification",
                "s3:PutBucketPolicy",
                "s3:PutBucketTagging",
                "s3:PutBucketWebsite",
                "s3:PutEncryptionConfiguration",
                "s3:PutObject",
                "sns:CreateTopic",
                "sns:DeleteTopic",
                "sns:GetSubscriptionAttributes",
                "sns:GetTopicAttributes",
                "sns:ListSubscriptions",
                "sns:ListSubscriptionsByTopic",
                "sns:ListTopics",
                "sns:SetSubscriptionAttributes",
                "sns:SetTopicAttributes",
                "sns:Subscribe",
                "sns:Unsubscribe",
                "states:CreateStateMachine",
                "states:DeleteStateMachine"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ],
    "Version": "2012-10-17"
}

デプロイの実行結果

先程のデプロイコマンドの出力ログを見てみます:mag:
image.png

image.png

改めて概要図を見ると、--verboseオプションで出力したログの流れと合っていることが確認できるかと思います。

Serverless Frameworkの概要図

  • ローカルディレクトリを見るとデプロイ用に.serverlessが作成されていることが分かります
  • 実際にAWSコンソールを見ると、各リソースが作成されています(Lambda関数が見つからない場合は東京リージョンになっていないかもしれません)

ローカルから実行してログを見る

デプロイしたLambda関数をローカルから実行してみましょう。

テンプレートにあるhello関数(`src/functions/hello/handler.ts`)を見ると、引数に`event`を受け取り、`event.body.name`を出力していることが分かります。
src/functions/hello/handler.ts
const hello: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
  return formatJSONResponse({
    message: `Hello ${event.body.name}, welcome to the exciting Serverless world!`,
    event,
  });
}

このeventに同階層にあるmock.jsonを渡したい

src/functions/hello/mock.json
{
  "headers": {
    "Content-Type": "application/json"
  },
  "body": "{\"name\": \"Frederic\"}"
}

このeventは、--pathor-pオプションでjsonファイルを指定することで渡すことができます。コマンドは以下の通りです。

ローカルから実行するコマンド
$ sls invoke -f hello -p src/functions/hello/mock.json

> Running "serverless" from node_modules
> {
>     "statusCode": 200,
>     "body": "{\"message\":\"Hello Frederic, welcome to the exciting Serverless world!\",\"event\":{\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"name\":\"Frederic\"},\"rawBody\":\"{\\\"name\\\": \\\"Frederic\\\"}\"}}"
> }
ログを見るコマンド
$ sls logs -f hello

> Running "serverless" from node_modules
> INIT_START Runtime Version: nodejs:14.v39       Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:78812e04a9860255e6713cb1e4f6e6092ec4c3cc2ad1a87256041a81269654fd
> START
> END Duration: 4.86 ms (init: 189.27 ms) Memory Used: 58 MB
  • Lambdaの実行ログがローカルからも確認できました

サービスを削除する

お金かかるしね、消しときましょう:joy:

$ sls remove --verbose

おわりに

初めて業務でServerless Frameworkを触りましたが、公式ドキュメントが丁寧に書いてあるので理解しやすかったです。サーバーに負荷の高い画像処理なんかを任せちゃえるし、今後も使っていきたいなと思いました:relaxed:

Ateam Brides Inc. Advent Calendar 2021の3日目は、
@Shuni がお送りします!!どんなネタを用意してくるのか楽しみです!!

参考

33
19
0

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
33
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?