Supervisor 是基於 Python 的程式管理工具,只能執行在 Unix-Like 的系統上,也就是無法執行在 Windows 上。Supervisor 官方版目前只能執行在 Python 2.4 以上版本,但是還無法執行在 Python 3 上,不過已經有一個 Python 3 的移植版 supervisor-py3k。
什麼情況下我們需要程式管理呢?就是執行一些需要以守護程式方式執行的程式,比如一個後臺任務,我最常用的是用來啟動和管理基於 Tornado 寫的 Web 程式。
除此之外,Supervisor 還能很友好的管理程式在命令列上輸出的日誌,可以將日誌重定向到自定義的日誌檔案中,還能按檔案大小對日誌進行分割。
Supervisor 有兩個主要的組成部分:
supervisord
,執行 Supervisor 時會啟動一個程式 supervisord,它負責啟動所管理的程式,並將所管理的程式作為自己的子程式來啟動,而且可以在所管理的程式出現崩潰時自動重啟。supervisorctl
,是命令列管理工具,可以用來執行 stop、start、restart 等命令,來對這些子程式進行管理。
安裝
1 |
sudo pip install supervisor |
建立配置檔案
1 2 |
echo_supervisord_conf > /etc/supervisord.conf |
如果出現沒有許可權的問題,可以使用這條命令
1 |
sudo su - root -c "echo_supervisord_conf > /etc/supervisord.conf" |
配置檔案說明
想要了解怎麼配置需要管理的程式,只要開啟 supervisord.conf 就可以了,裡面有很詳細的註釋資訊。
開啟配置檔案
1 2 |
vim /etc/supervisord.conf |
預設的配置檔案是下面這樣的,但是這裡有個坑需要注意,supervisord.pid 以及 supervisor.sock 是放在 /tmp 目錄下,但是 /tmp 目錄是存放臨時檔案,裡面的檔案是會被 Linux 系統刪除的,一旦這些檔案丟失,就無法再通過 supervisorctl 來執行 restart 和 stop 命令了,將只會得到unix:///tmp/supervisor.sock
不存在的錯誤 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
[unix_http_server] ;file=/tmp/supervisor.sock ; (the path to the socket file) ;修改為 /var/run 目錄,避免被系統刪除 file=/var/run/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=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) ;修改為 /var/log 目錄,避免被系統刪除 logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) logfile_backups=10 ; (num of main logfile rotation backups;default 10) loglevel=info ; (log level;default info; others: debug,warn,trace) ;pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) ;修改為 /var/run 目錄,避免被系統刪除 pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) ... ;設定啟動supervisord的使用者,一般情況下不要輕易用root使用者來啟動,除非你真的確定要這麼做 ;user=chrism ; (default is current user, required if root) ... [supervisorctl] ; 必須和'unix_http_server'裡面的設定匹配 ;serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket ;修改為 /var/run 目錄,避免被系統刪除 serverurl=unix:///var/run/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 http_username if set ;password=123 ; should be same as http_password if set ... |
預設情況下,程式的日誌檔案達到50MB時,將進行分割,最多保留10個檔案,當然這些配置也可以對每個程式單獨配置。
許可權問題
設定好配置檔案後,應先建立上述配置檔案中新增的資料夾。如果指定了啟動使用者 user,這裡以 oxygen 為例,那麼應注意相關檔案的許可權問題,包括日誌檔案,否則會出現沒有許可權的錯誤。例如設定了啟動使用者 oxygen,然後啟動 supervisord 出現錯誤
1 2 |
Error: Cannot open an HTTP server: socket.error reported errno.EACCES (13) |
就是由於上面的配置檔案中 /var/run 資料夾,沒有授予啟動 supervisord 的使用者 oxygen 的寫許可權。/var/run 資料夾實際上是連結到 /run,因此我們修改 /run 的許可權。
1 2 |
sudo chmod 777 /run |
這樣有點簡單粗暴,也可以考慮把上述配置檔案中 .sock
,.pid
等檔案修改到其他資料夾中,並確保有相應的許可權即可。一般情況下,我們可以用 root 使用者啟動 supervisord 程式,然後在其所管理的程式中,再具體指定需要以那個使用者啟動這些程式。
使用瀏覽器來管理
supervisor 同時提供了通過瀏覽器來管理程式的方法,只需要註釋掉如下幾行就可以了。
1 2 3 4 5 6 7 8 9 10 |
;[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)) [supervisorctl] ... ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket ;username=chris ; should be same as http_username if set ;password=123 ; should be same as http_password if set |
使用 include
在配置檔案的最後,有一個 [include] 的配置項,跟 Nginx 一樣,可以 include 某個資料夾下的所有配置檔案,這樣我們就可以為每個程式或相關的幾個程式的配置單獨寫成一個檔案。
1 2 |
[include] files = /etc/supervisord.d/*.ini |
程式的配置樣例
一個簡單的例子如下
1 2 3 4 5 6 7 8 9 10 11 12 |
; 設定程式的名稱,使用 supervisorctl 來管理程式時需要使用該程式名 [program:your_program_name] command=python server.py --port=9000 ;numprocs=1 ; 預設為1 ;process_name=%(program_name)s ; 預設為 %(program_name)s,即 [program:x] 中的 x directory=/home/python/tornado_server ; 執行 command 之前,先切換到工作目錄 user=oxygen ; 使用 oxygen 使用者來啟動該程式 ; 程式崩潰時自動重啟,重啟次數是有限制的,預設為3次 autorestart=true redirect_stderr=true ; 重定向輸出的日誌 stdout_logfile = /var/log/supervisord/tornado_server.log loglevel=info |
設定日誌級別
loglevel
指定了日誌的級別,用 Python 的 print 語句輸出的日誌是不會被記錄到日誌檔案中的,需要搭配 Python 的 logging 模組來輸出有指定級別的日誌。
多個程式
按照官方文件的定義,一個 [program:x] 實際上是表示一組相同特徵或同類的程式組,也就是說一個 [program:x] 可以啟動多個程式
。這組程式的成員是通過 numprocs
和 process_name
這兩個引數來確定的,這句話什麼意思呢,我們來看這個例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
; 設定程式的名稱,使用 supervisorctl 來管理程式時需要使用該程式名 [program:foo] ; 可以在 command 這裡用 python 表示式傳遞不同的引數給每個程式 command=python server.py --port=90%(process_num)02d directory=/home/python/tornado_server ; 執行 command 之前,先切換到工作目錄 ; 若 numprocs 不為1,process_name 的表示式中一定要包含 process_num 來區分不同的程式 numprocs=2 process_name=%(program_name)s_%(process_num)02d; user=oxygen ; 使用 oxygen 使用者來啟動該程式 autorestart=true ; 程式崩潰時自動重啟 redirect_stderr=true ; 重定向輸出的日誌 stdout_logfile = /var/log/supervisord/tornado_server.log loglevel=info |
上面這個例子會啟動兩個程式,process_name 分別為 foo:foo_01
和 foo:foo_02
。通過這樣一種方式,就可以用一個 [program:x] 配置項,來啟動一組非常類似的程式。
再介紹兩個配置項 stopasgroup
和 killasgroup
1 2 3 4 5 |
; 預設為 false,如果設定為 true,當程式收到 stop 訊號時,會自動將該訊號發給該程式的子程式。如果這個配置項為 true,那麼也隱含 killasgroup 為 true。例如在 Debug 模式使用 Flask 時,Flask 不會將接收到的 stop 訊號也傳遞給它的子程式,因此就需要設定這個配置項。 stopasgroup=false ; send stop signal to the UNIX process ; 預設為 false,如果設定為 true,當程式收到 kill 訊號時,會自動將該訊號發給該程式的子程式。如果這個程式使用了 python 的 multiprocessing 時,就能自動停止它的子執行緒。 killasgroup=false ; SIGKILL the UNIX process group (def false) |
更詳細的配置例子,可以參考如下,官方文件在這裡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
;[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) ;autorestart=unexpected ; whether/when to restart (default: unexpected) ;startsecs=1 ; number of secs prog must stay running (def. 1) ;startretries=3 ; max # of serial start failures (default 3) ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) ;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 (default 10) ;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) ;stdout_events_enabled=false ; emit events on stdout writes (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 (default 10) ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) ;stderr_events_enabled=false ; emit events on stderr writes (default false) ;environment=A="1",B="2" ; process environment additions (def no adds) ;serverurl=AUTO ; override serverurl computation (childutils) |
將多個程式按組管理
Supervisor 同時還提供了另外一種程式組的管理方式,通過這種方式,可以使用 supervisorctl 命令來管理一組程式。跟 [program:x] 的程式組不同的是,這裡的程式是一個個的 [program:x] 。
1 2 3 |
[group:thegroupname] programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions priority=999 ; the relative start priority (default 999) |
當新增了上述配置後,progname1
和 progname2
的程式名就會變成thegroupname:progname1
和 thegroupname:progname2
以後就要用這個名字來管理程式了,而不是之前的 progname1
。
以後執行 supervisorctl stop thegroupname:
就能同時結束 progname1
和 progname2
,執行 supervisorctl stop thegroupname:progname1
就能結束 progname1
。supervisorctl 的命令我們稍後介紹。
啟動 supervisord
執行 supervisord 命令,將會啟動 supervisord 程式,同時我們在配置檔案中設定的程式也會相應啟動。
1 2 3 4 5 6 |
# 使用預設的配置檔案 /etc/supervisord.conf supervisord # 明確指定配置檔案 supervisord -c /etc/supervisord.conf # 使用 user 使用者啟動 supervisord supervisord -u user |
更多引數請參考文件
supervisorctl 命令介紹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 停止某一個程式,program_name 為 [program:x] 裡的 x supervisorctl stop program_name # 啟動某個程式 supervisorctl start program_name # 重啟某個程式 supervisorctl restart program_name # 結束所有屬於名為 groupworker 這個分組的程式 (start,restart 同理) supervisorctl stop groupworker: # 結束 groupworker:name1 這個程式 (start,restart 同理) supervisorctl stop groupworker:name1 # 停止全部程式,注:start、restart、stop 都不會載入最新的配置檔案 supervisorctl stop all # 載入最新的配置檔案,停止原有程式並按新的配置啟動、管理所有程式 supervisorctl reload # 根據最新的配置檔案,啟動新配置或有改動的程式,配置沒有改動的程式不會受影響而重啟 supervisorctl update |
注意:顯示用 stop 停止掉的程式,用 reload 或者 update 都不會自動重啟。也可以參考這裡
開機自動啟動 Supervisord
Supervisord 預設情況下並沒有被安裝成服務,它本身也是一個程式。官方已經給出了指令碼可以將 Supervisord 安裝成服務,可以參考這裡檢視各種作業系統的安裝指令碼,但是我用官方這裡給的 Ubuntu 指令碼卻無法執行。
安裝方法可以參考 serverfault 上的回答。
比如我是 Ubuntu 系統,可以這麼安裝,這裡選擇了另外一個指令碼
1 2 3 4 5 6 7 8 9 |
# 下載指令碼 sudo su - root -c "sudo curl https://gist.githubusercontent.com/howthebodyworks/176149/raw/d60b505a585dda836fadecca8f6b03884153196b/supervisord.sh > /etc/init.d/supervisord" # 設定該指令碼為可以執行 sudo chmod +x /etc/init.d/supervisord # 設定為開機自動執行 sudo update-rc.d supervisord defaults # 試一下,是否工作正常 service supervisord stop service supervisord start |
注意:這個指令碼下載下來後,還需檢查一下與我們的配置是否相符合,比如預設的配置檔案路徑,pid 檔案路徑等,如果存在不同則需要進行一些修改。
其實還有一個簡單的方法,因為 Linux 在啟動的時候會執行 /etc/rc.local
裡面的指令碼,所以只要在這裡新增執行命令就可以
1 2 |
# 如果是 Ubuntu 新增以下內容 /usr/local/bin/supervisord -c /etc/supervisord.conf |
1 2 |
# 如果是 Centos 新增以下內容 /usr/bin/supervisord -c /etc/supervisord.conf |
在新增前,先在終端測試一下命令是否能正常執行,如果找不到 supervisord,可以用如下命令找到以上內容需要新增在 exit 命令前,而且由於在執行 rc.local 指令碼時,PATH 環境變數未全部初始化,因此命令需要使用絕對路徑。
1 |
sudo find / -name supervisord |