zkt26/z2
2026-04-29 09:47:09 +02:00
..
app Second Assignment 2026-04-29 09:47:09 +02:00
k8s Second Assignment 2026-04-29 09:47:09 +02:00
sql Second Assignment 2026-04-29 09:47:09 +02:00
k8s-todo-app-documentation.docx Second Assignment 2026-04-29 09:47:09 +02:00
prepare-app.sh Second Assignment 2026-04-29 09:47:09 +02:00
README.md Second Assignment 2026-04-29 09:47:09 +02:00
start-app.sh Second Assignment 2026-04-29 09:47:09 +02:00
stop-app.sh Second Assignment 2026-04-29 09:47:09 +02:00

K8s Todo App

A full-stack Todo List web application deployed on Kubernetes using Minikube.
The app stores tasks in a PostgreSQL database and serves a modern web UI via a Python Flask backend.


What the Application Does

Users can:

  • Add new tasks via a text input form
  • Mark tasks as done by clicking the circle toggle button
  • Delete tasks with the ✕ button
  • View a live progress counter (e.g. 2/5 done)

All data is persisted to a PostgreSQL database backed by a Kubernetes PersistentVolume.


Architecture Overview

Browser
  │
  ▼ NodePort :30080
┌──────────────────────────────────────┐
│  Namespace: todo-app                 │
│                                      │
│  ┌────────────────────┐              │
│  │  Deployment        │              │
│  │  todo-app (x2 pods)│ ──────────► │─── postgres-service:5432
│  │  Flask + Gunicorn  │              │         │
│  └────────────────────┘              │  ┌──────────────────┐
│                                      │  │  StatefulSet     │
│                                      │  │  postgres (x1)   │
│                                      │  │  PostgreSQL 16   │
│                                      │  │       │          │
│                                      │  │  PVC ──► PV      │
│                                      │  │  (hostPath)      │
│                                      │  └──────────────────┘
└──────────────────────────────────────┘

Container Images Used

Image Version Purpose
todo-app latest (built locally) Flask web server + Gunicorn WSGI
postgres 16-alpine Relational database
busybox 1.36 Init container — waits for PostgreSQL before app starts

todo-app (custom image)

  • Built from app/Dockerfile using Python 3.12-slim
  • Runs a Flask application with Gunicorn (2 workers)
  • Connects to PostgreSQL via environment variables
  • Exposes port 5000
  • Runs as a non-root user (appuser) for security

postgres:16-alpine

  • Official PostgreSQL image (Alpine variant for smaller size)
  • Initialises the tododb database using a mounted SQL ConfigMap
  • Data is persisted via a PersistentVolumeClaim

Kubernetes Objects

Object Name Description
Namespace todo-app Isolates all app resources from the rest of the cluster
Deployment todo-app Runs 2 replicas of the Flask web app with rolling updates
StatefulSet postgres Manages the PostgreSQL pod with stable network identity
PersistentVolume postgres-pv 2Gi host-path volume on the minikube node
PersistentVolumeClaim postgres-pvc Binds to postgres-pv, used by the StatefulSet
Service todo-app-service NodePort (30080) — exposes the Flask app to the browser
Service postgres-service ClusterIP — internal access to PostgreSQL on port 5432
Service postgres-headless Headless — required by StatefulSet for stable DNS
Secret postgres-secret Stores DB credentials (username, password, DB name)
ConfigMap postgres-init-sql Holds the SQL init script run by PostgreSQL on first boot

Networks and Volumes

Virtual Networks (Services)

  • todo-app-serviceNodePort type. Maps external port 30080 → pod port 5000. Accessible at http://<minikube-ip>:30080.
  • postgres-serviceClusterIP type. Only reachable inside the cluster at postgres-service.todo-app.svc.cluster.local:5432.
  • postgres-headlessClusterIP: None. A DNS-only headless service required by the StatefulSet so each pod gets a stable DNS entry (postgres-0.postgres-headless.todo-app.svc.cluster.local).

Persistent Volumes

  • postgres-pvhostPath pointing to /mnt/data/postgres inside the minikube VM. Survives pod restarts and redeployments.
  • postgres-pvc — Claims 2Gi from postgres-pv. Mounted at /var/lib/postgresql/data inside the PostgreSQL container.

Container Configuration

Flask App (todo-app)

  • DB_HOST — hostname of the PostgreSQL service (postgres-service.todo-app.svc.cluster.local)
  • DB_NAME — database name (tododb)
  • DB_USER / DB_PASS — injected from the postgres-secret Kubernetes Secret
  • Liveness probe: GET /health every 10s — restarts the pod if it hangs
  • Readiness probe: GET /health every 5s — holds traffic until the app is truly ready
  • Init container: busybox runs nc -z postgres-service 5432 in a loop until PostgreSQL accepts connections before the main container starts

PostgreSQL (postgres)

  • POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB — injected from postgres-secret
  • PGDATA=/var/lib/postgresql/data/pgdata — prevents permission errors on mounted volumes
  • SQL init script from ConfigMap is placed at /docker-entrypoint-initdb.d/init.sql and runs automatically on first start
  • Liveness probe: pg_isready -U postgres
  • Readiness probe: pg_isready -U postgres

Step-by-Step Instructions

Prerequisites — Install Everything on Ubuntu

Run the following commands in sequence in your terminal:

# 1. Update system
sudo apt-get update && sudo apt-get upgrade -y

# 2. Install Docker
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo usermod -aG docker $USER
newgrp docker

# 3. Install kubectl
curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client

# 4. Install minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube version

Prepare the Application (run once)

# Navigate to the project directory
cd k8s-todo-app

# Make all scripts executable
chmod +x prepare-app.sh start-app.sh stop-app.sh

# Run the preparation script (starts minikube, builds Docker image, creates host dir)
bash prepare-app.sh

Start the Application

bash start-app.sh

This script creates all Kubernetes objects in order:

  1. Namespace
  2. ConfigMap
  3. PersistentVolume, PVC, Secret, StatefulSet (PostgreSQL)
  4. Services
  5. Deployment (Flask app)

View the Application in a Browser

# Option A — get the URL automatically
minikube service todo-app-service -n todo-app

# Option B — open directly
minikube service todo-app-service -n todo-app --url
# Copy the URL and open it in your browser
# e.g. http://192.168.49.2:30080

Useful Commands While Running

# See all running pods
kubectl get pods -n todo-app

# See all objects
kubectl get all -n todo-app

# Watch pods in real time
kubectl get pods -n todo-app -w

# View Flask app logs
kubectl logs -l app=todo-app -n todo-app --tail=50

# View PostgreSQL logs
kubectl logs postgres-0 -n todo-app --tail=50

# Connect to PostgreSQL shell
kubectl exec -it postgres-0 -n todo-app -- psql -U postgres -d tododb

# Scale up/down the web app
kubectl scale deployment todo-app -n todo-app --replicas=3

Pause / Delete the Application

# Delete all Kubernetes objects (data on PV is preserved)
bash stop-app.sh

# To also remove all stored database data
minikube ssh -- "sudo rm -rf /mnt/data/postgres"

# To stop minikube entirely
minikube stop

# To delete the minikube cluster completely
minikube delete

Re-deploy After Stop

bash start-app.sh   # no need to run prepare-app.sh again unless minikube was deleted

Troubleshooting

Problem Fix
ImagePullBackOff on todo-app Re-run bash prepare-app.sh to rebuild image inside minikube
Postgres pod in Pending Run kubectl describe pv postgres-pv — check hostPath exists
App shows "Cannot connect to database" Wait 30s, postgres is still initialising; check kubectl logs postgres-0 -n todo-app
Can't reach browser URL Run minikube service todo-app-service -n todo-app to get the correct URL
Permission error on PV Run minikube ssh -- 'sudo chmod 777 /mnt/data/postgres'