Serverless Framework 素振り

Serverless Framework とは AWS Lambda などの FaaS を構成管理するツールです。AWS 専用というわけではなくさまざなクラウドプロバイダの FaaS をサポートしています。

今回は AWS Lambda の構成管理を試したかったので下記のドキュメントを参考に素振りしてみました。

素振り

serverless コマンドをインストールします。下記のワンライナーでインストールできます。

curl -o- -L https://slss.io/install | bash
serverless --version

最小っぽい構成でデプロイしてみます。関数が未定義なので実質何もない構成です。

# serverless.yml
service: serverless-aws-hello
provider:
  name: aws
  region: ap-northeast-1

serverless deploy でデプロイを実行します。

serverless deploy -v

Serverless Framework は serverless.yml の内容を元に CloudFormation のテンプレートを作って構成をデプロイします。-v オプションを付けると CloudFormation によるデプロイの進捗も表示されます。

デプロイ後にマネジメントコンソールで CloudFormation を見てみると次のリソースが作成されていました。

  • AWS::S3::Bucket
  • AWS::S3::BucketPolicy

以下のドキュメントによると、この S3 バケットへ Lambda 関数のコードがパッケージングされた zip アーカイブがアップロードされ、CloudFormation で S3 オブジェクトをソースに Lambda へデプロイされます。

なので、何もない最小の構成でもこの S3 バケットは最初に作成されます。

次のように deploymentBucket.name で既存の作成済バケットを指定すればこの S3 バケットを作成しないようにもできます。

ervice: serverless-aws-hello
provider:
  name: aws
  region: ap-northeast-1
  deploymentBucket:
    name: ore-no-serverless-deployment-bucket

初めの構成に Lambda 関数を追加してみます。

service: serverless-aws-hello
provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs12.x
functions:
  hello:
    handler: handler.hello

次のリソースが追加されました。

  • AWS::Logs::LogGroup
  • AWS::IAM::Role
  • AWS::Lambda::Function
  • AWS::Lambda::Version

serverless invokeserverless logs で Lambda 関数を実行したり、ログを表示したりできます。

# 実行
serverless invoke -f hello

# ログ表示
serverless logs -f hello -t

serverless invoke local でローカル実行できます。

# ローカル実行
serverless invoke local -f hello

js のコードを適当に書き換えで再デプロイしてみます。

vim handler.js
serverless deploy -v

次のリソースが追加/更新されました。Lambda 関数の新しいバージョンが作成されて LATEST がそのバージョンに更新されています。

  • AWS::Lambda::Version (CREATE)
  • AWS::Lambda::Function (UPDATE)

serverless deploy function で関数名を指定してデプロイすると CloudFormation を経由せずに Lambda に直接アップロードされるため素早く更新できます。

serverless deploy function -v -f hello

REST API になるように構成を変更してみます。

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

デプロイすると次のリソースが作成されました。

  • AWS::Lambda::Function (UPDATE)
  • AWS::ApiGateway::RestApi (CREATE)
  • AWS::ApiGateway::Resource (CREATE)
  • AWS::ApiGateway::Method (CREATE)
  • AWS::ApiGateway::Deployment (CREATE)
  • AWS::Lambda::Permission (CREATE)

HTTP で呼べるようになっています。

curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello

Cloud Watch Event からスケジュール実行させてみます。

functions:
  hello:
    handler: handler.hello
    events:
      - schedule: cron(* * * * ? *)

次のリソースが作成されました。

  • AWS::Lambda::Function (UPDATE)
  • AWS::Events::Rule (CREATE)
  • AWS::Lambda::Permission (CREATE)

SNS をイベントソースにしてみます。

functions:
  hello:
    handler: handler.hello
    events:
      - sns: hello

次のリソースが作成されました。SNS トピック自体もこれだけで作成されています。

  • AWS::Lambda::Function (UPDATE)
  • AWS::Lambda::Permission (CREATE)
  • AWS::SNS::Topic (CREATE)

serverless を実行するための最小の IAM ポリシー

serverless を実行する側の IAM に必要なポリシーについて、下記のドキュメントから、

下記の gist が参照されていました。

Lambda の Execution role を作るためには仕方ないですが IAM 関係のアクションが許可されているなら実質 AdministratorAccess と同じなのではという気がしないでもないです。

次のように作成済の Role を指定すれば新たに作成されることはないので、必要なものだけに絞ったもっと小さいポリシーにもできそうです。

service: serverless-aws-hello
provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs12.x
  role: arn:aws:iam::999999999999:role/service-role/ore-no-lambda-role
functions:
  hello:
    handler: handler.hello

パッケージング

下記のドキュメントによると、

https://www.serverless.com/framework/docs/providers/aws/guide/packaging/

下記のパターンにマッチするファイルは Lambda にアップロードされるコードから自動的に除外されます。

.git/**
.gitignore
.DS_Store
npm-debug.log
.serverless/**
.serverless_plugins/**

serverless.yml で exclude や include を指定して細かく制御もできます。

service: serverless-aws-hello
provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs12.x
package:
  exclude:
    - .envrc
    - src/*.js
  include:
    - src/handler.js
functions:
  hello:
    handler: handler.hello

exclude には !src/handler.js のような記述もできます。.gitignore などでお馴染みですが、! を前置すればそのパターンにマッチするファイルは他の exclude のパターンにマッチしていたとしても除外されなくなります。

exclude! の前置と、include について、どちらも効果は似たようなものです。どちらも exclude で除外したもののうち含めたいものを指定します。ただし後述の通り違いもあります。

デフォでは node_modules/ から devDependencies のパッケージは自動的に除外されます。ただ includenode_modules/** などが含まれると devDependencies のパッケージでもすべて含まれるようになります。exclude!node_modules/** などとすれば devDependencies は除外されたままです。

例えば、特定のディレクトリのみを含めるため、次のようにすべてを exclude したうえで include で必要なディレクトリを指定したとします。

package:
  exclude:
    - '**'
  include:
    - 'dist/**'
    - 'node_modules/**'

これだと devDependencies が含まれてしまします。

次のように exclude! 前置で指定すれば期待した結果になります。

package:
  exclude:
    - '**'
    - '!dist/**'
    - '!node_modules/**'

resources

serverless.ymlresources に記載したないようはそのまま CloudFormation のテンプレートの内容になります。

また、serverless.yml の内容は最終的には CloudFormation のテンプレートになるので、resources 以外でもいろいろなば場所で CloudFormation の記法が利用可能です。

例えば resources で SQS キューを作成して、その ARN や URL を Lambda の環境変数に入れたりできます。

functions:
  hello:
    handler: handler.hello
    environment:
      # ↓で作成したリソースを参照する
      HELLO_SQS_QUEUE_URL: { Ref: HelloSqs }

resources:
  # CloudFormation のテンプレートそのまま
  Resources:
    HelloSqs:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: hello

iamRoleStatements

iamRoleStatements で Lambda 関数が使用する IAM Role のポリシーが定義できます。

provider:
  name: aws
  runtime: nodejs12.x
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "ssm:GetParameter"
      Resource:
        - "*"

ポリシーはこの内容でそのまま作成されるわけではなく、Lambda 関数がログ出力やイベントを処理するために必要なポリシーが自動的に追加されます。なので logs:PutLogEvents などのポリシーは記述する必要はありません。

または role で ARN を指定すれば既存の IAM Role が使用できます。この場合は自動でポリシーが追加されたりはしないので必要なポリシーを設定しておく必要があります。

provider:
  name: aws
  role: arn:aws:iam::9999999999:role/ore-no-lambda-role

destinations

destinations.onSuccessdestinations.onFailure で Lambda 関数の成功時や失敗時の後段の宛先を指定できます。

functions:
  hello:
    handler: handler.hello
    destinations:
      onSuccess: success
      onFailure: arn:aws:sns:ap-northeast-1:9999999999:ore-no-sns-topic
  success:
    handler: handler.success

この要素では CloudFormation の記法は使えず、以下のどちらかをベタに指定する必要があります。

  • serverless.yml で定義された他の関数名
  • SNS トピックや SQS キューなどの ARN をベタに記述

下記あたりで startsWith メソッドが呼ばれているため文字列しか指定できないためです。CloudFormation の記法だと { Ref: Hoge } みたいなオブジェクトになるのでエラーになります。

ので SNS トピックなどを指定しようとするとリージョン名やアカウント番号をベタに書く必要があります。

あるは Serverless Pseudo Parameters プラグインを使えばベタ書きを回避できます。

まずプラグインを次のようにインストールします。

serverless plugin install -n serverless-pseudo-parameters

次のようにリージョン名やアカウント番号に書き換えられます。

plugins:
  - serverless-pseudo-parameters
functions:
  hello:
    handler: handler.hello
    destinations:
      onFailure: arn:aws:sns:#{AWS::Region}:#{AWS::AccountId}:ore-no-sns-topic

さいごに

いらなくなったら serverless remove でキレイに消せます。

serverless remove -v

Lambda 関数を作るとき、手動でマネジメントコンソールからポチポチするのは辛いので AWS リソースは Terraform で管理しつつ Lambda 関数のコードは Makefile でデプロイ するようにしていました。

が、今日日は Serverless Framework とかのほうが主流なのかなー名前よく聞くし、と思ったので、試してみました。

試してみたものの、Serverless Framework は CloudFormation ありきだし、デプロイのために S3 バケットが作られたりするので、操作可能なポリシーが制限されている状況だと使えなさそうでした(IAM Role の作成などができない制限されたポリシーの範囲内で作業することがちょいちょいある)。

普通に CloudFormation が使える状況なのなら便利かもしれません。Terraform で API Gateway を管理するの、エンドポイントがたくさんあると怠すぎると思います・・ただ、Lambda 関数以外にいろいろリソースを作ろうとすると CloudFormation の知識が必要になってくるので、AWS の構成管理を Terraform に振り切ってる今のこの現状からあえて CloudFormation に切り替えるのも大変そうです。

簡単な Lambda 関数なら今後も Terraform + Makefike で良いかなと思います。

あるいは API Gateway とかと絡める必要があるなら Lambda 周りだけ Serverless Framework で、その他の resources に書くような内容は Terraform で、のように使い分けるのも有りかもしれません。