💭

Ruby on Jetsでサーバーレス

2021/10/12に公開

これはなに?

普段からRubyを使っているのですが、バッチ主体のシステムを開発するにあたり安価にサーバーレスを使いたく、Ruby on Jetsを選択して半年ほど運用した上での共有です。

Ruby on Jets
https://rubyonjets.com/

Ruby on Jetsの特徴

  • Railsライク書いたコードでAWSのアーキテクチャを扱えるフレームワークです。
  • これらのRailsに慣れた人なら見覚えのあるコマンド群が使える。
jets new
jets generate scaffold
jets db:migrate
jets server
jets console
jets deploy
jets routes
jets call
jets status
jets url
jets delete

ActiveSupportやActiveRecordなども使えるので、Railsのコードをほとんどそのまま持っていけます。

console

rails consoleと同じようなコンソールが使えます。

jets console

デプロイ

JETS_ENV=production jets deploy

これにより、JetsがCloudFormationを構築し、AWS上に展開を行ってくれるためCloudFormationでポシャるとツラいが、Railsライクに書いたコードが以下のように展開されるのでLambdaが扱いやすい。

  • コントローラーやジョブのコードがAWS上でLambdaにデプロイされる。
  • routes.rbの内容がAPI Gatewayに展開される。

Lambdaの権限設定

application.rbで以下のようにご所望の権限を記述すれば設定できます。

  config.iam_policy = [
    {
      action: ["logs:*"],
      effect: "Allow",
      resource: [
        "arn:aws:logs:#{Jets.aws.region}:#{Jets.aws.account}:log-group:*",
      ],
    },
    {
      action: ["s3:Get*", "s3:List*"],
      effect: "Allow",
      resource: "arn:aws:s3:::#{Jets.aws.s3_bucket}*",
    },
    {
      action: ["rds-db:connect"],
      effect: "Allow",
      resource: "!Sub arn:aws:rds-db:#{Jets.aws.region}:#{Jets.aws.account}:xxx:*/*",
    },
    {
      action: ["secretsmanager:GetSecretValue"],
      effect: "Allow",
      resource: "!Sub arn:aws:secretsmanager:#{Jets.aws.region}:#{Jets.aws.account}:secret:#{config.project_name}/#{Jets.env}/*",
    },
  ]

カスタムドメイン

こんな感じでカスタムドメインも設定できます。

  config.domain.hosted_zone_name = "xxxx.co.jp"
  if Jets.env.production?
   config.domain.apex = true
  else
   config.domain.name = "#{Jets.env}.xxxx.co.jp"
  end
  config.domain.cert_arn = "arn:aws:acm:xxxxxxxxxx:certificate/xxxxxxxxxxxx"

その他、細かいところもドキュメントを見れば代替はセッティング可能。
https://rubyonjets.com/docs/

面倒なのはgem周り

前提として、gemのコードはLambdaの環境用にコンパイルする必要があります。

Jetsはデフォルトでは「Serverless Gems」というサービスを使っていて、このサービスからLambda用にコンパイルされたgemコードをzipでダンロードしてLambdaに展開する作りになっています。
そして、このサービスが無料版だとダウンロード数制限(30回/日)があります。
https://www.serverlessgems.com/

無料で使いたい場合

通常なら課金してこれを使えば楽なんですが、回避する手法も用意されています。
独自にgemをコンパイルしてLambdaのレイヤーとしてプッシュすることで、以下のように自前でコンパイルしたgemをLambdaレイヤーとして利用可能です。

Jets.application.configure do
  config.gems.disable = true
  config.lambda.layers = [
    "arn:aws:lambda:xxxxx:layer:gem-layer:xx",
  ]
end

このように独自のレイヤーでgemを利用する場合は、amazon/aws-sam-cli-build-image-ruby2.7のコンテナ内でbundle installし、zipにしてs3に転送する必要があり、ざっくりですが以下のような処理で可能です。

mkdir -p tmp/gemlayer/
cp Gemfile* tmp/gemlayer/
cd tmp/gemlayer
mkdir -p lib
mkdir -p bin

docker run --rm \
    -v $PWD:/var/layer \
    -w /var/layer \
    -e BUNDLE_GITHUB__COM \
    -e HOST_UID=`id -u` \
    -e HOST_GID=`id -g` \
    amazon/aws-sam-cli-build-image-ruby2.7 \
    bash bundle install --path ruby

rm -rf lib/ruby/2.7.0/cache
mv ruby/ruby ruby/gems
zip -q -r zip.zip ruby/gems/
zip -q -r zip.zip lib
zip -q -r zip.zip bin
export HASH=$(md5sum zip.zip | cut -f1 -d ' ')
aws s3 cp zip.zip s3://gemlayer-bucket-name/$HASH
cd ../../
aws cloudformation update-stack \
    --stack-name xxxx \
    --template-body xxxx \
    --capabilities CAPABILITY_AUTO_EXPAND \
    --parameters ParameterKey=Hash,ParameterValue=$HASH

困ったこと

半年以上運用して結構使いやすいんですが、たまにCloudFormationでうまくデプロイできなかったときがツラかったです。
※ なので、ロールバックしたりCloudFormationについてのナレッジもあると安心。

特にgemレイヤーを更新するデプロイ時にロールバックする羽目になると辛くて、Lambdaのレイヤーは最新バージョンのみ保持できてバージョン番号を巻き戻せないため、アプリのロールバック時に旧レイヤーバージョンが存在しない事態になりロールバックできなかったです。
このときはアプリのプロジェクト自体をdeleteして再度createする形になりました。
RDSやパラメーターストアなど永続化するべきものはアプリのプロジェクトに含まず構築していたので再作成も楽にできました。

それ以外はいい感じで、Slackボットやジョブをたくさん捌いてもサーバーレスならではの安価な利用ができています。

その他の選択肢

最近見知ったのでまだ使えていないのですが、RubyでサーバーレスをするにあたってRuby on Jets以外にも以下の選択肢も気になっています。
https://github.com/customink/lamby

現場からは以上です!

Happy Elements

Discussion