Python部署:WSGI与枪支
#网络开发人员 #python #devops

在最后一部分中,WSGI的基础知识以及通过werkzeug的一些有用的增强。现在,有许多实施WSGI的服务器可以用于更多的生产水平环境。在本文中,我将把Gunicorn视为WSGI服务器解决方案之一。虽然我最初的计划是在一篇文章中介绍所有WSGI服务器,但我发现描述每个服务器的内容都有足够的内容用于一篇文章。考虑到这一点,本系列的未来帖子将引入WSGI服务器解决方案作为他们自己的专门文章。

在基准上

在这里和将来的服务器帖子中不会有太多基准测试。软件正在不断发展,现在可能会在一周内变成非常表现的人。 WSGI服务器在更多的生产环境中也可以成为集群的一部分,也可以多样化以满足不同的应用需求。即使在绩效指标很重要的情况下,也应在更接近部署的更具控制的环境中进行测量。

gunicorn概述

Gunicorn是一款纯Python WSGI的服务器,可在前叉工具模型以及other alternatives上运行。纯粹是Python,它加载了WSGI应用程序via module import。纯Python体系结构还意味着与PyPy进行更轻松的集成,从而在长期运行过程中提供JIT优化。

基本运行

可以通过pip install gunicorn安装枪支。给定一个简单的WSGI应用程序:

wsgi_test.py

def application(env, start_response):
    data = b'Hello World'

    status = '200 OK'
    response_headers = [
        ('Content-Type', 'text/plain'),
        ('Content-Length', str(len(data))),
    ]
    start_response(status, response_headers)
    return [data]

枪支将被这样执行:

$ gunicorn --workers=2 wsgi_test:application
[2023-08-23 05:00:33 +0000] [13126] [INFO] Starting gunicorn 21.2.0
[2023-08-23 05:00:33 +0000] [13126] [INFO] Listening at: http://127.0.0.1:8000 (13126)
[2023-08-23 05:00:33 +0000] [13126] [INFO] Using worker: sync
[2023-08-23 05:00:33 +0000] [13176] [INFO] Booting worker with pid: 13176
[2023-08-23 05:00:33 +0000] [13177] [INFO] Booting worker with pid: 13177

其中wsgi_test.py没有扩展名作为模式,然后是:,然后是callable的名称(在这种情况下为application函数)。

配置

可以将Python文件用于配置。配置从设置described in the documentation中撤出。它还可以用作标准的Python文件,这意味着您可以执行以下操作:

gunicorn.config.py

import multiprocessing

bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
wsgi_app = "wsgi_test:application"

然后可以检查配置以确保其有效:

$ gunicorn --check-config -c gunicorn.config.py

终于使用--config/-c选项和配置文件的名称运行Gunicorn:

$ gunicorn -c gunicorn.config.py
[2023-08-23 02:50:37 +0000] [18928] [INFO] Starting gunicorn 21.2.0
[2023-08-23 02:50:37 +0000] [18928] [INFO] Listening at: http://127.0.0.1:8000 (18928)
[2023-08-23 02:50:37 +0000] [18928] [INFO] Using worker: sync
[2023-08-23 02:50:37 +0000] [18984] [INFO] Booting worker with pid: 18984
[2023-08-23 02:50:37 +0000] [18985] [INFO] Booting worker with pid: 18985
[2023-08-23 02:50:37 +0000] [18986] [INFO] Booting worker with pid: 18986
[2023-08-23 02:50:37 +0000] [18987] [INFO] Booting worker with pid: 18987
[2023-08-23 02:50:37 +0000] [18988] [INFO] Booting worker with pid: 18988
[2023-08-23 02:50:37 +0000] [18989] [INFO] Booting worker with pid: 18989
[2023-08-23 02:50:37 +0000] [18990] [INFO] Booting worker with pid: 18990
[2023-08-23 02:50:37 +0000] [18991] [INFO] Booting worker with pid: 18991
[2023-08-23 02:50:37 +0000] [18992] [INFO] Booting worker with pid: 18992

著名的HTTP功能

在这里,我将查看支持分解输入,块输出和范围标题的能力。这些功能可能取决于您的用例。

块的输入支持

枪支支持通过wsgi.input_terminated的块输入,如此简单应用所示:

wsgi_chunked_input

def application(environ, start_response):
    input = environ['wsgi.input']
    with open('test.json', 'wb') as stream_fp:
        stream_fp.write(input.read())

    status = '200 OK'
    body = b'Hello World\n'
    response_headers = [
        ('Content-Type', 'text/plain'),
        ('Content-Length', str(len(body))),
    ]
    start_response(status, response_headers)
    return [body]

发送25MB JSON文件还带回:

$ curl -v -H "Transfer-Encoding: chunked" -d @large-file.json http://127.0.0.1:8000
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> POST / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.74.0
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 100 Continue
* Signaling end of chunked upload via terminating chunk.
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: gunicorn
< Date: Tue, 22 Aug 2023 17:47:21 GMT
< Connection: close
< Content-Type: text/plain
< Content-Length: 12
< 
Hello World
* Closing connection 0

在服务器端很好地显示:

$ ls -lh test.json 
-rw-r--r-- 1 john doe 25M Aug 22 18:47 test.json

响应支撑块

块的响应也可以工作,只需要按modified example添加的Transfer-Encoding: chunked标头:

class TestIter(object):

    def __iter__(self):
        lines = [b'line 1\n', b'line 2\n']
        for line in lines:
            yield line

def app(environ, start_response):
    status = '200 OK'
    response_headers = [
        ('Content-type', 'text/plain'),
        ('Transfer-Encoding', "chunked"),
    ]
    start_response(status, response_headers)
    return TestIter()

在通过卷发产生时,会产生:

$ curl -iv --raw -H "Transfer-Encoding: chunked" http://127.0.0.1:8000/
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.74.0
> Accept: */*
> Transfer-Encoding: chunked
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Server: gunicorn
Server: gunicorn
< Date: Tue, 22 Aug 2023 21:40:12 GMT
Date: Tue, 22 Aug 2023 21:40:12 GMT
< Connection: close
Connection: close
< Transfer-Encoding: chunked
Transfer-Encoding: chunked
< Content-type: text/plain
Content-type: text/plain

< 
7
line 1

7
line 2

0

* Closing connection 0

范围支持

范围没有明确的包装器,需要一个辅助功能,例如werkzeug.http.parse_range_header。要通过的价值将通过HTTP_RANGE获得:

from werkzeug.http import parse_range_header

def application(environ, start_response):
    range = parse_range_header(environ['HTTP_RANGE'])
    start, end = range.ranges[0]

    with open('large-file.json', 'rb') as stream_fp:
        stream_fp.seek(start)
        data = stream_fp.read(end - start)

    status = '200 OK'
    response_headers = [
        ('Content-type', 'application/json')
    ]
    start_response(status, response_headers)
    return [data]

当卷曲与卷发时:

$ curl -v -r 1200-1299 http://127.0.0.1:8000/
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> Range: bytes=1200-1299
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: gunicorn
< Date: Wed, 23 Aug 2023 10:59:27 GMT
< Connection: close
< Transfer-Encoding: chunked
< Content-type: application/json
< 
* Closing connection 0
pt"},"message":"Fix main header height on mobile","distinct":true,"url":"https://api.github.com/repo

PEX捆绑

作为纯粹的python,也可以使用pex(可执行文件)包装枪。例如,我将创建一个简单的WSGI应用程序wsgi_test.py以及简化的gunicorn.config.py

wsgi_test.py

def application(env, start_response):
    data = b'Hello World'

    status = '200 OK'
    response_headers = [
        ('Content-Type', 'text/plain'),
        ('Content-Length', str(len(data))),
    ]
    start_response(status, response_headers)
    return [data]

gunicorn.config.py

bind = "127.0.0.1:8000"
workers = 2
wsgi_app = "wsgi_test:application"

我将它们放在专用的文件夹中,以便易于复制。然后需要打包可执行文件:

$ pex gunicorn -c gunicorn -o wsgi_app.pex --python pypy --inject-args "--config gunicorn.config.py"

因此,第一部分是要包括的模块。我们将包括gunicorn,因为这是我们想要运行WSGI应用程序的内容。下一个是一个入口点,即setup.py of the project中定义的console_script枪支。这将允许所产生的可执行文件运行,就像我们在命令行上运行gunicorn一样。 --python pypy将pypy设置为要使用的python二进制。您需要确保这与目标计算机上可用的Python匹配。 --inject-args使得始终将配置文件参数传递,因此不必在执行时写出它们。最后,-o wsgi_app.pex是要输出的可执行文件。如果我将其复制到安装了PYPY的另一个Linux系统:

$ scp server:~/projects/pex_example/* .
$ ./wsgi_app.pex
[2023-08-23 05:00:33 +0000] [13126] [INFO] Starting gunicorn 21.2.0
[2023-08-23 05:00:33 +0000] [13126] [INFO] Listening at: http://127.0.0.1:8000 (13126)
[2023-08-23 05:00:33 +0000] [13126] [INFO] Using worker: sync
[2023-08-23 05:00:33 +0000] [13176] [INFO] Booting worker with pid: 13176
[2023-08-23 05:00:33 +0000] [13177] [INFO] Booting worker with pid: 13177

尽管没有在目标机器上安装枪支,但一切都在运行。请注意,这是由于配置文件指向wsgi_test:application而起作用。要将其与可执行文件一起捆绑在一起,您需要通过setup.py/pyproject.toml样式构建系统安装它。然后它可以像:
一样捆绑

$ pex wsgi_test gunicorn -c gunicorn -o wsgi_app.pex --python pypy --inject-args "--config gunicorn.config.py"

过程标题

安装steproctitle将允许Gunicorn以使其更容易管理的方式命名其工作过程。只需通过pip install setproctitle安装并查看过程列表将显示类似的内容:

  28928 pts/2    00:00:01 gunicorn: maste
  28984 pts/2    00:00:00 gunicorn: worke
  28985 pts/2    00:00:00 gunicorn: worke
  28986 pts/2    00:00:00 gunicorn: worke
  28987 pts/2    00:00:00 gunicorn: worke
  28988 pts/2    00:00:00 gunicorn: worke
  28989 pts/2    00:00:00 gunicorn: worke
  28990 pts/2    00:00:00 gunicorn: worke
  28991 pts/2    00:00:00 gunicorn: worke
  28992 pts/2    00:00:00 gunicorn: worke

可以轻松区分主工作和工作过程。

项目支持概述

这探讨了如何在考虑生产环境中使用的人维护项目。与任何软件评估一样,请务必在接近您期望生产环境的环境中测试事物。

文档

Gunicorn的主要网站有一个简单的documentation page。对于更广泛的文档,有一个full docs site。文档本身的井井有条。多次文档允许指向特定版本或版本指针,例如lateststable

来源可维护性

最后一个提交表明写作大约1个月大。有274期和85个开放率。最近的大量活动似乎是一个人的努力。鉴于代码基础置换性的大小,如果需要出现,则相当合理。

结论

我认为整体包装很好。如果您想快速进行原型原型,那么起床并运行非常简单。多种工人类型的可用性意味着如果进行生产部署,您需要评估每个工人的选择以找到最合适的选择(除非龙卷风工人可能不是一个好主意,因为WSGI是一个较小的奖励,而不是主要的重点)。至于源存储库,主要拥有一个人的维护者确实有一个关注点。确定一些共同维护者和虫子牧马人确实可以帮助该项目。