開発環境の rails server はデフォルトで localhost を bind している
VirtualBox (Vagrant 経由) 上で Rails アプリを立ち上げたところ ($ rails server
)、ホスト側から通信できなかった。
development 環境で rails server
コマンドを実行すると、デフォルトで localhost に bind して接続を待ち受けるらしく、これが原因だった。
-b
オプションで 0.0.0.0
などにバインドしてサーバを起動すれば解決する。
$ rails server -h
...
-b, [--binding=IP] # Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.
自分の場合は VirtualBox だったが、例えば Docker コンテナ上などでも同じことは起こる。現象からまずはゲスト OS やコンテナ側の設定 (ポートフォワードの設定など) を先に疑いがちなので、ちょっとハマってしまった。たぶん “あるある” なハマりどころな気はする。
以下は、せっかくなのでこのへんをもうちょっと深堀りしたメモ。
今回のケースの再確認
VirtualBox (Vagrant 経由) 上の Ubuntu 20.04 環境で rails のサーバを開発用に立ち上げた
# こんな感じ。オプションは何もなし $ rails s => Booting Puma => Rails 6.1.3 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.2.2 (ruby 3.0.0-p0) ("Fettisdagsbulle") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 815476 * Listening on http://127.0.0.1:3000 * Listening on http://[::1]:3000 Use Ctrl-C to stop
ホスト側からのリクエストが到達しなかった
$ curl 'http://127.0.0.1:5000' curl: (56) Recv failure: Connection reset by peerp # なおゲスト側からのリクエストは当然通る $ curl -v http://127.0.0.1:3000 ... < HTTP/1.1 200 OK
Vagrantfile のポートフォワード設定などを疑ったが、開発サーバが 127.0.0.1 を待ち受けているのが問題だった
rails s -b 0.0.0.0
などと0.0.0.0
をバインドすることで解決
ソケットプログラミングレベルでの再確認
- ここでの
{*.21, *.*}
という記法はソケットペアを表している- 第一要素が自ホスト側、第二要素が相手側で、それぞれドット区切りで ip とポート番号。
*
はワイルドカード
- 第一要素が自ホスト側、第二要素が相手側で、それぞれドット区切りで ip とポート番号。
- サーバは 2 つの IP アドレスを持っている
- 箱の上に書いてある
206.62.226.35
206.62.226.66
がそれを表現している
- 箱の上に書いてある
- サーバ側のリスニングソケットが指定した 21 番ポートで接続を待ち受けている
- この例ではサーバ側のポートだけを指定し、あとはワイルドカード指定になっている
{*.21, *.*}
- サーバはどのインタフェースから来た 21 番ポートへの接続でもうけつけるし、クライアントの ip, ポート を制限しない
- 具体的には bind 呼び出し時、ip アドレス指定部分に
INADDR_ANY
を指定するとワイルドカード扱いになる
- この例ではサーバ側のポートだけを指定し、あとはワイルドカード指定になっている
- 接続要求を受け付けると接続済みソケットを生成する
- サーバ側のソケットペアは、
{206.62.226.35.21, 198.69.10.2.1500}
となっている
- サーバ側のソケットペアは、
今回の Rails のケースでは、リスニングソケットの自ホスト側の ip (この例では *
になっている部分) が localhost だったため、ゲスト側からの接続を受け付けなかった。
127.0.0.1 (localhost) と 0.0.0.0
man にはこのように記載されていた。
There are several special addresses: INADDR_LOOPBACK (127.0.0.1) always refers to the local host via the loopback device; INADDR_ANY (0.0.0.0) means any address for binding;
0.0.0.0
は前述の INADDR_ANY 用の特別なアドレス- bind でのワイルドカード用
any address for binding
- なおこちらは言うまでもないが
127.0.0.1
INADDR_LOOPBACK
はローカルホスト用ということもここに記載があった
wikipedia にも同様の記載。
A way to specify “any IPv4 address at all”. It is used in this way when configuring servers (i.e. when binding listening sockets). This is known to TCP programmers as INADDR_ANY. (bind(2) binds to addresses, not interfaces.)
試してみる
デフォルトでの rails server
- わかりやすいように puma のワーカー、スレッド数を 1 にして、オプションなしで起動
- そもそもこの時点で明確に
Listening on http://127.0.0.1:3000
というログが出ている
- そもそもこの時点で明確に
$ RAILS_MAX_THREADS=1 WEB_CONCURRENCY=1 rails s
=> Booting Puma
=> Rails 6.1.3 application starting in development
=> Run `bin/rails server --help` for more startup options
[170796] Puma starting in cluster mode...
[170796] * Puma version: 5.2.2 (ruby 3.0.0-p0) ("Fettisdagsbulle")
[170796] * Min threads: 1
[170796] * Max threads: 1
[170796] * Environment: development
[170796] * Master PID: 170796
[170796] * Workers: 1
[170796] * Restarts: (✔) hot (✔) phased
[170796] * Listening on http://127.0.0.1:3000
[170796] * Listening on http://[::1]:3000
[170796] Use Ctrl-C to stop
[170796] - Worker 0 (PID: 170832) booted, phase: 0
- strace してみる
- 127.0.0.1 で bind している様子がみえた
$ sudo RAILS_MAX_THREADS=1 WEB_CONCURRENCY=1 strace -t -f -e trace=network /home/vagrant/.rbenv/shims/rails s
...
[pid 170917] 07:25:01 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 13
[pid 170917] 07:25:01 setsockopt(13, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
# 127.0.0.1 で bind して、それを listen している (fd 13 番)
[pid 170917] 07:25:01 bind(13, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
[pid 170917] 07:25:01 listen(13, 4096) = 0
ss
127.0.0.1:3000
でLISTEN
状態な様子が確認できた
$ ss -na | grep 3000
tcp LISTEN 0 1024 127.0.0.1:3000 0.0.0.0:*
tcp LISTEN 0 1024 [::1]:3000 [::]:*
-b 0.0.0.0
rails s -b 0.0.0.0
で起動して見るListening on http://0.0.0.0:3000
というログが出る
$ RAILS_MAX_THREADS=1 WEB_CONCURRENCY=1 rails s -b 0.0.0.0
=> Booting Puma
=> Rails 6.1.3 application starting in development
=> Run `bin/rails server --help` for more startup options
[169815] Puma starting in cluster mode...
[169815] * Puma version: 5.2.2 (ruby 3.0.0-p0) ("Fettisdagsbulle")
[169815] * Min threads: 1
[169815] * Max threads: 1
[169815] * Environment: development
[169815] * Master PID: 169815
[169815] * Workers: 1
[169815] * Restarts: (✔) hot (✔) phased
[169815] * Listening on http://0.0.0.0:3000
[169815] Use Ctrl-C to stop
[169815] - Worker 0 (PID: 169849) booted, phase: 0
- 今度は
0.0.0.0
で bind している様子が見える
$ sudo RAILS_MAX_THREADS=1 WEB_CONCURRENCY=1 strace -t -f -e trace=network /home/vagrant/.rbenv/shims/rails s -b 0.0.0.0
...
[pid 170457] 07:22:35 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 13
[pid 170457] 07:22:35 setsockopt(13, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
[pid 170457] 07:22:35 bind(13, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
[pid 170457] 07:22:35 listen(13, 4096) = 0
ss
でも確認
2021-03-08 07:26 vagrant@ubuntu-focal workspace main$ ss -na | grep 3000
tcp LISTEN 0 1024 0.0.0.0:3000 0.0.0.0:*
ホスト OS 側からの通信を受け持つインタフェースを指定してみる
そんなことをして意味があるかわからないけど、ワイルドカードではなく、具体的なインタフェースを指定しても通信できるか試してみた。
ip
コマンドでenp0s3
というインタフェース、ip は10.0.2.15
ということを調べる$ ip -4 a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3 valid_lft 86260sec preferred_lft 86260sec 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever
10.0.2.15
をバインドして起動$ rails s -b 10.0.2.15 => Booting Puma => Rails 6.1.3 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.2.2 (ruby 3.0.0-p0) ("Fettisdagsbulle") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 171868 * Listening on http://10.0.2.15:3000 Use Ctrl-C to stop
ホスト側からのリクエストが通った
Started GET "/" for 10.0.2.2 at 2021-03-08 07:30:12 +0000 ...
Docker のチュートリアルでも -b 0.0.0.0
を案内していた
Quickstart: Compose and Rails | Docker Documentation に CMD ["rails", "server", "-b", "0.0.0.0"]
という記載があった。
Cannot render console from x.x.x.x! Allowed networks: 127.0.0.0/127.255.255.255, ::1
今回とは別件だが、サーバ側でリクエストを受けた際にこういうログが出ている。
Started GET "/" for 10.0.2.2 at 2021-03-11 14:18:38 +0000
Cannot render console from x.x.x.x! Allowed networks: 127.0.0.0/127.255.255.255, ::1
これは web console という機能がデフォルトでは 127.0.0.1 しか許可していないせいらしい。
config/environments/development.rb
などで config.web_console.permissions = '192.168.0.0/16'
などと指定すれば解決する。
経緯はよくわからないが、allowed_ips
whitelisted_ips
という設定名でもよいらしい。
- もともと
whitelisted_ips
=>permissions
に変更しつつ、後方互換性のためwhitelisted_ips
も維持 => 昨今の運動もありwhiltelisted_ips
をallowed_ips
に変更 (でも古い方もまだ維持してる)、という感じなんだろうか。