Loading repository data…
Loading repository data…
eon01 / repository
⚙️ A Gentle introduction to Kubernetes with more than just the basics. 🌟 Give it a star if you like it.
In this workshop, we're going to:
We will start by developing then deploying a simple Python application (a Flask API that returns the list of trending repositories by programming language).
We are going to use Python 3.6.7
We are using Ubuntu 18.04 that comes with Python 3.6 by default. You should be able to invoke it with the command python3. (Ubuntu 17.10 and above also come with Python 3.6.7)
If you use Ubuntu 16.10 and 17.04, you should be able to install it with the following commands:
sudo apt-get update
sudo apt-get install python3.6
If you are using Ubuntu 14.04 or 16.04, you need to get Python 3 from a Personal Package Archive (PPA):
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.6
For the other operating systems, visit this guide, follow the instructions and install Python3.
Now install PIP, the package manager:
sudo apt-get install python3-pip
Follow this by the installation of Virtualenvwrapper, which is a virtual environment manager:
sudo pip3 install virtualenvwrapper
Create a folder for your virtualenvs (I use ~/dev/PYTHON_ENVS) and set it as WORKON_HOME:
mkdir ~/dev/PYTHON_ENVS
export WORKON_HOME=~/dev/PYTHON_ENVS
In order to source the environment details when the user login, add the following lines to ~/.bashrc:
source "/usr/local/bin/virtualenvwrapper.sh"
export WORKON_HOME="~/dev/PYTHON_ENVS"
Make sure to adapt the WORKON_HOME to your real WORKON_HOME. Now we need to create then activate the new environment:
mkvirtualenv --python=/usr/bin/python3 trendinggitrepositories
workon trendinggitrepositories
Let's create the application directories:
mkdir trendinggitrepositories
cd trendinggitrepositories
mkdir api
cd api
Once the virtual environment is activated, we can install Flask:
pip install flask
Inside the API folder api, create a file called app.py and add the following code:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, World!"
if __name__ == '__main__':
app.run(debug=True)
This will return a hello world message when a user requests the "/" route.
Now run it using: python app.py and you will see a similar output to the following one:
* Serving Flask app "api" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 465-052-587
We now need to install PyGithub since we need it to communicate with Github API v3.
pip install PyGithub
Go to Github and create a new app. We will need the application "Client ID" and "Client Secret":
from github import Github
g = Github("xxxxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
This is how the mini API looks like:
from flask import Flask, jsonify, abort
import urllib.request, json
from flask import request
app = Flask(__name__)
from github import Github
g = Github("xxxxxx", "xxxxxxxxxxxxx")
@app.route('/')
def get_repos():
r = []
try:
args = request.args
n = int(args['n'])
except (ValueError, LookupError) as e:
abort(jsonify(error="No integer provided for argument 'n' in the URL"))
repositories = g.search_repositories(query='language:python')[:n]
for repo in repositories:
with urllib.request.urlopen(repo.url) as url:
data = json.loads(url.read().decode())
r.append(data)
return jsonify({'repos':r })
if __name__ == '__main__':
app.run(debug=True)
Let's hide the Github token and secret as well as other variables in the environment.
from flask import Flask, jsonify, abort, request
import urllib.request, json, os
from github import Github
app = Flask(__name__)
CLIENT_ID = os.environ['CLIENT_ID']
CLIENT_SECRET = os.environ['CLIENT_SECRET']
DEBUG = os.environ['DEBUG']
g = Github(CLIENT_ID, CLIENT_SECRET)
@app.route('/')
def get_repos():
r = []
try:
args = request.args
n = int(args['n'])
except (ValueError, LookupError) as e:
abort(jsonify(error="No integer provided for argument 'n' in the URL"))
repositories = g.search_repositories(query='language:python')[:n]
for repo in repositories:
with urllib.request.urlopen(repo.url) as url:
data = json.loads(url.read().decode())
r.append(data)
return jsonify({'repos':r })
if __name__ == '__main__':
app.run(debug=DEBUG)
The code above will return the top "n" repositories using Python as a programming language. We can use other languages too:
from flask import Flask, jsonify, abort, request
import urllib.request, json, os
from github import Github
app = Flask(__name__)
CLIENT_ID = os.environ['CLIENT_ID']
CLIENT_SECRET = os.environ['CLIENT_SECRET']
DEBUG = os.environ['DEBUG']
g = Github(CLIENT_ID, CLIENT_SECRET)
@app.route('/')
def get_repos():
r = []
try:
args = request.args
n = int(args['n'])
l = args['l']
except (ValueError, LookupError) as e:
abort(jsonify(error="Please provide 'n' and 'l' parameters"))
repositories = g.search_repositories(query='language:' + l)[:n]
try:
for repo in repositories:
with urllib.request.urlopen(repo.url) as url:
data = json.loads(url.read().decode())
r.append(data)
return jsonify({
'repos':r,
'status': 'ok'
})
except IndexError as e:
return jsonify({
'repos':r,
'status': 'ko'
})
if __name__ == '__main__':
app.run(debug=DEBUG)
In a .env file, add the variables you want to use:
CLIENT_ID="xxxxx"
CLIENT_SECRET="xxxxxx"
ENV="dev"
DEBUG="True"
Before running the Flask application, you need to source these variables:
source .env
Now, you can go to http://0.0.0.0:5000/?n=1&l=python to get the trendiest Python repository or http://0.0.0.0:5000/?n=1&l=c for C programming language.
Here is a list of other programming languages you can test your code with:
C++
Assembly
Objective
Makefile
Shell
Perl
Python
Roff
Yacc
Lex
Awk
UnrealScript
Gherkin
M4
Clojure
XS
Perl
sed
The list is long, but our mini API is working fine. Now, let's freeze the dependencies:
pip freeze > requirements.txt
Before running the API on Kubernetes, let's create a Dockerfile. This is a typical Dockerfile for a Python app:
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /app
WORKDIR /app
COPY requirements.txt /app
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
COPY . /app
EXPOSE 5000
CMD [ "python", "app.py" ]
Now you can build it:
docker build --no-cache -t tgr .
Then run it:
docker rm -f tgr
docker run -it --name tgr -p 5000:5000 -e CLIENT_ID="xxxxxxx" -e CLIENT_SECRET="xxxxxxxxxxxxxxx" -e DEBUG="True" tgr
Let's include some other variables as environment variables:
from flask import Flask, jsonify, abort, request
import urllib.request, json, os
from github import Github
app = Flask(__name__)
CLIENT_ID = os.environ['CLIENT_ID']
CLIENT_SECRET = os.environ['CLIENT_SECRET']
DEBUG = os.environ['DEBUG']
HOST = os.environ['HOST']
PORT = os.environ['PORT']
g = Github(CLIENT_ID, CLIENT_SECRET)
@app.route('/')
def get_repos():
r = []
try:
args = request.args
n = int(args['n'])
l = args['l']
except (ValueError, LookupError) as e:
abort(jsonify(error="Please provide 'n' and 'l' parameters"))
repositories = g.search_repositories(query='language:' + l)[:n]
try:
for repo in repositories:
with urllib.request.urlopen(repo.url) as url:
data = json.loads(url.read().decode())
r.append(data)
return jsonify({
'repos':r,
'status': 'ok'
})
except IndexError as e:
return jsonify({
'repos':r,
'status': 'ko'
})
if __name__ == '__main__':
app.run(debug=DEBUG, host=HOST, port=PORT)
For security reasons, let's change the user inside the container from root to a user with less rights that we create:
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN adduser pyuser
RUN mkdir /app
WORKDIR /app
COPY requirements.txt /app
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
COPY . .
RUN chmod +x app.py
RUN chown -R pyuser:pyuser /app
USER pyuser
EXPOSE 5000
CMD ["python","./app.py"]
Now if we want to run the container, we need to add many environment variables to the docker run command. An easier solution is using --env-file with Docker run:
docker run -it --env-file .env my_container
Our .env file looks like the following one:
CLIENT_ID="xxxx"
CLIENT_SECRET="xxxx"
ENV="dev"
DEBUG="True"
HOST="0.0.0.0"
PORT=5000
After this modification, rebuild the image docker build -t tgr . and run it using:
docker rm -f tgr;
docker run -it --name tgr -p 5000:5000 --env-file .env tgr
Our application runs using python app.py which is the webserver that ships with Flask and it's great for development and local execution of your program, however, it's not designed to run in a production mode, whether it's a monolithic app or a microservice.
A production server typically receives abuse from spammers, script kiddies, and should be able to handle high