[Rails]development環境でPumaのsocketをbindしたらforemanのせいでできなかった話

2023/06/18

Rails

こんにちは。

Nginxをローカルで動かすテストをしてみるために、RailsのPumaにsocketをbindした時に、なかなかbindできねえ〜と思っていたら、原因はforemanだったので、その解決方法をご紹介します。

Nginxとは

Nginxとは、Webサーバーの一種です。

Railsのアプリケーションサーバーを本番環境で動かす時に、Nginxと組み合わせて使うことが多いです。

なんで必要なの?と思って少し調べてみたのですが、Pumaはあくまでアプリケーションサーバーで、バッファリングの処理などが遅くなることが原因のようです。

(実際に検証してみた!みたいなことはしていないので、下記の記事をご覧いただければと思います)

Pumaとは

Pumaとは、Railsのアプリケーションサーバーの一種です。

今だとDefaultでPumaが使われています。

Railsサーバーをrails sで起動すると、Pumaが起動します。

$ rails s

=> Booting Puma # ここでPumaが起動している
=> Rails 7.0.4.3 application starting in development 
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.2.1-p31) ("Birdie's Version")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 12
* Listening on http://0.0.0.0:3000
Use Ctrl-C to stop

foremanとは

foremanとは、複数のプロセスを管理するためのツールです。

bin/devコマンドを実行すると、foreman経由でRailsサーバーとbin/rails tailwindcss:watchだったりが起動します。

Procfile.devに記述されたコマンドを確認してみてください。

socketをbindしたい

さて、本題です。

NginxをDockerの開発環境で動かすために、Pumaのsocketをbindしたいと思いました。

config/puma.rb
# port ENV.fetch("PORT") { 3000 } # ここをコメントアウトして、下記を追加

app_root = File.expand_path('..', __dir__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"
niginx.conf
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid       /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

  access_log  /var/log/nginx/access.log  main;

  sendfile        on;
  #tcp_nopush     on;

  keepalive_timeout  65;

  #gzip  on;

  include /etc/nginx/conf.d/*.conf;

  server {
    listen       80;
    server_name  localhost;

    root /app/public;

    location / {
      try_files $uri $uri/index.html @app;
    }

    location @app {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;

      proxy_pass http://unix:/app/tmp/sockets/puma.sock;
    }
  }
}
FROM nginx:latest

RUN rm -f /etc/nginx/conf.d/*

COPY ./docker/development/nginx.conf /etc/nginx/nginx.conf

CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
docker-compose.yml
  app: # railsのコンテナ
    # 下記を追加
    volumes:
      - public:/app/public
      - tmp:/app/tmp
  nginx:
    build:
      context: .
      dockerfile: ./docker/development/Dockerfile.nginx
    volumes:
      - ./docker/development/nginx.conf:/etc/nginx/nginx.conf
      - public:/app/public
      - tmp:/app/tmp
    ports:
      - "80:80"
    depends_on:
      - app
volumes:
  public:
  tmp:

こんな感じの記事が調べるとたくさん出てくると思います。

しかし、これをやっても、Pumaがsocketをbindしてくれません。

$ docker compose up
Starting rails-nginx-example_app_1 ... done
Starting rails-nginx-example_nginx_1 ... done
Attaching to rails-nginx-example_app_1, rails-nginx-example_nginx_1
app_1    | => Booting Puma
app_1    | => Rails
app_1    |
app_1    | => Ctrl-C to shutdown server
app_1    | Puma starting in single mode...
app_1    | * Puma version: 5.6.5 (ruby 3.2.1-p31) ("Birdie's Version")
app_1    | *  Min threads: 5
app_1    | *  Max threads: 5
app_1    | *  Environment: development
app_1    | *          PID: 12
app_1    | * Listening on http://127.0.0.1:3000 # 本当はこれじゃなくて、socketをbindしたい
app_1    | Use Ctrl-C to stop

解決策

bin/devではなく、bundle exec pumactl startで実行すると、socketをbindしてくれます。

原因を調査しているとこんなpumaのissueを見つけました。

Bind is ignored when starting Rails with foreman

どうやら、foremanが自動でPORTを設定するようで、確かにPORT周りを消しても勝手に5000番になって動いていたんですよね。

ここに解決策としてあるunset PORTを追加してみる方法については、ローカルの開発環境の影響で試す前にやめておく判断をしてしまい、とりあえずbundle exec pumactl startでローカルでもsocket接続ができたことだけを確認して今回はよしとしました。

実際に開発環境でも常にNginxを使う方は上記のようにunset PORTを試す価値はあるかもしれません。

今回はこのあたりで。