【docker】Laravelのキューを使って非同期処理。Dockerにsupervisorをインストールして起動するまでのセットアップ手順

![L.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250884/d6312fb5-1c9b-427b-ed61-ec086a8c8efa.png)

開発でdockerとLaravelを使っており、非同期処理を実装する必要がありました。また、バックグラウンドでプロセスを永続的に実行する必要があったため、Supervisorを使用する必要がありました。そこで今回は**「dockerにSupervisorをインストールして使えるようになるまでのセットアップ手順」**を書いていきます。

```
【環境】
・「PHP」7.2.16
・「Laravel」5.8.12
・「docker」
　Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 OS/Arch:           windows/amd64

　Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  OS/Arch:          linux/amd64
```

#主なコマンドと設定ファイル
今回使用する主なコマンドと必要な設定ファイルが下記になります。
それぞれについて下で解説していきます。

##■easy_installからsupervisorの起動まで

```
wget https://bootstrap.pypa.io/ez_setup.py -O - | python
easy_install pip
pip install supervisor
supervisord -c ./supervisor/supervisord.conf
```

##■必要な設定ファイル

```
mypage/supervisor/supervisord.conf
mypage/supervisor/conf.d/laravel-worker.conf
```


#手順
###1.supervisorをインストールするためにeasy_install, pip をインストールする
```
// easy_installのインストール
wget https://bootstrap.pypa.io/ez_setup.py -O - | python

// 確認コマンド
which easy_install

// pipのインストール
pip install supervisor
```


###２．環境に合わせて設定ファイルを用意

ドキュメントや参考した記事では、設定ファイルは `/etc/supervisor` に入れるというようにありました。ただ、今回はgitでも管理がしたかったため、プロジェクト内に設定フォルダを作成して、その中に入れるようにしました。設定ファイルの場所は後ほどパスで指定するため、フォルダを作る場所はどこでも良いと思います。


```
// フォルダ作成
mkdir supervisor

// フォルダへ移動
cd supervisor

// supervisorの設定ファイル作成
touch supervisord.conf

// フォルダ作成
mkdir conf.d

// フォルダへ移動
cd conf.d

//  workerの設定ファイル作成
touch laravel-worker.conf
```

###３．設定ファイルの作成
下記コマンドでデフォルトの supervisord.conf  ファイルを作成します。
（\> の右はファイルを作成したいディレクトリ）

```
echo_supervisord_conf > /var/www/mypage/supervisor/supervisord.conf
```

###４．設定ファイルの修正
3.で作成したファイルに対して変更を加えます。
ほとんどがコメントアウトなので必要な変更箇所は見つけやすいと思います。

**【変更箇所】**
####１．下記をコメントイン
```
[inet_http_server]         ; inet (TCP) server disabled by default
port=127.0.0.1:9001        ; ip_address:port specifier, *:port for all iface
```

```
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket
serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
```

####２．ログの出力先を変更
```
[supervisord]
logfile=/var/www/mypage/storage/logs/supervisor/supervisord.log ; main log file; default $CWD/supervisord.log
```

####３．[include]に実行したいファイル(?)を作成
先程作成したディレクトリとファイルを指定。

```
[include]
files =  /var/www/mypage/supervisor/conf.d/*.conf
```

**最終的な「supervisord.conf」の内容**

```supervisord.conf
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
;  - Shell expansion ("~" or "$HOME") is not supported.  Environment
;    variables can be expanded using this syntax: "%(ENV_HOME)s".
;  - Quotes around values are not supported, except in the case of
;    the environment= options as shown below.
;  - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
;  - Command will be truncated if it looks like a config file comment, e.g.
;    "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".

[unix_http_server]
file=/tmp/supervisor.sock   ; the path to the socket file
;chmod=0700                 ; socket file mode (default 0700)
;chown=nobody:nogroup       ; socket file uid:gid owner
;username=user              ; default is no username (open server)
;password=123               ; default is no password (open server)

[inet_http_server]         ; inet (TCP) server disabled by default
port=127.0.0.1:9001        ; ip_address:port specifier, *:port for all iface
;username=user              ; default is no username (open server)
;password=123               ; default is no password (open server)

[supervisord]
logfile=/var/www/mypage/storage/logs/supervisor/supervisord.log ; main log file; default $CWD/supervisord.log
logfile_maxbytes=50MB        ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10           ; # of main logfile backups; 0 means none, default 10
loglevel=info                ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false               ; start in foreground if true; default false
minfds=1024                  ; min. avail startup file descriptors; default 1024
minprocs=200                 ; min. avail process descriptors;default 200
;umask=022                   ; process file creation umask; default 022
;user=supervisord            ; setuid to this UNIX account at startup; recommended if root
;identifier=supervisor       ; supervisord identifier, default is 'supervisor'
;directory=/tmp              ; default is not to cd during start
;nocleanup=true              ; don't clean up tempfiles at start; default false
;childlogdir=/tmp            ; 'AUTO' child log dir, default $TEMP
;environment=KEY="value"     ; key value pairs to add to environment
;strip_ansi=false            ; strip ansi escape codes in logs; def. false

; The rpcinterface:supervisor section must remain in the config file for
; RPC (supervisorctl/web interface) to work.  Additional interfaces may be
; added by defining them in separate [rpcinterface:x] sections.

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; The supervisorctl section configures how supervisorctl will connect to
; supervisord.  configure it match the settings in either the unix_http_server
; or inet_http_server section.

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket （編集済み） 
serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris              ; should be same as in [*_http_server] if set
;password=123                ; should be same as in [*_http_server] if set
;prompt=mysupervisor         ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history  ; use readline history if available

; The sample program section below shows all possible program subsection values.
; Create one or more 'real' program: sections to be able to control them under
; supervisor.

;[program:theprogramname]
;command=/bin/cat              ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1                    ; number of processes copies to start (def 1)
;directory=/tmp                ; directory to cwd to before exec (def no cwd)
;umask=022                     ; umask for process (default None)
;priority=999                  ; the relative start priority (default 999)
;autostart=true                ; start at supervisord start (default: true)
;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)
;startretries=3                ; max # of serial start failures when starting (default 3)
;autorestart=unexpected        ; when to restart if exited after running (def: unexpected)
;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT               ; signal used to kill process (default TERM)
;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
;killasgroup=false             ; SIGKILL the UNIX process group (def false)
;user=chrism                   ; setuid to this UNIX account to run the program
;redirect_stderr=true          ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)
;stdout_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false   ; emit events on stdout writes (default false)
;stdout_syslog=false           ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)
;stderr_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false   ; emit events on stderr writes (default false)
;stderr_syslog=false           ; send stderr to syslog with process name (default false)
;environment=A="1",B="2"       ; process environment additions (def no adds)
;serverurl=AUTO                ; override serverurl computation (childutils)

; The sample eventlistener section below shows all possible eventlistener
; subsection values.  Create one or more 'real' eventlistener: sections to be
; able to handle event notifications sent by supervisord.

;[eventlistener:theeventlistenername]
;command=/bin/eventlistener    ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1                    ; number of processes copies to start (def 1)
;events=EVENT                  ; event notif. types to subscribe to (req'd)
;buffer_size=10                ; event buffer queue size (default 10)
;directory=/tmp                ; directory to cwd to before exec (def no cwd)
;umask=022                     ; umask for process (default None)
;priority=-1                   ; the relative start priority (default -1)
;autostart=true                ; start at supervisord start (default: true)
;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)
;startretries=3                ; max # of serial start failures when starting (default 3)
;autorestart=unexpected        ; autorestart if exited after running (def: unexpected)
;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT               ; signal used to kill process (default TERM)
;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
;killasgroup=false             ; SIGKILL the UNIX process group (def false)
;user=chrism                   ; setuid to this UNIX account to run the program
;redirect_stderr=false         ; redirect_stderr=true is not allowed for eventlisteners
;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)
;stdout_events_enabled=false   ; emit events on stdout writes (default false)
;stdout_syslog=false           ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)
;stderr_events_enabled=false   ; emit events on stderr writes (default false)
;stderr_syslog=false           ; send stderr to syslog with process name (default false)
;environment=A="1",B="2"       ; process environment additions
;serverurl=AUTO                ; override serverurl computation (childutils)

; The sample group section below shows all possible group values.  Create one
; or more 'real' group: sections to create "heterogeneous" process groups.

;[group:thegroupname]
;programs=progname1,progname2  ; each refers to 'x' in [program:x] definitions
;priority=999                  ; the relative start priority (default 999)

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files ＊cannot＊
; include files themselves.

[include]
files =  /var/www/mypage/supervisor/conf.d/*.conf
```

###５．実行ファイル「laravel-worker.conf」を修正
まず、laravel公式ドキュメントのファイル内容をコピー。
そして開発環境に合わせて修正しました。

**【変更点】**
####１．実行コマンドの編集
公式ドキュメントはforgeを使っていました。ここは自分のディレクトリ構成に合わせて変更します。コマンドラインで `php artisan` コマンドを入力しているパスを入力すれば良いです。今回、キューはredisでの管理なので `queue:work sqs` のsqsをredisに変更します。また、ジョブ（非同期で実行する1処理）の失敗時に再度実行する回数を `--tries` オプションで設定できます。今回は1回失敗したら失敗のままにしたいので `--tries=1` と設定します。

```
// ドキュメントのコマンド
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3

// 修正したコマンド
command=php /var/www/mypage/artisan queue:work redis --sleep=3
```

####２．userをrootに変更

```
// ドキュメントのコマンド
user=forge

// 修正したコマンド
user=root
```

####３．同時に実行するプロセスを8から2へ変更

```
// ドキュメントのコマンド
numprocs=8

// 修正したコマンド
numprocs=2
```

####４．実行ファイルのログファイルの出力先を変更

```
// ドキュメントのコマンド
stdout_logfile=/home/forge/app.com/worker.log

// 修正したコマンド
stdout_logfile=/var/www/mypage/storage/logs/supervisor/worker.log
```

**最終的な「laravel-worker.conf」ファイル**

```
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mypage/artisan queue:work redis --sleep=3
autostart=true
autorestart=true
user=root
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/mypage/storage/logs/supervisor/worker.log
```
###６．envファイルの該当箇所をsyncからredisに変更
envファイルの下記箇所をsyncからredisに変更します。syncの場合は同期で実行されるので動作確認の際にはsyncに戻して実行しました。

```.env
QUEUE_CONNECTION=redis
```

###７．実行!
下記コマンドでsupervisorを起動することができます。

```
supervisord -c ./supervisor/supervisord.conf
```

###■気づき
・laravelの公式ドキュメントはforgeの使用を勧めてくる。
そのため、ドキュメントに書いてある設定ファイルにはforgeがふんだんに使われていたのでそれによるエラーが度々生じました。

↓めっちゃ勧めてくる。
>Tip!! Supervisoの設定に圧倒されそうならば、Laravelプロジェクトのために、Supervisorを自動的にインストールし、設定するLaravel Forgeの使用を考慮してください。

・dockerに導入する記事が余りない。
開発環境にdockerを使用していたため、dockerにsupervisorを入れて使用したかったのですが、dockerで非同期処理をする記事があまりありませんでした。今回の記事がお役に立てば嬉しいです。

**■プロセス確認コマンド等**
これで親のプロセス(supervisor)が生きていれば子のプロセス(queue)が死んでも生き続ける。
停止したいときには親のプロセスをkillする。killするしか停止する方法はないと思います。

```
■queueプロセスを確認したいとき
ps aux | grep queue

■supervisorのプロセスを確認したいとき
ps aux | grep super

■プロセスをkillするとき
kill プロセス番号
```

 
###■参考
・pythonのsetuptoolsをインストールする
https://cloudpack.media/15400


・Laravel 5.8 キュー
https://readouble.com/laravel/5.8/ja/queues.html#supervisor-configuration

・【Laravel5.5】EC2にSupervisorを入れてQueueのリスナーを監視する
https://nextat.co.jp/staff/archives/218

・Supervisorで簡単にデーモン化
https://qiita.com/yushin/items/15f4f90c5663710dbd56#supervisord%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%95%E3%82%A3%E3%82%B0