Ruby on Jetsでサーバーレス
これはなに?
普段からRubyを使っているのですが、バッチ主体のシステムを開発するにあたり安価にサーバーレスを使いたく、Ruby on Jetsを選択して半年ほど運用した上での共有です。
Ruby on Jets
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"
その他、細かいところもドキュメントを見れば代替はセッティング可能。
面倒なのはgem周り
前提として、gemのコードはLambdaの環境用にコンパイルする必要があります。
Jetsはデフォルトでは「Serverless Gems」というサービスを使っていて、このサービスからLambda用にコンパイルされたgemコードをzipでダンロードしてLambdaに展開する作りになっています。
そして、このサービスが無料版だとダウンロード数制限(30回/日)があります。
無料で使いたい場合
通常なら課金してこれを使えば楽なんですが、回避する手法も用意されています。
独自に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以外にも以下の選択肢も気になっています。
現場からは以上です!
Discussion