Build and deploy a Flask application
Introduction
This tutorial will take you through building and deploying a simple web application on the cluster, which will be reachable from the internet. It is built with the Python Flask framework, but many steps are not specific to Python or Flask.
To go through this tutorial, you will need access to the Kubernetes cluster; see Get Started. You will also need Docker on your machine.
Write the code
This simple application does nothing but give the current time. Put the code in a file clock.py
:
from datetime import datetime
from flask import Flask
import pytz
app = Flask('clock')
timezone = pytz.timezone('America/New_York')
@app.route('/')
def index():
now_utc = pytz.utc.localize(datetime.utcnow())
now_nyc = now_utc.astimezone(timezone)
return "In New York, it is currently " + now_nyc.strftime('%H:%M:%S')
If you have Python on your machine, you can install Flask and pytz and run this app with FLASK_APP=clock flask run
. However that is not required to continue.
Build a container image with Docker
Create the following Dockerfile
in the same directory. This file contains the steps that will create a container image, that we can later deploy on Kubernetes or any other platform that supports containers:
# Start from the Python image, automatically retrieved from Docker Hub
FROM python:3.10
# Install our dependencies, by running a command in the container
RUN pip install flask pytz uwsgi
# Add our application's code
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
COPY clock.py /usr/src/app/
# Have containers serve the application with uwsgi by default
EXPOSE 5000
CMD ["uwsgi", "--http", ":5000", "-w", "clock:app"]
You can now build the image from this file by running this command:
You should now be able to run this container locally with the following:
Push the image to a container registry
In order for the cluster to be able to get your container image, you have to put it on a registry. There are multiple free registries available, such as Docker Hub (200 image downloads per 6 hours for free) or Red Hat Quay.io (unlimited for public repositories). GitLab and GitHub also include their own free registries. In this case we will use HSRN's GitLab instance dev.hsrn.nyu.edu, which you can access with your NYU account.
Tag your images with the registry and a unique name (most registries will give you a prefix with your username or your project's name) and upload it:
$ docker tag flask-clock registry.hsrn.nyu.edu/myusername/flask-clock
$ docker push registry.hsrn.nyu.edu/myusername/flask-clock
$ # or for quay.io:
$ docker tag flask-clock quay.io/myusername/flask-clock
$ docker push quay.io/myusername/flask-clock
Create the Deployment
We are now ready to run containers on the cluster. On Kubernetes, the smallest unit of workload is a Pod, which can be multiple containers sharing a single network namespace (e.g. they can see each other as "localhost"). Running a Pod with a single container is very common.
A Pod runs to completion or until its liveness probe fails and is not recreated automatically. To make sure our application stays available, we will wrap it in a Deployment which can run multiple copies of the Pod (for performance), will keep the target number of Pods available (including starting more copies as soon as some go down due to host maintenance or preemption), and can perform rolling updates (changing to a new container image version with no downtime).
Write this manifest to deployment.yml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-clock
labels:
app: flask-clock
source: hsrn-tutorial
spec:
# Run two copies of the Pod
replicas: 2
# Perform rolling updates, starting containers before stopping the old ones
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
# This is how the Deployment recognizes its Pods, has to match the labels
# of the Pod template
app: flask-clock
template:
metadata:
labels:
app: flask-clock
spec:
containers:
# Here's our Flask container
- name: flask-app
# Put your own image here
image: registry.hsrn.nyu.edu/hsrn-projects/flask-clock
ports:
# This is the port we'll expose to the internet eventually
- name: web
containerPort: 5000
resources:
requests:
cpu: 10m # Request very little CPU
memory: 100Mi # Request that this memory be allocated to us
limits:
cpu: 100m # Throttle the container if using more CPU
memory: 100Mi # Terminate the container if using more memory
Create the Deployment on the cluster by running:
You can see the Pods get created and eventually get in the Running state:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
flask-clock-7f87997659-6b2hz 0/1 ContainerCreating 0 2s
flask-clock-7f87997659-9ldrs 0/1 ContainerCreating 0 2s
$ k get pod
NAME READY STATUS RESTARTS AGE
flask-clock-7f87997659-6b2hz 1/1 Running 0 1m23s
flask-clock-7f87997659-9ldrs 1/1 Running 0 1m23s
Create the Service
A Service is how Kubernetes routes network connections to containers. We will create a Service that will allow HTTP access to our application. Kubernetes will automatically route connections to any of the available Pods, wherever they are in the cluster.
Write this manifest to service.yml
:
apiVersion: v1
kind: Service
metadata:
name: flask-clock
labels:
app: flask-clock
source: hsrn-tutorial
spec:
type: ClusterIP # This is the default, a virtual IP address will be allocated
selector:
# This is how the Service will find the Pods
app: flask-clock
ports:
- name: web
protocol: TCP
port: 80 # The port exposed by the service
targetPort: 5000 # The port or port name of the Pod
Create the Service on the cluster by running:
You can see the Service using:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
flask-clock ClusterIP 10.0.238.29 <none> 80/TCP 2s
$ kubectl describe svc flask-clock
Name: flask-clock
Labels: k8s-app=flask-clock
source=hsrn-tutorial
Selector: k8s-app=flask-clock
Type: ClusterIP
IP: 10.0.238.29
Port: web 80/TCP
TargetPort: 5000/TCP
Endpoints: 10.0.27.101:5000,10.0.52.247:5000
Other containers in your namespace can use the Service's name (flask-clock
) or IP (for me, 10.0.238.29
) to access your application. You can also see that there are two endpoints, since the Deployment is set to replicas: 2
.
You can access your application locally by forwarding a port from the cluster:
You can now open http://localhost:5000/ in the browser. Kubectl will forward your connection to the Service, and you will be get an answered from one of the two containers running in the cluster.
Create an Ingress with a subdomain
We have exposed the application to other containers in the cluster, and to developers using Kubectl. By using an Ingress, we can make it available to users.
An Ingress is a set of rules to route HTTP requests for a subdomain and path to specific Service(s). Using an Ingress, you can control our HAProxy load-balancer to make it proxy requests to your own containers.
Write this manifest to ingress.yml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: flask-clock
labels:
app: flask-clock
source: hsrn-tutorial
annotations:
# Use the HAProxy ingress controller (currently the only option)
kubernetes.io/ingress.class: haproxy
# Instruct HAProxy to redirect HTTP to HTTPS, with the 301 "permanent" code
haproxy.org/ssl-redirect: "true"
haproxy.org/ssl-redirect-code: "301"
# Instruct HAProxy to provide the end-user's address in the 'X-Forwarded-For' header
haproxy.org/forwarded-for: "true"
spec:
rules:
# You can have multiple rules in one Ingress, or create multiple Ingresses
- host: flask-clock.users.hsrn.nyu.edu
# You are welcome to take advantage of our *.users.hsrn.nyu.edu domain,
# or you can use your own. In that case, you might have to provide a
# certificate to enable HTTPS
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: flask-clock
port:
number: 80
Create the Ingress on the cluster by running:
You can see the Ingress using:
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
flask-clock <none> flask-clock.users.hsrn.nyu.edu 80 2m32s
Note that you might need to change the host
to something more unique if other people are going through this tutorial.
And of course you can now access your application at https://flask-clock.users.hsrn.nyu.edu/.
Ingresses default to NYU-only
For security reasons, Ingresses are only accessible from NYU networks. This includes the NYU VPN. This keeps our systems safe from access from unknown internet users.
See the Ingress documentation for information on how to enable access from other networks or the public internet, optionally behind password authentication.
Clean up
You can delete all the objects you created in this tutorial by using the labels, like this: