0%

Python-部署运维

用于 Python 程序的的相关部署操作的模块。如:

  • uvicorn:基于 uvloop 与 httptools 构建的非常快速的 ASGI 服务器
  • Gunicorn:主流的、高性能的 Python WSGI UNIX HTTP 服务器
  • Paramiko:自动化运维,基于 Python 实现的 SSH2 远程连接、认证,进行远程命令、文件传输、中间 SSH 代码等功能。
  • fabric:简化系统管理任务,基于 Python 实现的 SSH 命令行工具
  • supervisor:基于 Python 的强大运维管理工具

概述

说明

web 框架:致力于如何生成 HTML 代码

web 服务器:用于处理和响应 HTTP 请求

WSGI:统一 web 框架与 web 服务器之间的接口。

CGI:通用网关接口,连接 web 服务器和应用程序的接口,用户通过 CGI 来获取动态数据或文件等。可以用几乎所有语言来写,包括 perl,c,lua,python 等等。

WSGI:Web Server Gateway Interface:web 服务网关接口,一种 web 服务器与应用程序/框架间的通用接口标准

uwsgi:通信协议,与 WSGI 是两种东西,该协议下速度比较快

uWSGI:Web Server,独占 uwsgi 协议,同时支持 WSGI 协议、HTTP 协方式等,它的功能是将 HTTP 协议转化成语言支持的网络协议供 python 使用。

uvicorn

uvicorn:基于 uvloop 与 httptools 构建的非常快速的 ASGI 服务器

  • uvloop:使用 Cython 实现的,比 asyncio 快 2-4 倍的事件循环工具
  • httptools:nodejs HTTP 解析器的 Python3 实现
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
# option
Usage: uvicorn [OPTIONS] APP

Options:
--host TEXT Bind socket to this host. [default:
127.0.0.1]
--port INTEGER Bind socket to this port. [default: 8000]
--uds TEXT Bind to a UNIX domain socket.
--fd INTEGER Bind to socket from this file descriptor.
--reload Enable auto-reload.
--reload-dir TEXT Set reload directories explicitly, instead
of using the current working directory.
--workers INTEGER Number of worker processes. Defaults to the
$WEB_CONCURRENCY environment variable if
available. Not valid with --reload.
--loop [auto|asyncio|uvloop|iocp]
Event loop implementation. [default: auto]
--http [auto|h11|httptools] HTTP protocol implementation. [default:
auto]
--ws [auto|none|websockets|wsproto]
WebSocket protocol implementation.
[default: auto]
--lifespan [auto|on|off] Lifespan implementation. [default: auto]
--interface [auto|asgi3|asgi2|wsgi]
Select ASGI3, ASGI2, or WSGI as the
application interface. [default: auto]
--env-file PATH Environment configuration file.
--log-config PATH Logging configuration file.
--log-level [critical|error|warning|info|debug|trace]
Log level. [default: info]
--access-log / --no-access-log Enable/Disable access log.
--use-colors / --no-use-colors Enable/Disable colorized logging.
--proxy-headers / --no-proxy-headers
Enable/Disable X-Forwarded-Proto,
X-Forwarded-For, X-Forwarded-Port to
populate remote address info.
--forwarded-allow-ips TEXT Comma separated list of IPs to trust with
proxy headers. Defaults to the
$FORWARDED_ALLOW_IPS environment variable if
available, or '127.0.0.1'.
--root-path TEXT Set the ASGI 'root_path' for applications
submounted below a given URL path.
--limit-concurrency INTEGER Maximum number of concurrent connections or
tasks to allow, before issuing HTTP 503
responses.
--backlog INTEGER Maximum number of connections to hold in
backlog
--limit-max-requests INTEGER Maximum number of requests to service before
terminating the process.
--timeout-keep-alive INTEGER Close Keep-Alive connections if no new data
is received within this timeout. [default:
5]
--ssl-keyfile TEXT SSL key file
--ssl-certfile TEXT SSL certificate file
--ssl-version INTEGER SSL version to use (see stdlib ssl module's)
[default: 2]
--ssl-cert-reqs INTEGER Whether client certificate is required (see
stdlib ssl module's) [default: 0]
--ssl-ca-certs TEXT CA certificates file
--ssl-ciphers TEXT Ciphers to use (see stdlib ssl module's)
[default: TLSv1]
--header TEXT Specify custom default HTTP response headers
as a Name:Value pair
--help Show this message and exit.

gunicorn

Gunicorn:主流的、高性能的 Python WSGI UNIX HTTP 服务器

  • VSCode 默认 flask、Django 运行环境。
  • 教程
  • 特点:
    • 轻量、低资源消耗,高性能。
    • 基于 gevent 等技术,与各框架兼容(flask,django)
    • 服务模型:Pre-fork:一个中心管理进程(master process)管理 worker 进程集合

快速上手

1
pip install gunicorn
1
2
3
4
5
python -m venv FlaskEnv   # 创建虚拟环境
source FlaskEnv/bin/activate # 激活虚拟环境
pip install -r requierements # 安装项目运行库
pip install gunicorn # 安装
gunicorn -h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app.py
from flask import Flask

def create_app():
app = Flask(__name__)
return app
app = create_app()

@app.route('/')
def index():
return 'hello world!'

if __name__ == '__main__':
app.run()
1
2
3
4
# server.py,服务启动入口,不涉及任何 flask 逻辑
from app import app
if __name__=='__main__':
app.run(debug=True, host='0.0.0.0', port=8080)
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
# shell
gunicorn server:app
gunicorn -D server:app #后台服务
# 第一个 app 指的是 app.py 文件,第二个指的是 flask 应用的名字;
gunicorn -w 4 -b 0.0.0.0:8000 app:app

gunicorn -b 0.0.0.0:8080 --workers=2 --threads=4 --timeout=120 --log-level=app:app
gunicorn app.wsgi:application -w 2 -b :8000 timeout 120

gunicorn [OPTIONS] MODULE_NAME:VARIABLE_NAME
gunicorn --workers=6 app:app -b 0.0.0.0:8888
# workers=6,进程数为 6,推荐值为 cpu 个数*2+1
# app:模埠文件名字,文件夹用.连接
# app:Flask 实例名称
# -b:监听地址和端口

# 配置文件,从配置文件加载配置
-c CONFIG, --config=CONFIG # gunicorn -c gunicorn.conf app:app
# 套接字,可多个
-b BIND, --bind=BIND # gunicorn -b 127.0.0:8000 -b 127.0.0.1:9000
# 等待服务的客户数量 64-2048
-- backlog # gunicorn --backlog 128
# 设定服务需要绑定的端口。建议使用 HOST:PORT。
-t # timeout,超过这么多秒,服务将重启,默认 30 秒。
--reload # 代码更新时重启工作,默认为 False
-w WORKERS, --workers=WORKERS
# 设置工作进程数。建议服务器每一个核心可以设置 2-4 个,推荐的数量为当前的CPU个数*2 + 1
CPU个数为:
import multiprocessing
workers = multiprocessing.cpu_count() * 2
# gunicorn依靠操作系统来进行负载均衡,gunicorn只需要启用4–12个workers,就足以每秒钟处理几百甚至上千个请求了
-k MODULE
# 选定异步工作方式使用的模块。

配置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-c CONFIG    : CONFIG,配置文件的路径,通过配置文件启动;生产环境使用;
-b ADDRESS : ADDRESS,ip 加端口,绑定运行的主机;
-w INT, --workers INT:用于处理工作进程的数量,为正整数,默认为 1;
-k STRTING, --worker-class STRTING:要使用的工作模式,默认为 sync 异步,可以下载 eventlet 和 gevent 并指定
--threads INT:处理请求的工作线程数,使用指定数量的线程运行每个 worker。为正整数,默认为 1。
--worker-connections INT:最大客户端并发数量,默认情况下这个值为 1000。
--backlog int:未决连接的最大数量,即等待服务的客户的数量。默认 2048 个,一般不修改;
-p FILE, --pid FILE:设置 pid 文件的文件名,如果不设置将不会创建 pid 文件
--access-logfile FILE : 要写入的访问日志目录
--access-logformat STRING:要写入的访问日志格式
--error-logfile FILE, --log-file FILE : 要写入错误日志的文件目录。
--log-level LEVEL : 错误日志输出等级。
--limit-request-line INT : HTTP 请求头的行数的最大大小,此参数用于限制 HTTP 请求行的允许大小,默认情况下,这个值为 4094。值是 0~8190 的数字。
--limit-request-fields INT : 限制 HTTP 请求中请求头字段的数量。此字段用于限制请求头字段的数量以防止 DDOS 攻击,默认情况下,这个值为 100,这个值不能超过 32768
--limit-request-field-size INT : 限制 HTTP 请求中请求头的大小,默认情况下这个值为 8190 字节。值是一个整数或者 0,当该值为 0 时,表示将对请求头大小不做限制
-t INT, --timeout # int,默认30 秒,超过这么多秒后工作将被杀掉,并重新启动。
--daemon: 是否以守护进程启动,默认 false;
--chdir: 在加载应用程序之前切换目录;
--graceful-timeout INT:默认情况下,这个值为 30,在超时(从接收到重启信号开始)之后仍然活着的工作将被强行杀死;一般使用默认;
--keep-alive INT:在 keep-alive 连接上等待请求的秒数,默认情况下值为 2。一般设定在 1~5 秒之间。
--reload:默认为 False。此设置用于开发,每当应用程序发生更改时,都会导致工作重新启动。
--spew:打印服务器执行过的每一条语句,默认 False。此选择为原子性的,即要么全部打印,要么全部不打印;
--check-config :显示现在的配置,默认值为 False,即显示。
--max_requests # 最大请求数,默认0,重启之前,worker将处理的最大请求数即当超过max_requests时,会重置worker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 以 gunicorn.conf 配置文件使用

# 并行工作进程数
workers = 4
# 指定每个工作者的线程数
threads = 2
# 监听内网端口 5000
bind = '127.0.0.1:5000'
# 设置守护进程,将进程交给 supervisor 管理
daemon = 'false'
# 工作模式协程
worker_class = 'gevent'
# 设置最大并发量
worker_connections = 2000
# 设置进程文件目录
pidfile = '/var/run/gunicorn.pid'
# 设置访问日志和错误信息日志路径
accesslog = '/var/log/gunicorn_acess.log'
errorlog = '/var/log/gunicorn_error.log'
# 设置日志记录水平
loglevel = 'warning'
1
gunicorn -c gunicorn.conf app:app

Paramiko

Paramiko:自动化运维,基于 Python 实现的 SSH2 远程连接、认证,进行远程命令、文件传输、中间 SSH 代码等功能。

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
pip install paramiko
# Successfully installed bcrypt-3.2.0 cryptography-3.4.7 paramiko-2.7.2 pynacl-1.4.0


import paramiko

t = paramiko.Transport((server_ip, server_port))
t.connect(username=user_name, password=password)
sftp = paramiko.SC .from_transport(t)
# sftp.put(local_file_path, remote_file_path)
sftp.put('./test.txt', os.path.join('/home/chen/', 'test.txt'))
t.close()


ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 指定当对方主机没有本机公钥的情况时应该怎么办,AutoAddPolicy表示自动在对方主机保存下本机的秘钥
ssh_client.connect(server_ip, server_port, user_name, password)
command = "ls -a"
stdin, stdout, stderr = ssh_client.exec_command(command)
out = stdout.readlines()
err = stderr.readlines()
ssh_client.close()
# print(f'stdin:{stdin.readlines()}')
print(f'stdout:{out}')
print(f'stderr:{err}')

scp:

fabric

fabric:简化系统管理任务,基于 Python 实现的 SSH 命令行工具,自动化运维利器

  • 用处:运维自动化工具:在远程服务器运行你写好的脚本
  • 特点:简化基于 SSH 的应用程序部署及系统管理任务
    • 在 paramiko 的基础上进行了封装,更容易使用
    • 为了自动化部署应用而生,用于多台服务器批量执行任务
    • 提供基础操作组件,如:本地与远程命令执行、文件上传、下载及日志输出等。
  • 官方文档
  • 可以:
    • 命令行调用方式(我不太用这个,Python 牛*):定义好远程任务要执行的操作后,通过命令行传入要执行的远程主机,要调用的任务等参数。
    • python 调用方式,用接口直接调用执行。
  • 核心依赖:
    • Invoke:用于作为命令行操作的接口
    • Paramiko:用于执行 ssh 连接操作
  • 比较:
    • pexpect:基于 pty,只能在 Linux 上跑
    • Ansible:过于复杂了,而且是更新倾向于 shell
    • 当前默认安装的是 v2.6.0,fabric 的 1.x 与 2.x 差异较大,2.x 无 env 模块,所有操作基于 Connection 对象完成
  • 开发流程:
    • 先在远程服务器上验证脚本
    • 再用 python fabric 实现,并验证

基本使用

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
from fabric import Connection

# class Connection
host = None # 主机名或IP地址: www.host.com, 66.66.66.66
user = None # 系统用户名: root, someone
port = None # 端口号(远程执行某些应用需提供)
config = None # 登录配置文件
gateway = None # 连接网关
forward_agent = None # 代理
connect_timeout = None # 超时时间
connect_kwargs = None # 连接参数,密码登录connect_kwargs={"password": "123456"}) 还是 密钥登录connect_kwargs={"key_filename": "/home/myuser/.ssh/private.key"}
inline_ssh_env = None
client = None # 客户端


conn = Connection('10.10.22.13', user='root', connect_kwargs={'password': '${my_password}'})
conn.run('ls')

conn.open()
conn.is_connected
conn.close()

result = conn.run(cmd_str,hide=True)
result.stdout # 正常输出
result.exited # 0
result.ok # True
result.failed
result.command # 运行的命令
result.connection.hosts # 所在的服务器
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
from fabric improt Connection
from invoke import task



# ${}里的内容自行填充
conn = Connection("${remote host}", user='${remote user}', connect_kwargs={'password': "${remote user's password}"})

# 执行本地命令
conn.local('uname -a')

# 执行远程命令
conn.run('free -m')

# sudo方式执行远程命令
# warn
# warn=True:防止类似systemctl status对于停止的服务返回非零错误代码产生异常
conn.sudo('/etc/init.d/httpd start',warn=True)

conn.run(
command:str# 要执行的命令字符串
warn, # 忽略异常信息(即exit code不等于0的命令),会将异常信息输出到stderr,若设置False则会报SystemExit异常,之前在测试环境就是因为没有处理这个SystemExit异常(SystemExit和Exception是同级关系),导致整个进程崩溃
hide, # None:默认:不隐藏,服务器的输出会在控制台打印
# "stdout"/"stderr":不打印stdout/stderr
# True:不打印stdout、stderr
# False:都显示?
disown, # 相当于 nohup command &
env, # dict,用于定义命令执行时的环境变量
encoding, # stdout与stderr的文本编码
watchers, # 定义监控,一般用于自动填写stdin
out_stream, # 文件对象,可以记录命令输出信息
err_stream, # 文件对象,可以记录命令错误输出信息
)

# 执行run/sudo前的操作,相当于&&
conn.prefix(command:str)

# 切换本地目录
api.lcd('/opt')

# 切换远程目录
conn.cd('/home')


# 上传本地文件至远程主机
conn.put('/home/user.info','/data/user.info')

# 从远程主机下载文件到本地
conn.get('/data/user.info','/home/user.info')

# 获得用户输入信息
conn.prompt('please input user password:')

# 获得提示信息确认
conn.confirm('Test failed,Continue[Y/N]?')

# 重启远程主机
conn.reboot()

前置命令

  • 常用在很多软件或模块的安装上,比如编译某 c 代码时指定 C 编译器 cc=xxx&&gcc xxx
    • 也可以通过 run 中带环境变量来实现
1
2
3
4
5

with c.prefix("cc=xxx"):
result = c.run("gcc xxxx", hide=True)
msg = f"Ran {result.command!r} on {result.connection.host}, got stdout:\n{result.stdout}"
print(msg)

交互式操作

  • 常用于需要输入 yesy 之类的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from invoke import Responder


sudopass = Responder(
pattern=r'\[sudo\] password:', # 正则字符串
response='mypassword\n', # 回应信息
)

conn.run('sudo whoami', pty=True, watchers=[sudopass])

responder = Responder(
pattern=r"Are you ready? \[Y/n\] ",
response="y\n",
)
c.run("excitable-program", watchers=[responder])

自动响应

有些命令如 passwd , adduser 会等候你继续输入下一步. 像这种需要手动响应的自动化程序应该怎么写? 在 fabric 里这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from invoke import Responder

# 其他代码
resp1 = Responder(
pattern=r'Enter new UNIX password:',
response=pwd + '\n'
)

resp2 = Responder(
pattern=r'Retype new UNIX password:',
response=pwd + '\n'
)

conn.run('passwd ' + username, pty=True, hide=True, watchers=[resp1, resp2])

Responder 是 invoke 框架的一部分 (fabric 已经包括 invoke 框架) . 它的使用方法如下:

  1. pattern: 正则表达式, 用来匹配命令行的输出. 如果里面有需要转义的字符, 千万要记得转义.
  2. response: 需要输入的内容.
    这两个参数结合起来的意思是: 如果匹配到命令输出, 需要输入信息的时候, 就自动输入 response 定好的字符串.

如果不确定某些命令的 pattern 和 resonse 是什么. 可以先在服务器手动输入命令看看结果, 然后再写程序. 如果程序多次需要输入, 那么就要写多个 responder, 用个数组包起来.

注意: 如果程序长时间没有反馈, 证明你的 pattern 写得有问题.

最常见的原因就是转义字符没有进行处理. 下面这是一个例子:

1
2
3
4
5
6
7
8
9
10
# 在yum上安装软件
def yum_install(conn, cmd):

resp = Responder(
# pattern=r'Is this ok [y/d/N]:', # 错误例子: 没有正则转义
pattern=r'Is this ok \[y/d/N\]:',
response='y\n'
)

conn.run(cmd, pty=True, watchers=[resp], hide=True)

命令行形式

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
@task    #函数修饰符,标识的函数为fab可调用的,非标记对fab不可见,纯业务逻辑
@runs_once #函数修饰符,标识的函数只会执行一次,不受多台主机影响
@roles() #运行指定的角色组里,通过env.roledefs里的定义


# @task装饰器是必须加的,保证fab能直接执行,参数c不用管它,但不能定义成与你实例化的conn同名
@task
def execute(c):
conn.run("uname -s")

# 列出某路径下的文件
@task
def ls_remote(c, dir_path):
with conn.cd(dir_path):
conn.run("ls -la")

# 还能在函数里实例化Connection对象
@task
def uname_rmt(c, host, user, password):
con = Connection(host, user=user, connect_kwargs={'password': password})

con.run("uname -s")

# shell中执行
$ fab uname_local
Darwin

$ fab ls_remote /home
trendy
td_root

$ fab uname_rmt 10.10.22.13 root ******* # 这个密码就不放出来了
Linux


# self.conn.run('pwd', hide=True, warn=True, encoding='utf-8')

# 正常运行时, 信息在 stdout里
# print('-------- 下面是 stdout 信息')
# print(result.stdout.strip())

# 出错时, 信息在 stderr 里
# print('-------- 下面是 stderr 信息')
# print(result.stderr.strip())

fab 命令参数

  • -l:打印可用的命令
  • –set=KEY=VALUE:设置环境变量,都好分隔
  • –shortlist:简短打印可用命令
  • -c PATH:指定本地配置文件
  • -D:不加载用户 known_hosts 文件
  • -f PATH:指定 fabfile 文件
  • -g HOST:逗号分隔要操作的主机
  • -i PATH:指定私钥文件
  • -k:不加载来自~/.ssh 下的私钥文件
  • -p PASSWORD:使用密码认证 and/or sudo
  • -P:默认为并行执行方法
  • –port=PORT:指定 ssh 连接端口
  • -R ROLES:根据角色操作,逗号分隔
  • -s SHELL:指定新 shell,默认为 /bin/bash -l -c
  • –show=LEVELS:以逗号分隔的输出
  • –ssh-config-path=PATH:SSH 配置文件路径
  • -t N:设置连接的超时时间,单位秒
  • -T N:设置远程命令超时时间,单位秒
  • -u USER:连接远程主机用户名
  • -x HOSTS:以逗号分隔排除主机
  • -z INT:并发进程数

批量操作 -group

  • 将一组主机定义为一个 Group,其 API 与 Connection 一致,即一个 Group 可简化地视为一个 Connection
1
2
3
4
5
6
7
8
9
10
11
12
13
# 按串行方式执行操作
from fabric import SerialGroup
# 按并发方式执行操作
from fabric import ThreadingGroup
from fabric import ThreadingGroup as Group
results = SerialGroup('web1', 'web2', 'mac1').run('uname -s')


with Group(conn1,conn2,conn3) as group:
results=group.run('uname -a',hide=True)
for conn,result in result.items():
msg = f"conn {conn.host} Ran {result.command!r} on {result.connection.host}, got stdout:\n{result.stdout}"
print(msg)

网络网关

  • 对于远程服务器是网络隔离的,无法直接被访问到,需要网关/代理/隧道,这个中间层的机器通常被称为跳板机或堡垒机
  • 对应 OpenSSH 客户端的两种选项:
    • ProxyJump:简单,开销少,可嵌套
      • ProxyJump 方式就是在一个 Connection 中嵌套一个 Connection 作为前者的网关,后者使用 SSH 协议的 direct-tcpip 为前者打开与实际远程主机的连接,而且后者还可以继续嵌套使用自己的网关。
    • ProxyCommand:开销大,不可嵌套,更灵活
      • ProxyCommand 方式是客户端在本地用 ssh 命令(类似“ssh -W %h:%p gatewayhost”),创建一个子进程,该子进程与服务端进行通信,同时它能读取标准输入和输出。
1
2
3
from fabric import Connection

c = Connection('internalhost', gateway=Connection('gatewayhost'))

supervisor

#最佳实践

supervisor:基于 Python 的强大运维管理工具

1
2
3
pip install supervisor
or
sudo apt install supervisor

名词解释

supervisord
是 supervisor 的服务端程序。

干的活:启动 supervisor 程序自身,启动 supervisor 管理的子进程,响应来自 clients 的请求,重启闪退或异常退出的子进程,把子进程的 stderr 或 stdout 记录到日志文件中,生成和处理 Event

supervisorctl
这东西还是有点用的,如果说 supervisord 是 supervisor 的服务端程序,那么 supervisorctl 就是 client 端程序了。supervisorctl 有一个类型 shell 的命令行界面,我们可以利用它来查看子进程状态,启动/停止/重启子进程,获取 running 子进程的列表等等。。。最牛逼的一点是,supervisorctl 不仅可以连接到本机上的 supervisord,还可以连接到远程的 supervisord,当然在本机上面是通过 UNIX socket 连接的,远程是通过 TCP socket 连接的。supervisorctl 和 supervisord 之间的通信,是通过 xml_rpc 完成的。 相应的配置在 [supervisorctl] 块里面

基本使用

  • supervisord:守护进程,服务相关的命令
  • supervisorctl:supervisor 客户端相关的命令,supervisorctl status 状态主
    • running:进程处于运行状态
    • starting:Supervisor 收到启动请求后,进程处于正在启动过程中
    • stopped:进程处于关闭状态
    • stopping:Supervisor 收到关闭请求后,进程处于正在关闭过程中
    • backoff:进程进入 starting 状态后,由于马上就退出导致没能进入 running 状态
    • fatal:进程没有正常启动
    • exited:进程从 running 状态退出

配置

配置所有用户可以用supervisorctl

1
2
3
4
5
6
# sudo vi /etc/supervisor/supervisor.conf

[unix_http_server]
file=/var/run/supervisor.sock ; 或 /tmp/supervisor.sock
chmod=0777 ; 关键:设置为所有用户可读写
; chown=nobody:nogroup ; 可选:修改属主
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
# sudo vim /etc/supervisor/supervisord.conf
[program:my_api]
# 命令搪行的目录
directory=/project/test
# 执行的命令行
command=/usr/bin/python3.8 /usr/bin/gunicorn app:app -b 127.0.0.1:8092 # 启动程序的命令,可带参数,command 只能是那种在终端运行的进程,不能是守护进程
numprocs=4 # 启动该程序的多个实例,不为 1 时,则为进程池
priority=999 # 1-999,权重,程序启动与关闭的顺序,权重越低,越早启动、越晚关闭
autostart=true # supervisord 启动时,进程自动启动
autorestart=true # false、true、unexpected,false:进程不会自动重启,unexpected:当程序退出时的退出码不是 exitcodes 中定义的时,进程会重启,true:进程会无条件重启当退出的时候。
startsecs=10 # 程序启动后等待多长时间后才认为程序启动成功
startretries=3 # 尝试启动一个程序的次数,默认为 3
stdout_logfile=/home/chen/test/log/stdout.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
stdout_capture_maxbytes=1MB
stderr_logfile=/home/chen/log/stderr.log
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10
stderr_capture_maxbytes=1MB
user = user_name
# environment 不对会找不到 package
environment = PYTHONPATH="/home/chen/.local/lib/python3.8/site-packages/", USER="chen"

stopsignal:当收到 stop 请求的时候,发送信号给程序,默认是 TERM 信号,也可以是 HUP, INT, QUIT, KILL, USR1, or USR2。
stopwaitsecs:在操作系统给 supervisord 发送 SIGCHILD 信号时等待的时间
stopasgroup:如果设置为 true,则会使 supervisor 发送停止信号到整个进程组
killasgroup:如果设置为 true,则在给程序发送 SIGKILL 信号的时候,会发送到整个进程组,它的子进程也会受到影响。
redirect_stderr:如果设置为 true,进程则会把标准错误输出到 supervisord 后台的标准输出文件描述符。
stdout_logfile:把进程的标准输出写入文件中,如果 stdout_logfile 没有设置或者设置为 AUTO,则 supervisor 会自动选择一个文件位置。
stdout_logfile_maxbytes:标准输出 log 文件达到多少后自动进行轮转,单位是 KB、MB、GB。如果设置为 0 则表示不限制日志文件大小
stdout_logfile_backups:标准输出日志轮转备份的数量,默认是 10,如果设置为 0,则不备份
stdout_capture_maxbytes:当进程处于 stderr capture mode 模式的时候,写入 FIFO 队列的最大 bytes 值,单位可以是 KB、MB、GB
stdout_events_enabled:如果设置为 true,当进程在写它的 stderr 到文件描述符的时候,PROCESS_LOG_STDERR 事件会被触发
stderr_logfile:把进程的错误日志输出一个文件中,除非 redirect_stderr 参数被设置为 true
stderr_logfile_maxbytes:错误 log 文件达到多少后自动进行轮转,单位是 KB、MB、GB。如果设置为 0 则表示不限制日志文件大小
stderr_logfile_backups:错误日志轮转备份的数量,默认是 10,如果设置为 0,则不备份
stderr_capture_maxbytes:当进程处于 stderr capture mode 模式的时候,写入 FIFO 队列的最大 bytes 值,单位可以是 KB、MB、GB
stderr_events_enabled:如果设置为 true,当进程在写它的 stderr 到文件描述符的时候,PROCESS_LOG_STDERR 事件会被触发
environment:一个 k/v 对的 list 列表
directory:supervisord 在生成子进程的时候会切换到该目录
umask:设置进程的 umask
serverurl:是否允许子进程和内部的 HTTP 服务通讯,如果设置为 AUTO,supervisor 会自动的构造一个 url

# 这样才会正常重启
stopasgroup=true
killasgroup=true

遇到的问题

  • supervisord unknown error making dispatchers for : ENOENT
  • sudo service supervisor restart

  • 问题:执行 sudo supervisorctl 时,报错 unix:///var/run/supervisor.sock no such file
  • 解决方案:
1
2
3
sudo touch /var/run/supervisor.sock
sudo chmod 777 /var/run/supervisor.sock
sudo service supervisor restart

  • 问题:执行 sudo supervisorctl 时,报错:unix:///var/run/supervisor.sock refused connection
  • 解决方案:
    • 是因为 supervisord 没有启动
1
sudo supervisord -c /etc/supervisor/supervisord.conf(换成你自己的配置文件目录) 

pexpect

pexpect:控制和自动化交互式应用的 Python 库

  • 适用于处理需要交互输入的对话
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pip install pexpect


import pexpect
创建SSH会话
child = pexpect.spawn('ssh your_username@your_host')
处理密码提示
child.expect('password:')
child.sendline('your_password')
执行命令
child.expect('$')
child.sendline('ls -l')
打印输出
child.expect('$')
print(child.before.decode())
关闭会话
child.close()
给博主来一杯卡布奇诺