# Task Manager on Kubernetes A web-based task manager (Nginx + Flask + PostgreSQL) deployed to a Kubernetes cluster (minikube). Migrated from the Docker Compose version in the first assignment. ## What the application does A simple CRUD task manager. Users can: - Add a new task by typing a title and pressing Enter. - Mark a task as completed by toggling its checkbox. - Delete a task by clicking the X button. - See all tasks sorted by creation date. The frontend is a static page served by Nginx. The backend is a Flask REST API. Tasks are stored in PostgreSQL and survive pod restarts thanks to a hostPath PersistentVolume. ## Prerequisites - **minikube** (any recent version, tested with 1.32+) - **kubectl** (any version compatible with the minikube cluster) - **Docker** CLI (used by `prepare-app.sh` to build images directly into minikube's docker daemon) - **bash** (Linux/macOS native; Git Bash on Windows) ## Containers used | Container | Image | Description | |-----------|-------|-------------| | `web` | `taskapp-web:v1` (built locally from `nginx:alpine`) | Serves static HTML/CSS/JS and reverse-proxies `/api/*` to the `api` Service. `nginx.conf` is mounted from a ConfigMap. | | `api` | `taskapp-api:v1` (built locally from `python:3.12-slim`) | Flask REST API on Gunicorn (2 workers). Exposes `GET/POST /api/tasks`, `PUT /api/tasks/:id`, `DELETE /api/tasks/:id`, `GET /api/health`. | | `db` | `postgres:15` (Docker Hub) | Stores `tasks` table. Data lives at `/var/lib/postgresql/data` on a PersistentVolume backed by hostPath. | ## Kubernetes objects | Object | Name | Description | |--------|------|-------------| | Namespace | `taskapp` | Isolates every other resource. | | Secret | `db-credentials` | Holds `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`. Consumed by both Postgres and Flask via `envFrom`. | | ConfigMap | `nginx-config` | Holds `nginx.conf`. Mounted into the `web` container at `/etc/nginx/nginx.conf` via `subPath`. | | PersistentVolume | `db-pv` | 1 Gi, `hostPath: /mnt/data/taskapp-db` on the minikube node. ReclaimPolicy `Retain`. StorageClass `manual`. | | PersistentVolumeClaim | `db-pvc` | 1 Gi, `ReadWriteOnce`, binds to `db-pv` via label selector. Mounted by the StatefulSet. | | StatefulSet | `db` | 1 replica, `postgres:15`. Uses the PVC. Has liveness and readiness probes (`pg_isready`). | | Deployment | `api` | 2 replicas, custom Flask image. Probes hit `GET /api/health` (which checks DB reachability). | | Deployment | `web` | 2 replicas, custom Nginx image. Probes hit `GET /`. | | Service | `web` | NodePort 30080 → 80. The only externally reachable Service. | | Service | `api` | ClusterIP, port 5000. Internal only. | | Service | `db` | Headless (`clusterIP: None`), port 5432. Internal only. Pairs with the StatefulSet for stable DNS. | ## Virtual networks The cluster's CNI plugin handles all pod-to-pod traffic. Service discovery is via Kubernetes DNS (`kube-dns` / `coredns`): - Inside the namespace, every Service is reachable by short name: `web`, `api`, `db`. - The `web` Service is the only one exposed outside the cluster (NodePort 30080). - The `db` Service is **headless** — it returns the pod IPs directly instead of load-balancing. This is the canonical pattern for StatefulSets and gives `db-0` a stable DNS identity (`db-0.db.taskapp.svc.cluster.local`). ## Named volumes | Volume | Mount | Backed by | Reclaim policy | |--------|-------|-----------|----------------| | `db-pv` (PVC `db-pvc`) | `/var/lib/postgresql/data` (in `db-0`) | hostPath `/mnt/data/taskapp-db` on the minikube node | Retain | `Retain` means deleting the PVC does not automatically wipe the underlying directory — the data survives even a full `stop-app.sh` and is reattached on the next `prepare-app.sh + start-app.sh`. To wipe it manually: ```bash minikube ssh -- sudo rm -rf /mnt/data/taskapp-db ``` ## Container configuration performed - **`web`** — built from `nginx:alpine` plus the static frontend files. The `nginx.conf` is supplied at runtime via the `nginx-config` ConfigMap, mounted with `subPath: nginx.conf` so only that single file is overlaid (not the whole config directory). This means the proxy rule (`proxy_pass http://api_backend;`) and `large_client_header_buffers` setting can be edited by changing the ConfigMap and reloading, without rebuilding the image. - **`api`** — built from `python:3.12-slim`, installs `libpq-dev` and `gcc` for the `psycopg2` build, then `pip install` of `requirements.txt` (Flask + Gunicorn + psycopg2). Runs Gunicorn with 2 workers on port 5000. Reads DB credentials from env vars sourced from the `db-credentials` Secret. - **`db`** — official `postgres:15` image, unmodified. Env (DB name, user, password) sourced from the same Secret. `PGDATA` is set to `/var/lib/postgresql/data/pgdata` (a subdir) because Postgres refuses to initialize a non-empty data directory and hostPath dirs occasionally have stray entries. Resource requests/limits are set on every container so the cluster scheduler can place pods predictably; values are conservative and tested against a 4 GiB minikube VM. ## Usage ### Prepare the application Builds images directly into minikube's docker daemon, creates the hostPath directory on the node, and applies Namespace + Secret + ConfigMap + PV + PVC + StatefulSet. ```bash ./prepare-app.sh ``` ### Start the application Applies the Deployments and Services. (Re-applies prepare-stage resources idempotently, so it's safe to run on its own too.) ```bash ./start-app.sh ``` The script then opens your default browser at `http://:30080`. ### Pause / stop the application `stop-app.sh` removes every Kubernetes object (Deployments, Services, StatefulSet, PVC, PV, Namespace, Secret, ConfigMap): ```bash ./stop-app.sh ``` The hostPath data on the minikube node is **retained** (PV ReclaimPolicy = `Retain`). To resume from where you left off, run `./prepare-app.sh && ./start-app.sh` again. ### Delete everything (including data) ```bash ./stop-app.sh minikube ssh -- sudo rm -rf /mnt/data/taskapp-db ``` ## Viewing the application in a web browser After `./start-app.sh` finishes, run: ```bash minikube service web -n taskapp ``` This opens the browser at the right URL automatically. Alternatively, navigate manually: ```bash echo "http://$(minikube ip):30080" ``` You'll see the Task Manager interface where you can add, complete, and delete tasks. ## API endpoints | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/health` | Health check (200 if DB reachable, 503 otherwise). Used by readiness probe. | | GET | `/api/tasks` | List all tasks. | | POST | `/api/tasks` | Create a task. Body: `{"title": "..."}`. | | PUT | `/api/tasks/:id` | Toggle completion. | | DELETE | `/api/tasks/:id` | Delete a task. | ## Project structure ``` qubernetees/ ├── README.md # this file ├── prepare-app.sh # build images, create PV, apply prepare-stage manifests ├── start-app.sh # apply Deployments + Services, open browser ├── stop-app.sh # full teardown ├── namespace.yaml # Namespace + Secret + ConfigMap ├── statefulset.yaml # PV + PVC + StatefulSet ├── deployment.yaml # api + web Deployments ├── service.yaml # api + web + db Services ├── nginx.conf # source of the ConfigMap (kept for readability) ├── backend/ # Flask REST API │ ├── Dockerfile │ ├── requirements.txt │ └── app.py # + /api/health endpoint └── frontend/ # static HTML/CSS/JS ├── Dockerfile ├── index.html ├── style.css └── app.js ``` ## Sources - First assignment (Docker version of the same app) — code copied from `cloud assiagment/` - [Kubernetes documentation](https://kubernetes.io/docs/) - [Kubernetes StatefulSet docs](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) - [Postgres official Docker image](https://hub.docker.com/_/postgres) - [minikube documentation](https://minikube.sigs.k8s.io/docs/) ## Use of Artificial Intelligence This Kubernetes deployment was designed and implemented with the assistance of **Claude** (Anthropic), an AI assistant. Claude was used for: - Designing the K8s object topology (Namespace, Deployments, StatefulSet, PV, PVC, Services). - Authoring the manifest files, lifecycle scripts, and this documentation. - Reviewing the design against the assignment requirements. **AI agent used:** Claude Opus 4.7 (Anthropic) via Claude Code CLI. The application logic itself (Flask backend, frontend) was carried over from the first assignment.