Flask App with Gunicorn on Nginx Server upon AWS EC2 Linux
The whole setup is modified from this tutorial, with the pain and gain from the alternative deployment on an AWS EC2 Linux server.
- Setup Environment
- Creating a Flask App
- Binding with Gunicorn
- Creating an Upstart Script for Running Gunicorn Server
- Running with Nginx on AWS EC2
Setup Environment
Install python development tools & nginx
.
$ sudo yum update
$ sudo yum install python-pip python-dev nginx
Install virtualenv
from pip
so that the python packages for the flask app will be in isolation.
$ sudo pip install virtualenv
Create the project & setup the virtual environment.
# create project
$ mkdir myproject
$ cd myproject
# create virtualenv
$ virtualenv venv
# activate virtualenv
$ source ./venv/bin/activate
Now the prompt should look like:
(venv)user@host:~/myproject$
Creating a Flask App
Install the dependencies under your virtualenv.
(venv)user@host:~/myproject$ pip install gunicorn flask
Create the app entry file ~/myproject/app.py
and write the simplest flask app:
from flask import Flask
application = Flask(__name__)
@application.route("/")
def index():
return "Hello World!"
if __name__ == "__main__":
application.run(host='0.0.0.0', port='8080')
Note that you need to make sure your app is run on an allowed port of the EC2 instance.
Check which ports are allowed under AWS EC2 Dashboard > Instances > (select your instance) > Security groups > view inbound rules
.
Test your flask app.
(venv)user@host:~/myproject$ python app.py
Go to your browser and enter the url to your server, appending the port number you specified in app.py
.
You should see Hello World!
displayed.
Binding with Gunicorn
Create the WSGI entrypoint ~/myproject/wsgi.py
.
from app import application
if __name__ == "__main__":
application.run()
Test it.
(venv)user@host:~/myproject$ gunicorn --bind 0.0.0.0:8080 wsgi
If you didn't name your app as application
, for example as app
,
use wsgi:app
instead of wsgi
, since application
is the name to be picked up by default.
Go to your browser again and read the Hello World!
response.
Creating an Upstart Script for Running Gunicorn Server
Now let's make Linux automatically start the server upon booting by providing the upstart script.
Create a configuration file:
$ sudo vim /etc/init/myproject.conf
Write a little more complicated version than the original tutorial to help you debug:
description "Gunicorn application server running myproject"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
env PATH=/home/ec2-user/myproject/venv/bin
env PROGRAM_NAME="myproject"
env USERNAME="ec2-user"
# Main script to be run
script
echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Ready to run..." >> /var/log/$PROGRAM_NAME.sys.log
export HOME="/home/ec2-user"
echo $$ > /var/run/$PROGRAM_NAME.pid
cd /home/ec2-user/myproject
# exec sudo -u ec2-user gunicorn --workers 3 --bind unix:myproject.sock -m 000 wsgi >> /var/log/$PROGRAM_NAME.sys.log 2>&1
# exec su -s /bin/sh -c 'exec "$0" "$@"' ec2-user -- gunicorn --workers 3 --bind unix:myproject.sock -m 000 wsgi >> /var/log/$PROGRAM_NAME.sys.log 2>&1
exec gunicorn --workers 3 --bind unix:myproject.sock -m 000 wsgi >> /var/log/$PROGRAM_NAME.sys.log 2>&1
end script
# Script for debug purpose, run before starting
pre-start script
echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> /var/log/$PROGRAM_NAME.sys.log
end script
# Script for debug purpose, run before stopping
pre-stop script
rm /var/run/$PROGRAM_NAME.pid/
echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> /var/log/$PROGRAM_NAME.sys.log
end script
Notes here:
PATH
is for running the server under our virtual environment- Note the commented out
exec
scripts that produce errors; I intended to switch user by doing that, sincesetuid
andsetgid
is not supported on EC2 Linux instance. These commands are from these places and here. Feel free to provide a correct version... So now the server is run underroot
. -m
flag is the umask; for umask value000
, the permission would be777
. This is insecure though, but since I have not found a way to set the access right to a specific user and group, the hooking with nginx only works when the permission is allowed for all users (as the nginx server we will set up later runs as usernginx
).- Echos and
>>
are for debugging; see the logs at/var/log/myproject.sys.log
if you cannot start your server.
Test it.
# reload configuration files from /etc/init/*.conf
$ sudo initctl reload-configuration
# see if the new job is listed
$ sudo initctl list
# try start your server (job); the job name is without the '.conf' extension
$ sudo initctl start myproject
# if job is not listed, or error displays and says 'myproject' is not known, there's probably errors in the conf file
# fix them and go on
# check if it's actually running
$ sudo initctl status myproject
> myproject start/running, process xxxx
# or check with
$ ps aux | grep gunicorn
# if the job is not running, see the log at '/var/log/myproject.sys.log'
# you can echo more messages in the conf file for your own debug purpose
# you should also notice a socket file created at '/home/ec2-user/myproject/myproject.sock'
Running with Nginx on AWS EC2
Now setup the nginx server to redirect the traffic received at port 80 (http) to the WSGI (Gunicorn) server running at the unix socket.
Open the /etc/nginx/nginx.conf
file, find the section and write:
...
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name <your-domain-name>; # <- replace with your own one
root /usr/share/nginx/html;
...
location / {
proxy_pass http://unix:/home/ec2-user/myproject/myproject.sock; # <- add this
}
...
This will route the traffic to the specified socket.
Test it.
$ sudo nginx -t
If ok, start the server:
$ sudo service nginx restart
Go to the browser, and without specifying the port number now (default to 80). The request will hit the nginx proxy server, and the nginx server will pass it to the WSGI server, which talks to the flask app. Check if it successfully returns Hello World!
.
If not, there may be multiple reasons. The one that I encountered is solved by changing the permission of the home directory:
$ chmod 711 /home/ec2-user
Remember to restart.
Debug Tips
- Echo message to
/var/log/xxx.sys.log
tail -f /var/log/nginx/access.log
to check nginx logsnetstat -anp | less
to show network status