docs: README covering app, K8s objects, networks, volumes, lifecycle
This commit is contained in:
parent
b9f9b45b2e
commit
7cbbb6a1f9
180
README.md
Normal file
180
README.md
Normal file
@ -0,0 +1,180 @@
|
||||
# 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://<minikube-ip>: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.
|
||||
Loading…
Reference in New Issue
Block a user