上一篇文翻译了部署的相关概念后,对自己真正实践部署FastAPI有了更多的信心。特别是理解其中的细节以及每个服务所起的的作用(What, Why, How)。
这边就记录下Web服务部署阿里云时候的一些过程。
1. Gunicorn与Uvicorn 虽然我们可以直接Uvicorn裸跑,而且这不仅在调试时候,生产非高并发也足够了。
因为Uvicorn也可以选择worker参数,轻量级方式,只是不提供任何进程监控。处理工作进程的能力比Gunicorn更有限。
如果想在这个级别(Python级别)有一个进程管理器 ,那么尝试用Gunicorn作为进程管理器可能更好一些。
在生产环境中,Gunicorn 可能是运行和管理 Uvicorn 最简单的方式。Uvicorn 包括一个Gunicorn工人类,这意味着尽可能少的配置。
1.1 使用 你可以使用 Gunicorn 管理 Uvicorn 并运行多个这些并发进程。这样,就可以最大限度地利用并发和并行性
命令如下:
gunicorn main:app --workers 17 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8989
我们参照 Gunicorn 的官方文档,把worker数量设置为 2 * CPU 核心数 + 1
,我的机器是8核,所以此处为17。
1.2 日志处理 如果是使用Gunicorn运行,我们需要将FastAPI日志和Uvicorn日志整合 到Gunicorn日志并输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if __name__ == "__main__" : log_config = uvicorn.config.LOGGING_CONFIG log_config["formatters" ]["access" ]["fmt" ] = "%(asctime)s - %(levelname)s - %(message)s" ch = logging.StreamHandler(stream=None ) fastapi_logger.addHandler(ch) fastapi_logger.setLevel(logging.DEBUG) uvicorn.run("__main__:app" , host="0.0.0.0" , port=8989 , reload=True , workers=1 )else : gunicorn_error_logger = logging.getLogger("gunicorn.error" ) uvicorn_access_logger = logging.getLogger("uvicorn.access" ) uvicorn_access_logger.handlers = gunicorn_error_logger.handlers uvicorn_access_logger.setLevel(gunicorn_error_logger.level) fastapi_logger.handlers = gunicorn_error_logger.handlers fastapi_logger.setLevel(gunicorn_error_logger.level)
Tips:Python中logging模块StreamHandle默认是标准stderr输出!
终端上就会显示FastAPI,Uvicorn以及Gunicorn这三者的日志,后续无论使用Docker还是Supervisor部署,日志都会统一,最多再做个重定向输出,一劳永逸。
2. 容器部署 部署 FastAPI 应用程序时,一种常见的方法是构建Linux 容器映像。通常使用Docker完成。然后可以通过几种可能的方式之一部署该容器映像。
使用 Linux 容器有几个优点,包括安全性、可复制性、简单性等。
2.1 官方 Docker 镜像 有一个官方的 Docker 镜像 ,包含了Uvicorn和Gunicorn。
包含一个自动调整 机制,用于根据可用的 CPU 内核设置工作进程的数量。
它具有合理的默认值 ,但您仍然可以使用环境变量 或配置文件更改和更新所有配置。
它还支持在容器启动之前运行prestart.sh
(下面的启动脚本中有相关代码),做一些前期操作,例如数据库迁移。
2.2 如何使用 项目里有一个文件 requirements.txt,那么Dockerfile就类似于:
1 2 3 4 5 6 7 FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 COPY ./requirements.txt /app/requirements.txt RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt COPY ./app /app
入口文件在/app/app/main.py
或者在/app/main.py
中
然后就可以制作镜像了docker build -t myimage ./
如果涉及到PgSQL或者Redis之类的,那么Docker-Compose也完全可以来整一整,不展开。
2.3 容器配置源代码 Docker部署非常简单,但也非常弹性, 可以通过不同环境变量控制一些设置。在这官方Docker 镜像 文档中我们就可以看到。具体参数不谈,这边就记录下相关内部代码。
2.3.1 容器启动时的脚本 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 set -eif [ -f /app/app/main.py ]; then DEFAULT_MODULE_NAME=app.mainelif [ -f /app/main.py ]; then DEFAULT_MODULE_NAME=mainfi MODULE_NAME=${MODULE_NAME:-$DEFAULT_MODULE_NAME } VARIABLE_NAME=${VARIABLE_NAME:-app} export APP_MODULE=${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"} if [ -f /app/gunicorn_conf.py ]; then DEFAULT_GUNICORN_CONF=/app/gunicorn_conf.pyelif [ -f /app/app/gunicorn_conf.py ]; then DEFAULT_GUNICORN_CONF=/app/app/gunicorn_conf.pyelse DEFAULT_GUNICORN_CONF=/gunicorn_conf.pyfi export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF } export WORKER_CLASS=${WORKER_CLASS:-"uvicorn.workers.UvicornWorker"} PRE_START_PATH=${PRE_START_PATH:-/app/prestart.sh} echo "Checking for script in $PRE_START_PATH " if [ -f $PRE_START_PATH ] ; then echo "Running script $PRE_START_PATH " . "$PRE_START_PATH " else echo "There is no script $PRE_START_PATH " fi exec gunicorn -k "$WORKER_CLASS " -c "$GUNICORN_CONF " "$APP_MODULE "
2.3.2 Gunicorn配置脚本 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 import jsonimport multiprocessingimport os workers_per_core_str = os.getenv("WORKERS_PER_CORE" , "1" ) max_workers_str = os.getenv("MAX_WORKERS" ) use_max_workers = None if max_workers_str: use_max_workers = int (max_workers_str) web_concurrency_str = os.getenv("WEB_CONCURRENCY" , None ) host = os.getenv("HOST" , "0.0.0.0" ) port = os.getenv("PORT" , "80" ) bind_env = os.getenv("BIND" , None ) use_loglevel = os.getenv("LOG_LEVEL" , "info" )if bind_env: use_bind = bind_envelse : use_bind = f"{host} :{port} " cores = multiprocessing.cpu_count() workers_per_core = float (workers_per_core_str) default_web_concurrency = workers_per_core * coresif web_concurrency_str: web_concurrency = int (web_concurrency_str) assert web_concurrency > 0 else : web_concurrency = max (int (default_web_concurrency), 2 ) if use_max_workers: web_concurrency = min (web_concurrency, use_max_workers) accesslog_var = os.getenv("ACCESS_LOG" , "-" ) use_accesslog = accesslog_var or None errorlog_var = os.getenv("ERROR_LOG" , "-" ) use_errorlog = errorlog_var or None graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT" , "120" ) timeout_str = os.getenv("TIMEOUT" , "120" ) keepalive_str = os.getenv("KEEP_ALIVE" , "5" ) loglevel = use_loglevel workers = web_concurrency bind = use_bind errorlog = use_errorlog worker_tmp_dir = "/dev/shm" accesslog = use_accesslog graceful_timeout = int (graceful_timeout_str) timeout = int (timeout_str) keepalive = int (keepalive_str) log_data = { "loglevel" : loglevel, "workers" : workers, "bind" : bind, "graceful_timeout" : graceful_timeout, "timeout" : timeout, "keepalive" : keepalive, "errorlog" : errorlog, "accesslog" : accesslog, "workers_per_core" : workers_per_core, "use_max_workers" : use_max_workers, "host" : host, "port" : port, }print (json.dumps(log_data))
3. 压力测试 ab就是Apache Benchmark的缩写,顾名思义它是Apache组织开发的一款web压力测试工具,优点是使用方便,统计功能强大。
首先是安装,Ubuntu和CentOS目前都提供自动安装命令 (至少Ubuntu 14, CentOS 6可以)
3.1 使用 ab 一般常用参数 就是 -n, -t ,和 -c。
-c (concurrency)表示用多少并发来进行测试;
-t 表示测试持续多长时间;
-n 表示要发送多少次测试请求。
一般 -t 或者 -n 选一个用。
例如模拟GET请求进行测试:
ab -n 20000 -c 1000 http://0.0.0.0:8989/firmware/latest?model=xxx
3.2 结果分析 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 Server Software: uvicorn Server Hostname: 192.168 .52 .224 Server Port: 8989 Document Path: /firmware/latest?model=RLM2003EI Document Length: 204 bytes Concurrency Level: 1000 Time taken for tests: 3.843 seconds Complete requests: 20000 Failed requests: 0 Total transferred: 6600000 bytes HTML transferred: 4080000 bytes Requests per second: 5204.07 [Time per request: 192.157 [ms ] (mean) Time per request: 0.192 [ms ] (mean , across all concurrent requests) Transfer rate: 1677.09 [Kbytes/sec ] received Connection Times (ms) min mean [+/-sd ] median max Connect: 0 2 4.5 0 31 Processing: 1 186 285.7 77 3721 Waiting: 1 183 285.6 74 3721 Total: 1 187 286.8 78 3730 Percentage of the requests served within a certain time (ms) 50 % 78 66 % 138 75 % 193 80 % 245 90 % 505 95 % 783 98 % 1092 99 % 1470 100 % 3730 (longest request)
我们对产生的结果进行分析,具体不展开,详细看Apache ab性能测试结果分析
3.3 socket: Too many open files ulimit -n
查看最大文件打开数 ,一般情况下都是1024
我们可以ulimit -n 65535
,临时设置下最大文件数,重启终端无效。
永久设置方法:vim /etc/security/limits.conf
加入 * - nofile 8192