Next.js × Railsアプリケーションの環境構築【Docker使用】
はじめに
本記事は、Dockerを使用してNext.jsとRuby on Railsで構築したアプリケーションをVercelとFly.ioにデプロイした際のプロセスを記録しています。プロジェクトは、モノリポ構成を採用しており、フロントエンドとバックエンドのコードを同一のリポジトリで管理するようになっています。
また、RedisやRailsキューイングバックエンドSolid Queueの設定方法についても含みます。
同様のアーキテクチャを検討している方はぜひ読んでみてください。
前提
Vercel、Fly.ioの登録方法については記載しませんので、事前に登録しておいてください。
使用技術のバージョン
Ruby: 3.2.2
Rails: 7.1.3
React: 18.2.0
Next.js: 14.1.0
リポジトリ作成
- GitHub上にリポジトリを作成して、ローカルにクローンします。
git clone <GitHubのURL>
- ディレクトリを移動
(ここでは、test
というリポジトリ名にしています)
cd test
-
test
ディレクトリ配下にfront
とback
ディレクトリを作成
mkdir front back
雛形作成
compose.yml
ルートディレクトリに compose.yml
を作成します。
また、他にもRedisやバックグラウンドワーカーの設定を後ほど追加していくので、暫定版です。
services:
front:
build:
context: ./front
dockerfile: Dockerfile
environment:
TZ: Asia/Tokyo
volumes:
- ./front:/app
- front_node_modules:/app/node_modules
command: yarn dev -p 4000
ports:
- "4000:4000"
back:
build:
context: ./back
dockerfile: Dockerfile
environment:
RAILS_ENV: development
TZ: Asia/Tokyo
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -b '0.0.0.0'"
volumes:
- ./back:/app
depends_on:
- db
ports:
- "3000:3000"
tty: true
stdin_open: true
db:
image: postgres:16.2
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: password
POSTGRES_DB: app_development
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
front_node_modules:
postgres_data:
フロントエンド準備
Next.jsアプリケーションを作成
node -v
で使用したいNode.jsのバージョンになっているか確認します。今回使用したバージョンは、20.11.0です。
1. front
ディレクトリにて以下のコマンドを実行し、Next.jsアプリケーションを作成
yarn create next-app .
以下のような質問がされると思います。今回は、以下のように回答して作成しました。
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … No
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No
2. ルートディレクトリに .gitignore
ファイルを作成し、front
ディレクトリの .gitignore
の内容を移動
先頭に front
をつけるようにしてください。終わったら、 front
ディレクトリの .gitignore
は削除してください。
例)
- /node_modules
+ front/node_modules
Docker
front
ディレクトリ直下に以下のDockerfileを作成します。
FROM node:20.11.0
ENV TZ Asia/Tokyo
WORKDIR /app
COPY package.json yarn.lock /app/
RUN yarn install
COPY . /app
CMD ["yarn", "dev", "-p", "4000"]
バックエンド準備
Docker
1. back
ディレクトリに以下の Dockerfile
を作成
FROM ruby:3.2.2
WORKDIR /app
RUN apt-get update -qq && \
apt-get install -y build-essential nodejs postgresql-client vim
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN gem install bundler
RUN bundle install
COPY . /app
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
2. back
ディレクトリに、entrypoint.sh
を作成
entrypoint.sh
は、コンテナが開始された時に実行されるスクリプトです。
サーバー起動時にプロセスIDが server.pid
に書き込まれ、終了する際に削除されますが、異常終了などでこのファイルが残ったままになってしまうと、サーバーが既に起動中であると誤認されてしまうため、プロセスIDファイルを削除する処理を行っています。
#!/bin/bash
set -e
rm -f /app/tmp/pids/server.pid
exec "$@"
Railsアプリケーションを作成
1. back
ディレクトリに以下の Gemfile
と、空の Gemfile.lock
を作成
source "https://rubygems.org"
ruby "3.2.2"
gem "rails", "~> 7.1.3"
2. イメージのビルド
docker compose build
3. 以下のコマンドを実行し、Railsアプリケーションを作成
--rm
: コンテナを終了時にコンテナを自動的に削除
--api
: RailsアプリケーションをAPIモードで作成
docker compose run --rm back bundle exec rails new . --api
-
Gemfileがコンフリクトするので、
Overwrite /app/Gemfile? (enter "h" for help) [Ynaqdhm]
と表示されます。上書きするので、Y
を入力します。 -
Rails7.1から、新規アプリケーション作成時に
Dockerfile
が生成されるようになったため、DockerfileもOverwrite /app/Dockerfile? (enter "h" for help) [Ynaqdhm]
と表示されます。ここはn
を入力してください。理由としては、自動生成されるDockerfile
は本番環境用のもので、開発用のものではないからです。また、Fly.ioのデプロイに最適なDockerfile
を後ほど自動生成するので、本番環境用のDockerfile
はそちらをベースに作ります。
現状あるDockerfileはDockerfile.dev
にリネームし、開発用にしておきましょう。また、compose.yml
の以下の部分を修正しておきます。
back:
build:
context: ./back
- dockerfile: Dockerfile
+ dockerfile: Dockerfile.dev
4. 自動生成された.git
は削除
5. 自動生成された .gitignore
の内容をルートの .gitignore
に移動
先頭に back
をつけるようにしてください。終わったら、 back
ディレクトリの .gitignore
は削除してください。
6. 基本設定
タイムゾーンとロケールの設定を application.rb
に記載します。
module App
class Application < Rails::Application
# Set timezone
config.time_zone = "Tokyo"
config.active_record.default_timezone = :local
# Set locale
config.i18n.default_locale = :ja
config.i18n.available_locales = [:en, :ja]
end
end
7. DB作成
- database.ymlの修正
default: &default
adapter: postgresql
encoding: utf8
port: 5432
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
username: root
password: password
database: app_development
host: db
本番環境用の設定は不要です。なぜなら、Fly.ioで後ほどPostgreSQLサーバーを作成する際に、 DATABASE_URL
がSecretsとして自動で設定され、それを通じてDBにアクセスできるためです。
-
Gemfile
に指定するDBをPostgreSQLに変更
- gem "sqlite3", "~> 1.4"
+ gem 'pg', '~> 1.1'
-
bundle install
実行
docker compose run --rm back bundle install
開発環境起動確認
1. イメージをビルド
docker compose build
2. コンテナ起動
docker compose up
3. http://localhost:3000
にアクセスし、Railsの初期ページが表示されることを確認
4. http://localhost:4000
にアクセスし、Next.jsの初期ページが表示されることを確認
デプロイ
ここまでの内容をGitHubにプッシュして、デプロイしていきます。
フロントエンドデプロイ
1. https://vercel.com/dashboard へアクセス
2. 「Add New」の「Project」をクリック
3. 「Import Git Repository」から対象のリポジトリの「Import」をクリック
4.「Configure Project」の「Root Directory」を「front」に変更し、「Continue」
5. 「Deploy」をクリック
Vercelは今後 main
ブランチにpushされると自動でデプロイしてくれます。
バックエンドデプロイ
1. back
ディレクトリに移動
2. Fly CLIをインストール
brew install flyctl
3. Fly.ioにログイン
fly auth login
4. Fly.ioへのデプロイ設定
fly launch
? Do you want to tweak these settings before proceeding? (y/N)
と質問されますので、「y」を入力します。すると、ダッシュボードがブラウザ上で開かれますので、そちらで設定していきます。
今回は以下のような設定にしました。Redisが必要な方はここで Upstash for Redis
を選択しておきましょう。
設定が完了したら、「Confirm Settings」をクリックして、ターミナルに戻ります。
特に自分で下記の接続情報を用いなければならない場面は私はなかったのですが、念の為、ここでターミナルに表示されているものを控えておくと安心です。
- Username
- Password
- Hostname
- Flycast
- Proxy port
- Postgres port
- Connection string
- DATABASE_URL
.dockerignore
ファイルがコンフリクトし、Overwrite .dockerignore? (enter "h" for help) [Ynaqdhm] h
と表示されています。「d」を入力すると差分が見れるので、確認してみます。
+ # Ignore assets.
+ /node_modules/
+ /app/assets/builds/*
+ !/app/assets/builds/.keep
+ /public/assets
アセット関連のファイルやディレクトリをDockerイメージビルドコンテキストから除外するための記述のようです。今回は、CSSやJavaScriptはNext.js側で扱いますので、.dockerignore
に追記されたものによる影響はないと思いますが、仮にRails側でアセットを管理していた場合、この追記によりDockerイメージのビルドが効率化されるようになるので、とりあえず自動生成されたもので上書きます(「Y」を入力してください)。
docker-entrypoint
はFly.ioの自動生成ファイルにはほとんど何も書かれていないので、上書きせず、既存のものを使います。
5. bundle install
実行
fly launch
実行時に、Gemfileに自動で追加が入っているので、一度 back
コンテナに入って bundle install
を実行しておきます。
6. デプロイ
fly deploy
※ Fly.ioのデプロイ先URLにアクセスしても、今回はAPIモードなので、Railsの初期ページは表示されません。開発環境ではRailsが特別に表示させてくれるだけだそうです。後ほどサンプルアプリを作成しますので、そこで動作確認をまとめて行います。
バックエンドデプロイ用のGitHub Actions作成
Fly.ioはVercelのようにGitHubの変更を自動で検知してデプロイするしくみがないので、GitHub Actionsを作成して、デプロイを自動化します。
1. Fly.ioのトークンを生成
- Fly.ioのダッシュボードから、Account > Access Tokensをクリック
- 「Create token」にトークン名を入力し、「Create」をクリック
2. GitHubのリポジトリにトークンを設定
- GitHubリポジトリの Settings > Secrets and variables > Repository secrets > New repository secretを選択
- 「Name」には
FLY_API_TOKEN
を、「Secret」には、1.で作成したトークンを設定し、「Add secret」をクリック
3. GitHub Actions用ファイル作成
ルート直下に .github/workflows/fly.yml
を以下のように作成します。
name: Fly Deploy
on:
push:
branches:
- main
paths:
- 'back/**'
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy back/ --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
main
ブランチの back
ディレクトリに対しての変更がpushされたときのみ実行されるようにしています。
動作確認
CORS設定
-
docker compose exec back bash
でback
コンテナに入る -
Gemfile
のgem 'rack-cors'
のコメントアウトを外して、bundle install
-
config/initializers/cors.rb
を以下のように変更し、フロントエンドからのアクセスができるように設定
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '127.0.0.1:4000', 'localhost:4000', '<Vercel URL>'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
-
docker compose down
でコンテナを停止し、docker compose up --build
で再度立ち上げ
サンプルアプリ作成
簡易的なサンプルアプリを作成します。
Rails
1. Post
モデル作成
-
docker compose exec back bash
でback
コンテナに入る - 以下実行してリソース
Post
を作成
bundle exec rails g scaffold Post title:string body:text
bundle exec rails db:migrate
2. seedデータ作成
-
seeds.rb
に以下追加
Post.create!(
[
{ title: "秋の始まり", body: "秋の涼しい風が心地よい季節が始まりました。木々の葉が色づき、秋の収穫が待ち遠しいです。この時期には、散歩や読書に最適な時間が増え、心が落ち着きます。" },
{ title: "テクノロジーの最新トレンド", body: "テクノロジーの世界では、AIと機械学習の進化が止まりません。これらの技術は、仕事から日常生活まで、私たちの生活を根本的に変えています。最新のトレンドを追いかけることは、これからの未来を形作る上で欠かせません。" },
{ title: "健康的な生活のための簡単なヒント", body: "健康的な生活を送ることは、多忙な日々の中でも可能です。バランスの取れた食事、定期的な運動、十分な睡眠が鍵となります。小さな変更から始めて、徐々にライフスタイルに組み込んでいきましょう。" },
]
)
- 以下のコマンド実行
bundle exec rails db:seed
3. http://localhost:3000/posts
にアクセスすると、先ほど作成したテストデータがJSON形式で表示されることを確認
Next.js
1. 投稿のタイトルと内容を取得し、表示するコードを作成 (CSSは省略)
src/app/page.tsx
import CreatePost from "@/app/features/components/CreatePost/CreatePost";
import { getAllPosts } from "@/app/features/lib/fetchPost";
type Post = {
id: number;
title: string;
body: string;
};
export default async function Home() {
const posts: Post[] = await getAllPosts();
return (
<main>
<CreatePost />
<ul>
{posts.map((post) => (
<li key={post.id}>
<span>タイトル: {post.title}</span>
<span>内容: {post.body}</span>
</li>
))}
</ul>
</main>
);
}
src/features/components/CreatePost/CreatePost.tsx
"use client";
import { useRef } from "react";
import { createPost } from "@/app/features/lib/fetchPost";
export default function CreatePost() {
const ref = useRef<HTMLFormElement>(null);
return (
<form
ref={ref}
action={ async (formData) => {
await createPost(formData)
ref.current?.reset()
}}
>
<div>
<label htmlFor="title">タイトル</label>
<input type="text" name="title" id="title" required />
</div>
<div>
<label htmlFor="body">内容</label>
<textarea name="body" id="body" required />
</div>
<button>投稿</button>
</form>
)
}
src/lib/fetchPost.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export const getAllPosts = async () => {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/posts`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
return res.json();
}
export const createPost = async (formData: FormData) => {
const title = formData.get("title");
if (!title) {
throw new Error("タイトルが入力されていません");
}
const body = formData.get("body");
if (!body) {
throw new Error("本文が入力されていません");
}
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/posts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title, body }),
});
revalidatePath("/");
redirect("/");
}
2. APIサーバーのURLを設定
- 開発環境用
先程の page.tsx
で使用している環境変数 NEXT_PUBLIC_API_URL
を設定します。
まず、back
コンテナのコンテナIDを確認します。
docker ps
コンテナのIPアドレスを取得します。
docker inspect <コンテナID> | grep IPAddress
.env.local
ファイルを作成し、以下のように記入します。
NEXT_PUBLIC_API_URL=http://<コンテナIPアドレス>:3000
コンテナのIPアドレスを固定するため、 compose.yml
を修正します。
例)
services:
front:
networks:
fixed_compose_network:
ipv4_address: 172.27.0.2
back:
networks:
fixed_compose_network:
ipv4_address: 172.27.0.3
db:
networks:
fixed_compose_network:
ipv4_address: 172.27.0.4
networks:
fixed_compose_network:
ipam:
driver: default
config:
- subnet: 172.27.0.0/24
http://localhost:4000
にアクセスすると、画像のように投稿が表示されます。
試しに以下のようなデータを追加してみます。
追加した投稿が表示されることが確認できました。
- 本番環境用
Vercelのプロジェクトダッシュボード Project Settings > Environment Variablesを開きます。
Keyに NEXT_PUBLIC_API_URL
を、ValueにFly.ioのデプロイ先URLを入力し、環境変数として登録します。
Vercelのデプロイ先URLを開くと、seedデータは投入していないのでデータは表示されませんが、投稿フォームが表示されていることが確認できます。フォームから投稿して、開発環境と同様フォームの下に表示されていれば成功です。
Solid Queue
バックエンドで非同期処理を行うためにSolid Queueを導入します。
同じくDBを使用するバックエンドワーカーの Delayed Job
ではなく、 Solid Queue
を採用した理由は以下です。
- DHH氏がRailsリポジトリのisssueで、Rails8ではデフォルトActive Jobのバックエンドとして
Solid Queue
を採用することを提案しているようだということ
https://github.com/rails/rails/issues/50442 -
Delayed Job
より更新が頻繁に行われていること
設定
1. Gemfileに以下を追加し、 bundle install
gem "solid_queue"
2. マイグレーション
- マイグレーションファイルの生成
bundle exec rails solid_queue:install:migrations
- マイグレーションの実行
bundle exec rails db:migrate
3. Active Jobアダプタの設定
config.active_job.queue_adapter = :solid_queue
4. ジョブの作成
- 以下のコマンドを実行してジョブファイルを生成
bundle exec rails g job PostLogsJob
- ジョブファイル修正
class PostLogsJob < ApplicationJob
queue_as :default
def perform(post)
puts "*** PostLogsJob performed (title: #{post.title}, body: #{post.body}) ***"
end
end
- コントローラーを修正
投稿を追加する際にジョブのキューに登録するようにします。
def create
@post = Post.new(post_params)
if @post.save
+ # モデルのインスタンスを渡してジョブをキューに登録する
+ PostLogsJob.perform_later(@post)
render json: @post, status: :created, location: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
5. compose.yml
にバックグラウンドワーカーサービスを追記
services:
・・・
worker:
build:
context: ./back
dockerfile: Dockerfile.dev
environment:
RAILS_ENV: development
TZ: Asia/Tokyo
command: bash -c "bundle exec rake solid_queue:start"
volumes:
- ./back:/app
networks:
fixed_compose_network:
ipv4_address: 172.27.0.5
6. fly.toml
に追記
[processes]
app = "bin/rails server"
worker = "bundle exec rake solid_queue:start"
また、デフォルトでは auto_stop_machines
がtrueに設定されているため、自動でマシンが停止するようになっています。わたしは停止させないように以下falseに変更しました。
[http_service]
auto_stop_machines = false
動作確認
- 開発環境
一度サーバーを停止させ、イメージのビルドをし直した上で再度サーバーを立ち上げてください。
http://localhost:4000
にアクセスし、なにかデータを追加してみると、workerのログにジョブで定義した出力内容が表示されていることを確認できれば成功です。
- 本番環境
Vercelのデプロイ先URLにアクセスします。
開発環境と同様に、データを追加した際に、Fly.ioのMachinesログに想定したメッセージが出力されていればOKです。
nrt [info] *** PostLogsJob performed (title: Solid Queueテスト, body: Solid Queueテスト) ***
Redis
SidekiqやResqueはRedisを必要としますが、Solid QueueはRDBを使用するので、バックグラウンドジョブ用にはRedisは不要です。ですが、私が作成していたアプリではActionCableを使用したかったので、サブスクリプションアダプタとしてRedisを用意しました。また、私は極力開発と本番で環境を同じにしたかったので、開発環境でもRedisを導入しました。
設定
- 本番環境用
fly lauch
実行時にRedisを選択して、使える準備は整っているはずなので、確認のためにRedisアクセス用URLを見てみます。
Fly.ioのダッシュボードからUpstash for Redisの使用を有効化した際に、 REDIS_URL
は自動でfly secretsに追加されているはずなので、以下のコマンドで確認します。
fly ssh console -C "printenv REDIS_URL"
redis://default:secretpassword@my-apps-redis-host.internal:6379
のような表示が見つかると思います。これがRedisアクセスURLです。
Fly.ioにシークレットとして登録済みなので、 credentials.yml
でこれを設定しなくても大丈夫です。
- 開発環境用
1. compose.yml
に以下を追加
services:
...
back:
build:
context: ./back
dockerfile: Dockerfile.dev
environment:
RAILS_ENV: development
TZ: Asia/Tokyo
+ REDIS_URL: "redis://redis:6379"
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -b '0.0.0.0'"
volumes:
- ./back:/app
+ depends_on:
+ - redis
- db
ports:
- "3000:3000"
tty: true
stdin_open: true
networks:
fixed_compose_network:
ipv4_address: 172.27.0.3
...
+ redis:
+ image: "redis:alpine"
+ ports:
+ - "6379:6379"
+ volumes:
+ - ./redis/data:/data
+ networks:
+ fixed_compose_network:
+ ipv4_address: 172.27.0.5
2. コントローラーの修正
正常に設定できているか確認するため、Redisに値を格納し、その値を取得した結果を出力するテストをしてみます。
def create
@post = Post.new(post_params)
if @post.save
- # モデルのインスタンスを渡してジョブをキューに登録する
- PostLogsJob.perform_later(@post)
+ # Redisに接続
+ redis = Redis.new(url: ENV["REDIS_URL"])
+
+ # PostのIDをRedisに保存
+ redis.set("post:#{@post.id}", @post.to_json)
+
+ # 保存した値をRedisから取得
+ value = redis.get("post:#{@post.id}")
+ puts value
render json: @post, status: :created, location: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
3. .gitignore
に追記
Redisがデータのスナップショットを自動で保存すると、redis/data/dump.rdb
ファイルができますが、これをGit管理しないようにします。
redis/data/*
4. サーバー再起動
動作確認
- 開発環境
http://localhos:4000
にアクセスし、データを追加してみます。
Redisに格納されたPostデータの内容が出力されていることが確認できます。
- 本番環境
Vercelのデプロイ先URLにアクセスし、同様にデータを追加します。
Fly.ioのログの出力が確認できれば成功です。
nrt [info] {"id":5,"title":"Redisテスト","body":"Redisテスト","created_at":"2024-04-29T19:43:02.890+09:00","updated_at":"2024-04-29T19:43:02.890+09:00"}
おまけ1: Sidekiq
私は初めバックグラウンドジョブの実行に、Sidekiq を使用しようとしていました。
しかし、なんのジョブも実装していない状態でも、バックグラウンドでSidekiqがRedisに行うコールが大量にあり、数時間で1日の利用制限に達してしまう状態でした。調べた結果、Upstash for Redisの無料枠の範囲で運用するのは厳しいと判断し、今回は導入を見送りました。(念の為問い合わせもしてみましたが、無料枠では難しいようだと回答をもらいました)
[参考]
一旦Sidekiqを使用したテストデプロイはできましたので、従量課金制のプランに移行するなどして、Sidekiqを利用したい方は、以降ご参考ください。
Redis
Sidekiqの実行にはRedisが必要ですので、導入します。
こちらで設定した内容と同じです。
Sidekiq
設定
1. Gemfileに以下を追加し、 bundle install
gem 'sidekiq'
2. Active Jobのキューアダプターに sidekiq
を設定
config.active_job.queue_adapter = :sidekiq
3. compose.yml
にバックグラウンドワーカーサービスを追記
services:
・・・
worker:
build:
context: ./back
dockerfile: Dockerfile.dev
environment:
RAILS_ENV: development
TZ: Asia/Tokyo
REDIS_URL: "redis://redis:6379"
command: bash -c "bundle exec sidekiq"
volumes:
- ./back:/app
depends_on:
- redis
networks:
fixed_compose_network:
ipv4_address: 172.27.0.5
Sidekiqの場合、 worker
サービスに必ずRedisへのアクセスが必要なので、depends_on
と environments
の REDIS_URL
の追加を忘れないようにしてください。
4. Sidekiq から接続するRedisのURLとLoggerを設定
Sidekiq.configure_client do |config|
config.redis = { url: ENV['REDIS_URL'] }
end
Sidekiq.configure_server do |config|
config.redis = { url: ENV['REDIS_URL'] }
config.logger = Sidekiq::Logger.new($stdout)
end
5. Sidekiqの状態を管理画面から参照できるように設定
第三者がアクセスできないよう、認証つきの管理画面が開けるようにします。
+require 'sidekiq/web'
+Sidekiq::Web.use ActionDispatch::Cookies
+Sidekiq::Web.use Rails.application.config.session_store, Rails.application.config.session_options
Rails.application.routes.draw do
+ mount Sidekiq::Web, at: '/sidekiq'
+ Sidekiq::Web.use Rack::Auth::Basic do |username, password|
+ username == Rails.application.credentials.sidekiq_user[:username] &&
+ password == Rails.application.credentials.sidekiq_user[:password]
+ end
resources :posts
get "up" => "rails/health#show", as: :rails_health_check
end
6. credentials.yml
にSidekiq用のusenameとpasswordを設定
sidekiq_user:
username: <ユーザー名>
password: <パスワード>
7. ジョブの作成
- 以下のコマンドを実行してジョブファイルを生成
bundle exec rails g job PostLogsJob
- ジョブファイル修正
class PostLogsJob < ApplicationJob
queue_as :default
def perform(post)
logger.info "*** PostLogsJob performed (title: #{post.title}, body: #{post.body}) ***"
end
end
- コントローラーを修正
投稿を追加する際にジョブのキューに登録するようにします。
def create
@post = Post.new(post_params)
if @post.save
+ # モデルのインスタンスを渡してジョブをキューに登録する
+ PostLogsJob.perform_later(@post)
render json: @post, status: :created, location: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
8. fly.toml
に追記
+ [processes]
+ app = "bin/rails server"
+ worker = "bundle exec sidekiq"
9. 高度な設定が必要であれば、 sidekiq.yml
も追加
動作確認
- 開発環境
サーバーを停止させ、イメージをビルドし直し、再度サーバを起動させたら、http://localhost:4000
にアクセスします。
データを追加して、ログに出力されていることを確認できたらOKです。
次に、管理画面にアクセスできるか確認します。こちらは、 http://localhost:3000/sidekiq
です。認証に成功すると、管理画面が表示されます。
- 本番環境
Vercelのデプロイ先URLにアクセスし、データを追加します。
Fly.ioの worker
マシンログに想定するログが表示されていれば成功です。
nrt [info] I, [2024-02-04T17:24:35.127213 #305] INFO -- : [ActiveJob] [PostLogsJob] [8c869072-7e97-408f-8640-c834a751a009] *** PostLogsJob performed (title: Sidekiqテスト, body: Sidekiqテスト) ***
管理画面は、<fly.ioデプロイ先URL>/sidekiq
で確認できます。
おまけ2: Delayed Job
Solid Queueを導入する前にDelayed Jobも試しましたので、そちらの記録も残しておきます。
設定
1. Gemfileに以下を追加し、 bundle install
gem 'delayed_job_active_record'
2. マイグレーション
- Delayed Jobがジョブを保存するために使用する
delayed_jobs
テーブル用のマイグレーションファイルの生成
bundle exec rails g delayed_job:active_record
- マイグレーションの実行
bundle exec rails db:migrate
3. Active Jobのキューアダプターに delayed_job
を設定
config.active_job.queue_adapter = :delayed_job
4. ジョブの作成
- 以下のコマンドを実行してジョブファイルを生成
bundle exec rails g job PostLogsJob
- ジョブファイル修正
class PostLogsJob < ApplicationJob
queue_as :default
def perform(post)
puts "*** PostLogsJob performed (title: #{post.title}, body: #{post.body}) ***"
end
end
- コントローラーを修正
投稿を追加する際にジョブのキューに登録するようにします。
def create
@post = Post.new(post_params)
if @post.save
+ # モデルのインスタンスを渡してジョブをキューに登録する
+ PostLogsJob.perform_later(@post)
render json: @post, status: :created, location: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
5. ログの出力設定
ログがバッファリングされて、ログメッセージが即時に表示されないような事象を防ぐために、以下を追加します。
$stdout.sync = true
config.logger = ActiveSupport::Logger.new($stdout)
config.log_level = :info
$stdout.sync = true
# Log to STDOUT by default
config.logger = ActiveSupport::Logger.new(STDOUT)
.tap { |logger| logger.formatter = ::Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
6. compose.yml
にバックグラウンドワーカーサービスを追記
services:
・・・
worker:
build:
context: ./back
dockerfile: Dockerfile.dev
environment:
RAILS_ENV: development
TZ: Asia/Tokyo
command: bash -c "bundle exec rails jobs:work"
volumes:
- ./back:/app
networks:
fixed_compose_network:
ipv4_address: 172.27.0.5
7. fly.toml
に追記
+ [processes]
+ app = "bin/rails server"
+ worker = "bundle exec rails jobs:work"
動作確認
- 開発環境
一度サーバーを停止させ、イメージのビルドをし直した上で再度サーバーを立ち上げてください。
http://localhost:4000
にアクセスし、なにかデータを追加してみて、ログにその内容が表示されていることを確認できれば成功です。
- 本番環境
Vercelのデプロイ先URLにアクセスします。
開発環境と同様に、データを追加した際に、Fly.ioのworkerマシンのログに想定したメッセージが出力されていればOKです。
nrt [info] *** PostLogsJob performed (title: テスト, body: テスト) ***
参考
Discussion