Skip to content

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:

$ docker build . -t flask-clock

You should now be able to run this container locally with the following:

$ docker run --rm -p 5000:5000 flask-clock

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:

$ kubectl apply -f deployment.yml
deployment.apps/flask-clock created

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:

$ kubectl apply -f service.yml
service/flask-clock created

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:

$ kubectl port-forward svc/flask-clock 5000:80
Forwarding from 127.0.0.1:5000 -> 5000

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:

$ kubectl apply -f ingress.yml
ingress.networking.k8s.io/flask-clock created

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:

$ kubectl delete deploy,service,ingress -l source=hsrn-tutorial
deployment.apps "flask-clock" deleted
service "flask-clock" deleted
ingress.networking.k8s.io "flask-clock" deleted