Skip to content

Latest commit

 

History

History
263 lines (193 loc) · 8.62 KB

ubuntu_16_04.md

File metadata and controls

263 lines (193 loc) · 8.62 KB

Installing a Flask webapp on a Digital Ocean VPS (or Linode, etc.)

Here I sum up how to install a Flask webapp on a Digital Ocean VPS that has Ubuntu GNU/Linux 16.04 on it. The following topics will be covered:

  • server configuration
  • the big picture
  • Nginx
  • a simple Flask app in a virtual environment served by gunicorn
  • gunicorn is started automatically with a systemd script

server configuration

See https://github.com/jabbalaci/DigitalOceanNotes for more info.

the big picture

So, you have a Flask app that works well on your local machine and you want to share it with the world. Instead of a PaaS (like Heroku for instance) you want to host it on a virtual private server (VPS). How to do it?

When you buy the VPS and receive its IP and root password, spend some time with its basic configuration. I wrote about it here: https://github.com/jabbalaci/DigitalOceanNotes.

Then, we will need a real web server that will serve the pages of our Flask application. Our choice will be Nginx. Nginx will listen on the standard http port (80) and forward every request to port 9000.

On port 9000 an application server namely gunicorn will run. This application server will run our Python application. Nginx will handle static files (like CSS, images, etc.) itself. If our Python app is called, then Nginx forwards the request to Gunicorn who executes your Flask app, returns the result to Nginx, who will return that result to the client.

If gunicorn dies for some reason, we want it to restart automatically. A systemd script will do exactly this.

Nginx

Install Nginx:

$ sudo apt install nginx

If you use UFW (firewall), then don't forget to open port 80:

$ sudo ufw status verbose    # verify what's open
$ sudo ufw allow 80/tcp      # open port 80

Verify if nginx is running:

$ sudo service nginx status
$ sudo service nginx start    # start nginx (if it was not running)

Verify it in your browser. Visit http://1.2.3.4 (instead of 1.2.3.4 use the IP address of your VPS).

Flask app

Let's put Nginx aside for a while and concentrate on our Flask app. Here, in this repo you can find a simple sample app that I will use for the demonstration. It was written in Python 3.

My Flask app will be here: /home/demo/projects/ave_caesar. Its virtual environment is located in a dedicated folder here: /home/demo/.virtualenvs/ave_caesar.

Create a virtual environment for the project, activate the environment and install the requirements. Start the app with ./main.py and open it in your browser: http://1.2.3.4:9000. If you use UFW, make port 9000 open. You should see an image and a text below it. If it's OK, then stop main.py (it is running in debug mode and the host is '0.0.0.0', which means that anybody can visit your app). Debug mode is absolutely not recommended in a production environment.

The next step will be to run our app with Gunicorn instead of the built-in dev server of Flask. While the virt. env. is active, install gunicorn and gevent:

$ pip install gunicorn gevent

And now start the application with Gunicorn (the virt. env. is still activated):

$ ./01_start_with_gunicorn.py

Again, visit http://1.2.3.4:9000. You should see the same page, however this time it's served by Gunicorn! If it's OK, then stop gunicorn (press Ctrl-C).

Configure Nginx to forward to port 9000

Now let's configure Nginx to forward requests that arrive to port 80 to port 9000.

The prompt # indicates the root prompt, while $ is the prompt of normal users.

# cd /etc/nginx
# cd sites-available
# vi flask

Add the following content:

upstream app_server {
    server 127.0.0.1:9000 fail_timeout=0;
}

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    client_max_body_size 4G;
    server_name _;

    keepalive_timeout 5;

    # your Flask project's static files - amend as required
    location /static {
        alias /home/demo/projects/ave_caesar/static;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app_server;
    }
}

Here app_server is a unique ID. Thus, if you want to serve later another webapp, and you make a copy of this file, rename app_server to something else in the copy.

What does this config file do? When something arrives at port 80, redirect it to port 9000.

Add a symbolic link to sites-enabled that points on this file:

# cd /etc/nginx/sites-enabled
# rm default    # remove the default
# ln -s ../sites-available/flask
# ls -al

Restart nginx:

# service nginx restart
# service nginx status

Let's test if redirection from port 80 -> port 9000 works

Before moving to Systemd, let's test if the redirection works. Go to the project folder of the webapp, activate the virt. env., and start gunicorn like before:

$ ./01_start_with_gunicorn.py

The address http://1.2.3.4:9000 should work. But now remove the port and try simply http://1.2.3.4. You may have to clear the cache, force reload the page (Ctrl-r), but you should see our basic app. And this goes through Nginx!

If it works, then stop gunicorn. If you have UFW, at this point you can hide again port 9000 if you want.

What next?

We are close to victory. In the previous section we started gunicorn manually. But we want gunicorn to start automatically upon boot. A Systemd script will do exactly that:

# vi /etc/systemd/system/gunicorn.service

Add the following content:

[Unit]
Description=Gunicorn daemon for a Flask project
After=network.target

[Service]
User=demo
Group=demo
WorkingDirectory=/home/demo/projects/ave_caesar
Environment="PATH=/home/demo/.virtualenvs/ave_caesar/bin"
ExecStart=/home/demo/.virtualenvs/ave_caesar/bin/gunicorn --config /etc/gunicorn.d/gunicorn.py main:app

[Install]
WantedBy=multi-user.target

The webapp is in the HOME folder of the user demo, who is in the group demo. The app will run under his/her name, that's what the lines User and Group mean. How to verify:

$ cd /home/demo/projects
$ ls -al
drwxr-xr-x  5 demo demo 4096 Mar 21 20:27 ave_caesar

See? It's demo and demo.

So, what happens after? Enter the project directory and start the app with gunicorn. Note that gunicorn is executed in the virt. env.! You don't need to install it globally with sudo! Additional settings are in the /etc/gunicorn.d/gunicorn.py file that we will see in the next section.

additional settings of gunicorn

Let's create /etc/gunicorn.d/gunicorn.py:

# mkdir /etc/gunicorn.d
# vi /etc/gunicorn.d/gunicorn.py

The folder /etc/gunicorn.d may not exist, that's why we create it first.

Add the following content:

"""gunicorn WSGI server configuration."""
from multiprocessing import cpu_count
from os import environ

def max_workers():
    return cpu_count() * 2 + 1

max_requests = 1000
worker_class = 'gevent'
workers = max_workers()

name = 'ave_caesar'
bind = '127.0.0.1:9000'
pidfile = 'gunicorn_from_systemd.pid'

accesslog = 'gunicorn_access.log'
errorlog = 'gunicorn_error.log'

The process gunicorn will listen on port 9000. The accesslog and errorlog files are useful for debugging and monitoring. The PID of the gunicorn process will be written in a file. All these three files will be written to the root folder of the webapp.

start the systemd script

Now enable and start the systemd script:

# systemctl enable gunicorn
# systemctl start gunicorn
# systemctl status gunicorn

Here gunicorn comes from the name of the file /etc/systemd/system/gunicorn.service that was created in a previous step.

If it runs for the first time, you can drink a champagne :)

502 Bad Gateway

It means there is a problem between the communication of Nginx and Gunicorn.

504 Gateway Time-out

I ran into this error after a good while. My REST API was running too long (for several minutes), and Nginx raised a timeout error. To cure this problem, add the following lines to your nginx config file:

proxy_connect_timeout       600;
proxy_send_timeout          600;
proxy_read_timeout          600;
send_timeout                600;

In our example the nginx config file is here: /etc/nginx/sites-available/flask.