AWS LambdaでSidekiqのJobをEnqueueする
Rails 外から Sidekiq の Job を動かしたい場合に、Railsが起動していなくてもEnqueue だけ出来たらいいなと思い試したメモです。
注意点として ActiveJob は使えませんでした(使いませんでした?)
モチベーション
Job管理は Sidekiq のままで、Slack や他の WebサービスからのJob起動がRails の起動状態に依存せずに Enqueue 出来るようにしたいというところです。
環境
Ruby 2.7.6
Ruby on Rails 7.0.3
Sidekiq 6.4.2
AWS SAM CLI 1.57.0
Enqueueを 行う AWS Lambda を構築
AWS Lambda のローカル環境の構築やデプロイが簡単に行える AWS SAM で進めます。
アプリケーションの作成
AWS SAM CLI のインストールはこちらを参考にします。
最初はサンプルから作ってしまうのが楽なので以下のコマンドで進めます。
sam init
ガイドに従って順番に進めればアプリケーションフォルダが作成されます。
ちなみにランタイムは ruby2.7 を選択しています。
Sidekiq を追加します。
# hello_world/Gemfile
source "https://rubygems.org"
ruby '~> 2.7.0'
gem "httparty"
gem "sidekiq"
Enqueue 処理を書きます。
# hello_world/app.rb
require 'sidekiq'
def lambda_handler(event:, context:)
job = event["job"]
args = event["args"]
p "#{job}, #{args}"
Sidekiq.configure_server do |config|
config.redis = { url: ENV["REDIS_URL"] }
end
Sidekiq::Client.push('class' => Object.const_get(job), 'args' => args)
{
statusCode: 200
}
end
class NormalJob
include Sidekiq::Job
queue_as :normal
end
class HighJob
include Sidekiq::Job
queue_as :high
end
補足しておくと、event["args"]で受け取るパラメータは配列となります。RailsからEnqueueした際に、Sidekiqのダッシュボードなどで確認するとパラメータの状態が分かるかと思います。
Redis の URL を環境変数から指定出来るように Sidekiq.configure_server で設定しておきます(環境変数は後程 sam template で指定)
Sidekiq::Client.push で Redis に Enqueue することが出来ます(参考)
また、その際に event["job"] で受け取った文字列を Object.const_get(job) でオブジェクト(クラス)として引数に渡しています。
Job Class は Rails からコピーしてくる必要がありますが中身は不要です。
ここで、注意点として元の Rails では ActiveJob を使っていたかもしれませんが、AWS Lambda から Enqueue する場合は Sidekiq の Job としておく必要があります。
Sidekiq の Job として登録されたものを ActiveJob で実行しようとするとエラーとなるためです。
No 「< ActiveJob::Base」, Yes 「include Sidekiq::Job」
ActiveJob も Ruby 単体で使えるのかもしれませんが、現状では Rails に組み込まれており今回は読み解くのはやめました。
AWS SAM Template の設定
Resources 部分です。
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: hello-world
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: ruby2.7
Architectures:
- x86_64
Environment:
Variables:
REDIS_URL: redis://localhost:6379/0
VpcConfig:
SecurityGroupIds:
- sg-xxxxx
SubnetIds:
- subnet-xxxxx
AWS Lambda 関数が作成された時に FunctionName が無いとランダム文字列が付与されるので指定しておきます。
Environment に Redis の URL を書いておきます。
VPC の内部で Redis にアクセスしたいため VpcConfig で指定しておきます。
また合わせて SecurityGroup も指定しておきます。
この辺りは事前に構築されたものを利用するためIDを指定するようにしています。
AWS SAM Build
ローカルのテスト実行とデプロイのために Build します。
sam build --use-container
ローカルでAPIの確認などもしていたため --use-container を付けてましたが、無くてもいけるとおもいます(たぶん)
ローカルでのテスト実行
パラメータを定義しておきます。
# events/event.json
{
"job": "NormalJob",
"args": ["arg1", 10]
}
環境変数(Redis の URL)も変えたい場合はそちらも定義しておきます。
# events/env.json
{
"Parameters": {
"REDIS_URL": "redis://192.168.0.3:6379/3"
}
}
ローカルでのテスト実行。
ちなみに Redis は別途 Docker で起動してました。
sam local invoke HelloWorldFunction --env-vars events/env.json -e events/event.json
デプロイ
初回は --guided でデプロイすると samconfig.toml も作成してくれます。
実際にデプロイする前には hello_world の名前は変更してます。
sam deploy --guided
これにより、CloudFormation が作成・実行されて、AWS Lambda が作成されます。
Railsの手直し(ActiveJobからSidekiq Jobへ変更)
ActiveJob と Sidekiq Job では Redis に登録される内容が異なり、Job 実行時にエラーになりました。
そのため、ActiveJob に強いこだわりは無かったため Sidekiq Job に変更しました。
ActiveJob で作成されている場合は以下のようになっているかと思います。
# app/jobs/normal_job.rb
class NormalJob < ApplicationJob
end
# app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
end
これを Sidekiq Job にするには以下のようにします。
# app/jobs/normal_job.rb
class NormalJob
include Sidekiq::Job
end
また、Railsで Enqueue している箇所もこのようになっていたかと思います。
NormalJob.perform_later arg1, arg2
こちらもこのように変更します。
NormalJob.perform_async arg1, arg2
Rails 側もこれで普通に動きます。
これで Sidekiq Client は Redis にアクセス出来ればどこで動かすことも可能となりました!
たとえ Rails API が落ちていても、
「何れかのAP」→「AWS Lambda」→「Redis」→「Sidekiq Client」
で動作してくれます。
さいごに
今回は Rails の Job を全て AWS Lambda 経由可能とはせずに、一部だけで試しています。そのため ActiveJob と Sidekiq Job が混在した状態となっています。
また、SQS など他のメッセージングサービスを利用するなどもよぎりましたが、今回の手段の方が少し手間が少なく試せるかと思い進めてみました。
割とシンプルに可用性を向上できたのではないかなと思っています。
何かしら運用の手間が増えるかは少し様子見していこうかと思います。