はじめに
個人開発のアプリにて、Docker環境にてSolid Queueを利用したため備忘録として残させていただきます。
環境
- Rails 8.10 APIモード
- Ruby 3.3.6
Dockerfile
FROM ruby:3.3.6-alpine AS builder
RUN apk update && \
apk add --no-cache \
build-base \
postgresql-dev
RUN gem install bundler
COPY Gemfile Gemfile.lock ./
ARG RAILS_ENV
RUN RAILS_ENV=${RAILS_ENV} bundle install
FROM ruby:3.3.6-alpine AS app
RUN apk update && \
apk add --no-cache \
tzdata \
nodejs \
postgresql-client \
vim
WORKDIR /algo_sangaku_back
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY . /algo_sangaku_back
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
compose.yaml
services:
db:
image: postgres:18.0-alpine
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
networks:
- app_name-network
web:
build:
context: .
dockerfile: Dockerfile
args:
- RAILS_ENV=development
command: bundle exec puma -C config/puma.rb
env_file:
- .env
volumes:
- .:/app_name
- public-data:/app_name/public
- tmp-data:/app_name/tmp
- log-data:/app_name/log
environment:
TZ: Asia/Tokyo
networks:
- app_name-network
depends_on:
- db
nginx:
build:
context: ./nginx_docker_dev
volumes:
- public-data:/app_name/public
- tmp-data:/app_name/tmp
ports:
- 80:80
depends_on:
- web
networks:
- app_name-network
volumes:
public-data:
tmp-data:
log-data:
db-data:
networks:
app_name-network:
external: true
Solid Queueのインストール
gemの追加
$ docker compose exec web bundle add solid_queue
gemを追加したためDockerコンテナのビルドを実行後に下記コマンドを実行
$ docker compose exec web rails solid_queue:install
configへの追記
solid_queue:installを実行することで追加された項目を編集します
開発環境でも利用するため、config/enviroments/development.rbにもコピーしてください
# Replace the default in-process and non-durable queuing backend for Active Job.
config.active_job.queue_adapter = :solid_queue
# 単一のDBで実行するため下行はコメントアウトする
# config.solid_queue.connects_to = { database: { writing: :queue } }
migrationの実行
solid_queue:installによってdb/queue_schema.rbが生成されますが今回は単一のDBで利用するためこれを元にmigrationファイルを作成します
migrationファイル
class SolidQueueInstall < ActiveRecord::Migration[8.1]
def change
create_table :solid_queue_blocked_executions do |t|
t.bigint :job_id, null: false
t.string :queue_name, null: false
t.integer :priority, null: false, default: 0
t.string :concurrency_key, null: false
t.datetime :expires_at, null: false
t.datetime :created_at, null: false
t.index [ :concurrency_key, :priority, :job_id ], name: "index_solid_queue_blocked_executions_for_release"
t.index [ :expires_at, :concurrency_key ], name: "index_solid_queue_blocked_executions_for_maintenance"
t.index [ :job_id ], unique: true
end
create_table :solid_queue_claimed_executions do |t|
t.bigint :job_id, null: false
t.bigint :process_id
t.datetime :created_at, null: false
t.index [ :job_id ], unique: true
t.index [ :process_id, :job_id ]
end
create_table :solid_queue_failed_executions do |t|
t.bigint :job_id, null: false
t.text :error
t.datetime :created_at, null: false
t.index [ :job_id ], unique: true
end
create_table :solid_queue_jobs do |t|
t.string :queue_name, null: false
t.string :class_name, null: false
t.text :arguments
t.integer :priority, null: false, default: 0
t.string :active_job_id
t.datetime :scheduled_at
t.datetime :finished_at
t.string :concurrency_key
t.timestamps null: false
t.index :active_job_id
t.index :class_name
t.index :finished_at
t.index [ :queue_name, :finished_at ], name: "index_solid_queue_jobs_for_filtering"
t.index [ :scheduled_at, :finished_at ], name: "index_solid_queue_jobs_for_alerting"
end
create_table :solid_queue_pauses do |t|
t.string :queue_name, null: false
t.datetime :created_at, null: false
t.index [ :queue_name ], unique: true
end
create_table :solid_queue_processes do |t|
t.string :kind, null: false
t.datetime :last_heartbeat_at, null: false
t.bigint :supervisor_id
t.integer :pid, null: false
t.string :hostname
t.text :metadata
t.datetime :created_at, null: false
t.string :name, null: false
t.index [ :last_heartbeat_at ]
t.index [ :name, :supervisor_id ], unique: true
t.index [ :supervisor_id ]
end
create_table :solid_queue_ready_executions do |t|
t.bigint :job_id, null: false
t.string :queue_name, null: false
t.integer :priority, null: false, default: 0
t.datetime :created_at, null: false
t.index [ :job_id ], unique: true
t.index [ :priority, :job_id ], name: "index_solid_queue_poll_all"
t.index [ :queue_name, :priority, :job_id ], name: "index_solid_queue_poll_by_queue"
end
create_table :solid_queue_recurring_executions do |t|
t.bigint :job_id, null: false
t.string :task_key, null: false
t.datetime :run_at, null: false
t.datetime :created_at, null: false
t.index [ :job_id ], unique: true
t.index [ :task_key, :run_at ], unique: true
end
create_table :solid_queue_recurring_tasks do |t|
t.string :key, null: false
t.string :schedule, null: false
t.string :command, limit: 2048
t.string :class_name
t.text :arguments
t.string :queue_name
t.integer :priority, default: 0
t.boolean :static, null: false, default: true
t.text :description
t.timestamps null: false
t.index [ :key ], unique: true
t.index [ :static ]
end
create_table :solid_queue_scheduled_executions do |t|
t.bigint :job_id, null: false
t.string :queue_name, null: false
t.integer :priority, null: false, default: 0
t.datetime :scheduled_at, null: false
t.datetime :created_at, null: false
t.index [ :job_id ], unique: true
t.index [ :scheduled_at, :priority, :job_id ], name: "index_solid_queue_dispatch_all"
end
create_table :solid_queue_semaphores do |t|
t.string :key, null: false
t.integer :value, null: false, default: 1
t.datetime :expires_at, null: false
t.timestamps null: false
t.index [ :expires_at ]
t.index [ :key, :value ]
t.index [ :key ], unique: true
end
add_foreign_key :solid_queue_blocked_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_claimed_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_failed_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_ready_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_recurring_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
add_foreign_key :solid_queue_scheduled_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
end
end
$ docker compose exec web rails db:migrate
キュー実行用コンテナの追加
Dokerfileはwebコンテナのものを利用
webコンテナの内容をコピーしてcommandをbin/jobsに変更
services:
db:
image: postgres:18.0-alpine
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
networks:
- app_name-network
web:
build:
context: .
dockerfile: Dockerfile
args:
- RAILS_ENV=development
command: bundle exec puma -C config/puma.rb
env_file:
- .env
volumes:
- .:/app_name
- public-data:/app_name/public
- tmp-data:/app_name/tmp
- log-data:/app_name/log
environment:
TZ: Asia/Tokyo
networks:
- app_name-network
depends_on:
- db
# ここから
queue:
build:
context: .
dockerfile: Dockerfile
args:
- RAILS_ENV=development
command: bin/jobs
env_file:
- .env
volumes:
- .:/app_name
- public-data:/app_name/public
- tmp-data:/app_name/tmp
- log-data:/app_name/log
environment:
TZ: Asia/Tokyo
networks:
- app_name-network
depends_on:
- db
# ここまで
nginx:
build:
context: ./nginx_docker_dev
volumes:
- public-data:/app_name/public
- tmp-data:/app_name/tmp
ports:
- 80:80
depends_on:
- web
networks:
- app_name-network
volumes:
public-data:
tmp-data:
log-data:
db-data:
networks:
app_name-network:
external: true
mission_control-jobsの追加
キューの実行自体は前項までの内容で対応可能ですが、正常に実行されたかの確認及び失敗したjobの再試行を行えるようにキューの管理画面を追加します
gemの追加
$ docker compose exec web bundle add mission_control-jobs
apiモードのみpropshaftを追加
$ docker compose exec web bundle add propshaft
APIモードにて本番環境でも利用する場合はコンテナ起動のコマンドにアセットプリコンパイルコマンドを追加してください
misson_controll-jobsのマウント
Rails.application.routes.draw do
# ...
mount MissionControl::Jobs::Engine, at: "/jobs"
# ...
end
認証の設定
HttpBasic認証を利用するため認証情報の設定を行います
$ docker compose exec web rails mission_control:jobs:authentication:configure
credentialsに保存されるため、rails credentials:editで編集可能です
カスタム認証を利用することも可能です
https://github.com/rails/mission_control-jobs?tab=readme-ov-file#custom-authentication
上記の設定を完了することでmission_control-jobのキュー管理画面を利用することができます
