Server Setup
Post Contents
1. Overview
2. Creating the Server & Installing Dependencies
3. Flask & gunicorn Configuration
4. Nginx Configuration
1. Overview
This part of the workshop will be focused on application setup, everyone’s favorite part of hacking. We’ll be using digital ocean, because it doesn’t come with all the security hassles that AWS enforces. If you’re going to be doing production level stuff, then we recommend using AWS or GCP, since they make it difficult to botch initial security group configurations. Let’s get started m80s! Oh wait, one more thing, why are we using flask? This workshop is meant for beginners. Flask & python do an incredible job at reducing the mental gymnastics required to get something up and running without obfuscating too much work that it seems like black magic is happening under the hood. This makes it the perfect choice for you to learn the high level components of server communication with mobile devices. If you would like to know more about how flask works with the operating system, and how you could build your own web server, then check out this surprisingly short, but incredibly useful network programming guide: http://beej.us/guide/bgnet/output/html/multipage/index.html
Without further adu, let us begin!
2. Creating the Server & Installing Dependencies
Go to https://www.digitalocean.com and create a 16.04 ubuntu droplet. Digital Ocean will send you the deetz about this server via email so that you can login.
Now from your local machine, access the server via
ssh root@<insert your droplet IP address here>
Once you have access to the remote server, let’s start by creating a new user on the server to isolate all our changes:
adduser sammy
No need to worry about the details for sammy. Now let’s add sammy to the sudo group, so that we won’t have any socket permission hiccups later on:
usermod -aG sudo sammy
Fantastic! Now one last thing, exit out of root, and ssh into sammy’s account.
ssh sammy@<insert your droplet IP address here>
Now that we have that out of the way, let’s start installing the python & nginx dependencies via the following ez commands:
sudo apt-get update
sudo apt-get install python-pip python-dev nginx
This is great, but let’s say we also want to have a django application that has a bunch of legacy python packages. Remember kids, always practice safe server side dependency control. How can we isolate our swanky new flask application from the dinosaur django application? We’ll use virtualenv. If you’re coming from node, then think of this as a heavy duty version of packages.json. Enough talk, run these commands:
sudo pip install virtualenv
To use virtualenv, let’s create our application’s folder first:
mkdir ~/myproject
cd ~/myproject
Now let’s tell virtualenv to create a new virtual environment, so that it can keep a track of what python dependencies (in this case, pip and python) belong to which virtual environment (in this case, the virtual environment called “myprojectenv”).
virtualenv myprojectenv
If you check your directory, you’ll see that virtualenv created a virtual environment file directory. You should activate this virtual environment using virtualenv before installing the rest of the dependencies by doing:
source myprojectenv/bin/activate
Great! Simple stuff, eh.
3. Flask & gunicorn Configuration
3.1 Flask configuration
Alright, even though you’re a 1337 ninja wizard ‘rockstar’ hacker in my books, we still have a lot of work to do. Let’s move on to getting flask, testing it out, and then getting gunicorn configured. Let’s begin:
pip install gunicorn flask
Now we will actually use the flask web framework to code an API endpoint. This will enable mobile devices to send a network request to our server at this endpoint over the internet. Let’s use nano to create the python file which will contain our code that uses flask:
nano ~/myproject/myproject.py
Now let’s put the following python code in this file:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def hello():
return "<h1 style='color:blue'>Hello There!</h1>"
if __name__ == "__main__":
app.run(host='0.0.0.0')
What is happening here? Two things, first let’s just look at the following lines:
from flask import Flask
app = Flask(__name__)
if __name__ == "__main__":
app.run(host='0.0.0.0')
the first thing happening is just defining the module namespace for the Flask
module to use. Then, we tell app
to run with the local host’s addresses, which is always gonna be ‘0.0.0.0’ for the ubuntu operating system. Now let’s look at the other part.
@app.route("/")
def hello():
return "<h1 style='color:blue'>Hello There!</h1>"
This is a bit stranger looking. However, let’s break it down. The ‘@’ is part of python, and it is just a shorthand notation for decorators. Basically, the decorator here creates a flask route, and links this route with a function to run when that route is accessed, but the linked function is only run after the function that directly follows the ‘@’ symbol has run. To read more about python and decorators, see here: https://realpython.com/blog/python/primer-on-python-decorators/
Now that we have that covered, let’s test it out. First let’s get rid of some pesky firewalls for port 5000:
sudo ufw allow 5000
Now, what we’ve all been waiting for, run it:
python myproject.py
Then you can access this application running on your server by using a browser like so:
http://server_domain_or_IP:5000
A big blue hello world should show up. If it didn’t, throw your hands in the air and let an instructor know. Assuming it did, we can go back to our terminal and terminate the python program via ctrl-c
3.2 gunicorn Configuration
I don’t know about you, but that was way too exciting for me, let’s slow it down a bit. Let’s configure gunicorn to work with our flask application. First off, what is gunicorn? gunicorn is a pre-made server which is capable of interoperating with frameworks that run on wsgi like flask or django. The main idea here is that python has a protocol specification called wsgi, and so to serve our flask application, we need a server that can communicate properly with flask via wsgi. All of these things are open source, so please feel free to contribute to them if and when you feel comfortable.
First, create a file wsgi.py
nano ~/myproject/wsgi.py
and then add the following code:
from myproject import app
if __name__ == "__main__":
app.run()
This file will serve as a way to pluck our app
module from our myproject.py
file, and then tell gunicorn that this is the application that will be receiving data.
Let’s continue by entering the following in the terminal:
cd ~/myproject
gunicorn --bind 0.0.0.0:5000 wsgi:app
This will run the gunicorn server and configure it to bind to a socket with port 5000. Finally, the second argument supplies the name of the entry point file and it’s type, or wsgi.py
with type app
.
Now, access this application running on your server by using a browser like so:
http://server_domain_or_IP:5000
Is the big blue text still there? Yes? Good! Let’s terminate the server, and continue hacking, but now let’s do things WITHOUT the virtual environment running, so do this:
deactivate
Now, let’s automate the actual ubuntu instance to run gunicorn on startup. Go ahead and create the following systemd
file:
sudo nano /etc/systemd/system/myproject.service
systemd is a init system used by some flavors of linux, so we might as well take advantage of it since we’re running ubuntu. Inside it, put the following content:
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target
[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myproject
Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
[Install]
WantedBy=multi-user.target
We can now start the Gunicorn service we created and enable it so that it starts at boot:
sudo systemctl start myproject
sudo systemctl enable myproject
4. Nginx Configuration
The final stretch of this workshop will be configuring Nginx. Before we begin, let’s define what Nginx is. There’s a lot of confusion over what a reverse proxy server is. For simplicity, let’s just define nginx as a plain server. Any decent server should be able to handle tcp connections from the network. Nginx does this, but it does it really, really well. Likewise, it implements a ton of other server functionality in a super efficient manner. Therefore, we want nginx to handle network requests for us and then pass the data to the gunicorn server, which will ultimately pass it to our flask application. Nginx communicates to gunicorn via the wsgi protocol, or web server gateway interface. Let’s begin by creating the nginx configuration file:
sudo nano /etc/nginx/sites-available/myproject
server {
listen 80;
server_name <server IP address goes here>;
location / {
include proxy_params;
proxy_pass http://unix:/home/sammy/myproject/myproject.sock;
}
}
note remember to replace
In essence all nginx needs to know is which port it should listen on for requests from the internet (port 80), and where it should pass those requests on to (myproject.sock). We also define some other parameters, but they’re less important to understand what the black box of nginx is.
After defining that configuration file, we need to link it as an enabled website like so:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
Also, we need to check that the configuration file supplied to nginx is syntactically correct using the following command:
sudo nginx -t
Finally, we can restart the nginx service and give it full permissions.
sudo systemctl restart nginx
sudo ufw allow 'Nginx Full'
Now you should be able to travel to your website and view it on the ‘/’ route:
note this is no longer on port 5000, so it’s a different URL
http://server_domain_or_IP
notex2 if you are not getting the big blue text, debug using some of these tips:
ps -e
are there nginx and gunicorn processes?
cat /var/log/nginx/error.log
google any errors that show up here
Another tip is you might not have followed this guide in the right order which would lead to you installing somewhere in the wrong place. This would cause errors in your .service file AND your nginx configuration file. If worst comes to worst, ask an instructor.
Congrats! You now have the very basic building blocks to set up a microservice. Checkout the following posts in this series to use flask in a more advanced manner. Also, if you’re interested, this post was in part derived from the https://realpython.com/ website. They have way better blog posts than this one, but the idea for this post was to stay very simple for getting an endpoint up on the internet.