はじめに
この記事は、UUUM Advent Calendar 2018 21日目の記事です。
すでに出遅れている感がありますが、今年のre:Inventで発表されたLambdaのruby対応(Custome runtimes)とLambda LayersをServerless Frameworkで試してみました!
AWS Lambda Layers
今までは各Lambda関数が共通で使用するライブラリであっても、各関数のパッケージに同梱する必要があったためライブラリの管理が手間だったりパッケージのサイズが肥大化したりなどといった問題がありました。
AWS Lambda Layersが登場したことで共通的な処理をLayerとしてまとめて切り出すことができ、管理がしやすくなり、各パッケージに同じ処理を毎回同梱させる必要がなくなりました。
Layerの用意
まず、Layer側から用意していきます。
今回はDB(DynamoDB)処理周りをlayerとして切り出してみました。
serverless.yaml
Layerに関する定義と、併せてDynamoDBの定義も記載しておきます。
当然のことですが、LayerとFunctionでregionは同じにする必要があるので明示的にap-northeast-1を指定しておきます。
service: aws-ruby-layers
provider:
name: aws
region: ap-northeast-1
layers:
dbLayer:
path: layers
compatibleRuntimes:
- ruby2.5
resources:
Resources:
FoodsDynamoDbTable:
Type: 'AWS::DynamoDB::Table'
Properties:
AttributeDefinitions:
-
AttributeName: name
AttributeType: S
KeySchema:
-
AttributeName: name
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: "foods"
layerのソースコード
DynamoDB上のfoodテーブルからnameを指定してevaluationを取得するコードです。
require 'aws-sdk'
class Food
def initialize
@dynamoDB = Aws::DynamoDB::Resource.new(region: 'ap-northeast-1')
end
def evaluation(food_name)
table = @dynamoDB.table('foods')
resp = table.get_item({
key: { 'name' => food_name }
})
resp.item['evaluation']
end
end
deploy
これでLayer側は完成なので、deployします。
sls deploy
と入力するだけで諸々のリソースが構築されます。
また、最後に出てくるarnは後から使うのでメモしておきます。
$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (376 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.........
Serverless: Stack update finished...
Service Information
service: aws-ruby-layers
stage: dev
region: ap-northeast-1
stack: aws-ruby-layers-dev
api keys:
None
endpoints:
None
functions:
None
layers:
dbLayer: arn:aws:lambda:ap-northeast-1:xxxxxxxxxxx:layer:dbLayer:1
Functionの用意
続いてLayerの処理を呼び出すFunctionを構築していきます。
まず sls create
で雛形を作成します。
$ sls create --template aws-ruby --name lambda-ruby-func
Serverless: Generating boilerplate...
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.35.1
-------'
Serverless: Successfully generated boilerplate for template: "aws-ruby"
これで、以下のようなファイルが生成されます。
.
├── handler.rb
└── serverless.yml
serverless.yaml
生成されたserverless.yamlを編集していきます。
DynamoDBを利用するためのIAMRoleの設定は、layer側ではなくfunction側で定義します。
また、functionにlayerを利用する旨定義し、layerのdeploy時に記載されていたarnを定義しておきます。
service: lambda-ruby-func
provider:
name: aws
runtime: ruby2.5
region: ap-northeast-1
iamRoleStatements:
- Effect: "Allow"
Resource: "arn:aws:dynamodb:ap-northeast-1:*:*"
Action:
- "dynamodb:*"
functions:
hello:
handler: handler.hello
layers:
- arn:aws:lambda:ap-northeast-1:xxxxxxxxx:layer:dbLayer:1
Functionのソースコード
Layerを呼び出す際のコードです。
ここで少しハマったのですが、Layerのコードは/opt配下に格納されます。
Pythonなどではlayerの/opt配下は自動的にライブラリ検索パスに追加されるとのことだったのですが、
rubyの$LOAD_PATHには追加されていませんでした。。
そのためとりあえず絶対パスでrequireして読み込んでいます。
(もっといいやり方があったらどなたか教えてください!)
require 'json'
require '/opt/dynamodb_client'
def hello(event:, context:)
food = Food.new
name = 'カレー'
evaluation = "#{name}は#{food.evaluation(name)}"
{ statusCode: 200, body: JSON.generate(evaluation) }
end
deploy & invoke
今回はlambdaのイベントソースを特に指定していないので、ローカルから直接invokeします。
DynamoDBにはあらかじめ適当なデータを登録してあります。
$ sls deploy
〜省略〜
$ sls invoke --function hello
{
"statusCode": 200,
"body": "\"カレーは最高に美味い\""
}
まとめ
共通処理をLayerに切り分けることができるようになり、サーバーレス関連の設計がより柔軟にできるようになったと感じました。
今回は雑な切り分けでLayerを分けましたが、今後はより良いLayerの切り分け方を模索していけたらと思います。
宣伝
UUUMではエンジニアを募集しています!!
詳しくは下記のリンクをご参照ください。
https://www.wantedly.com/projects/9783
https://www.wantedly.com/projects/25995