5616 字
28 分钟
Supervisor 从入门到精通
2025-04-16

Supervisor 从入门到精通:不止是进程守护,更是应用管理的瑞士军刀#

大家好,我是bangwu。在后端开发和运维的世界里,确保我们的应用程序(无论是 Web 服务、后台任务队列还是其他关键进程)能够稳定、持续地运行至关重要。进程意外退出、服务器重启后需要手动恢复,这些都是令人头疼的问题。今天,我们就来深入探讨一个优雅解决这些问题的利器——Supervisor

Supervisor 不仅仅是一个简单的进程监控和重启工具,它更是一个功能强大的客户端/服务器系统,允许用户在类 UNIX 操作系统上控制和监控多个进程。这篇文章将带你从 Supervisor 的基础概念出发,一步步掌握其安装、配置、实战应用,并深入了解其高级特性和最佳实践,助你成为 Supervisor 的使用专家。

1. Supervisor 是什么?为什么选择它?#

想象一下,你部署了一个重要的 Python Web 应用,它通过 Gunicorn 运行。如果 Gunicorn 进程因为某些错误意外崩溃了,或者服务器重启了,会发生什么?服务中断,用户无法访问,你需要手动登录服务器去重启它。

Supervisor 就是来解决这个问题的。它是一个用 Python 编写的进程控制系统,可以:

  • 启动进程:在后台启动你的应用程序。
  • 监控进程:持续关注你的进程状态。
  • 自动重启:如果进程意外退出,Supervisor 会自动尝试重启它。
  • 管理多个进程:轻松管理多个不同的应用程序或同一应用的不同实例。
  • 日志管理:捕获进程的标准输出 (stdout) 和标准错误 (stderr) 到指定文件。
  • 提供控制接口:通过 supervisorctl 命令行工具或 Web UI 来管理进程(启动、停止、重启、查看状态等)。

为什么选择 Supervisor?

  • 简单易用:配置相对直观,上手快。
  • 稳定可靠:经过广泛使用和测试,非常稳定。
  • 资源占用低:相比一些重量级解决方案,Supervisor 本身消耗的资源很少。
  • 功能适中:提供了足够强大的功能,但又不像 systemd 那样与操作系统深度绑定,更侧重于应用层进程管理。
  • 跨平台性好:虽然主要用于类 UNIX 系统,但因为它基于 Python,理论上可在任何支持 Python 的平台上运行(尽管 Windows 支持可能有限)。

2. 核心概念解析#

理解 Supervisor 的工作方式,需要了解几个核心组件:

  • supervisord: 这是 Supervisor 的服务器端进程。它负责启动、管理和监控所有在配置文件中定义的子进程。它本身通常也需要被某种方式(如 systemd、init.d 脚本)守护,以确保 Supervisor 自身的稳定运行。
  • supervisorctl: 这是 Supervisor 的客户端命令行工具。通过它,用户可以连接到 supervisord,并对其管理的进程进行各种操作(如 start, stop, restart, status 等)。
  • 配置文件: Supervisor 的行为由一个或多个配置文件定义。通常有一个主配置文件(如 /etc/supervisord.conf),它可以包含(include)其他目录下的配置文件(如 /etc/supervisor/conf.d/*.conf),每个文件通常定义一个或多个要管理的程序(program)。
  • 程序 (Program): 指你希望 Supervisor 管理的实际应用程序进程。在配置文件中以 [program:your_program_name] 部分定义。
  • 进程组 (Group): 可以将多个相关的程序定义在一个组内,方便统一管理。

3. 安装 Supervisor#

安装 Supervisor 最常见的方式是通过包管理器或 pip

使用 pip (推荐,通常版本较新):

# 确保你有 pip
# python -m ensurepip --upgrade  # 或者 python3 -m ensurepip --upgrade
# pip install --upgrade pip

# 安装 supervisor
pip install supervisor

# 安装后,可能需要手动创建配置文件和目录
# 通常配置文件位于 /etc/supervisord.conf 或 /etc/supervisor/supervisord.conf
# 程序配置文件目录通常是 /etc/supervisor/conf.d/
# 可以运行 echo_supervisord_conf 将默认配置输出到文件
echo_supervisord_conf > /etc/supervisord.conf # 可能需要 sudo
mkdir -p /etc/supervisor/conf.d/ # 可能需要 sudo

使用系统包管理器 (以 Ubuntu/Debian 和 CentOS/RHEL 为例):

  • Ubuntu/Debian:

    sudo apt update
    sudo apt install supervisor
    # 配置文件通常在 /etc/supervisor/supervisord.conf
    # 程序配置文件在 /etc/supervisor/conf.d/
    # 安装后通常会自动启动 supervisord 服务
    sudo systemctl status supervisor
    
  • CentOS/RHEL (可能需要 EPEL 源):

    sudo yum install epel-release # 如果没有安装 EPEL
    sudo yum install supervisor
    # 配置文件通常在 /etc/supervisord.conf
    # 程序配置文件在 /etc/supervisor/conf.d/
    # 安装后需要手动启动并设置开机自启
    sudo systemctl start supervisord
    sudo systemctl enable supervisord
    sudo systemctl status supervisord
    

安装完成后,supervisord (服务) 和 supervisorctl (客户端) 命令就可用了。


4. 初识配置:管理第一个进程#

让我们来管理一个简单的长时间运行的脚本。

  1. 创建示例脚本 (/opt/myapp/loop.sh):

    #!/bin/bash
    echo "My simple loop script started at $(date)"
    while true; do
      echo "Looping... $(date)"
      sleep 5
    done
    

    给它执行权限: chmod +x /opt/myapp/loop.sh (确保 /opt/myapp 目录存在)。

  2. 创建 Supervisor 程序配置文件 (/etc/supervisor/conf.d/loop_script.conf):

    [program:loop_script]
    command=/opt/myapp/loop.sh             ; 要执行的命令
    autostart=true                         ; supervisord 启动时自动启动该程序
    autorestart=true                       ; 程序退出时自动重启 (意外退出时)
    user=nobody                            ; 以哪个用户身份运行 (建议非 root)
    stdout_logfile=/var/log/supervisor/loop_script_stdout.log ; 标准输出日志文件路径
    stderr_logfile=/var/log/supervisor/loop_script_stderr.log ; 标准错误日志文件路径
    directory=/tmp                         ; 命令执行前切换到的目录 (可选)
    

    注意: 确保 /var/log/supervisor/ 目录存在并且 Supervisor 运行的用户(通常是 root 或 supervisor)有权写入。mkdir -p /var/log/supervisor && chown nobody:nogroup /var/log/supervisor (用户 nobody 可能因系统而异,根据实际情况调整)。

  3. 让 Supervisor 加载新配置:

    sudo supervisorctl reread  # 读取配置文件 (包括 conf.d 下的新文件)
    # 输出类似: loop_script: available
    
    sudo supervisorctl update  # 应用新的配置,启动新增的程序
    # 输出类似: loop_script: added process group
    
    # 或者,如果你想明确启动它
    # sudo supervisorctl start loop_script
    
  4. 检查状态:

    sudo supervisorctl status
    # 输出类似:
    # loop_script                      RUNNING   pid 12345, uptime 0:00:15
    
  5. 查看日志:

    sudo tail -f /var/log/supervisor/loop_script_stdout.log
    # 你会看到 "Looping..." 的输出
    
  6. 停止进程:

    sudo supervisorctl stop loop_script
    # 再次 status 查看,状态应变为 STOPPED 或 EXITED
    

恭喜!你已经成功使用 Supervisor 管理了你的第一个进程。


5. 配置文件深度解析 (supervisord.conf)#

Supervisor 的强大之处在于其灵活的配置。主配置文件(通常是 /etc/supervisord.conf)定义了 supervisord 自身行为、supervisorctl 连接方式以及包含其他配置文件的路径。

; /etc/supervisord.conf (示例片段)

[unix_http_server]
file=/var/run/supervisor.sock   ; socket 文件路径,supervisorctl 默认连接这个
;chmod=0700                 ; socket 文件的权限
;chown=nobody:nogroup       ; socket 文件的所有者

[inet_http_server]         ; HTTP 服务器,用于 Web UI 和远程 XML-RPC
;port=127.0.0.1:9001        ; 监听地址和端口 (默认不启用)
;username=user              ; (可选) 访问 Web UI/RPC 的用户名
;password=123               ; (可选) 密码

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; supervisord 自身日志
pidfile=/var/run/supervisord.pid ; supervisord 进程 ID 文件
childlogdir=/var/log/supervisor            ; 子进程日志目录 (如果 program 中没指定绝对路径)
;nodaemon=false             ; false 表示后台运行 (守护进程模式),true 表示前台运行 (调试用)
;minfds=1024                ; 最小可用文件描述符
;minprocs=200               ; 最小可用进程数
;user=root                  ; supervisord 进程的运行用户 (通常保持 root,以便管理不同用户的子进程)

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

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; supervisorctl 连接的服务器 URL
;serverurl=http://127.0.0.1:9001 ; 如果使用 inet_http_server
;username=user              ; 如果服务器设置了认证
;password=123               ;
;prompt=mysupervisor        ; supervisorctl 的提示符

; 包含 conf.d 目录下的所有 .conf 文件
[include]
files = /etc/supervisor/conf.d/*.conf

[program:x] 部分详解 (重点中的重点):

这是定义你要管理的应用程序的地方。x 是你为程序起的名字,在 supervisorctl 中会用到。

[program:my_web_app]
command=/usr/bin/python /opt/myapp/app.py --port=8080 ; ★ 启动进程的完整命令
process_name=%(program_name)s_%(process_num)02d ; 进程名模板 (用于多实例)
numprocs=1                 ; 启动多少个进程实例 (默认为 1)
directory=/opt/myapp       ; ★ 执行命令前切换到的工作目录
umask=022                  ; 进程的文件模式创建屏蔽码
priority=999               ; 进程启动/关闭优先级 (值越小越高,默认 999)
autostart=true             ; ★ supervisord 启动时自动启动
autorestart=true           ; ★ 进程退出时是否自动重启 (true, false, unexpected)
                           ; unexpected: 仅在退出码不是 exitcodes 定义的值时重启
startsecs=10               ; ★ 进程启动后持续运行多少秒才认为启动成功 (默认 1)
startretries=3             ; ★ 启动失败时的重试次数 (默认 3)
stopsignal=TERM            ; ★ 停止进程时发送的信号 (默认 TERM)
stopwaitsecs=10            ; ★ 发送停止信号后等待多少秒,超时则发送 KILL (默认 10)
stopasgroup=false          ; 是否向整个进程组发送停止信号 (默认 false)
killasgroup=false          ; stop 超时后,是否向整个进程组发送 KILL 信号 (默认 false)
user=www-data              ; ★ 以哪个用户身份运行进程 (重要!)
redirect_stderr=false      ; 是否将 stderr 重定向到 stdout (默认 false)
stdout_logfile=/var/log/supervisor/my_web_app_stdout.log ; ★ 标准输出日志
stdout_logfile_maxbytes=50MB ; ★ 日志文件最大大小 (默认 50MB, 到达后轮转)
stdout_logfile_backups=10  ; ★ 保留多少个轮转后的日志文件 (默认 10)
stdout_capture_maxbytes=1MB ; 捕获到内存用于事件通知的最大字节数
stdout_events_enabled=false ; 是否为 stdout 产生事件 (用于事件监听)
stderr_logfile=/var/log/supervisor/my_web_app_stderr.log ; ★ 标准错误日志
stderr_logfile_maxbytes=50MB ; 同上
stderr_logfile_backups=10  ; 同上
stderr_capture_maxbytes=1MB ; 同上
stderr_events_enabled=false ; 同上
environment=APP_ENV="production",SECRET_KEY="mysecret" ; ★ 设置环境变量 (键值对,逗号分隔)
serverurl=AUTO             ; 通常不需要设置,除非有特殊 RPC 需求

[group:x] 部分:

用于将多个 [program:y] 归为一个逻辑组,方便统一管理。

[group:my_app_cluster]
programs=my_web_app,my_worker ; program 名称列表,逗号分隔
priority=998                ; 组的优先级

之后可以用 supervisorctl start my_app_cluster:* 来启动组内所有程序。

[include] 部分:

允许你将配置分散到多个文件中,保持主配置文件的整洁。

[include]
files = /etc/supervisor/conf.d/*.conf /opt/myapp/supervisor_extra.conf

6. supervisorctl 命令行详解#

supervisorctl 是与 supervisord 交互的主要工具。

  • 进入交互模式:

    sudo supervisorctl
    # prompt> status
    # prompt> help
    # prompt> quit
    
  • 常用命令:

    • status [<name>]: 查看所有或指定程序的运行状态。
    • start <name|group:*>|all: 启动指定程序、组内所有程序或所有程序。
    • stop <name|group:*>|all: 停止指定程序、组内所有程序或所有程序。
    • restart <name|group:*>|all: 重启指定程序、组内所有程序或所有程序。
    • reread: 重新读取配置文件,检测是否有新增或删除的程序/组。不会影响正在运行的程序。
    • update [<gname> ...]: 应用 reread 后检测到的配置变更(启动新增的,停止删除的)。
    • reload: 重启 supervisord 进程本身(相当于 stop all + 重启 supervisord 服务 + start all)。通常用 reread + update 更好。
    • shutdown: 关闭 supervisord 服务(所有管理的进程也会被停止)。
    • pid [<name>]: 查看指定程序(或所有程序)的进程 ID。
    • tail [-f] <name> [stdout|stderr]: 查看指定程序的日志尾部 (-f 持续跟随)。
    • fg <name>: 将程序附加到前台运行 (主要用于调试,按 Ctrl+C 会停止进程)。
    • signal <signal_name> <name|group:*>|all: 向指定程序发送信号 (如 signal HUP my_web_app)。
    • maintail -f: 同时查看所有程序的 stdout 日志。

7. 实战教程:使用 Supervisor 管理 Web 应用 (以 Flask/Gunicorn 为例)#

假设我们有一个简单的 Flask 应用 (/opt/myflaskapp/app.py):

# /opt/myflaskapp/app.py
from flask import Flask
import os
import logging

app = Flask(__name__)

# 配置日志记录到标准输出,以便 Supervisor 捕获
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')

@app.route('/')
def hello():
    app.logger.info('Root endpoint was hit!')
    env = os.environ.get('APP_ENV', 'development')
    return f"Hello from Flask in {env} environment!"

if __name__ == '__main__':
    # 注意:直接运行 'python app.py' 不适用于生产环境
    # 我们将使用 Gunicorn 来运行它
    app.run(host='0.0.0.0', port=5000, debug=True)

目标: 使用 Gunicorn 作为 WSGI 服务器运行此应用,并由 Supervisor 管理。

步骤:

  1. 创建项目目录和虚拟环境:

    sudo mkdir -p /opt/myflaskapp
    sudo chown $USER:$USER /opt/myflaskapp # 临时更改所有权以便操作
    cd /opt/myflaskapp
    python3 -m venv venv
    source venv/bin/activate
    pip install Flask gunicorn
    deactivate
    sudo chown -R www-data:www-data /opt/myflaskapp # 将所有权给运行应用的用户 (假设是 www-data)
    
  2. 确定 Gunicorn 启动命令: 我们需要找到虚拟环境中的 gunicorn 可执行文件路径。通常是 /opt/myflaskapp/venv/bin/gunicorn。 启动命令类似: /opt/myflaskapp/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 app:app

    • --workers 3: 启动 3 个工作进程。
    • --bind 0.0.0.0:8000: 监听在 8000 端口。
    • app:app: 指定模块 app.py 中的 app 对象。
  3. 创建 Supervisor 配置文件 (/etc/supervisor/conf.d/myflaskapp.conf):

    [program:myflaskapp]
    command=/opt/myflaskapp/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 app:app
    directory=/opt/myflaskapp             ; ★ 必须设置,Gunicorn 需要找到 app.py
    user=www-data                         ; ★ 以 www-data 用户运行
    autostart=true
    autorestart=true
    stopsignal=QUIT                       ; Gunicorn 推荐使用 QUIT 实现优雅关闭
    stopwaitsecs=60                       ; 等待 Gunicorn worker 优雅退出的时间
    killasgroup=true                      ; ★ 重要:确保 Gunicorn 的 master 和 worker 都被正确关闭
    stdout_logfile=/var/log/supervisor/myflaskapp_stdout.log
    stderr_logfile=/var/log/supervisor/myflaskapp_stderr.log
    stdout_logfile_maxbytes=100MB
    stdout_logfile_backups=5
    stderr_logfile_maxbytes=100MB
    stderr_logfile_backups=5
    environment=APP_ENV="production"      ; ★ 设置环境变量
    
    • 关键点:
      • command 使用虚拟环境中的 gunicorn
      • directory 设置为项目根目录。
      • user 设置为安全的非 root 用户 (www-data 或其他)。
      • stopsignal=QUITkillasgroup=true 配合 Gunicorn 实现优雅重启/停止。
      • environment 用于传递配置。
      • 确保 /var/log/supervisor/ 目录存在且 www-data 用户有权写入日志文件,或者让 supervisord (通常以 root 运行) 创建它们。
  4. 加载配置并启动:

    sudo supervisorctl reread
    sudo supervisorctl update myflaskapp # 或者直接 update
    sudo supervisorctl status myflaskapp
    # 应显示 RUNNING
    
  5. 测试访问: 用 curl http://localhost:8000 或浏览器访问,应看到 “Hello from Flask in production environment!”。

  6. 查看日志:

    sudo tail -f /var/log/supervisor/myflaskapp_stdout.log
    sudo tail -f /var/log/supervisor/myflaskapp_stderr.log
    

现在,你的 Flask 应用就由 Supervisor 可靠地管理了!如果 Gunicorn 进程崩溃,Supervisor 会自动重启它。


8. 高级特性与技巧#

  • 进程组 (Process Groups): 如前所述,用 [group:x] 将多个 [program:y] 묶在一起管理。例如,一个 Web 应用可能需要一个 Web 服务器进程和一个后台任务 Worker 进程,可以将它们放在同一组中。

    [group:webapp]
    programs=webapp-server,webapp-worker
    priority=100
    
    [program:webapp-server]
    command=... (gunicorn command)
    ...
    
    [program:webapp-worker]
    command=... (celery worker command)
    ...
    

    管理:supervisorctl start webapp:*, supervisorctl stop webapp:*

  • 事件监听 (Event Listeners): Supervisor 可以监控进程状态变化(如 STARTING, RUNNING, STOPPING, EXITED)、心跳(TICK_5, TICK_60, TICK_3600)以及进程的 stdout/stderr 输出,并在事件发生时执行一个特定的程序(监听器)。 这对于实现自定义监控、告警或自动化任务非常有用。

    [eventlistener:mylogger]
    command=/usr/bin/python /opt/scripts/log_event.py ; 监听器脚本
    events=PROCESS_STATE,TICK_60                   ; 监听的事件类型
    autostart=true
    autorestart=true
    

    监听器脚本 (log_event.py 示例) 会从 stdin 读取事件信息,并在 stdout 写 READY 表示准备就绪,OKFAIL 表示处理结果。

    # /opt/scripts/log_event.py (简化示例)
    import sys
    import os
    
    def write_stdout(s):
        sys.stdout.write(s)
        sys.stdout.flush()
    
    def write_stderr(s):
        sys.stderr.write(s)
        sys.stderr.flush()
    
    def main():
        while True:
            write_stdout('READY\n') # 告诉 supervisord 我准备好了
            line = sys.stdin.readline() # 读取事件头信息
            headers = dict([ x.split(':') for x in line.split() ])
            data = sys.stdin.read(int(headers['len'])) # 读取事件体
    
            # 在这里处理事件 (例如记录到日志或发送通知)
            log_path = "/var/log/supervisor/events.log"
            with open(log_path, "a") as f:
                f.write(f"Header: {line}\n")
                f.write(f"Data: {data}\n---\n")
    
            write_stdout('RESULT 2\nOK') # 告诉 supervisord 处理成功
    
    if __name__ == '__main__':
        # 确保日志文件可写 (简单处理,生产环境应更健壮)
        log_dir = os.path.dirname("/var/log/supervisor/events.log")
        if not os.path.exists(log_dir):
            os.makedirs(log_dir) # 可能需要权限
    
        main()
    
  • XML-RPC 接口与 Web UI: 通过在主配置文件中启用 [inet_http_server],可以开启 Supervisor 的 HTTP 服务。

    • Web UI: 浏览器访问 http://<server_ip>:9001 (或配置的地址端口),可以看到一个简单的 Web 界面,用于查看和管理进程。建议设置用户名密码 (username, password) 并考虑防火墙或反向代理保护。
    • XML-RPC: 提供了编程接口,可以用 Python (或其他语言的库) 远程控制 Supervisor。
import xmlrpc.client

# 如果没有认证
# server = xmlrpc.client.ServerProxy('http://localhost:9001/RPC2')

# 如果有认证
server = xmlrpc.client.ServerProxy('http://user:123@localhost:9001/RPC2')

try:
    print("Supervisor version:", server.supervisor.getSupervisorVersion())
    print("All process info:", server.supervisor.getAllProcessInfo())
    # 启动进程
    # server.supervisor.startProcess('myflaskapp')
except xmlrpc.client.Fault as err:
    print("XML-RPC Fault:", err)
except Exception as e:
    print("Error:", e)
  • 日志管理与轮转: Supervisor 内建了简单的日志轮转功能 (stdout_logfile_maxbytes, stdout_logfile_backups)。对于更复杂的日志管理需求(如按日期轮转、压缩、发送到中心化日志系统),建议:

    1. 让应用程序自己处理日志轮转(如 Python 的 logging.handlers.RotatingFileHandlerTimedRotatingFileHandler)。
    2. 或者将 Supervisor 日志输出到 stdout/stderr (stdout_logfile=/dev/stdout, stderr_logfile=/dev/stderr),然后由外部工具(如 logrotate, Fluentd, Docker logging drivers)来管理。
  • 环境变量配置: environment=KEY1="value1",KEY2="value2" 是设置环境变量的标准方式。值可以包含空格,用引号括起来即可。对于大量或敏感变量,可以考虑:

    • 从文件加载:environment=PYTHONPATH="/opt/myapp/lib",%(ENV_VAR_FROM_SUPERVISORD)s (从 supervisord 进程继承)。
    • 或者在启动脚本中 source 一个 .env 文件。
  • 优雅停止与信号处理:

    • stopsignal: 定义了 supervisorctl stop 时发送给进程的信号。常见信号:
      • TERM: 通用终止信号,程序应捕获并优雅退出。
      • INT: 中断信号,类似 Ctrl+C。
      • QUIT: 类似 TERM,有时用于请求更优雅的退出(如 Gunicorn)。
      • HUP: 通常用于通知进程重新加载配置。
      • USR1, USR2: 用户自定义信号。
    • stopwaitsecs: 发送 stopsignal 后等待进程退出的时间。如果超时,Supervisor 会发送 KILL 信号强制终止。
    • stopasgroup=true: 如果你的主进程会创建子进程,并且希望停止时能同时停止所有子进程,可以设为 true(前提是主进程是进程组的领导者)。
    • killasgroup=true: 如果 stopwaitsecs 超时后发送 KILLkillasgroup=true 会将 KILL 信号发送给整个进程组。对于像 Gunicorn/uWSGI 这样的多进程模型,这通常是必要的,以确保所有 worker 都被清理。

9. 常见问题与故障排查 (Troubleshooting)#

  • supervisorctl 无法连接 (unix:///var/run/supervisor.sock no such file):

    • supervisord 服务是否正在运行?(ps aux | grep supervisord, systemctl status supervisor(d))
    • 配置文件中的 [unix_http_server][supervisorctl]file/serverurl 路径是否正确且一致?
    • socket 文件 (/var/run/supervisor.sock) 是否存在?权限是否正确 (运行 supervisorctl 的用户需要有读写权限)?
  • 程序状态为 FATALEXITED:

    • 检查程序的 stderr_logfile (sudo tail /var/log/supervisor/program_stderr.log),通常会包含错误信息。
    • 检查程序的 stdout_logfile
    • 尝试手动在终端中以配置文件中的 user 身份和 directory 运行 command,看是否能成功启动并保持运行。
    • 检查 startsecs 设置是否过短,程序可能需要更长时间才能稳定。
    • 检查系统资源是否耗尽(内存、文件描述符)。
    • 检查程序依赖是否正确安装(特别是虚拟环境路径)。
  • 配置文件修改后未生效:

    • 是否运行了 sudo supervisorctl reread
    • 是否运行了 sudo supervisorctl update 来应用变更?
    • 检查配置文件语法是否有误 (supervisord -n -c /etc/supervisord.conf 可以检查配置但不启动)。
  • 日志文件过大:

    • 检查 stdout_logfile_maxbytesstdout_logfile_backups (以及 stderr 的对应项) 配置是否正确。
    • 考虑使用外部日志轮转工具或让应用自己管理日志。
  • 权限问题:

    • 确保配置文件中指定的 user 有权限执行 command、读写 directory 以及写入日志文件。
    • 如果 Supervisor 以 root 运行,它通常能创建日志文件,但需要确保后续进程(以 user 身份运行)有权写入。可以预先创建日志文件并设置正确的所有权和权限。

10. Supervisor vs. Systemd vs. PM2 等#

  • Supervisor:

    • 优点: 简单易用,专注于应用进程管理,跨平台性较好(类 UNIX),配置直观,资源占用低。
    • 缺点: 不像 systemd 那样与 OS 深度集成(如 cgroups 资源限制、依赖管理、socket 激活等功能较弱),主要面向单个主机。
    • 适用场景: 管理单个服务器上的多个用户态应用程序进程,快速部署和管理后台任务、Web 服务。
  • Systemd:

    • 优点: Linux 系统的标准 init 系统,功能强大,与 OS 深度集成(日志管理 journald, cgroups 资源控制, socket 激活, 定时任务, 服务依赖等),非常稳定。
    • 缺点: 配置相对复杂,学习曲线陡峭,强绑定于使用 systemd 的 Linux 发行版。
    • 适用场景: 管理系统级服务和需要精细 OS 资源控制的应用,已成为现代 Linux 发行版的标准。
  • PM2:

    • 优点: 专门为 Node.js 应用设计,功能丰富(集群模式、负载均衡、零停机重启、监控面板、模块系统),开箱即用体验好。
    • 缺点: 主要面向 Node.js 生态,虽然也能管理其他类型进程,但不如 Supervisor 或 Systemd 通用。
    • 适用场景: Node.js 应用的生产环境部署和管理。
  • Docker/Kubernetes:

    • 优点: 提供容器化环境,隔离性强,易于打包和分发,支持跨主机集群管理、自动伸缩、服务发现等。
    • 缺点: 学习曲线更陡峭,资源消耗相对较高,引入了新的抽象层。
    • 适用场景: 微服务架构,需要标准化部署、弹性和跨云环境的复杂应用。

选择建议:

  • 如果只需要在单台 Linux 服务器上管理几个应用进程,且追求简单快捷,Supervisor 是个绝佳选择。
  • 如果你已经在使用现代 Linux 发行版,并且需要更强大的 OS 集成功能或管理系统级服务,学习并使用 Systemd 是值得的。
  • 如果你主要开发和部署 Node.js 应用,PM2 提供了量身定做的优秀体验。
  • 如果你的应用需要部署在 容器 环境或 集群 中,DockerKubernetes 是更现代化的解决方案(注意:Supervisor 也可以在 Docker 容器 内部 使用,用于管理容器内的多个进程,但这通常被认为是一种反模式,推荐一个容器只运行一个主进程)。

11. 总结与最佳实践#

Supervisor 是一个成熟、可靠且易于使用的进程控制系统,极大地简化了类 UNIX 系统上应用程序进程的管理。它通过自动启动、监控和重启,确保了服务的可用性。

最佳实践回顾:

  • 使用非 Root 用户: 在 [program:x] 中始终指定 user 为一个权限受限的用户。
  • 明确工作目录: 使用 directory 指定命令执行的上下文。
  • 管理日志: 合理配置日志路径、大小和轮转 (stdout_logfile, stderr_logfile, ...maxbytes, ...backups),或结合外部日志工具。
  • 优雅关闭: 理解并配置好 stopsignal, stopwaitsecs, stopasgroup, killasgroup,特别是对于多进程应用。
  • 模块化配置: 使用 [include]conf.d 目录组织配置文件。
  • 环境变量: 使用 environment 传递配置,避免硬编码。
  • 监控与告警: 利用 supervisorctl status、日志、Web UI 或事件监听器进行监控。
  • 安全: 如果启用 inet_http_server,务必设置认证并使用防火墙或反向代理保护。unix_http_server 的 socket 文件权限也要注意。
  • 理解 autorestart: unexpected 选项通常比 true 更安全,避免程序在正常退出(如完成任务)后被无限重启。
  • rereadupdate: 掌握这两个命令,安全地更新配置。

掌握 Supervisor,无疑会让你在应用部署和运维管理上更加得心应手。希望这篇从入门到精通的指南能帮助你充分利用 Supervisor 的强大功能!

Supervisor 从入门到精通
https://bangwu.top/posts/supervisor/
作者
棒无
发布于
2025-04-16
许可协议
CC BY-NC-SA 4.0