Serverless FrameworkとRubyでLambda Layers

Serverless FrameworkとRubyでLambda Layers:


はじめに

この記事は、UUUM Advent Calendar 2018 21日目の記事です。

すでに出遅れている感がありますが、今年のre:Inventで発表されたLambdaのruby対応(Custome runtimes)とLambda LayersServerless Frameworkで試してみました!


AWS Lambda Layers

https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-use-any-programming-language-and-share-common-components/

今までは各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

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)