0%

使用 supervisor 来管理 WSL 上的服务并实现开机自启动

WSL 是 Windows 下的 Linux 子系统,通过兼容层实现在 Windows 平台上模拟 Linux 环境,不像 WSL2 拥有完整的 Linux 内核。这使得很多依赖内核机制的应用都无法使用,比如说 system-init 和 docker。没有 system-init 支持之后常用的服务管理工具 systemctl 也就失效了,虽然可以通过 service 命令来操作,但是遇到需要自定义服务的时候,service 就显得有些简陋了:需要手写较长的脚本配置外加不支持自动重启等。此时就需要 supervisor 了,基于子进程的服务管理方式不需要内核支持,能够在 WSL 环境很好的工作

相关细节

supervisor 可以通过系统自带包管理器安装,又因为是 python 编写,支持通过 pip 安装。但是两者安装的版本上有一些区别。

  • 通过包管理器安装的 supervisor 会向 /etc/init.d 目录释放用于 service 命令管理的配置文件(其实就是个脚本文件,所以说有点麻烦写),但是它安装的 supervisor 所依赖的 python 版本会根据当前系统的初始版本变化。比如我这里使用的是 Ubuntu 系统,系统默认为 python2 ,虽然我已经手动切换到了 python 3,但是所安装的 supervisor 依然是基于 python2 版本(这个依赖应该是由 Ubuntu 的包所决定的,大概制作包的时候就是以 python2 为基准制作的吧,并没有根据环境自动选择),于是在安装 python2 版本的 supervisor 而运行环境为 python3 时,运行 supervisor 就会出现如下错误,找不到对应包:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Traceback (most recent call last):
    File "/usr/bin/supervisord", line 6, in <module>
    from pkg_resources import load_entry_point
    File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 3088, in <module>
    @_call_aside
    File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 3072, in _call_aside
    f(*args, **kwargs)
    File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 3101, in _initialize_master_working_set
    working_set = WorkingSet._build_master()
    File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 574, in _build_master
    ws.require(__requires__)
    File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 892, in require
    needed = self.resolve(parse_requirements(requirements))
    File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 778, in resolve
    raise DistributionNotFound(req, requirers)
    pkg_resources.DistributionNotFound: The 'supervisor==3.3.1' distribution was not found and is required by the application
  • 通过 pip 安装的 supervisor 能够正常匹配所使用的 python 版本(废话),但是
    • 使用当前用户安装时程序会被放置到家目录的 .local/lib/python*/site-packages/ 目录下,虽然能够调用但是不是常规位置,不便于管理
    • 使用 sudo 或 root 权限安装时会安装到 /usr/local/lib/ 目录下,可执行文件在 /usr/local/bin/ 目录中,比较正常

最终方案

配置安装 supervisor 并使其支持通过 service 命令控制启停,然后在 Windows 下设置开机时启动 supervisor 进行,借此启动注册在 supervisor 下的各种服务

  • 使用默认 python 版本的话可以直接从包管理器安装 supervisor,之后通过以下命令来启动进程,相关配置文件在 /etc/supervisor/ 下
    1
    sudo service supervisor start

配置步骤

安装 supervisor 并配置

  • 使用 python3 但系统默认版本为 python2 的情况,使用 pip 进行全局安装,但配置文件使用包管理器自带版本,即 /etc/init.d/ 目录下相关服务管理脚本与 /etc/supervisor/ 配置文件目录。这里为了部署方便我会直接贴出从包管理器安装版本提取的服务管理脚本以及 supervisor 配置文件。以下为部署步骤:
    • 使用 pip 全局安装 supervisor
      1
      sudo pip install supervisor
    • 创建服务管理脚本
      1
      sudo vi /etc/init.d/supervisor
      以下为脚本内容,已修改程序路径
      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
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      #! /bin/sh
      #
      # skeleton example file to build /etc/init.d/ scripts.
      # This file should be used to construct scripts for /etc/init.d.
      #
      # Written by Miquel van Smoorenburg <[email protected]>.
      # Modified for Debian
      # by Ian Murdock <[email protected]>.
      # Further changes by Javier Fernandez-Sanguino <[email protected]>
      #
      # Version: @(#)skeleton 1.9 26-Feb-2001 [email protected]
      #
      ### BEGIN INIT INFO
      # Provides: supervisor
      # Required-Start: $remote_fs $network $named
      # Required-Stop: $remote_fs $network $named
      # Default-Start: 2 3 4 5
      # Default-Stop: 0 1 6
      # Short-Description: Start/stop supervisor
      # Description: Start/stop supervisor daemon and its configured
      # subprocesses.
      ### END INIT INFO

      . /lib/lsb/init-functions

      PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
      DAEMON=/usr/local/bin/supervisord
      NAME=supervisord
      DESC=supervisor

      test -x $DAEMON || exit 0

      RETRY=TERM/30/KILL/5
      LOGDIR=/var/log/supervisor
      PIDFILE=/var/run/$NAME.pid
      DODTIME=5 # Time to wait for the server to die, in seconds
      # If this value is set too low you might not
      # let some servers to die gracefully and
      # 'restart' will not work

      # Include supervisor defaults if available
      if [ -f /etc/default/supervisor ] ; then
      . /etc/default/supervisor
      fi
      DAEMON_OPTS="-c /etc/supervisor/supervisord.conf $DAEMON_OPTS"

      set -e

      running_pid()
      {
      # Check if a given process pid's cmdline matches a given name
      pid=$1
      name=$2
      [ -z "$pid" ] && return 1
      [ ! -d /proc/$pid ] && return 1
      (cat /proc/$pid/cmdline | tr "\000" "\n"|grep -q $name) || return 1
      return 0
      }

      running()
      {
      # Check if the process is running looking at /proc
      # (works for all users)

      # No pidfile, probably no daemon present
      [ ! -f "$PIDFILE" ] && return 1
      # Obtain the pid and check it against the binary name
      pid=`cat $PIDFILE`
      running_pid $pid $DAEMON || return 1
      return 0
      }

      case "$1" in
      start)
      echo -n "Starting $DESC: "
      start-stop-daemon --start --quiet --pidfile $PIDFILE \
      --startas $DAEMON -- $DAEMON_OPTS
      test -f $PIDFILE || sleep 1
      if running ; then
      echo "$NAME."
      else
      echo " ERROR."
      fi
      ;;
      stop)
      echo -n "Stopping $DESC: "
      # [ -f $PIDFILE ] && killproc $prog || success $"$prog shutdown"
      start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
      echo "$NAME."
      ;;
      #reload)
      #
      # If the daemon can reload its config files on the fly
      # for example by sending it SIGHUP, do it here.
      #
      # If the daemon responds to changes in its config file
      # directly anyway, make this a do-nothing entry.
      #
      # echo "Reloading $DESC configuration files."
      # start-stop-daemon --stop --signal 1 --quiet --pidfile \
      # /var/run/$NAME.pid --exec $DAEMON
      #;;
      force-reload)
      #
      # If the "reload" option is implemented, move the "force-reload"
      # option to the "reload" entry above. If not, "force-reload" is
      # just the same as "restart" except that it does nothing if the
      # daemon isn't already running.
      # check wether $DAEMON is running. If so, restart
      start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
      --startas $DAEMON \
      && $0 restart \
      || exit 0
      ;;
      restart)
      echo -n "Restarting $DESC: "
      start-stop-daemon --stop --retry=$RETRY --quiet --oknodo --pidfile $PIDFILE
      echo "$NAME."
      ;;
      status)
      echo -n "$NAME is "
      if running ; then
      echo "running"
      else
      echo " not running."
      exit 1
      fi
      ;;
      *)
      N=/etc/init.d/$NAME
      # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
      echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
      exit 1
      ;;
      esac

      exit 0
    • 创建配置目录
      1
      2
      3
      sudo mkdir /etc/supervisor
      sudo mkdir /etc/supervisor/conf.d
      sudo vi /etc/supervisor/supervisord.conf
      以下为脚本内容
      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
      ; supervisor config file

      [unix_http_server]
      file=/var/run/supervisor.sock ; (the path to the socket file)
      chmod=0700 ; sockef file mode (default 0700)

      [supervisord]
      logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
      pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
      childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)

      ; the below 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: sections
      [rpcinterface:supervisor]
      supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

      [supervisorctl]
      serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket

      ; 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 = /etc/supervisor/conf.d/*.conf
    • 至此配置结束,启动服务的命令为
      1
      sudo service supervisor start

设置自启动

  • 使用 Windows + R 快捷键调出运行窗口,输入
    1
    shell:startup
  • 在弹出的文件目录下创建一个快捷方式,名称任意,其执行命令设为
    1
    C:\Windows\System32\wsl.exe -u root service supervisor start

至此所有的操作就完成了,向 supervisor 配置的各种服务也能够正常运作,具体的配置方式可以查阅官方文档。

Welcome to my other publishing channels