# 部署

在生产环境中,不应该再使用 flask 自带的 werkzueg 进行服务的部署,而应该要使用性能更好的 web 服务器。在模板中默认使用 gunicorn 来进行服务的部署。

注意,对于 WSGI web server,模板设立了 STRONG 的等级,虽然还有其他很多可选项,如 uwsgi,但都不做推荐。

这是因为后续考虑到要使用链路追踪 (opens new window),为了能够统一的对服务器软件进行追踪,必须要统一 web 服务器的选型。

# 启动命令

由于当前服务的启动命令比较简单,因此被直接写在 Dockerfile 中,命令如下:

gunicorn -c deploy/gunicorn_conf.py

WARNING

这个命令不能修改,也不能包装成一个 bash 脚本,入口必须是一个 python 程序。

因为基础镜像中添加了对 CMD 的增强 (opens new window)以进行链路追踪,要求以 python 程序的方式进行启动。

主要注意的是,如果在本机直接运行可能会报错,需要再运行前指定一下下面两个环境变量。

export DG_SDK_RDB_AUTO_INIT=False
export DG_SDK_RDB_APP_CTX_TYPE=flask

镜像中已经设置好了这两个变量

如果还是启动失败,并显示类似下面的报错:

ImportError: 没有安装 loguru 或者 dglogging,无法对日志进行持久化,请选择安装上述两个包,或者手动修改 LOG_DICT 的内容

那是因为在正式环境需要 loguru 或者 dg-logging 对日志进行持久化,请将其中之一添加到项目的依赖中,详见 logger handler

# gunicorn_conf.py

deploy 文件夹的 gunicorn_conf.py 文件预置了一些 gunicorn 的配置 (opens new window)

开发者可以对 gunicorn 配置进行调整,但有几项请保持现状,否则可能会存在问题。

  • bind: 默认跑在容器 5000 端口上,内部端口号最好不变,可以修改映射出来的端口号。
  • worker_class: worker 尽量使用 sync,gevent 的 worker 有着各种各样蜜汁问题。我这里也有一个简单的 benchmark 来展示到底 gevent worker 提升有没有那么的巨大;
  • logconfig_dict: 对于 gunicorn 日志的配置尽量保持原样,请参考gunicorn_logging.py来获得更多信息;

# gunicorn_logging.py

gunicorn_logging.py 是一个专门用来配置 logger 的文件。

该文件一共配置了三个logger,分别是:

  • gunicorn.access: gunicorn 的 access 日志,会记录所有的客户端的请求信息
  • gunicorn.error: gunicorn 的运行日志,会记录 worker 的一些状态,WSGI 的一些错误等等
  • {flask-app-name}: 名字为 Flask import_name 的 logger,和 Flask.logger 是同一个logger,同时也是 logger_process.py 中装配的 logger,开发者可以用来打印一切业务日志

该文件主要是对这三个 logger 的 handler 和 formatter 进行了配置。也正因为如此,在 gunicorn_conf.py 中不要再出现和 logging 相关的其他配置 (opens new window)了,直接修改本文件就行。

详见模板如何处理logger

# gunicorn_conf_loader.py

这个文件允许在 gunicorn 启动时从 flask-boot 的统一配置中加载额外的配置项。

如此一来,项目上只需要挂载 app.yaml 文件就可以对 gunicorn 行为进行控制,更加方便!

注意,这个文件会默认读取 app.yaml 下的 gunicorn section,如:

gunicorn:
    timeout: 60
    reload: true
    # 也可以使用统一配置从环境变量注入的功能
    max_requests: ${G_MAX_REQUESTS:100}

flask:
    ...

还请注意,不是所有配置项都支持通过统一配置进行注入的。

由于日志收集STRONG 级别的约束,gunicorn logging 的配置不支持通过统一配置进行修改,目前只通过 gunicorn_logging.py 文件进行控制。

另外,gunicorn 的一些 hook 配置是直接定义 python 函数的,这部分的配置也无法直接通过配置文件进行注入,比如以下几个 hook:

def on_starting(server):
    pass

def on_reload(server):
    pass

def when_ready(server):
    pass

def pre_fork(server, worker):
    pass

def post_fork(server, worker):
    pass

def post_worker_init(worker):
    pass

def worker_int(worker):
    pass

def worker_abort(worker):
    pass

def pre_exec(server):
    pass

def pre_request(worker, req):
    worker.log.debug("%s %s" % (req.method, req.path))

def post_request(worker, req, environ, resp):
    pass

def child_exit(server, worker):
    pass

def worker_exit(server, worker):
    pass

def nworkers_changed(server, new_value, old_value):
    pass

def on_exit(server):
    pass

如果需要使用上述 Hook 对 gunicorn 进行高级控制,请直接修改 gunicorn_conf.py 文件。

# 其他文件

deploy 下可以放一切和部署相关的文件,比如执行数据库迁移脚本、启动其他 sidecar 服务。

建议这些脚本都通过基础镜像s6-overlay进行启动,详见基础镜像s6-overlay文档 (opens new window)