当前位置:   article > 正文

详解supervisor配置与案例_supervisor ini

supervisor ini

1.安装 supervisor

yum install -y epel-release 
yum install -y supervisor
  • 1
  • 2

2.修改supervisord的配置文件,引入任务文件

默认配置文件:/etc/supervisord.conf
进程管理配置文件放到:/etc/supervisord.d/目录下即可

# 修改 /etc/supervisord.conf 的include下的files = supervisord.d/*.ini修改为自己项目的地址

[include]
files = /www/business_district/tasks/supervisor/*.ini
  • 1
  • 2
  • 3
  • 4

3.celery实例

[program:celery-work]
; 运行的命令
command = celery -A tasks.celery.celery_task worker -l INFO --logfile=./tasks/celery/work.log
; 命令执行的目录
directory = /www/business_district
; 是否自动启动
autostart = true
; 程序意外退出是否自动重启
autorestart = true
; 自动重启间隔
startsecs = 10
; 进程执行的用户身份
user = root

[program:celery-beat]
; 运行的命令
command = celery -A tasks.celery.celery_task beat -l INFO --logfile=./tasks/celery/beat.log -s ./tasks/celery/celerybeat-schedule
; 命令执行的目录
directory = /www/business_district
; 是否自动启动
autostart = true
; 程序意外退出是否自动重启
autorestart = true
; 自动重启间隔
startsecs = 10
; 进程执行的用户身份
user = root
  • 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

3.设置开机启动

systemctl enable supervisord
  • 1

4.supervisord 常用命令

命令说明
supervisord启动
supervisorctl status查看进程运行状态
supervisorctl start 进程名启动进程
supervisorctl stop 进程名关闭进程
supervisorctl restart 进程名重启进程
supervisorctl update重新载入配置文件
supervisorctl shutdown关闭supervisord
supervisorctl clear 进程名清空进程日志
supervisorctl进入到交互模式下。使用help查看所有命令。
supervisorctl restart all重启全部进程
supervisorctl start all开启全部进程
supervisorctl stop all关闭全部进程
supervisorctl status查看supervisorctl 状态

5.supervisord 基础

5.1 Supervisor简单介绍

​ supervisor是一个 Client/Server模式的系统,允许用户在类unix操作系统上监视和控制多个进程,或者可以说是多个程序。supervisor与launchd,daemontools,runit等程序有着相同的功能,与其中某些程序不同的是,它并不作为“id 为 1的进程”而替代init。相反,它用于控制应用程序,像启动其它程序一样,通俗理解就是,把Supervisor服务管理的进程程序,它们作为supervisor的子进程来运行,而supervisor是父进程。supervisor来监控管理子进程的启动关闭和异常退出后的自动启动。

​ 至于为什么要用supervisor来管理进程,是因为相对于linux传统的进程管理(即系统自带的init 进程管理)方式来说,它有很多的优势:

5.1.1 简单方便

​ 通常管理linux进程的时候,一般来说都需要自己编写一个能够实现进程start/stop/restart/reload功能的脚本,然后丢到/etc/init.d/下面。其实这么做有很多不好的地方:

  • 编写这个脚本,耗时耗力。
  • 当这个进程挂掉的时候,linux不会自动重启它的,想要自动重启的话,还要自己另外写一个监控重启脚本。

​ supervisor则可以完美的解决上面这那两个问题! 那么supervisor怎么解决呢?

​ supervisor管理进程,就是通过fork/exec的方式把这些被管理的进程,当作supervisor的子进程来启动。这样的话,只要在supervisor的配置文件中,把要管理的进程的可执行文件的路径写进去就OK了。这样就省下了自己写脚本管理linux进程的麻烦了。
​ 被管理进程作为supervisor的子进程,当子进程挂掉的时候,父进程可以准确获取子进程挂掉的信息的,所以也就可以对挂掉的子进程进行自动重启了, 至于重启还是不重启,也要看配置文件里面有没有设置autostart=true。

5.1.2 精确

​ linux对进程状态的反馈有时候不太准确, 也就是说linux进程通常很难获得准确的up/down状态, Pidfiles经常说谎! 而supervisor监控子进程,得到的子进程状态无疑是准确的。supervisord将进程作为子进程启动,所以它总是知道其子进程的正确的up/down状态,可以方便的对这些数据进行查询.

5.1.3 进程分组

​ 进程支持分组启动和停止,也支持启动顺序,即‘优先级’,supervisor允许为进程分配优先级,并允许用户通过supervisorctl客户端发出命令,如“全部启动”和”重新启动所有“,它们以预先分配的优先级顺序启动。还可以将进程分为”进程组“,一组逻辑关联的进程可以作为一个单元停止或启动。进程组supervisor可以对进程组统一管理,也就是说我们可以把需要管理的进程写到一个组里面,然后把这个组作为一个对象进行管理,如启动,停止,重启等等操作。而linux系统则是没有这种功能的,想要停止一个进程,只能一个一个的去停止,要么就自己写个脚本去批量停止。

5.1.4 集中式管理

​ supervisor管理的进程,进程组信息,全部都写在一个ini格式的文件里就OK了。管理supervisor时, 可以在本地进行管理,也可以远程管理,而且supervisor提供了一个web界面,可以在web界面上监控,管理进程。 当然了,本地,远程和web管理的时候,需要调用supervisor的xml_rpc接口。

5.1.5 可扩展性

​ supervisor有一个简单的事件(event)通知协议,还有一个用于控制的XML-RPC接口,可以用Python开发人员来扩展构建。

5.1.6 权限

​ 总所周知, linux的进程特别是侦听在1024端口之下的进程,一般用户大多数情况下,是不能对其进行控制的。想要控制的话,必须要有root权限。然而supervisor提供了一个功能,可以为supervisord或者每个子进程,设置一个非root的user,这个user就可以管理它对应的进程了。

5.1.7 兼容性,稳定性

​ supervisor由Python编写,在除Windows操作系统以外基本都支持,如linux,Mac OS x,solaris,FreeBSD系统

5.2 Supervisor组成部分

5.2.1 supervisord: 服务守护进程

​ supervisor服务器的进程名是supervisord。它主要负责在自己的调用中启动子程序,响应客户端的命令,重新启动崩溃或退出的进程,记录其子进程stdout和stderr的输出,以及生成和处理对应于子进程生命周期中的"event"服务器进程使用的配置文件,通常路径存放在/etc/supervisord.confa中。此配置文件是INI格式的配置文件。

5.2.2 supervisorctl:命令行客户端

​ supervisor命令行的客户端名称是supervisorctl。它为supervisord提供了一个类似于shell的交互界面。使用supervisorctl,用户可以查看不同的supervisord进程列表,获取控制子进程的状态,如停止和启动子进程

5.2.3 Web Server:提供与功能相当的WEB操作界面

​ 一个可以通过Web界面来查看和控制进程的状态,默认监听在9091上。

5.2.4 XML-RPC Interface:XML-RPC接口

​ supervisor用于控制的XML-RPC接口

5.3 配置文件

[unix_http_server]
;socket文件的路径,supervisorctl用XML_RPC和supervisord通信就是通过它进行的。如果不设置的话,supervisorctl也就不能用了不设置的话,默认为none。
file = /tmp/supervisor.sock 
; 这个简单,就是修改上面的那个socket文件的权限为0700,不设置的话,默认为0700。 非必须设置
chmod = 0700
; 这个一样,修改上面的那个socket文件的属组为user.group,不设置的话,默认为启动supervisord进程的用户及属组,非必须设置
chown = nobody:nogroup    
; 使用supervisorctl连接的时候,认证的用户,不设置的话,默认为不需要用户,非必须设置
username = user默认不设置,非必须设置
; 和上面的用户名对应的密码,可以直接使用明码,也可以使用SHA加密,如:{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d,默认不设置,非必须设置
password = 123

; 侦听在TCP上的socket,Web Server和远程的supervisorctl都要用到,他不设置的话,默认为不开启,非必须设置
[inet_http_server]     
; 这个是侦听的IP和端口,侦听所有IP用 :9001或*:9001。这个必须设置,只要上面的[inet_http_server]开启了,就必须设置它
port = 127.0.0.1:9001    
; 这个和上面的uinx_http_server一个样。非必须设置
username = user
; 这个也一个样。非必须设置
password = 123        

;这个主要是定义supervisord这个服务端进程的一些参数的,这个必须设置,不设置,supervisor就不用干活了
[supervisord]
; 这个是supervisord这个主进程的日志路径,注意和子进程的日志不搭嘎。默认路径$CWD /supervisord.log,$CWD是当前目录,非必须设置
logfile = ``/tmp/supervisord``.log 
; 这个是上面那个日志文件的最大的大小,当超过50M的时候,会生成一个新的日志文件。当设置为0时,表示不限制文件大小默认值是50M,非必须设置
logfile_maxbytes = 50MB    
; 日志文件保持的数量,上面的日志文件大于50M时,就会生成一个新文件。文件数量大于10时,最初的老文件被新文件覆盖,文件数量将保持为10当设置为0时,表示不限制文件的数量。默认情况下为10,非必须设置
logfile_backups = 10
; 日志级别,有critical, error, warn, info, debug, trace, or blather等,默认为info,非必须设置项
loglevel = info        
; supervisord的pid文件路径。默认为$CWD /supervisord.pid 非必须设置
pidfile = /tmp/supervisord.pid 
; 如果是true,supervisord进程将在前台运行,默认为 false,也就是后台以守护进程运行,非必须设置
nodaemon = false
; 这个是最少系统空闲的文件描述符,低于这个值supervisor将不会启动。系统的文件描述符在这里设置cat /proc/sys/fs/file-max,默认情况下为1024,非必须设置
minfds = 1024         
; 最小可用的进程描述符,低于这个值supervisor也将不会正常启动。ulimit -u这个命令,可以查看linux下面用户的最大进程数,默认为200,非必须设置
minprocs = 200         
; 进程创建文件的掩码,默认为022,非必须设置项
umask = 022
;这个参数可以设置一个非root用户,当我们以root用户启动supervisord之后。我这里面设置的这个用户,也可以对supervisord进行管理,默认情况是不设置,非必须设置项
user=chrism   
;这个参数是supervisord的标识符,主要是给XML_RPC用的,当你有多个supervisor的时候,而且想调用XML_RPC统一管理,就需要为每个supervisor设置不同的标识符了默认是supervisord,非必需设置
identifier = supervisor   
; 这个参数是当supervisord作为守护进程运行的时候,设置这个参数的话,启动supervisord进程之前,会先切换到这个目录默认不设置,非必须设置
directory = /tmp
; 这个参数当为false的时候,会在supervisord进程启动的时候,把以前子进程产生的日志文件(路径为AUTO的情况下)清除掉。有时候咱们想要看历史日志,当然不想日志被清除了。所以可以设置为true默认是false,有调试需求的同学可以设置为true,非必须设置
nocleanup = true
; 当子进程日志路径为AUTO的时候,子进程日志文件的存放路径。默认路径是这个东西,执行下面的这个命令看看就OK了,处理的东西就默认路径python -c "import tempfile;print tempfile.gettempdir()"非必须设置
childlogdir = /tmp
; 这个是用来设置环境变量的,supervisord在linux中启动默认继承了linux的supervisord启动子进程时,子进程会拷贝父进程的内存空间内容。 所以设置的这些环境变量也会被子进程继承。小例子:environment=name="haha",age="hehe"默认为不设置,非必须设置
environment = 'KEY=value'
; 这个选项如果设置为true,会清除子进程日志中的所有ANSI 序列。什么是ANSI序列呢?就是我们的\n\t这些东西。默认为false,非必须设置
strip_ansi = false

; 这个选项是给XML_RPC用的,当然你如果想使用supervisord或者web server 这个选项必须要开启的
[rpcinterface:supervisor]  
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
 
; 这个主要是针对supervisorctl的一些配置
[supervisorctl]
; 默认值就是unix:///tmp/supervisor.sock,非必须设置,这个是supervisorctl本地连接supervisord的时候,本地UNIX socket路径,注意这个是和前面的[unix_http_server]对应的默认值就是unix:///tmp/supervisor.sock,非必须设置
serverurl = unix:///tmp/supervisor.sock 
; 这个是supervisorctl远程连接supervisord的时候,用到的TCP socket路径注意这个和前面的[inet_http_server]对应默认就是http://127.0.0.1:9001,非必须项
serverurl = http://127.0.0.1:9001 
; 用户名,默认空,非必须设置
username=chris       
; 密码,默认空,非必须设置
password=123        
; 输入用户名密码时候的提示符,默认supervisor,非必须设置
prompt=mysupervisor,
; 这个参数和shell中的history类似,我们可以用上下键来查找前面执行过的命令,默认是no,file的,所以我们想要有这种功能,必须指定一个文件,非必须设置
history_file=~/.sc_history 

;这个就是咱们要管理的子进程了,":"后面的是名字,最好别乱写和实际进程有点关联最好。这样的program我们可以设置一个或多个,一个program就是要被管理的一个进程
[program:theprogramname]   
; 这个就是我们的要启动进程的命令路径了,可以带参数例子:/home/test.py -a 'hehe'有一点需要注意的是,我们的command只能是那种在终端运行的进程,不能是守护进程。这个想想也知道了,比如说command = service httpd start。httpd这个进程被linux的service管理了,我们的supervisor再去启动这个命令这已经不是严格意义的子进程了。这个是个必须设置的项
command=`/bin/cat`
; 这个是进程名,如果我们下面的numprocs参数为1的话,就不用管这个参数了,它默认值%(program_name)s也就是上面的那个program冒号后面的名字,但是如果numprocs为多个的话,那就不能这么干了。想想也知道,不可能每个进程都用同一个进程名吧。
process_name=%(program_name)s 
; 启动进程的数目。当不为1时,就是进程池的概念,注意process_name的设置默认为1,非必须设置
numprocs=1          
; 进程运行前,会前切换到这个目录默认不设置,非必须设置
directory=`/tmp`
; 进程掩码,默认none,非必须
umask=022
; 子进程启动关闭优先级,优先级低的,最先启动,关闭的时候最后关闭默认值为999,非必须设置
priority=999
; 如果是true的话,子进程将在supervisord启动后被自动启动默认就是true,非必须设置
autostart=`true`
; 这个是设置子进程挂掉后自动重启的情况,有三个选项,false,unexpected和true。如果为false的时候,无论什么情况下,都不会被重新启动,如果为unexpected,只有当进程的退出码不在下面的exitcodes里面定义的退出码的时候,才会被自动重启。当为true的时候,只要子进程挂掉,将会被无条件的重启
autorestart=unexpected    
; 这个选项是子进程启动多少秒之后,此时状态如果是running,则我们认为启动成功了默认值为1,非必须设置
startsecs=1   
; 当进程启动失败后,最大尝试启动的次数。。当超过3次后,supervisor将把此进程的状态置为FAIL默认值为3,非必须设置
startretries=3     
 ; 注意和上面的的autorestart=unexpected对应,exitcodes里面的定义的退出码是expected的。
exitcodes=0,2    
 ; 进程停止信号,可以为TERM, HUP, INT, QUIT, KILL, USR1, or USR2等信号默认为TERM,当用设定的信号去干掉进程,退出码会被认为是expected非必须设置
stopsignal=QUIT  
; 这个是当我们向子进程发送stopsignal信号后,到系统返回信息给supervisord,所等待的最大时间。 超过这个时间,supervisord会向该子进程发送一个强制kill的信号。默认为10秒,非必须设置
stopwaitsecs=10       
 ; 这个东西主要用于,supervisord管理的子进程,这个子进程本身还有子进程。那么我们如果仅仅干掉supervisord的子进程的话,子进程的子进程有可能会变成孤儿进程。所以咱们可以设置可个选项,把整个该子进程的整个进程组都干掉。 设置为true的话,一般killasgroup也会被设置为true。需要注意的是,该选项发送的是stop信号默认为false,非必须设置
stopasgroup=`false`      
; 这个和上面的stopasgroup类似,不过发送的是kill信号
killasgroup=`false`     
; 如果supervisord是root启动,我们在这里设置这个非root用户,可以用来管理该program默认不设置,非必须设置项
user=chrism          
; 如果为true,则stderr的日志会被写入stdout日志文件中默认为false,非必须设置
redirect_stderr=`true`    
; 子进程的stdout的日志路径,可以指定路径,AUTO,none等三个选项,设置为none的话,将没有日志产生。设置为AUTO的话,将随机找一个地方生成日志文件,而且当supervisord重新启动的时候,以前的日志文件会被清空。当 redirect_stderr=true的时候,sterr也会写进这个日志文件
stdout_logfile=`/a/path`    
; 日志文件最大大小,和[supervisord]中定义的一样。默认为50
stdout_logfile_maxbytes=1MB 
 ; 和[supervisord]定义的一样。默认10
stdout_logfile_backups=10  
; 这个东西是设定capture管道的大小,当值不为0的时候,子进程可以从stdout发送信息,而supervisor可以根据信息,发送相应的event。默认为0,为0的时候表达关闭管道,非必须项
stdout_capture_maxbytes=1MB  
; 当设置为ture的时候,当子进程由stdout向文件描述符中写日志的时候,将触发supervisord发送PROCESS_LOG_STDOUT类型的event默认为false,非必须设置
stdout_events_enabled=`false`
; 这个东西是设置stderr写的日志路径,当redirect_stderr=true。这个就不用设置了,设置了也是白搭。因为它会被写入stdout_logfile的同一个文件中默认为AUTO,也就是随便找个地存,supervisord重启被清空,非必须设置
stderr_logfile=`/a/path` 
; 这个出现好几次了,就不重复了
stderr_logfile_maxbytes=1MB  
; 这个也是
stderr_logfile_backups=10   
; 这个一样,和stdout_capture一样。 默认为0,关闭状态
stderr_capture_maxbytes=1MB  
; 这个也是一样,默认为false
stderr_events_enabled=`false`  `
; 这个是该子进程的环境变量,和别的子进程是不共享的
environment=A="1",B="2"
;这个是supervisorctl本地连接supervisord的时候,本地UNIX socket路径,注意这个是和前面的[unix_http_server]对应的默认值就是unix:///tmp/supervisor.sock。。非必须设置
serverurl=AUTO 


;这个东西其实和program的地位是一样的,也是suopervisor启动的子进程,不过它干的活是订阅supervisord发送的event。他的名字就叫listener了。我们可以在listener里面做一系列处理,比如报警等等楼主这两天干的活,就是弄的这玩意
[eventlistener:theeventlistenername] 
; 这个和上面的program一样,表示listener的可执行文件的路径
comma=/bin/eventlistener
; 这个也一样,进程名,当下面的numprocs为多个的时候,才需要。否则默认就OK了
process_name=%(program_name)s 
; 相同的listener启动的个数
numprocs=1          
 ; event事件的类型,也就是说,只有写在这个地方的事件类型。才会被发送
events=EVENT        
; 这个是event队列缓存大小,单位不太清楚,楼主猜测应该是个吧。当buffer超过10的时候,最旧的event将会被清除,并把新的event放进去。默认值为10,非必须选项
buffer_size=10        
; 进程执行前,会切换到这个目录下执行默认为不切换,非必须
directory=`/tmp`
; 淹没,默认为none,不说了uma=022
; 启动优先级,默认-1,也不扯了
priority=-1
; 是否随supervisord启动一起启动,true
autostar=true
 ; 是否自动重启,和program一个样tr,false,,unexpected等,注意unexpected和exitcodes的关系
autorestart=unexpected   
; 也是一样,进程启动后跑了几秒钟,才被认定为成功启动,默认1
startsecs=1   
; 失败最大尝试次数,默认3
startretries=3        
; 期望或者说预料中的进程退出码
exitcodes=0,2         
 ; 干掉进程的信号,默认为TERM,比如设置为QUIT,那么如果QUIT来干这个进程那么会被认为是正常维护,退出码也被认为是expected中的
stopsignal=QUIT       
 ; max num secs to wait b4 SIGKILL (default 10)
stopwaitsecs=10       
; send stop signal to the UNIX process group (default ``false``)
stopasgroup=``false`       `
; SIGKILL the UNIX process group (def ``false``)
killasgroup=``false`       `
; 设置普通用户,可以用来管理该listener进程。默认为空。。非必须设置
user=chrism         
; 为true的话,stderr的log会并入stdout的log里面默认为false,非必须设置
redirect_stderr=`true`
; 这个不说了,好几遍了
stdout_logfile=`/a/path`
; 这个也是
stdout_logfile_maxbytes=1MB  
; 这个也是
stdout_logfile_backups=10   
; 这个其实是错的,listener是不能发送event
stdout_events_enabled=`false`
; 这个也是
stderr_logfile=`/a/path`
; 这个也是
stderr_logfile_maxbytes=1MB  
 ; 这个不说了
stderr_logfile_backups   
; 这个也是错的,listener不能发送event
stderr_events_enabled=`false`
; 这个是该子进程的环境变量默认为空,非必须设置
environment=A="1",B="2"
; override serverurl computation (childutils)  
serverurl=AUTO        
 

; 这个东西就是给programs分组,划分到组里面的program。我们就不用一个一个去操作了我们可以对组名进行统一的操作。 注意:program被划分到组里面之后,就相当于原来的配置从supervisor的配置文件里消失了,supervisor只会对组进行管理,而不再会对组里面的单个program进行管理了
[group:thegroupname]  
; 组成员,用逗号分开这个是个必须的设置项
programs=progname1,progname2 
; 优先级,相对于组和组之间说的默认999,非必须选项
priority=999         

;这个东西挺有用的,当我们要管理的进程很多的时候,写在一个文件里面就有点大了。我们可以把配置信息写到多个文件中,然后include过来
[include]            
files = relative/directory/*.ini
  • 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
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/409651
推荐阅读
相关标签
  

闽ICP备14008679号