LoginSignup
4
2

More than 5 years have passed since last update.

Serverless FrameworkでGemをLayersに切り出す

Last updated at Posted at 2019-02-27

環境

  • Mac OS X 10.14.3
  • Serverless Framework 1.36.1
  • Ruby 2.5

はじめに

Serverless FrameworkでRubyを使用する場合にパッケージ容量をもっとも圧迫するvendor(gemの内包ディレクトリ)を切り出す方法について書きます。

こちらの記事を読んで出来るようになること

  • Serverless Framework x RubyでGemをLayerに切り出す
  • Layersのバージョンアップにも対応可能

Lambda Layersとは

ざっくりと、各Lambda関数で共通化した処理をLambda関数の中に内包するのではなく、外に切り出してしまう仕組みのこと。

1. サンプルアプリケーションの作成

この記事ではserverless frameworkの使い方などは書きません

以下のコマンドでサンプルアプリケーションを作成します。

$ sls create -t aws-ruby -p sample

上記を実行すると、以下のようなディレクトリが作成されます。

sample/
- serverless.yml
- handler.rb

2. bundle init & gem install

$ cd sample

上記で、sampleディレクトリに移動しておきます。
sampleディレクトリ内で、以下を実行していきます。

$ bundle init

上記で、Gemfileが作成されるので、エディタでGemfileを編集していきます。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# 以下を追加
gem "business_time"

適当なGemをGemfileに追加して、以下を実行します。

$ bundle install --path vendor/bundle

3. serverless.ymlを編集

service: sample

provider:
  name: aws
  runtime: ruby2.5
  region: ap-northeast-1
  role: arn:aws:iam::ロールのarn

package:
 exclude:
   - vendor/**
   - Gemfile
   - Gemfile.lock

functions:
  hello:
    handler: handler.hello

layers:
  gems:
    path: vendor

この時点で一度デプロイします。

$ sls deploy

デプロイを実行すると、layersが作成されるので、再度serverless.ymlを編集します。

同一のserverless.yml内であれば、REFで参照をすることが出来ます。
公式ドキュメント

service: sample

provider:
  name: aws
  runtime: ruby2.5
  region: ap-northeast-1
  role: arn:aws:iam::ロールのarn

package:
 exclude:
   - vendor/**
   - Gemfile
   - Gemfile.lock

functions:
  hello:
    handler: handler.hello
    layers:
      - {Ref: GemsLambdaLayer}

layers:
  gems:
    path: vendor

functionlayersを追加して、Refで参照させるのですが、この時に参照方法のルールとして、Layer名をTitleCaseで記載し、LambdaLayerを末尾に結合させる必要があります。
今回のLayer名はgemsなので、GemsLambdaLayerとなっています。

Layer名がhogeだったら、HogeLambdaLayerとなるかと思います。

これで再度デプロイします。

$ sls deploy

これでAWSのLambda関数ページを見ても、まだLayerが反映されません。

どこが問題かと言うと、serverless.ymlには記載する順番を気をつける必要があるようで、以下のように、functionsより上にlayersを記載する必要があります。

上記を修正したserverless.ymlが以下

service: sample

provider:
  name: aws
  runtime: ruby2.5
  region: ap-northeast-1
  role: arn:aws:iam::ロールのARN

package:
 exclude:
   - vendor/**
   - Gemfile
   - Gemfile.lock

layers:
  gems:
    path: vendor

functions:
  hello:
    handler: handler.hello
    layers:
      - {Ref: GemsLambdaLayer}

これで再度デプロイすることでLambda関数にLayerが適用されます。

4. Lambda関数でgemを使用する

handler.rbを以下のようにします

require 'json'
require 'business_time'

def hello(event:, context:)
  puts Date.today.workday?

  { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') }
end

image.png

これで、適当にテストを作成し、「テスト」ボタンを押下すると、以下のようなエラーが発生します。

{
  "errorMessage": "cannot load such file -- business_time",
  "errorType": "Init<LoadError>",
  "stackTrace": [
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/task/handler.rb:2:in `<top (required)>'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'"
  ]
}

bussiness_timeを参照出来なくなっています。
Lambda Layersを使用する際、格納したファイルやディレクトリは、/optディレクトリに格納される仕様になっています。なので、こちらの対応が必要になります。

対応方法は以下の二つです。
* gemの参照先を絶対パスで記述する
* LOAD_PATHを変更する

後者で対応することにしました。
以下のように、handler.rbを変更します。

load_path = Dir["/opt/bundle/ruby/2.5.0/gems/**/lib"]
$LOAD_PATH.unshift(*load_path)

require 'json'
require 'business_time'

def hello(event:, context:)
  puts Date.today.workday?

  { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') }
end

ちなみに、/opt/bundle/ruby/2.5.0/gems/**/libのパスはlayersの作成方法によって変わってしまうため、適宜変更してください。

これでテストを実行すると、200が返却され、ログにtrueが返ってくるかと思います。

5. sls invokeを使用出来るようにする

上記までだと、LOAD_PATHを変更してしまうため、ローカルでsls invokeが使用できなくなってしまうため、対応したいと思います。

serverless frameworkのsls invokeコマンドを実行すると、IS_LOCALという環境変数が作成され、trueがセットされます。※ こちらを参照ください

なので、これをフラグにLambda関数に条件分岐を設定します。

handler.rbを以下のように変更します。

if ENV['IS_LOCAL'].nil?
  load_paths = Dir["/opt/bundle/ruby/2.5.0/gems/**/lib"]
  $LOAD_PATH.unshift(*load_paths)
end

require 'json'
require 'business_time'

def hello(event:, context:)
  puts Date.today.workday?

  { statusCode: 200, body: JSON.generate('Go Serverless v1.0! Your function executed successfully!') }
end

これで、sls invokeだった時は、LOAD_PATHが参照されなくなるので、sls invokeが使えるようになるかと思います。
当然この方法だと、関数が増えた時に全てに分岐を加えなければいけないので、対応策としては微妙ですが...

もっといい方法があればコメントいただけますと幸いです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2