How To Flask, Python, Centos7, Apache, uWSGI
Following is a walk-through for deploying a simple python-flask application with Linux Centos7, apache, virtualenv and uWSGI
This shouldn’t have been so hard to pull together
But it was, so i’m documenting for your reading pleasure
I’ve been questing for python literacy lately. I’ve been developing a flask website, but had problems deploying it under centos7. I come from the world of perl, where it’s all roses and sunshine, so I’m always suprised when deployments fail. This failed in spectacularly weird ways.
Before I attack those problems, I want to see a simple flask app deploy under these conditions:
- Fresh VPS instance under CentOS7
- Python3 from official repo (EPEL repo carries python 3.4)
- Apache as the web server. (I have much love for apache, shut up about nginx)
- Application operating under a virtualenv
- Application operating under an unprivileged user account other than apache
- Installed configuration stable across reboots and package upgrades
- Configuration compatible with installation into another VPS I maintain for pet projects, so it doesn’t require it’s own server
Once I made this happen, I have a template for hacking deployment problems with my other sprawling flask pet projects. Online docs for flask and wsgi sent me down several dead-end rabbit-holes. But it always pulls together in the end.
If you follow these directions, you will have a working sample flask app. I’m going to deploy this sample as a linode stackscript for one-click deployments of this configuration
Basic Server Preparation
This isn’t necessary, but you should never spin up a linux instance and not secure it.
# Appropriate values should be plugged in here
export WHITELIST_IPADDR=0.0.0.0
# Lock down the firewall immediately
# - Block all incoming connections, even ssh
# - Allow all incoming from our specified IP
# SSH is only going to function from this allowed IP
# - Allow incoming connections on web ports
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --zone=public --add-service=http
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --zone=public --add-service=https
firewall-cmd --zone=public --add-service=https --permanent
firewall-cmd --zone=trusted --add-source=${WHITELIST_IPADDR}
firewall-cmd --zone=trusted --add-source=${WHITELIST_IPADDR} --permanent
firewall-cmd --zone=public --remove-service=ssh
firewall-cmd --zone=public --remove-service=ssh --permanent
# Update system, install LAMP packages
yum -y update
yum -y install epel-release
yum -y install \
httpd \
psacct
yum clean all
# SSH Hardening
sed -i 's/^#?PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#?AllowTcpForwarding yes/AllowTcpForwarding no/' /etc/ssh/sshd_config
sed -i 's/^#?ClientActiveCountMax 3/ClientActiveCountMax 2/' /etc/ssh/sshd_config
sed -i 's/^#?Compression DELAYED/Compression no/' /etc/ssh/sshd_config
sed -i 's/^#?LogLevel INFO/LogLevel VERBOSE/' /etc/ssh/sshd_config
sed -i 's/^#?MaxAuthTries 6/MaxAuthTries 1/' /etc/ssh/sshd_config
sed -i 's/^#?MaxSessions 10/MaxSessions 2/' /etc/ssh/sshd_config
sed -i 's/^#?TCPKeepAlive yes/TCPKeepAlive no/' /etc/ssh/sshd_config
sed -i 's/^#?UseDNS yes/UseDNS no/' /etc/ssh/sshd_config
sed -i 's/^#?X11Forwarding yes/X11Forwarding no/' /etc/ssh/sshd_config
sed -i 's/^#?AllowAgentForwarding yes/AllowAgentForwarding no/' /etc/ssh/sshd_config
Install Python and prereqs
yum -y install python34 python34-pip
# DO NOT use yum -y install python-virtualenv
sudo yum -y install python34 python34-devel python34-pip mod_proxy_uwsgi gcc
sudo pip3 install --upgrade pip
sudo pip3 install virtualenv flask uwsgi
Flask Demo Application
Let’s create the least exciting flask demo application possible.
Create user and virtualenv for app.
# Create the unprivileged user
sudo adduser flaskdemo
sudo passwd flaskdemo
sudo su flaskdemo
# Create our project directory
cd
mkdir flaskdemo
cd flaskdemo
# init and activate a python-virtualenv with prereqs
virtualenv -p python3 env
source env/bin/activate
Create File: ~/flaskdemo/flaskdemo.py
This is our demo application. Exciting.
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def root():
return "You have found somewhere on your road to nowhere.<br>Congrats"
if __name__ == "__main__":
app.run(host='0.0.0.0')
Create File: ~/flaskdemo/wsgi.py
This is the wsgi wrapper. Not strictly useful here, but will be very useful for a real flask app
from flaskdemo import app as application
if __name__ == "__main__":
application.run()
Create File: ~/flaskdemo/flaskdemo.ini
Configuration options for the uwsgi process.
mod_proxy_uwsgi doesn’t support sockets until apache 2.4.9. Currently, CentOS is sporting apache 2.4.6
Sockets would be faster, but we’re using network transport instead on port 8000
[uwsgi]
module = wsgi
master = true
processes = 5
socket = 127.0.0.1:8000
chmod-socket = 660
vacuum = true
die-on-term = true
Test sample flask app
If you want, test yourself for typos.
uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi
Create system service for uWSGI
/etc/systemd/system/flaskdemo.service
[Unit]
Description=uWSGI server for flaskdemo
After=network.target
[Service]
User=flaskdemo
Group=apache
WorkingDirectory=/home/flaskdemo/flaskdemo
Environment="PATH=/home/flaskdemo/flaskdemo/env/bin"
ExecStart=/home/flaskdemo/flaskdemo/env/bin/uwsgi --ini flaskdemo.ini
[Install]
WantedBy=multi-user.target
Start the system service
sudo systemctl enable flaskdemo
sudo systemctl start flaskdemo
Configure and launch Apache httpd
append to end of /etc/httpd/conf/httpd.conf
LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
<VirtualHost *>
ServerName flaskdemo.com
ProxyPass / uwsgi://127.0.0.1:8000
</VirtualHost>
Test the apache config
httpd -t
If this doesn’t return OK, I’ve steered you wrong, pal
Start apache
sudo systemctl enable httpd
sudo systemctl start httpd
We have a functioning, flask app under apache and centos7!
Test your program and rejoice