dockerでRails8+sidekiq+Redis環境構築

# Rails8でキュー処理の環境をつくる
Rails8のバックグラウンドキュー処理（ActiveJob)をsidekiqで。
Redisもdockerで構築する。

https://zenn.dev/overflow_offers/articles/20230130-how-to-use-sidekiq

### RailsとSidekiqが連携する仕組み
+ Rails側でジョブを登録する
　→ 「やってほしい仕事」をキュー（Redis）に入れる
+ Sidekiq側がそのキューを見てる
　→ Redisからジョブを取ってきて、処理を実行する
+ RailsとSidekiqは「Redisを介して」間接的につながっている

# まずはRails8の環境構築
+ 参考

https://qiita.com/devzooiiooz/items/5c724fe36a55af9ba16d
+ sidekiqディレクトリを作成し、compose.yamlを作成
```sh:
mkdir sidekiq
cd sidekiq && touch compose.yaml
```
+ backendディレクトリを作成し、更に配下にapiディレクトリを作成
```sh:
mkdir backend && mkdir backend/api
```
+ 必要ファイル(Dockerfile,Gemfile,Gemfile.lock)作成
```sh:
cd backend && touch {Dockerfile,Gemfile,Gemfile.lock}
```
##### この時点でのディレクトリ構成
```sh:
sidekiq/
├── backend/
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Gemfile.lock
│   └── api/
└── compose.yaml
```
+ Dockerfile
```docker:Dockerfile
FROM ruby:3.3.0
ENV APP /api
ENV TZ Asia/Tokyo
WORKDIR $APP
ADD Gemfile $APP/Gemfile
ADD Gemfile.lock $APP/Gemfile.lock
RUN bundle install
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
```
+ Gemfile
```ruby:Gemfile
source 'https://rubygems.org'
gem "rails", "~> 8.0.0"
```
+ compose.yaml
```yaml:compose.yaml
services:
    api:
        build: ./backend/
        platform: linux/x86_64
        environment:
          TZ: Asia/Tokyo
        volumes:
          - type: bind
            source: ./backend/api
            target: /api
          - type: volume
            source: gemdata
            target: /usr/local/bundle
        ports:
          - "3000:3000"
volumes:
   gemdata:
```
## 最低限のRails環境を構築する（apiモード＋minimal)
+ rails new
まずはノーマルのapiモードでrails new。
minimalだとactive-jobがskipされるので--no-skip-active-jobオプションをつける。
```sh:
docker compose run --rm api rails new . --api --minimal --no-skip-active-job
```
ずらずらずら
backend/apiにインストールされる。
+ api/config/environments/development.rbに以下を追加
```rb:api/config/environments/development.rb
config.hosts.clear
```
### この状態でrailsを起動
```sh:
docker compose up -d
```
Rails8の起動を確認。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82975/8381ccfb-4eb2-95f1-edb5-b0b60bd49348.png">

## Redisコンテナの追加
>「Redis」は、Railsそのものの機能ではなく、オープンソースのインメモリデータストアです。バックグラウンドジョブのキュー管理: 例えば、Sidekiq などのジョブキューライブラリが、ジョブの状態管理やキューとしてRedisを利用します。
##### まずredis用のディレクトリを作成
```sh:
mkdir redis
```
### dockerにredisを設定

```yml:compose.yaml
redis:
  image: redis:latest
  volumes:
    - type: bind
      source: ./redis
      target: /data
  ports:
    - "6379:6379"
```

### docker compose upでredisコンテナを作成
(Railsを起動したままだったら、docker compose down後にup)
+ redisの起動確認
```sh:
docker compose exec redis redis-cli
```
以下のredisプロンプトが出るのでpingを打ってPONGと返ってきたらOK
```sh:
127.0.0.1:6379> ping
PONG
```
これでredisは完了

# Railsにsidekiqを追加
+ 参考

https://qiita.com/kandalog/items/8bd2b176c63eeccddc54


### sidekiqのgemをinstall
bundle addはGemfileに追加してからinstallする
```shell
docker compose run --rm api bundle add sidekiq
```

### configを編集/設定ファイル作成

```rb:config/application.rb
module Myapp
  class Application < Rails::Application
# ...  
    config.active_job.queue_adapter = :sidekiq
#...
```

### config/initializersにsidekiq.rbを作成
sidekiq.rbは無いので作成
```sh:
touch backend/api/config/initializers/sidekiq.rb
```

```rb:config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: 'redis://redis:6379' }
end

Sidekiq.configure_client do |config|
  config.redis = { url: 'redis://redis:6379' }
end
```
### configにsidekiqの設定ファイルを作成
初期状態ではdefautのキューが1つだけなので、設定ファイルを作成し追加。
```sh
touch backend/api/config/sidekiq.yml
```
```yml:config/sidekiq.yml
# 基本設定
# デフォルトで同時に5つのジョブを並行処理する。
# スレッド数が5本。
:concurrency: 5
# 環境ごとの設定
# staging 環境とproduction 環境では、並行処理数（スレッド数）を10にする。
staging:
  :concurrency: 10
production:
  :concurrency: 10
# ジョブの優先度
# :queues で、優先的に処理するジョブのキュー順序を指定しています。
# 1. critical（最優先）
# 2. default
# 3. low（最後）
:queues:
  - critical
  - default
  - low
```
### ForemanでRailsサーバーとsidekiqを同時に起動
+ foremanをインストール
```sh:
docker compose exec api bundle add foreman
```
+ Procfileを作成し編集
```sh:
touch backend/api/Procfile
```
```:Procfile
web: rm -f tmp/pids/server.pid && bundle exec rails s -b 0.0.0.0
worker: bundle exec sidekiq
```
+ Dockerfileの最終行に以下を追記
foremanから起動させる
```dockerfile:Dockerfile
# foremanで起動
CMD ["foreman", "start"]
```
+ Dockerfileをいじったのでbuild
```sh:
docker compose build
```
### APIモードのRailsではセッションを有効化する
Sidekiq Web UIをAPIモードのRailsで使いたい場合、
セッションを有効化する設定が必要。
```rb:
# config/application.rb
module YourApp
  class Application < Rails::Application
    # 以下を追加
    # セッション周り追加
    config.middleware.use ActionDispatch::Cookies
    # ここのkeyは何でもいい
    config.middleware.use ActionDispatch::Session::CookieStore, key: '_sidekiq_session'
  end
end
```
### sidekiq Web UIのroute設定
railsのルーティングにsidekiqのキュー確認画面を追加する。

```rb:config/routes.rb
require 'sidekiq/web'

Rails.application.routes.draw do
  # 以下を追加
  mount Sidekiq::Web => "/sidekiq"
end
```
+ dockerを起動する
```sh
docker compose up -d
```
+ railsのURI(http://localhost:3000/sidekiq) 
以下のような確認画面になる。
![スクリーンショット 2025-02-20 20.47.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82975/fe3001f1-52ec-45a8-b584-20dc43bcb46d.png)

## Jobの作成
+ 例としてファイルをコピーするだけのジョブを作成する
```sh:
docker compose exec api rails g job CopyFile
```
+ ジョブファイルができるので中身を次のようにする
```rb:app/job/copy_file_job.rb
require 'fileutils'
class CopyFileJob < ApplicationJob
  queue_as :default
  def perform(source_path, destination_path)
    # ファイルをコピー
    FileUtils.cp(source_path, destination_path)
    puts "ファイルコピー完了: #{source_path} -> #{destination_path}"
  end
end
```
## 動作確認
 tmpにtest.txtを作成
```sh
touch backend/api/tmp/test.txt
```
 rails cコマンドでCopyFileJobを動かす
```sh
docker compose exec api rails c
```
+ perform_now
キューに積まないでそのまま実行
sidekiqにも表示されない
```sh:rails c
api(dev) > CopyFileJob.perform_now('/api/tmp/test.txt','/api/tmp/test_cp.txt')
ファイルコピー完了: /api/tmp/test.txt -> /api/tmp/test_cp.txt
```
+ perform_later
ActiveJob経由でSidekiqキューに積む
戻り値はJobオブジェクトになるのでprovider_job_idを取得すればjob IDが表示される。
```sh:rails c
api(dev) > job = CopyFileJob.perform_later('/api/tmp/test.txt','/api/tmp/test_cp.txt')
api(dev) > job.provider_job_id
=> "e9160212ef0134245ce7a105"
```
# Sidekiqは完了したジョブの履歴を保存しない
Sidekiqはデフォルトでは完了したジョブの履歴を保存しません。
そのため、標準のAPI (Sidekiq::Queue, Sidekiq::RetrySet, Sidekiq::DeadSet) では完了済みのジョブを取得することはできません。

なんだと...。この辺は別に書きます。