Nginx + Unicorn on CentOS 8 with SELinux

前のエントリで web アプリを Unicorn で動かすことに成功したので、今度は Nginx と連携する。

その前に unicorn.conf をちょっと修正して、ポートじゃなくて UNIX ソケットを利用するようにする。

#listen "9000"
listen "/run/lcstorage/unicorn.sock"
worker_processes 2
working_directory "/home/lcstorage/lcstorage"
pid "/home/lcstorage/lcstorage/unicorn.pid"
stdout_path "/home/lcstorage/lcstorage/unicorn.log"
stderr_path "/home/lcstorage/lcstorage/unicorn.log"
preload_app true

そしてソケットファイルを置くディレクトリを作る。

[lcstorage@rollo lcstorage]$ sudo mkdir /run/lcstorage
[lcstorage@rollo lcstorage]$ sudo chown lcstorage:lcstorage /run/lcstorage

Unicorn の準備はこれで終わり。起動しておく。

[lcstorage@rollo lcstorage]$ bundle exec unicorn -c unicorn.conf -E production

さて、つぎは Nginx の設定だ。storage1 というホスト名でアクセスできるよう、/etc/nginx/conf.d の下に storage1.conf という名前で設定ファイルを作る。

upstream lcstorage {
    server unix:/run/lcstorage/unicorn.sock;
    #server 127.0.0.1:9000;
}

server {
    # port
    listen      80;

    # server name
    server_name storage1;

    # document root
    #root        /home/lcstorage/lcstorage;

    # index
    #index       index.php index.html index.htm;

    client_max_body_size    4G;

    # log files
    access_log /var/log/nginx/storage1/access.log main;
    error_log  /var/log/nginx/storage1/error.log  warn;

    keepalive_timeout     60;
    proxy_connect_timeout 60;
    proxy_read_timeout    60;
    proxy_send_timeout    60;

    location / {
        #root       /home/lcstorage/lcstorage;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://lcstorage;
    }
}

ログを書き込むディレクトリを忘れずに作っておく。

これで準備はできたはずだ。Nginx を再起動しよう。

[takatoh@rollo conf.d]$ sudo systemctl restart nginx

で、別マシンから http://storage1/ にアクセスしてみると、結果はこうだった。

502 Bad Gateway

あー、これはあれだ、SELinux のせいだ。試しに SELinux を Permissive モードにしてみる。

[takatoh@rollo ~]$ sudo setenforce 0
[takatoh@rollo ~]$ getenforce
Permissive

この状態でもう一度アクセスしていると、ちゃんと File not found. と表示された。なんどもいうけど、返すファイルがないのでこれが正しい動作だ。

つまり、SELinux をどうにかしてやらないといけないわけだ。だけどググってみると、解決策として出てくるのは SELinux を無効にしろ、っていうのばっかり。ちゃんと使い方を説明しているページはほとんど無い。そんな中で参考になったのが以下のページ。

 cf. 【ゼロから始める】初期設定のみのLinuxサーバーに既存のRailsアプリとWebサーバーを導入する – Takeshi’s Blog

と、そこからリンクされているページ。

 cf. RedmineをCentOS 7上で動かすーUnicornとNginx編 – ソフトウェアエンジニアリング

Nginx のエラーログを調べると:

2019/11/23 11:33:23 [crit] 2526#0: *1 connect() to unix:/run/lcstorage/unicorn.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.1.11, server: storage1, request: "GET / HTTP/1.1", upstream: "http://unix:/run/lcstorage/unicorn.sock:/", host: "storage1"

となっていて、ソケットファイルにアクセスできないようだ。じゃあ、ソケットファイルのパーミッションはどうなってるかというと:

[takatoh@rollo ~]$ ls -lZ /run/lcstorage
合計 0
srwxrwxrwx. 1 lcstorage lcstorage unconfined_u:object_r:var_run_t:s0 0 11月 23 11:42 unicorn.sock

Z は SELinux のアクセス権限を表示するオプション。うん、よくわからん。SELinux が原因かどうかは /var/log/audit/audit.log を調べるとわかるらしい。再度 SELinux を有効にしてアクセスしてみてから:

[takatoh@rollo ~]$ sudo cat /var/log/audit/audit.log | grep nginx | tail
type=SERVICE_STOP msg=audit(1574476367.153:1276): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=nginx comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'UID="root" AUID="unset"
type=SERVICE_START msg=audit(1574476367.207:1277): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=nginx comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'UID="root" AUID="unset"
type=AVC msg=audit(1574476403.391:1281): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0
type=SYSCALL msg=audit(1574476403.391:1281): arch=c000003e syscall=42 success=no exit=-13 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"
type=AVC msg=audit(1574476651.650:1290): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=1
type=SYSCALL msg=audit(1574476651.650:1290): arch=c000003e syscall=42 success=yes exit=0 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"
type=AVC msg=audit(1574477463.658:1309): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=1
type=SYSCALL msg=audit(1574477463.658:1309): arch=c000003e syscall=42 success=yes exit=0 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"
type=AVC msg=audit(1574477957.417:1347): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0
type=SYSCALL msg=audit(1574477957.417:1347): arch=c000003e syscall=42 success=no exit=-13 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"

さぱーりわからん。けど、どうやら↓ここらしい。

type=AVC msg=audit(1574477957.417:1347): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0

nginx が /run/lcstorage/unicorn.sock にアクセスしようとして denied になってるようだ。つまりここを何とかすればいいわけ。

SELinux の上のアクセスを許可する設定は、/var/log/audit/audit.log ファイルから audit2allow コマンドで作れるようだ。まず確認。

[takatoh@rollo ~]$ sudo grep nginx /var/log/audit/audit.log | audit2allow

#============= httpd_t ==============

#!!!! This avc can be allowed using the boolean 'httpd_can_network_connect'
allow httpd_t glance_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, httpd_graceful_shutdown, httpd_can_network_relay, nis_enabled
allow httpd_t http_port_t:tcp_socket name_connect;
allow httpd_t unconfined_t:unix_stream_socket connectto;

設定用のファイルを作るにはこうする。

[takatoh@rollo ~]$ sudo grep nginx /var/log/audit/audit.log | audit2allow -M nginx
[sudo] takatoh のパスワード:
******************* 重要 ********************
このポリシーパッケージを有効にするには、以下を実行して下さい:

semodule -i nginx.pp

指示にあるようにして、SELinux に設定する。

[takatoh@rollo ~]$ sudo semodule -i nginx.pp

さてこれでどうだろう。はたして、別のマシンからブラウザでアクセスしてみると、ちゃんと File not found. って表示された。なんどもいうけどこれが(ry。

つぎは web アプリをサービスとして登録する。/etc/systemd/system ディレクトリの下に lcstorage.service ファイルを作る。

[Unit]
Description=LatherCraft storage service
After=network.target

[Service]
Type=forking
PIDFile=/home/lcstorage/lcstorage/unicorn.pid
ExecStart=/usr/local/bin/bundle exec unicorn -c unicorn.conf -E production -D
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -QUIT $MAINPID
WorkingDirectory=/home/lcstorage/lcstorage
User=lcstorage
Group=lcstorage

[Install]
WantedBy=multi-user.target

これで起動してみる。

[takatoh@rollo system]$ sudo systemctl start lcstorage

あれ?おかしい。プロンプトが戻ってこない。ほかのターミナルからステータスを見てみると:

[lcstorage@rollo lcstorage]$ systemctl status lcstorage
● lcstorage.service - LatherCraft storage service
   Loaded: loaded (/etc/systemd/system/lcstorage.service; disabled; vendor pres>
   Active: activating (start) since Sat 2019-11-23 12:57:57 JST; 1min 14s ago
  Process: 3931 ExecStart=/usr/local/bin/bundle exec unicorn -c unicorn.conf -E production -D (code=exited, status=0/SUCCESS)
    Tasks: 6 (limit: 26213)
   Memory: 31.4M
   CGroup: /system.slice/lcstorage.service
           ├─3936 unicorn master -c unicorn.conf -E production -D  production -D
           ├─3939 unicorn worker[0] -c unicorn.conf -E production -D  productio>
           └─3942 unicorn worker[1] -c unicorn.conf -E production -D  productio>
11月 23 12:57:57 rollo systemd[1]: Starting LatherCraft storage service…
11月 23 12:57:58 rollo systemd[1]: lcstorage.service: Can't convert PID files /home/lcstorage/lcstorage/unicorn.pid O_PATH file descriptor to proper file descriptor: Permission denied
11月 23 12:57:58 rollo systemd[1]: lcstorage.service: Can't convert PID files /home/lcstorage/lcstorage/unicorn.pid O_PATH file descriptor to proper file descriptor: Permission denied

pid ファイルにアクセスできないっぽい?じゃあ、場所を変えてみようか。/home/lcstorage/lcstorage/unicorn.conf と /etc/systemd/system/lcstorage.service の両方で pid ファイルを作る場所を /run に変えてみた。

でもまだダメ。もとに戻す。もう一度やってみてもこうなる。

[takatoh@rollo system]$ sudo systemctl start lcstorage
Job for lcstorage.service failed because a timeout was exceeded.
See "systemctl status lcstorage.service" and "journalctl -xe" for details.

で、SELinux を無効にしてやってみた。

[takatoh@rollo system]$ sudo setenforce 0
[takatoh@rollo system]$ getenforce
Permissive
[takatoh@rollo system]$ sudo systemctl start lcstorage

あ、これだとすんなりいく。ブラウザからアクセスしてもうまくいった。またお前か、SELinux!

さっきとおなじように /var/log/audit/audit.log を調べてみるとこうなってた。

type=AVC msg=audit(1574483152.115:1720): avc:  denied  { open } for  pid=1 comm="systemd" path="/home/lcstorage/lcstorage/unicorn.pid" dev="dm-2" ino=24382 scontext=system_u:system_r:init_t:s0 tcontext=system_u:object_r:user_home_t:s0 tclass=file permissive=1

やっぱり pid ファイルにアクセスできないみたいだ。ポリシーを作ってやる。

[takatoh@rollo ~]$ sudo grep unicorn /var/log/audit/audit.log | audit2allow -M unicorn
[takatoh@rollo ~]$ sudo semodule -i unicorn.pp

これでどうだろう。

[takatoh@rollo ~]$ sudo setenforce 1
[takatoh@rollo ~]$ getenforce
Enforcing
[takatoh@rollo ~]$ sudo systemctl start lcstorage

お、うまくいったっぽい。ブラウザからアクセスしても大丈夫だった。

最後に。マシンを起動した時に自動起動するようにする。

[takatoh@rollo ~]$ sudo systemctl enable lcstorage
Created symlink /etc/systemd/system/multi-user.target.wants/lcstorage.service → /etc/systemd/system/lcstorage.service.

はぁ、長かった。

Ruby 製アプリを Unicorn で動かす

昨日に引き続きデプロイの続きをするよ。今日の作業はまず Unicorn でアプリを動かすことだ。

Gemfile に unicorn を書き足して bundle install。

source "https://rubygems.org"

gem 'sinatra'
gem 'httpclient'
gem 'json'
gem 'filestorage'
gem 'unicorn'
[lcstorage@rollo lcstorage]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/………….
Resolving dependencies…
Using bundler 2.0.2
Using filestorage 0.1.0
Using httpclient 2.8.3
Using json 2.2.0
Fetching kgio 2.11.2
Installing kgio 2.11.2 with native extensions
Using mustermann 1.0.3
Using rack 2.0.7
Using rack-protection 2.0.7
Fetching raindrops 0.19.0
Installing raindrops 0.19.0 with native extensions
Using tilt 2.0.10
Using sinatra 2.0.7
Fetching unicorn 5.5.1
Installing unicorn 5.5.1 with native extensions
Bundle complete! 5 Gemfile dependencies, 12 gems now installed.
Bundled gems are installed into ./vendor/bundle

Unicorn の設定ファイル unicorn.conf を書く。

listen "9000"
worker_processes 2
working_directory "/home/lcstorage/lcstorage"
pid "/home/lcstorage/lcstorage/unicorn.pid"
stdout_path "/home/lcstorage/lcstorage/unicorn.log"
stderr_path "/home/lcstorage/lcstorage/unicorn.log"
preload_app true

これでいいはず。試しに起動してみる。

[lcstorage@rollo lcstorage]$ bundle exec unicorn -c unicorn.conf -E production

昨日と同じく、別のコンソールからアクセス。

[takatoh@rollo ~]$ curl http://localhost:9000/
File not found.[takatoh@rollo ~]$ 
[takatoh@rollo ~]$ curl http://localhost:9000/path/to/file
File not found.[takatoh@rollo ~]$

うん、ちゃんと File not found. が返ってきてる。昨日も書いたけど、返すファイルがまだ無いのでこれは正しい動作だ。

さて、あとは Nginx との連携だけど、これはエントリを分ける。というわけでとりあえずここまで。