【Rails】WEB/APサーバ構成に応じたNginx設定

# もくじ
1. WEB/APサーバが一体化された構成
2. WEB/APサーバが分離された構成(WEB:AP = 1:1)
3. WEB/APサーバが分離された構成(WEB:AP = 1:多)
4. WEB/APサーバが分離された構成(共有ディレクトリ有り)
5. UNIXドメインソケット利用時の注意点

# 1. WEB/APサーバが一体化された構成

WEB(Nginx)とAP(Rails)を1台のサーバ内に収める構成です。

NginxとRailsの連携にはUNIXドメインソケットを利用します。

【[Nginx ポケットリファレンス](http://gihyo.jp/book/2015/978-4-7741-7633-8)より抜粋】
UNIXドメインソケットのでの接続は同一サーバ内に制限されますが、TCP接続と比べて接続時のオーバーヘッドが少ないので、細かい大量のアクセスを処理する際に負荷の軽減を図ることができます。


## Rails

UNIXドメインソケットを利用するために、pumaのコンフィグにsockファイルのパスを指定します。
アプリの`tmp/sockets`配下にsockファイルを配置する場合は以下のように記述します。

UNIXドメインソケットでの連携時、TCP通信での連携は不要になるので、port設定をコメントアウトすることでtcpでのlistenを行わなくなります。

```ruby:config/puma.rb
# port ENV.fetch("PORT") { 3000 }
...
# UNIXドメインソケット
bind "unix://#{Rails.root}/tmp/sockets/puma.sock"
```

上記の設定後は`rails server`起動時にsockファイルがlistenされるようになります。

```shell-session
$ rails s
...
* Listening on unix:///var/www/my-app/tmp/sockets/puma.sock
Use Ctrl-C to stop
```

ポートを指定した状態で`rails server`を起動した場合はsockファイルがlistenされなくなり、tcpがlistenされます。

```shell-session
$ rails s -p 8080
...
* Listening on tcp://localhost:8080
Use Ctrl-C to stop
```

## Nginx

nginx.confの`http`ディレクティブ配下にRailsと連携させるための設定を記述します。`upstream > server`にUNIXドメインソケットのパスを指定します。

```nginx:nginx.conf(一部抜粋)
upstream backend {
  server unix:/var/www/my-app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name .*;

  root /var/www/my-app/public;

  location / {
    try_files $uri @app;
  }

  location @app {
    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-Host    $host;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;
    proxy_pass http://backend;
  }
}
```

# 2. WEB/APサーバが分離された構成(WEB:AP = 1:1)

WEB(Nginx)とAP(Rails)を異なるサーバに収める構成です。

1台のWEBサーバに対して1台のAPサーバが割り当てられるケース(WEB:AP = 1:1)では、Nginxのリバースプロキシ機能を利用して連携できます。

## Nginx

```nginx:nginx.conf(一部抜粋)
server {
  listen 80;
  server_name .*;
  root /var/www/my-app/public;

  location / {
    try_files $uri @app;
  }

  location @app {
    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-Host    $host;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;
    proxy_pass          http://xx.xx.xx.xx:yyy; # AP(Rails)サーバのIP/PORTを指定する
  }
}
```

## WEB/APサーバが一体化された構成での利用
`proxy_pass`にローカルホスト( http://127.0.0.1:3000 )を指定すれば、`WEB/APサーバが一体化された構成`でも利用可能です。

# 3. WEB/APサーバが分離された構成(WEB:AP = 1:多)

WEB(Nginx)とAP(Rails)を異なるサーバに収める構成です。

1台のWEBサーバに対して複数台のAPサーバが割り当てられるケース(WEB:AP = 1:多)では、Nginxのロードバランシング機能を利用して連携できます。

## Nginx

```nginx:nginx.conf(一部抜粋)
upstream backend {
  server xx.xx.xx.xx:yyy; # AP(Rails)サーバ1のIP/PORTを指定する
  server xx.xx.xx.xx:yyy; # AP(Rails)サーバ2のIP/PORTを指定する
  server xx.xx.xx.xx:yyy; # AP(Rails)サーバ3のIP/PORTを指定する
}

server {
  listen 80;
  server_name .*;
  root /var/www/my-app/public;

  location / {
    try_files $uri @app;
  }

  location @app {
    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-Host    $host;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;
    proxy_pass          http://backend;
  }
}
```

## WEB/APサーバが一体化された構成での利用

serverにローカルホスト( http://127.0.0.1:3000 )を指定すれば、`WEB/APサーバが一体化された構成`でも利用可能です。

## WEB/APサーバが分離された構成(WEB:AP = 1:1)での利用

`upstream > server`の指定を1つだけにすれば、`WEB/APサーバが分離された構成(WEB:AP = 1:1)`構成でも利用可能です。


# 4. WEB/APサーバが分離された構成(共有ディレクトリ有り)

WEB(Nginx)とAP(Rails)を異なるサーバに収める構成であっても、共有ディスクの利用やNFSマウントによってWEB(Nginx)とAP(Rails)の両者から同一の`sockファイル`にアクセスできる環境を構築すれば、UNIXドメインソケットを利用して「WEB:AP = 1:多」環境を構築することも可能です。

## Rails

pumaのコンフィグにsockファイルのパスを指定します。
sockファイルのパスにはWEB(Nginx)と共通しているディレクトリ配下を指定する必要があります。

例：`/mnt/web-ap1`がWEB(Nginx)と共有しているディレクトリの場合

```ruby:config/puma.rb
# port ENV.fetch("PORT") { 3000 }
...
# UNIXドメインソケット
bind "unix:///mnt/web-ap1/my-app/tmp/puma.sock"
```

## Nginx

`upstream > server`にUNIXドメインソケットのパスを指定します。
sockファイルのパスには各AP(Rails)と共有しているディレクトリ配下を指定する必要があります。

例：`/mnt/web-ap1~3`が各AP(Rails)と共有しているディレクトリの場合

```nginx:nginx.conf(一部抜粋)
upstream backend {
  server unix:/mnt/web-ap1/my-app/tmp/puma.sock; # AP(Rails)サーバ1のsockファイル
  server unix:/mnt/web-ap2/my-app/tmp/puma.sock; # AP(Rails)サーバ2のsockファイル
  server unix:/mnt/web-ap3/my-app/tmp/puma.sock; # AP(Rails)サーバ3のsockファイル
}

server {
  listen 80;
  server_name .*;
  root /var/www/my-app/public;

  location / {
    try_files $uri @app;
  }

  location @app {
    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-Host    $host;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;
    proxy_pass          http://backend;
  }
}
```


# 5. UNIXドメインソケット利用時の注意点

UNIXドメインソケットを利用するためにはWEB(Nginx), AP(Rails)両者が同一のsockファイルへアクセスできる必要があります。

sockファイルへのアクセス権や配置ディレクトリが適切ではない場合、ブラウザからアクセスした際にNginxで50xエラーとなりRailsアプリまで到達できませんのでご注意ください。

## Permission denied

### Nginx

nginxのworkerプロセスを起動するユーザがsockファイルにアクセスする権限がない場合、ブラウザからアクセスした際に503エラー等になります。

その際、Nginxのエラーログには以下のように`Permission denied`と出力されます。

```shell-session
$ sudo tail /var/log/nginx/error.log

2017/10/21 01:46:29 [crit] 3467#0: *82 connect() to unix:/var/www/my-app/tmp/sockets/puma.sock failed (
13: Permission denied) while connecting to upstream, client: xx.xx.xx.xx, server: *, request: "GET / HTTP/1.1", u
pstream: "http://unix:/var/www/my-app/tmp/sockets/puma.sock:/", host: "xx.xx.xx.xx"
```

対応として、

- アクセス権を設定してworkerプロセス起動ユーザがsockファイルへアクセスできるようにする
- workerプロセス起動ユーザをsockファイルへのアクセス権があるユーザに変更する

などを行います。


### Rails

`rails server`起動ユーザがsockファイルにアクセスする権限がない場合、サーバ起動に失敗します。

その際、Railsのログには以下のように`Permission denied`と出力されます。

```shell-session
$ rails s
...
* Listening on unix:///mnt/web-ap1/my-app/tmp/puma.sock
Exiting
/xxx/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/binder.rb:366:in `initialize': 
 Permission denied - connect(2) for /mnt/web-ap1/my-app/tmp/puma.sock (Errno::EACCES)
```

対応として、

- アクセス権を設定してrails server起動ユーザがsockファイルへアクセスできるようにする
- rails server起動ユーザをsockファイルへのアクセス権があるユーザに変更する

などを行います。

## No such file or directory

### Nginx

nginxのworkerプロセスを起動するユーザがsockファイルにアクセスできない場合、ブラウザからアクセスした際に502エラー等になります。

その際、Nginxのエラーログには以下のように`No such file or directory`と出力されます。

```shell-session
$ sudo tail /var/log/nginx/error.log
2017/10/22 02:30:13 [crit] 3855#0: *1 connect() to unix:/mnt/web-ap1/my-app/tmp/puma.sock failed (2: No such file or directory) whil
e connecting to upstream, client: xx.xx.xx.xx, server: *, request: "GET / HTTP/1.1", upstream: "http://unix:/mnt/web-ap1/my-app/tm
p/puma.sock:/", host: "xx.xxx.xx.xx"
```

対応として、

- sockファイルのパスを正しい値に修正する
- `rails server`を起動してsockファイルが存在する状態にする
- AP(Rails)との共有ディレクトリをマウントして、WEB(Nginx)からsockファイルにアクセスできるようにする

などを行います。

### Rails

`rails server`起動ユーザがsockファイルにアクセスできない場合、サーバ起動に失敗します。

その際、Railsのログには以下のように`No such file or directory`と出力されます。

```shell-session
$rails s
...
* Listening on unix:///mnt/web-ap1/my-app/tmp/puma.sock
Exiting
/xxx/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/binder.rb:366:in `initialize': 
No such file or directory - connect(2) for /mnt/web-ap1/my-app/tmp/puma.sock(Errno::ENOENT)
```

対応として、

- sockファイルを配置するディレクトリを作成する
- WEB(Nginx)との共有ディレクトリをマウントして、AP(Rails)からsockファイルにアクセスできるようにする

などを行います。