Javascript required
Skip to content Skip to sidebar Skip to footer

Uploading Image to Server That Is Too Big Flask Python

As a machine learning engineer and figurer vision expert, I find myself creating APIs and even spider web apps with Flask surprisingly often. In this post, I want to share some tips and useful recipes for building a complete production-ready Flask awarding.

We will cover the following topics:

  1. Configuration direction. Whatsoever real-life application has a lifecycle with specific stages—at the very least, information technology would exist development, testing, and deployment. On each stage, the application code should piece of work in a slightly different environs, which requires having a different set of settings, like database connectedness strings, external APIs keys, and URLs.
  2. Self-hosting Flask application with Gunicorn. Although Flask has a born web server, as we all know, information technology's not suitable for production and needs to be put behind a real web server able to communicate with Flask through a WSGI protocol. A common choice for that is Gunicorn—a Python WSGI HTTP server.
  3. Serving static files and proxying asking with Nginx. While being an HTTP web server, Gunicorn, in plow, is an application server not suited to face the web. That'south why we need Nginx as a reverse proxy and to serve static files. In case we demand to scale up our application to multiple servers, Nginx volition accept care of load balancing as well.
  4. Deploying an app inside Docker containers on a dedicated Linux server. Containerized deployment has been an essential part of software design for quite a long fourth dimension now. Our application is no different and will be neatly packaged in its own container (multiple containers, in fact).
  5. Configuring and deploying a PostgreSQL database for the application. Database structure and migrations will be managed by Alembic with SQLAlchemy providing object-relational mapping.
  6. Setting upwards a Celery task queue to handle long-running tasks. Every application will somewhen require this to offload fourth dimension or computation intensive processes—be it mail sending, automatic database housekeeping or processing of uploaded images—from web server threads on external workers.

Creating the Flask App

Let'due south start by creating an application code and assets. Please note that I will not address proper Flask application construction in this post. The demo app consist of minimal number of modules and packages for the sake of brevity and clarity.

Outset, create a directory structure and initialize an empty Git repository.

          mkdir flask-deploy cd flask-deploy # init GIT repo git init # create folder structure mkdir static tasks models config  # install required packages with pipenv, this volition create a Pipfile pipenv install flask flask-restful flask-sqlalchemy flask-migrate celery # create test static asset repeat "Hello Globe!" > static/hello-earth.txt                  

Adjacent, nosotros'll add the code.

config/__init__.py

In the config module, nosotros'll ascertain our tiny configuration direction framework. The idea is to make the app carry co-ordinate to configuration preset selected past the APP_ENV environs variable, plus, add an option to override any configuration setting with a specific environment variable if required.

          import os import sys import config.settings  # create settings object corresponding to specified env APP_ENV = os.environ.become('APP_ENV', 'Dev') _current = getattr(sys.modules['config.settings'], '{0}Config'.format(APP_ENV))()  # re-create attributes to the module for convenience for atr in [f for f in dir(_current) if not '__' in f]:    # environs can override anything    val = os.environ.get(atr, getattr(_current, atr))    setattr(sys.modules[__name__], atr, val)   def as_dict():    res = {}    for atr in [f for f in dir(config) if non '__' in f]:        val = getattr(config, atr)        res[atr] = val    return res                  

config/settings.py

This is a ready of configuration classes, one of which is selected by the APP_ENV variable. When the application runs, the code in __init__.py volition instantiate 1 of these classes overriding the field values with specific environment variables, if they are present. We will use a concluding configuration object when initializing Flask and Celery configuration subsequently.

          grade BaseConfig():    API_PREFIX = '/api'    TESTING = False    DEBUG = False   class 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//'   course 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@banker-rabbitmq//'   class TestConfig(BaseConfig):    FLASK_ENV = 'evolution'    TESTING = Truthful    DEBUG = Truthful    # make celery execute tasks synchronously in the same process    CELERY_ALWAYS_EAGER = True                  

tasks/__init__.py

The tasks parcel contains Celery initialization code. Config package, which will already have all settings copied on module level upon initialization, is used to update Celery configuration object in case nosotros will accept some Celery-specific settings in the time to come—for example, scheduled tasks and worker timeouts.

          from celery import Celery import config   def make_celery():    celery = Celery(__name__, broker=config.CELERY_BROKER)    celery.conf.update(config.as_dict())    return celery   celery = make_celery()                  

tasks/celery_worker.py

This module is required to start and initialize a Celery worker, which volition run in a split Docker container. It initializes the Flask application context to have admission to the same surround equally the application. If that's not required, these lines tin can be safely removed.

          from app import create_app  app = create_app() app.app_context().button()  from tasks import celery                  

api/__init__.py

Next goes the API package, which defines the Balance API using the Flask-Restful package. Our app is simply a demo and will take only two endpoints:

  • /process_data – Starts a dummy long operation on a Celery worker and returns the ID of a new job.
  • /tasks/<task_id> – Returns the status of a task past task ID.
          import time from flask import jsonify from flask_restful import Api, Resource from tasks import celery import config  api = Api(prefix=config.API_PREFIX)   form TaskStatusAPI(Resource):    def become(self, task_id):        task = celery.AsyncResult(task_id)        return jsonify(task.consequence)   form DataProcessingAPI(Resource):    def mail service(cocky):        task = process_data.delay()        render {'task_id': chore.id}, 200   @celery.task() def process_data():    time.sleep(60)   # data processing endpoint api.add_resource(DataProcessingAPI, '/process_data')  # job status endpoint api.add_resource(TaskStatusAPI, '/tasks/<string:task_id>')                  

models/__init__.py

Now we'll add a SQLAlchemy model for the User object, and a database engine initialization code. The User object won't exist used by our demo app in any meaningful manner, but nosotros'll demand it to make certain database migrations piece of work and SQLAlchemy-Flask integration is set up correctly.

          import uuid  from flask_sqlalchemy import SQLAlchemy  db = SQLAlchemy()   form User(db.Model):    id = db.Column(db.String(), primary_key=Truthful, default=lambda: str(uuid.uuid4()))    username = db.Column(db.String())    email = db.Column(db.String(), unique=True)                  

Note how UUID is generated automatically equally an object ID past default expression.

app.py

Finally, let'due south create a primary Flask application file.

          from flask import Flask  logging.basicConfig(level=logging.DEBUG,                    format='[%(asctime)s]: {} %(levelname)due south %(message)due south'.format(os.getpid()),                    datefmt='%Y-%m-%d %H:%M:%S',                    handlers=[logging.StreamHandler()])  logger = logging.getLogger()   def create_app():    logger.info(f'Starting app in {config.APP_ENV} environs')    app = Flask(__name__)    app.config.from_object('config')    api.init_app(app)    # initialize SQLAlchemy    db.init_app(app)     # define hullo world page     @app.route('/')    def hello_world():        return 'Hello, Earth!'    render app   if __name__ == "__main__":    app = create_app()    app.run(host='0.0.0.0', debug=Truthful)</td>   </tr>   <tr>     <td>                  

Hither we are:

  • Configuring basic logging in a proper format with time, level and process ID
  • Defining the Flask app creation function with API initialization and "Hi, world!" folio
  • Defining an entry betoken to run the app during development time

wsgi.py

As well, we will need a separate module to run Flask application with Gunicorn. Information technology will take but two lines:

          from app import create_app app = create_app()                  

The application code is ready. Our side by side pace is to create a Docker configuration.

Building Docker Containers

Our application will require multiple Docker containers to run:

  1. Awarding container to serve templated pages and expose API endpoints. Information technology's a good thought to split these 2 functions on the production, but we don't have whatsoever templated pages in our demo app. The container volition run Gunicorn spider web server which volition communicate with Flask through WSGI protocol.
  2. Celery worker container to execute long tasks. This is the same application container, but with custom run command to launch Celery, instead of Gunicorn.
  3. Celery vanquish container—similar to in a higher place, but for tasks invoked on a regular schedule, such every bit removing accounts of users who never confirmed their email.
  4. RabbitMQ container. Celery requires a message broker to communicate between workers and the app, and store task results. RabbitMQ is a common choice, merely you lot as well can use Redis or Kafka.
  5. Database container with PostgreSQL.

A natural way to easily manage multiple containers is to use Docker Compose. But first, nosotros will need to create a Dockerfile to build a container image for our application. Allow's put it to the project directory.

          FROM python:3.7.2  RUN pip install pipenv  Add . /flask-deploy  WORKDIR /flask-deploy  RUN pipenv install --arrangement --skip-lock  RUN pip install gunicorn[gevent]  EXPOSE 5000  CMD gunicorn --worker-grade gevent --workers 8 --bind 0.0.0.0:5000 wsgi:app --max-requests 10000 --timeout five --keep-live 5 --log-level info                  

This file instructs Docker to:

  • Install all dependencies using Pipenv
  • Add an application folder to the container
  • Expose TCP port 5000 to the host
  • Set the container's default startup command to a Gunicorn telephone call

Permit's discuss more what happens in the last line. It runs Gunicorn specifying the worker course every bit gevent. Gevent is a lightweight concurrency lib for cooperative multitasking. It gives considerable operation gains on I/O bound loads, providing better CPU utilization compared to Os preemptive multitasking for threads. The --workers parameter is the number of worker processes. Information technology's a good idea to set it equal to an number of cores on the server.

In one case we accept a Dockerfile for application container, nosotros tin can create a docker-compose.yml file, which volition define all containers that the application volition require to run.

          version: '3' services:  broker-rabbitmq:    image: "rabbitmq:3.vii.14-direction"    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}    command: flask db upgrade    depends_on:      - db-postgres  api:    build: .    ports:     - "5000:5000"    surround:      - APP_ENV=${APP_ENV}    depends_on:      - banker-rabbitmq      - db-postgres      - migration  api-worker:    build: .    command: celery worker --workdir=. -A tasks.celery --loglevel=info    environment:      - APP_ENV=${APP_ENV}    depends_on:      - banker-rabbitmq      - db-postgres      - migration  api-beat:    build: .    command: celery beat -A tasks.celery --loglevel=info    surround:      - APP_ENV=${APP_ENV}    depends_on:      - broker-rabbitmq      - db-postgres      - migration                  

We divers the following services:

  • banker-rabbitmq – A RabbitMQ message broker container. Connection credentials are defined by environment variables
  • db-postgres – A PostgreSQL container and its credentials
  • migration – An app container which will perform the database migration with Flask-Migrate and get out. API containers depend on information technology and volition run afterwards.
  • api – The main application container
  • api-worker and api-beat – Containers running Celery workers for tasks received from the API and scheduled tasks

Each app container volition too receive the APP_ENV variable from the docker-etch upwards command.

Once nosotros have all application assets ready, permit'south put them on GitHub, which will help us deploy the code on the server.

          git add * git commit -a -m 'Initial commit' git remote add together origin git@github.com:your-proper noun/flask-deploy.git git push button -u origin master                  

Configuring the Server

Our lawmaking is on a GitHub at present, and all that's left is to perform initial server configuration and deploy the awarding. In my example, the server is an AWS instance running AMI Linux. For other Linux flavors, instructions may differ slightly. I also assume that server already has an external IP accost, DNS is configured with A record pointing to this IP, and SSL certificates are issued for the domain.

Security tip: Don't forget to let ports 80 and 443 for HTTP(Due south) traffic, port 22 for SSH in your hosting console (or using iptables) and close external admission to all other ports! Be sure to practise the same for the IPv6 protocol!

Installing Dependencies

First, we'll need Nginx and Docker running on the server, plus Git to pull the code. Permit's login via SSH and use a bundle director to install them.

          sudo yum install -y docker docker-compose nginx git                  

Configuring Nginx

Side by side stride is to configure Nginx. The chief nginx.conf configuration file is often good equally-is. Withal, exist certain to check if information technology suits your needs. For our app, we'll create a new configuration file in a conf.d folder. Top-level configuration has a directive to include all .conf files from it.

          cd /etc/nginx/conf.d sudo vim flask-deploy.conf                  

Hither is a Flask site configuration file for Nginx, batteries included. It has the following features:

  1. SSL is configured. Yous should have valid certificates for your domain, e.g., a free Allow's Encrypt certificate.
  2. www.your-site.com requests are redirected to your-site.com
  3. HTTP requests are redirected to secure HTTPS port.
  4. Reverse proxy is configured to pass requests to local port 5000.
  5. Static files are served past Nginx from a local binder.
          server {     listen   fourscore;     listen   443;     server_name  world wide web.your-site.com;     # bank check your certificate path!     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 Loftier:!aNULL:!MD5;     # redirect to not-www domain     return   301 https://your-site.com$request_uri; }  # HTTP to HTTPS redirection server {         heed fourscore;         server_name your-site.com;         return 301 https://your-site.com$request_uri; }  server {         mind 443 ssl;         # check your document path!         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;         # affects the size of files user tin can upload with HTTP Mail         client_max_body_size 10M;         server_name your-site.com;         location / {                 include  /etc/nginx/mime.types;                 root /home/ec2-user/flask-deploy/static;                 # if static file not found - laissez passer asking to Flask                 try_files $uri @flask;         }   location @flask {                 add_header 'Access-Control-Let-Origin' '*' ever;                 add_header 'Admission-Control-Allow-Methods' 'Go, Mail, PUT, DELETE, OPTIONS';                 add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,10-Requested-With,If-Modified-Since,Enshroud-Control,Content-Type,Range,Authorisation';                 add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';                  proxy_read_timeout 10;                 proxy_send_timeout 10;                 send_timeout threescore;                 resolver_timeout 120;                 client_body_timeout 120;                                  # set up headers to pass request info to Flask                 proxy_set_header   Host $http_host;                 proxy_set_header   X-Forwarded-Proto $scheme;                 proxy_set_header   X-Forwarded-For $remote_addr;                 proxy_redirect     off;                      proxy_pass http://127.0.0.ane:5000$uri;         } }                  

After editing the file, run sudo nginx -s reload and see if there are any errors.

Setting Up GitHub Credentials

It is a proficient practise to take a separate "deployment" VCS account for deploying the projection and CI/CD organization. This way yous don't adventure by exposing your own account'south credentials. To further protect the project repository, you can likewise limit permissions of such business relationship to read-only access. For a GitHub repository, you'll need an arrangement account to do that. To deploy our demo application, we'll just create a public key on the server and register it on GitHub to get access to our project without entering credentials every time.

To create a new SSH fundamental, run:

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

Then log in on GitHub and add your public key from ~/.ssh/id_rsa.pub in business relationship settings.

Deploying an App

The concluding steps are pretty straightforward—we demand to go awarding code from GitHub and commencement all containers with Docker Compose.

          cd ~ git clone https://github.com/your-name/flask-deploy.git git checkout master APP_ENV=Production docker-etch upwardly -d                  

It might be a proficient idea to omit -d (which starts the container in discrete style) for a first run to see the output of each container right in the terminal and check for possible bug. Another option is to inspect each individual container with docker logs afterward. Let's see if all of our containers are running with docker ps.

image_alt_text

Great. All 5 containers are upward and running. Docker Etch assigned container names automatically based on the service specified in docker-compose.yml. At present information technology's time to finally exam how the entire configuration works! It's all-time to run the tests from an external machine to brand certain the server has correct network settings.

          # test HTTP protocol, you lot should get a 301 response gyre your-site.com # HTTPS request should return our Hello World message curl https://your-site.com # and nginx should correctly send test static file: curl https://your-site.com/hello-world.txt                  

That's it. We have a minimalistic, just fully production-ready configuration of our app running on an AWS example. Hope information technology will aid you to start building a existent-life application apace and avoid some mutual mistakes! The complete code is available on a GitHub repository.

Conclusion

In this article, nosotros discussed some of the best practices of structuring, configuring, packaging and deploying a Flask application to production. This is a very big topic, impossible to fully embrace in a single blog mail service. Here is a list of important questions we didn't accost:

This article doesn't cover:

  • Continuous integration and continuous deployment
  • Automatic testing
  • Log shipping
  • API monitoring
  • Scaling up an awarding to multiple servers
  • Protection of credentials in the source lawmaking

However, you can larn how to do that using some of the other dandy resource on this blog. For example, to explore logging, run across Python Logging: An In-Depth Tutorial, or for a general overview on CI/CD and automated testing, meet How to Build an Constructive Initial Deployment Pipeline. I go out the implementation of these as an exercise to you, the reader.

Thanks for reading!

spyerthatimensfa.blogspot.com

Source: https://www.toptal.com/flask/flask-production-recipes