Docker部署FastApi详解,这一篇就够了

首先废话一下,FastAPI是一种现代,快速(高性能)的Web框架,用于基于标准Python类型提示使用Python 3.6+构建API。据说是go+nodejs的竞争对手。

FastAPI 官方文档

这里主要讲的是uvicorn-gunicorn-fastapi 这个Fastapi的官方镜像,主要的技术细节:

Uvicorn

Uvicorn是一款闪电般的“ ASGI”服务器。

它在单个过程中运行异步Python Web代码。

Gunicorn

您可以使用Gunicorn管理Uvicorn和运行多个这些并发进程。

这样,您将获得最佳的并发性和并行性。

FastAPI

FastAPI是一种现代,快速(高性能)的Web框架,用于使用Python 3.6+构建API。

反正主要使用的技术就是UvicornGunicorn官网介绍说其是站在巨人肩膀上的框架,也确实有其流弊之处吧。

这里主要介绍 tiangolo/uvicorn-gunicorn-fastapi ,适用于生产环境,官网的其他镜像也至少改变的操作系统的版本为了缩减体积。

git 传送 ☞ uvicorn-gunicorn-fastapi

用法:

#Dockerfile

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7

COPY ./app /app
image.gif

dockerfile的意思就是把你的代码(./app)复制到 /app文件夹中。

当然前提是至少需要一个main.py的配置文件下面是镜像能读取的两个默认位置,选择一个去放就好了。

/app/app/main.py

/app/main.py

main.py类似于这样:

#这个其实就是镜像中自带的main.py 位置/app/main.py
import sys

from fastapi import FastAPI

version = f"{sys.version_info.major}.{sys.version_info.minor}"

app = FastAPI()

@app.get("/")
async def read_root():
    message = f"Hello world! From FastAPI running on Uvicorn with Gunicorn. Using Python {version}"
    return {"message": message}
image.gif

你自己的目录结构大概就是这样:

.
├── app
│   └── main.py
└── Dockerfile

image.gif

然后就是构建镜像:

# 在dockerfile的路径下执行 myimage 替换成自己的起的名字作为镜像名
docker build -t myimage ./  

然后你可以试着启动一下了:

docker run -d --name mycontainer -p 80:80 myimage

当然,上面讲的情况是把你自己代码放进去部署,如果只是想验证以下效果不用去看上面的直接执行:

docker pull tiangolo/uvicorn-gunicorn-fastapi:python3.7

docker run -d --name fastapidemo -p 80:80 tiangolo/uvicorn-gunicorn-fastapi:python3.7

然后用浏览器打开以下链接测试以下:

http://127.0.0.1/   
#返回{"message":"Hello world! From FastAPI running on Uvicorn with Gunicorn. Using Python 3.7"}

http://127.0.0.1/docs
#API文档 

http://127.0.0.1/redoc
# 备用API文档

API文档类似:

image

不用惊讶,FastApi内置了swgger文档 ,你只要正常写方法就可以了,但是类型还是要注意一下像下面这样是完全没用问题的:

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

当然肯定也有更复杂的需求,比如:安装一个依赖管理工具 Poetry,你可以这样组织你的dockerfile:

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7

# Install Poetry
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
    cd /usr/local/bin && \
    ln -s /opt/poetry/bin/poetry && \
    poetry config virtualenvs.create false

# Copy using poetry.lock* in case it doesn't exist yet
COPY ./app/pyproject.toml ./app/poetry.lock* /app/

RUN poetry install --no-root --no-dev

COPY ./app /app

用法挺简单,但是里面包含的功能一点也不简单。

我们上面使用的镜像 tiangolo/uvicorn-gunicorn-fastapi:python3.7 如果追溯它的源镜像那就是下面这个:

# DockerFile tiangolo/uvicorn-gunicorn-fastapi:python3.7
FROM python:3.7

LABEL maintainer="Sebastian Ramirez <tiangolo@gmail.com>"

RUN pip install --no-cache-dir uvicorn gunicorn

COPY ./start.sh /start.sh
RUN chmod +x /start.sh

COPY ./gunicorn_conf.py /gunicorn_conf.py

COPY ./start-reload.sh /start-reload.sh
RUN chmod +x /start-reload.sh

COPY ./app /app
WORKDIR /app/

ENV PYTHONPATH=/app

EXPOSE 80

# Run the start script, it will check for an /app/prestart.sh script (e.g. for migrations)
# And then will start Gunicorn with Uvicorn
CMD ["/start.sh"]

git 地址我也贴一下 ☞ tiangolo/uvicorn-gunicorn-fastapi:python3.7 构建代码

下面重点来了 ,如何使用 环境变量 去更改默认的配置:

(1)MODULE_NAME:

由Gunicorn导入的Python“模块”(文件),该模块将在变量中包含实际的应用程序,就是上面说的main.lpy

默认值:

如果你的main.py 是这个路径 /app/app/main.py 那么 值为 app.main

路径是 /app/main.py 值为main

在运行的时候这样就改变默认的值:

docker run -d -p 80:80 -e MODULE_NAME="custom_app.custom_main" myimage

(2)VARIABLE_NAME

默认: app

如果你的main文件是这样写的 你需要把它改成api

from fastapi import FastAPI

api = FastAPI()

@api.get("/")
def read_root():
    return {"Hello": "World"}
docker run -d -p 80:80 -e VARIABLE_NAME="api" myimage
# 运行时修改VARIABLE_NAME

(3)APP_MODULE

把上面两个变量结合起来就是这个变量

docker run -d -p 80:80 -e APP_MODULE="custom_app.custom_main:api" myimage

(4)GUNICORN_CONF

Gunicorn Python配置文件的路径。

默认:

  • /app/gunicorn_conf.py 如果存在
  • /app/app/gunicorn_conf.py 如果存在
  • /gunicorn_conf.py (包含的默认值)

设置方法:

docker run -d -p 80:80 -e GUNICORN_CONF="/app/custom_gunicorn_conf.py" myimage

(4)常用的 HOST(容器内使用)PORT BIND

默认就是0.0.0.0:80 略。

(5)LOG_LEVEL Gunicorn****日志级别

  • debug
  • info
  • warning
  • error
  • critical

默认:info

docker run -d -p 80:8080 -e LOG_LEVEL="warning" myimage

(6)TIMEOUT沉默(silent ****)了多少秒的Workers 被杀死并重新启动。

默认:120

像FastAPI这样的Uvicorn和ASGI框架是异步的,而不是同步的。因此,与同步工作器相比,拥有更高的超时可能是安全的。

您可以将其设置为:

docker run -d -p 80:8080 -e TIMEOUT="20" myimage

(7)GRACEFUL_TIMEOUT

正常工作人员的超时重新启动。在Gunicorn文档中了解更多有关此内容的信息:graceful-timeout

默认:120

docker run -d -p 80:8080 -e GRACEFUL_TIMEOUT="20" myimage

(8)KEEP_ALIVE

等待“保持活动”连接上的请求的秒数。

Gunicorn docs:keepalive中阅读更多有关它的信息。

默认情况下,设置为2

您可以将其设置为:

docker run -d -p 80:8080 -e KEEP_ALIVE="20" myimage

(9)ACCESS_LOG

要写入的访问日志文件。

默认情况下"-",表示stdout(在Docker日志中打印)。

如果要禁用ACCESS_LOG,请将其设置为空值。

例如,您可以通过以下方式禁用它:

docker run -d -p 80:8080 -e ACCESS_LOG=  myimage
#注意这里表示设置为空值

(10)ERROR_LOG

要写入的错误日志文件。

默认情况下"-",表示stderr(在Docker日志中打印)。

如果要禁用ERROR_LOG,请将其设置为空值。

例如,您可以通过以下方式禁用它:

docker run -d -p 80:8080 -e ERROR_LOG = myimage

(11)GUNICORN_CMD_ARGS

Gunicorn的任何其他命令行设置都可以在GUNICORN_CMD_ARGS环境变量中传递。

Gunicorn文档:设置中阅读更多相关信息。

这些设置将优先于其他环境变量和任何Gunicorn配置文件。

例如,如果您具有要使用的自定义TLS / SSL证书,则可以将其复制到Docker映像或将其安装在容器中,然后设置--keyfile--certfile文件的位置,例如:

docker run -d -p 80:8080 -e GUNICORN_CMD_ARGS = “” --keyfile = / secrets / key.pem --certfile = / secrets / cert.pem “ -e PORT = 443

注意:建议不要使用Traefik之类的“ TLS终止代理”,而是自己处理TLS / SSL并在容器中进行配置。您可以在有关HTTPSFastAPI文档中阅读有关它的更多信息。

(12)PRE_START_PATH

预启动脚本的路径

默认:/app/prestart.sh

设置方法:

docker run -d -p 80:8080 -e PRE_START_PATH="/custom/script.sh" myimage

这个重点说一下默认的文件如下:

#! /usr/bin/env sh
# 文件路径:/app/prestart.sh 
echo "Running inside /app/prestart.sh, you could add migrations to this file, e.g.:"

echo "
#! /usr/bin/env bash

# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head
"

启动时就是这样的,在这里加载一些需要的脚本就好,一般写等待10s是给数据库启动留下的时间。

image

像 /app/prestart.sh 还有gunicorn_conf.py这种开发常用的配置文件推荐的方法:

是写你自己的dockerfile的时候,编写命令把他们的从默认位置替换掉就好了。

  • /app/gunicorn_conf.py
  • /app/app/gunicorn_conf.py
  • /gunicorn_conf.py

当然,环境变量也不是必须在启动时从docker run 后面指定,使用dockerfile的ENV设置能获得更好的体验

其他功能 :开发时重载

基于 : /start-reload.sh

生产环境是默认使用的是 /start.sh

使用方法:

docker run -d -p 80:80 -v $(pwd):/app myimage /start-reload.sh

  • -v $(pwd):/app:表示该目录$(pwd)应作为卷挂载到位于的容器内/app
    • $(pwd):运行pwd(“打印工作目录”),并将其作为字符串的一部分。
  • /start-reload.sh/start-reload.sh在命令末尾添加一些内容(如),用此命令替换默认的“命令”。在这种情况下,它将/start.sh开发替代项替换为default()/start-reload.sh

由于/start-reload.sh不与Gunicorn一起运行,因此您放入gunicorn_conf.py文件中的任何配置都将不适用。

但是这些环境变量的工作原理与上述相同:

  • MODULE_NAME
  • VARIABLE_NAME
  • APP_MODULE
  • HOST
  • PORT
  • LOG_LEVEL

贴一下这俩文件代码:

# /start.sh
#! /usr/bin/env sh
set -e

if [ -f /app/app/main.py ]; then
    DEFAULT_MODULE_NAME=app.main
elif [ -f /app/main.py ]; then
    DEFAULT_MODULE_NAME=main
fi
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.py
elif [ -f /app/app/gunicorn_conf.py ]; then
    DEFAULT_GUNICORN_CONF=/app/app/gunicorn_conf.py
else
    DEFAULT_GUNICORN_CONF=/gunicorn_conf.py
fi
export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF}
export WORKER_CLASS=${WORKER_CLASS:-"uvicorn.workers.UvicornWorker"}

# If there's a prestart.sh script in the /app directory or other path specified, run it before starting
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

# Start Gunicorn
exec gunicorn -k "$WORKER_CLASS" -c "$GUNICORN_CONF" "$APP_MODULE"
# /start-reload.sh
#! /usr/bin/env sh
set -e

if [ -f /app/app/main.py ]; then
    DEFAULT_MODULE_NAME=app.main
elif [ -f /app/main.py ]; then
    DEFAULT_MODULE_NAME=main
fi
MODULE_NAME=${MODULE_NAME:-$DEFAULT_MODULE_NAME}
VARIABLE_NAME=${VARIABLE_NAME:-app}
export APP_MODULE=${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"}

HOST=${HOST:-0.0.0.0}
PORT=${PORT:-80}
LOG_LEVEL=${LOG_LEVEL:-info}

# If there's a prestart.sh script in the /app directory or other path specified, run it before starting
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

# Start Uvicorn with live reload
exec uvicorn --reload --host $HOST --port $PORT --log-level $LOG_LEVEL "$APP_MODULE"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342