My First Minikube App

Posted on 2019-11-26

Aim: Serve “Hello world” using a docker container of a flask app, managed by kubernetes using minikube.

Main source: I have mildly modified this tutorial to run the app on minikube.

Prereqs

Installation of:

  • minikube
  • kubectl
  • VM driver
  • docker

The flask app requires python3 (used with version 3.7) with flask (obviously) and gunicorn.

A working knowledge of setting up a simple flask app in a docker container is probably helpful.

Code structure

$tree 
.
├── app
│   ├── app.py
│   ├── config.py
│   ├── Dockerfile
│   └── requirements.txt
└── kubes
    └── app.yaml

Flask app

The app.py:

from flask import Flask

import config

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!! 🎉"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=config.PORT, debug=config.DEBUG_MODE)

The config.py:

import os, multiprocessing

# Get enviornment variables or use defaults if not set
PORT = int(os.environ.get("PORT", 8080))
DEBUG_MODE = int(os.environ.get("DEBUG_MODE", 1))

# Gunicorn config
bind = ":" + str(PORT)
workers = multiprocessing.cpu_count() * 2 + 1
threads = 2 * multiprocessing.cpu_count()

The requirements.py:

Flask==1.1.1
gunicorn==20.0.3

Dockerfile

The Dockerfile:

FROM python:3.7-alpine
WORKDIR /app
ADD requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt
ADD . /app
ENV PORT 8080
CMD ["gunicorn", "app:app", "--config=config.py"]

Kubenetes config

The app.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    name: flask-app
spec:
  replicas: 1
  selector:
    matchLabels:
      name: flask-app
  template:
    metadata:
      name: flask-app
      labels:
        name: flask-app
    spec:
      containers:
        - name: flask-app
          image: flask-app 
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: 256Mi
            limits:
              memory: 512Mi
          env:
            - name: DEBUG_MODE
              value: "0"
---
apiVersion: v1
kind: Service
metadata:
  name: flask-app-service
spec:
  selector:
    name: flask-app
  ports:
    - port: 80
      targetPort: 8080
  type: LoadBalancer

Setting up on minikube

Launch

Start minikube and the dashboard:

$minikube start && minikube dashboard 
😄  minikube v1.5.2 on Ubuntu 18.04
🔥  Creating kvm2 VM (CPUs=2, Memory=2000MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.16.2 on Docker '18.09.9' ...
🚜  Pulling images ...
🚀  Launching Kubernetes ... 
  Waiting for: apiserver
🏄  Done! kubectl is now configured to use "minikube"
🤔  Verifying dashboard health ...
🚀  Launching proxy ...
🤔  Verifying proxy health ...
🎉  Opening http://127.0.0.1:40661/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...

Build image

In one terminal, establish environment variables that ensures docker builds images where minikube can see them:

$eval $(minikube docker-env)

Note that this is only relevant to the terminal in which it is run. Build the docker image in the apps/ directory

$docker build -t flask-app .
Sending build context to Docker daemon   5.12kB
Step 1/7 : FROM python:3.7-alpine
3.7-alpine: Pulling from library/python
...
Successfully tagged flask-app:latest

Deploy and service

In the kubes/ directory, we have the app.yaml which describes the deployment of the app, as well as the LoadBalancer service that exposes the deployment on the correct ports. This is setup by running the following (within the kubes/ directory):

$kubectl apply -f app.yaml
deployment.apps/flask-app created
service/flask-app-service created

Tunnelling

The exposed ports still aren’t available to the host machine (outside the VM). By either consulting the dashboard, or by running the following command:

$kubectl get service
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
flask-app-service   LoadBalancer   10.96.229.250   <pending>     80:31748/TCP   97s
kubernetes          ClusterIP      10.96.0.1       <none>        443/TCP        4m23s

We see no external IP is yet available.

Last step is to open a tunnel:

$minikube tunnel 
Status:	
	machine: minikube
	pid: 16289
	route: 10.96.0.0/12 -> 192.168.39.77
	minikube: Running
	services: [flask-app-service]
    errors: 
		minikube: no errors
		router: no errors
		loadbalancer emulator: no errors
...

Now:

$kubectl get service
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
flask-app-service   LoadBalancer   10.96.229.250   10.96.229.250   80:31748/TCP   2m44s
kubernetes          ClusterIP      10.96.0.1       <none>          443/TCP        5m30s

And finally we have:

$curl 10.96.229.250
Hello World!! 🎉

Yay!!