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 を組み合わせます。
これらのインフラの依存関係は、次のようになっているので、この順番に紹介します。
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 内に状態保存されるといった感じです。
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 には、こんな感じで登録されていきます(名前などは異なりますが)。
デプロイ方法まとめ
少し長くなりましたが、
- フォルダをわけて 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、定義ファイルの書きやすさなど、ユースケースにぴったりとハマることもあるかと思います。
それでは、よいサーバーレスライフを!