作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Ivan Poleschyuk
Verified Expert in Engineering

Ivan是一位充满激情的机器学习工程师和全栈软件开发人员,拥有计算机科学硕士学位.

Read More

PREVIOUSLY AT

livepeer
Share

作为一名机器学习工程师 计算机视觉专家我发现自己经常用Flask创建api甚至web应用程序. In this post, 我想分享一些技巧和有用的食谱来构建一个完整的生产就绪的Flask应用程序.

我们将讨论以下主题:

  1. 配置管理. 任何现实生活中的应用程序至少都有一个包含特定阶段的生命周期, 这就是发展, testing, and deployment. On each stage, 应用程序代码应该在稍微不同的环境中工作, 这需要不同的设置, 比如数据库连接字符串, external APIs keys, and URLs.
  2. 使用Gunicorn的自托管Flask应用程序. 尽管Flask有一个内置的web服务器, as we all know, 它不适合生产环境,需要放在一个真正的web服务器后面,能够通过WSGI协议与Flask通信. 一个常见的选择是 Gunicorn- Python WSGI HTTP服务器.
  3. 提供静态文件和代理请求 Nginx. 而作为一个HTTP web服务器, Gunicorn, in turn, 应用服务器不适合面向web吗. 这就是为什么我们需要Nginx作为反向代理并提供静态文件. 以防我们需要将应用程序扩展到多个服务器, Nginx也会照顾到负载平衡.
  4. 在专用Linux服务器上的Docker容器中部署应用程序. 很长一段时间以来,容器化部署一直是软件设计的重要组成部分. 我们的应用程序也不例外,它将被整齐地打包在自己的容器(多个容器)中, in fact).
  5. 为应用程序配置和部署PostgreSQL数据库. 数据库结构和 migrations will be managed by Alembic with SQLAlchemy 提供对象关系映射.
  6. Setting up a Celery 处理长时间运行任务的任务队列. 每个应用程序最终都需要它来卸载时间或计算密集型进程——无论是邮件发送, 自动管理数据库或处理上传的图像——来自外部工作者的web服务器线程.

创建Flask应用

让我们从创建应用程序代码和资产开始. 请注意,我不会在这篇文章中讨论正确的Flask应用程序结构. 为了简洁和清晰,演示应用程序由最少数量的模块和包组成.

首先,创建一个目录结构并初始化一个空的Git存储库.

mkdir flask-deploy
cd flask-deploy
# init GIT repo
git init
创建文件夹结构
Mkdir静态任务模型配置 
#使用pipenv安装所需的包,这将创建一个Pipfile
Pipenv安装flask-restful flask-sqlalchemy flask-migrate celery
#创建测试静态资产
echo "Hello World!" > static/hello-world.txt

接下来,我们将添加代码.

config/__init__.py

在配置模块中,我们将定义我们的小型配置管理框架. 这个想法是使应用程序的行为根据配置预先选择的 APP_ENV environment variable, plus, 添加一个选项,以便在需要时使用特定环境变量覆盖任何配置设置.

import os
import sys
import config.settings

创建与指定env对应的设置对象
APP_ENV = os.environ.get(“APP_ENV”、“开发”)
_current = getattr(sys ..modules['config.设置”,“{0}配置”.format(APP_ENV))()

#将属性复制到模块以方便使用
For atr in [f For f in dir(_current) if not '__' in f]:
   # environment可以覆盖任何内容
   val = os.environ.获取(atr, getattr(_current, atr))
   setattr(sys.module [__name__], atr, val)


def as_dict():
   res = {}
   For atr in [f For f in dir(config) if not '__' in f]:
       Val = getattr(config, atr)
       res[atr] = val
   return res

config/settings.py

这是一组配置类,其中一个由 APP_ENV variable. 当应用程序运行时,代码插入 __init__.py 将实例化这些类中的一个,用特定的环境变量覆盖字段值, if they are present. 我们将在稍后初始化Flask和芹菜配置时使用最终配置对象.

class BaseConfig():
   API_PREFIX = '/api'
   TESTING = False
   DEBUG = False


类DevConfig (BaseConfig):
   FLASK_ENV = 'development'
   DEBUG = True
   SQLALCHEMY_DATABASE_URI = 'postgresql://db_user:db_password@db-postgres:5432/flask-deploy'
   CELERY_BROKER = 'pyamqp://rabbit_user:rabbit_password@broker-rabbitmq//'
   CELERY_RESULT_BACKEND = 'rpc://rabbit_user:rabbit_password@broker-rabbitmq//'


类ProductionConfig (BaseConfig):
   FLASK_ENV = 'production'
   SQLALCHEMY_DATABASE_URI = 'postgresql://db_user:db_password@db-postgres:5432/flask-deploy'
   CELERY_BROKER = 'pyamqp://rabbit_user:rabbit_password@broker-rabbitmq//'
   CELERY_RESULT_BACKEND = 'rpc://rabbit_user:rabbit_password@broker-rabbitmq//'


类TestConfig (BaseConfig):
   FLASK_ENV = 'development'
   TESTING = True
   DEBUG = True
   #让芹菜在同一进程中同步执行任务
   CELERY_ALWAYS_EAGER = True

tasks/__init__.py

tasks包包含芹菜初始化代码. Config package, 在初始化时,已经在模块级别复制了所有设置, 用于更新芹菜配置对象,以防我们将来会有一些芹菜特定的设置,例如, 计划任务和工作超时.

从芹菜进口芹菜
import config


def make_celery():
   芹菜=芹菜(__name__, broker=config).CELERY_BROKER)
   celery.conf.update(config.as_dict())
   return celery


芹菜= make_芹菜()

tasks/celery_worker.py

启动和初始化一个Celery worker需要这个模块, 哪一个将在一个单独的Docker容器中运行. 它初始化Flask应用程序上下文,使其能够访问与应用程序相同的环境. 如果不需要,这些线可以安全地移除.

从应用程序导入create_app

app = create_app()
app.app_context().push()

从tasks中导入芹菜

api/__init__.py

接下来是API包,它使用Flask-Restful包定义REST API. 我们的应用程序只是一个演示,将只有两个端点:

  • /process_data -在一个Celery worker上启动一个虚拟的long操作,并返回一个新任务的ID.
  • /tasks/ —根据任务ID返回任务状态.
import time
从flask导入jsonify
从flask_rest中导入Api
从tasks中导入芹菜
import config

api = api (prefix=config . api.API_PREFIX)


类TaskStatusAPI(资源):
   Def (self, task_id):
       task = celery.AsyncResult(task_id)
       return jsonify(task.result)


类DataProcessingAPI(资源):
   def post(self):
       task = process_data.delay()
       返回{'task_id':任务.id}, 200


@celery.task()
def process_data():
   time.sleep(60)


#数据处理端点
api.add_resource (DataProcessingAPI / process_data)

#任务状态端点
api.add_resource(TaskStatusAPI, '/tasks/')

models/__init__.py

的SQLAlchemy模型 User 对象,以及数据库引擎初始化代码. The User 对象不会被我们的演示应用以任何有意义的方式使用, 但我们需要它来确保数据库迁移工作和SQLAlchemy-Flask集成设置正确.

import uuid

从SQLAlchemy导入SQLAlchemy

db = SQLAlchemy()


class User(db.Model):
   id = db.Column(db.String(), primary_key=True, default=lambda: str(uuid . key.uuid4()))
   username = db.Column(db.String())
   email = db.Column(db.字符串()、独特的= True)

注意UUID是如何通过默认表达式自动生成为对象ID的.

app.py

最后,让我们创建一个主Flask应用程序文件.

从烧瓶导入烧瓶

logging.basicConfig(=日志级别.DEBUG,
                   格式= ' [% (asctime) s]: {} % (levelname) %(消息)年代'.format(os.getpid()),
                   datefmt = ' % Y - % m - H % d %: % m: % S的,
                   handlers=[logging.StreamHandler()])

logger = logging.getLogger()


def create_app():
   logger.在{配置文件}中启动应用程序.APP_ENV}环境”)
   app = Flask(__name__)
   app.config.from_object(配置)
   api.init_app(app)
   #初始化SQLAlchemy
   db.init_app(app)

   # define hello world page

   @app.route('/')
   def hello_world():
       return 'Hello, World!'
   return app


如果__name__ == "__main__":
   app = create_app()
   app.run(host='0.0.0.0', debug=True)
  
  
    

Here we are:

  • 以适当的格式配置基本日志,包括时间、级别和进程ID
  • Defining the Flask app creation 函数的API初始化和“Hello, world .!” page
  • 定义在开发期间运行应用程序的入口点

wsgi.py

此外,我们还需要一个单独的模块来运行带有Gunicorn的Flask应用. 它将只有两行:

从应用程序导入create_app
app = create_app()

应用程序代码准备好了. 下一步是创建一个Docker配置.

建造码头集装箱

我们的应用需要多个Docker容器来运行:

  1. 应用程序容器,用于提供模板页面和公开API端点. 在生产环境中拆分这两个函数是个好主意, 但我们的演示应用中没有任何模板化页面. 容器将运行Gunicorn web服务器,该服务器将通过WSGI协议与Flask通信.
  2. 执行长任务的Celery worker容器. 这是同一个应用程序容器, 而是使用自定义的运行命令来启动芹菜, instead of Gunicorn.
  3. 芹菜搅拌器——和上面一样, 但对于定期调用的任务, 比如删除那些从未确认过电子邮件的用户的账户.
  4. RabbitMQ container. 芹菜需要一个消息代理来在worker和应用程序之间进行通信,并存储任务结果. RabbitMQ是一个常见的选择,但你也可以使用Redis或Kafka.
  5. 数据库容器与PostgreSQL.

轻松管理多个容器的一种自然方式是使用Docker Compose. 但首先,我们需要创建一个Dockerfile来为我们的应用程序构建一个容器映像. 让我们把它放到项目目录中.

FROM python:3.7.2

运行pip install pipenv

ADD . /flask-deploy

WORKDIR / flask-deploy

执行命令pipenv install——system——skip-lock

运行pip install gunicorn[gevent]

EXPOSE 5000

CMD gunicorn——worker-class gevent——workers 8——bind 0.0.0.0:5000 wsgi:app——max-requests 10000——timeout 5——keep-alive 5——日志级信息

这个文件指示Docker:

  • 使用Pipenv安装所有依赖项
  • 向容器中添加一个应用程序文件夹
  • 将TCP端口5000暴露给主机
  • 将容器的默认启动命令设置为一个Gunicorn调用

让我们进一步讨论最后一行发生了什么. 它运行Gunicorn,指定工人类为 gevent. Gevent是用于协作多任务处理的轻量级并发库. 它在I/O绑定负载上提供了相当大的性能提升, 与操作系统抢占式多任务线程相比,提供了更好的CPU利用率. The --workers 参数为工作进程数. 将其设置为服务器上的核数是个好主意.

一旦我们有了应用容器的Dockerfile,我们就可以创建一个 docker-compose.yml 文件,该文件将定义应用程序运行所需的所有容器.

version: '3'
services:
 broker-rabbitmq:
   image: "rabbitmq:3.7.14-management"
   environment:
     - RABBITMQ_DEFAULT_USER = rabbit_user
     - RABBITMQ_DEFAULT_PASS = rabbit_password
 db-postgres:
   image: "postgres:11.2"
   environment:
     - POSTGRES_USER = db_user
     - POSTGRES_PASSWORD = db_password
 migration:
   build: .
   environment:
     - APP_ENV=${APP_ENV}
   命令:flask db upgrade
   depends_on:
     - db-postgres
 api:
   build: .
   ports:
    - "5000:5000"
   environment:
     - APP_ENV=${APP_ENV}
   depends_on:
     - broker-rabbitmq
     - db-postgres
     - migration
 api-worker:
   build: .
   命令:celery worker——workdir=. -A tasks.芹菜——loglevel = info
   environment:
     - APP_ENV=${APP_ENV}
   depends_on:
     - broker-rabbitmq
     - db-postgres
     - migration
 api-beat:
   build: .
   命令:celery beat -A tasks.芹菜——loglevel = info
   environment:
     - APP_ENV=${APP_ENV}
   depends_on:
     - broker-rabbitmq
     - db-postgres
     - migration

我们定义了以下服务:

  • broker-rabbitmq —RabbitMQ消息代理容器. 连接凭据由环境变量定义
  • db-postgres -一个PostgreSQL容器及其凭证
  • migration -一个应用程序容器,它将使用Flask-Migrate执行数据库迁移并退出. API容器依赖于它,并将在之后运行.
  • api —主应用容器
  • api-worker and api-beat -为从API接收的任务和计划任务运行Celery worker的容器

每个应用容器也将接收 APP_ENV variable from the docker-compose up command.

一旦我们准备好了所有的应用程序资产, 我们把它们放到GitHub上, 哪个将帮助我们在服务器上部署代码.

git add *
git commit -a -m 'Initial commit'
Git远程添加origin git@github.com:你的名字/ flask-deploy.git
Git push -u origin master

配置服务器

我们的代码现在在GitHub上, 剩下的就是执行初始服务器配置和部署应用程序. 在我的例子中,服务器是一个运行AMI Linux的AWS实例. 对于其他版本的Linux,指令可能略有不同. 我还假设服务器已经有一个外部IP地址, DNS中配置了指向该IP的记录, 和为该域颁发SSL证书.

Security tip: 不要忘记为HTTP(S)通信允许端口80和443, 主机控制台中用于SSH的端口22(或使用 iptables),并关闭所有其他端口的外部访问! 一定要做同样的 IPv6 protocol!

安装依赖关系

首先,我们需要Nginx和Docker在服务器上运行,再加上Git来提取代码. 让我们通过SSH登录并使用包管理器来安装它们.

安装-y docker-compose nginx git

Configuring Nginx

下一步是配置Nginx. The main nginx.conf 配置文件通常是原样的. 不过,一定要检查一下它是否适合你的需要. 对于我们的应用程序,我们将创建一个新的配置文件 conf.d folder. 顶级配置有一个包含所有的指令 .conf files from it.

cd /etc/nginx/conf.d
执行命令vim flask-deploy.conf

这是一个Nginx的Flask站点配置文件,包括电池. 它具有以下特点:

  1. SSL is configured. 您应该拥有域的有效证书,例如.g., a free Let’s Encrypt certificate.
  2. www.your-site.com 请求被重定向到 your-site.com
  3. HTTP请求被重定向到安全的HTTPS端口.
  4. 反向代理配置为将请求传递到本地端口5000.
  5. 静态文件由Nginx从本地文件夹提供.
server {
    listen   80;
    listen   443;
    server_name  www.your-site.com;
    检查您的证书路径!
    ssl_certificate /etc/nginx/ssl/your-site.com/fullchain.crt;
    ssl_certificate_key /etc/nginx/ssl/your-site.com/server.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    #重定向到非www域名
    返回301 http://your-site.com$request_uri;
}

# HTTP到HTTPS重定向
server {
        listen 80;
        server_name站点.com;
        返回301 http://your-site.com$request_uri;
}

server {
        listen 443 ssl;
        检查您的证书路径!
        ssl_certificate /etc/nginx/ssl/your-site.com/fullchain.crt;
        ssl_certificate_key /etc/nginx/ssl/your-site.com/server.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers HIGH:!aNULL:!MD5;
        #影响用户可以通过HTTP POST上传的文件大小
        client_max_body_size 10米;
        server_name站点.com;
        location / {
                包括/etc/nginx/mime.types;
                根/home/ec2-user/flask-deploy /静态;
                #如果没有找到静态文件-将请求传递给Flask
                Try_files $uri @flask
        }
  location @flask {
                add_header 'Access-Control-Allow-Origin' '*' always;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';

                proxy_read_timeout 10;
                proxy_send_timeout 10;
                send_timeout 60;
                resolver_timeout 120;
                client_body_timeout 120;
                
                #设置headers向Flask传递请求信息
                proxy_set_header $http_host;
                proxy_set_header x - forward - proto $scheme;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_redirect;
    
                proxy_pass http://127.0.0.1:5000$uri;
        }
}

编辑完文件后,运行命令 sudo nginx -s reload 看看是否有错误.

设置GitHub凭据

有一个单独的“部署”VCS帐户来部署项目和CI/CD系统是一个很好的做法. 这样,您就不会有暴露自己帐户凭据的风险. 以进一步保护项目存储库, 您还可以将此类帐户的权限限制为只读访问. 对于GitHub存储库,您需要一个组织帐户来完成此操作. 部署我们的演示应用程序, 我们只需在服务器上创建一个公钥,并在GitHub上注册它,就可以访问我们的项目,而不必每次都输入凭据.

要创建一个新的SSH密钥,请运行:

cd ~/.ssh
Ssh-keygen -b 2048 -t rsa -f id_rsa.pub -q -N "" -C "deploy"

然后登录GitHub和 add your public key from ~/.ssh/id_rsa.pub in account settings.

Deploying an App

最后的步骤非常简单——我们需要从GitHub获取应用程序代码,并使用Docker Compose启动所有容器.

cd ~
Git克隆http://github.com/your-name/flask-deploy.git
git checkout master
APP_ENV=生产docker-compose up -d

省略它可能是个好主意 -d (它以分离模式启动容器),以便在终端中查看每个容器的输出并检查可能的问题. 另一种选择是使用 docker logs afterward. 让我们看看是否所有的容器都在运行 docker ps.

image_alt_text

Great. 所有五个容器都已启动并运行. Docker Compose根据在Docker - Compose中指定的服务自动分配容器名称.yml. 现在是测试整个配置如何工作的时候了! 最好从外部机器运行测试,以确保服务器具有正确的网络设置.

#测试HTTP协议,你应该得到一个301响应
curl your-site.com
HTTPS请求应该返回Hello World消息
curl http://your-site.com
#和nginx应该正确发送测试静态文件:
curl http://your-site.com/hello-world.txt

That’s it. 我们有一个极简主义, 而是在AWS实例上运行的应用程序的完全生产就绪配置. 希望它能帮助您快速开始构建实际应用程序并避免一些常见错误! 完整的代码是可用的 在GitHub存储库上.

Conclusion

In this article, 我们讨论了一些结构化的最佳实践, configuring, 将Flask应用打包并部署到生产环境中. 这是一个非常大的话题,不可能在一篇博客文章中完全涵盖. 以下是我们没有提到的一些重要问题:

本文不涉及以下内容:

  • 持续集成和持续部署
  • Automatic testing
  • Log shipping
  • API monitoring
  • 将应用程序扩展到多个服务器
  • 源代码中的凭证保护

但是,您可以使用本博客上的其他一些很棒的资源来学习如何做到这一点. 例如,要探索日志记录,请参见 Python日志:深度教程,或有关CI/CD和自动化测试的一般概述,请参阅 如何构建有效的初始部署管道. 我把这些方法的实现留给读者作为练习.

Thanks for reading!

了解基本知识

  • 什么是Flask应用程序?

    Flask应用程序是使用Flask库为web构建的Python应用程序.

  • Flask和Django哪个更好?

    这两个框架都适用于各种与web相关的任务. Flask通常用于构建web服务,而这些web服务并不是完全成熟的网站, 并以其灵活性而闻名.

  • What is Gunicorn?

    Gunicorn是一个Python WSGI HTTP服务器. 它的常见用例是在生产环境中通过WSGI接口为Flask或Django Python应用程序提供服务.

  • 什么是芹菜,为什么使用它与Flask?

    像Flask这样的Web服务器不适合长时间运行的任务,比如视频处理. 芹菜是一个任务队列,用于以方便和异步的方式处理这些任务. 任务数据存储在支持的后端存储引擎中,如RabbitMQ或Redis.

  • 为什么要使用Docker Compose?

    Docker Compose是一个方便的Docker工具, 哪一个允许通过定义服务在更高的抽象级别上使用容器. 它还处理启动和停止容器的常见场景.

  • 为什么我们需要一个Nginx web服务器为Flask?

    而Gunicorn则非常适合作为应用服务器, 由于安全考虑和web请求处理限制,让它面对Internet并不是一个好主意. 一个常见的模式是在Gunicorn前面使用Nginx作为反向代理来路由服务之间的请求.

  • 什么时候PostgreSQL是一个好的选择?

    PostgreSQL是一个成熟的高性能关系数据库引擎,有许多插件和库,适用于所有主要的编程语言. 如果您的项目(无论大小)只需要一个数据库,那么它是一个完美的选择. 另外,它是开源和免费的.

聘请Toptal这方面的专家.
Hire Now
Ivan Poleschyuk

Ivan Poleschyuk

Verified Expert in Engineering

阿拉伯联合酋长国迪拜

2018年2月21日成为会员

About the author

Ivan是一位充满激情的机器学习工程师和全栈软件开发人员,拥有计算机科学硕士学位.

Read More
作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

PREVIOUSLY AT

livepeer

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.