病みつきエンジニアブログ

機械学習、Python、Scala、JavaScript、などなど

Serverless Components を使った少しだけ実践的なアプリケーションの作り方

Serverless Framework の新しい機能「Serverless Components」を使って、サーバーレスなアプリケーションを作ってみました。

いくつかつまずくところもあったので、ブログに残します。ちなみに今回作ったアプリケーションは(特に紹介しませんが) Nature Remo の API を監視するアプリケーションです。

github.com

また、今回は AWS Lambda やサーバーレス自体は知っている読者を対象にしています。

Serverless Components とは

Serverless Framework という、サーバーレスなアプリケーションを作るためのツール/SaaS に新しく搭載された新機能(?)です。今年の4月にGAとなりました。

www.serverless.com

Serverless Components は無料で使えますが、GA 版に伴い serverless.com へのログインが必要になっています。

Serverless Components は今までの Serverless Framework と違い、

  • CloudFormation に非依存でデプロイが速い
  • 自作 Component (プラグイン的な)の定義が容易で、ベンダー非依存 (紹介しません)
  • 新機能「dev mode」

といった特長があります。dev mode は、 serverless dev を走らせておくと、自動デプロイ(≒ Lambda へのリアルタイム反映)をしてくれる開発用機能 です。便利!

定義ファイルもかなりシンプル で、もっともシンプルな serverless.yml の定義は

component: aws-lambda
name: your-lambda-function-name

だけです。便利!

使える Component のリストは、次の GitHub のグループにリストアップされているものです。

github.com

一方で、 Serverless Component の GA 版では、1つの serverless.yml に 1 つのデプロイ対象(関数等)しか書けない制約があります。これは制限というよりは、規約という方が近く、そういう思想で作られているように見えます*1。したがって、Serverless Components を使ってアプリケーションを作る場合は、Component を組み合わせるような作り方です。

DynamoDB を使った、AWS Lambda のアプリケーション

Serverless Components の考え方を紹介するため、 DynamoDB を使った Lambda のアプリケーションを考えてみます。

通常の Serverless Framework の serverless.yml は、次のように resources の下に生やしていったはずです*2

service: app.yml

provider:
  name: aws
  ...

resources:
  Resources:
    hogeTable:
      Type: AWS::DynamoDB::Table

Serverless Components では、1つの serverless.yml にリソースをどんどん生やしていくのではなく、複数の Component を組み合わせていきます。今回は、Lambda のための「aws-lambda」と、DynamoDB を定義するための「aws-dynamodb」、さらに、Lambda から DynamoDB にアクセスできる IAM Role を定義するための「aws-iam-role」の3つの component を組み合わせます。

これらのインフラの依存関係は、次のようになっているので、この順番に紹介します。

f:id:yamitzky:20200810172149p:plain

DynamoDB のテーブル定義

次のように serverless.yml を作ってください。inputs の詳細などは、公式ドキュメントに乗っています、attributeDefinitions などはテーブルのキー定義なので、ユースケースによって異なります。

ここで大事なのは、 org や app を指定することです。Component は組み合わせて使うので、app を指定してグルーピングをしやすくするのです。(ちなみに文字列決め打ちじゃなく、引数や環境変数でも指定できます。)

component: aws-dynamodb@1.1.2
name: your-table-name
org: your-org-name
app: your-app-name

inputs:
  attributeDefinitions:
    - AttributeName: id
      AttributeType: S
  keySchema:
    - AttributeName: id
      KeyType: HASH

この状態で sls deploy をすると、AWS 上に DynamoDB のテーブルが作られます。また、 app.serverless.com を見に行くと、次のようにデプロイしたインフラの情報が保存されています。CloudFormation の代わりに、serverless.com 内に状態保存されるといった感じです。

f:id:yamitzky:20200810173040p:plain

IAM Role から DynamoDB を使う

DynamoDB は、他の Component に依存していないので簡単でした。しかし、IAM Role は「このテーブルARNに書き込んでもいいよ!」という指定をしたいと思います。つまり、コンポーネント間で変数(この場合は、テーブルのARN)をやり取りする必要があります。

また、1つの serverless.yml には 1 つの Component のことしか書けないので、フォルダ分けをして複数の serverless.yml を定義する必要があります*3。つまり、こういう状態です。

.
├── table
│   └── serverless.yml
└── iam-role
    └── serverless.yml

iam-role/serverless.yml は、次のように定義をします。inputs の詳細などは、公式ドキュメントに乗っています。

component: aws-iam-role@2.0.2
name: your-iam-role-name
org: your-org-name
app: your-app-name

inputs:
  policy:
    - Effect: Allow
      Action:
        - sts:AssumeRole
      Resource: '*'
    - Effect: Allow
      Action:
        - logs:CreateLogGroup
        - logs:CreateLogStream
        - logs:PutLogEvents
      Resource: '*'
    - Effect: Allow
      Action:
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource: ${output:your-table-name.arn}

ここで、output という概念が出てきました。output を使うと、同じ app/organization/stage でグルーピングされた別リソースから、出力結果を持ってくることができます*4。このために、app、organization の指定をすべきだったのです。

こちらも同様に、serverless deploy すると、AWS 上に変更がデプロイされ、app.serverless.com 内にインフラの情報が登録されます。

Lambda のデプロイ

ここまで来るともはや面白いことはないですが、次のようなフォルダ構成で、

.
├── table
│   └── serverless.yml
├── function
│   ├── package.json
│   ├── serverless.yml
│   └── src
└── iam-role
    └── serverless.yml

serverless.yml を定義し、

component: aws-lambda@2.0.0
name: your-lambda-name
org: your-org-name
app: your-app-name

inputs:
  src: ./src
  roleArn: ${output:youriam-role-name.arn}
  env:
    TABLE_NAME: ${output:your-table-name.name}

src/index.js に適当に関数を書いていくだけです。無理やり TypeScript に対応したバージョンのサンプルもあります。

app.serverless.com には、こんな感じで登録されていきます(名前などは異なりますが)。

f:id:yamitzky:20200810175110p:plain

デプロイ方法まとめ

少し長くなりましたが、

  • フォルダをわけて serverless.yml を定義
  • 順番に serverless deploy を実行

していくだけです。Tips として、 .env ファイルは、親ディレクトリまでさかのぼります(organization を .env に入れるときとかに便利)。

また、一部の Component は、複数のリソースを1度に定義できるので(例:API Gateway と S3 と Lambda と...)、さらに簡単な場合もあります。

良かったところ

実践的な例では、Serverless Components のメリットが伝わりづらかったかもしれませんが、 デプロイの速さや、 dev mode などは便利です。

また、Component ごとにドキュメントが別れているのもわかりやすいなと感じました。DynamoDB の定義なども、旧型の resources に書くよりも、Serverless Component で書いた方がわかりやすいのではないでしょうか。

良くないところ

Serverless Components は GA になったとはいえ、Component が量/質ともに十分に用意されているとは言えません

Component の種類はここに定義されているものだけしかありません。たとえば GCP 用のものはまだ定義されていないようです。

そして各 Component の実装もまちまちです。例えば aws-lambda では IAM Role をカスタマイズする機能はありますが、aws-lambda-cron にはない機能です。また、aws-lambda はネイティブに TypeScript をサポートしていなかったり、Python もサポートされていません。

最後に、注意点として、GA 版とベータ版の Serverless Components には定義ファイルの互換性がありません。例えば、Next.js をサーバーレス環境にデプロイする serverless-next.js は GA 版に未対応です。(といっても、β版のまま使い続けることはできます)

まとめ

今までの Serverless Framework は、ある種完成品でしたが、 Serverless Components はまだ発展途上であるように感じます。タイトルに「少しだけ実践的」と書いた通り、実務では不足があるケースもあるかと思います。そのため、無理に移行する必要はないのではないでしょうか(事故りそうだし)。

とはいえ、デプロイの速さ、dev mode、定義ファイルの書きやすさなど、ユースケースにぴったりとハマることもあるかと思います。

それでは、よいサーバーレスライフを!

*1:リソースを1つのインフラスタックに詰め込むのではなく、分割するのが大事であるため、これを推奨している、と書いてある

*2:動かしてないので自信はない

*3:本当にこの方法しかないんでしょうか、、、

*4:ちなみに app、organization、stage を明示的に指定することもできます