diff --git a/z2/README.md b/z2/README.md new file mode 100644 index 0000000..e0683e3 --- /dev/null +++ b/z2/README.md @@ -0,0 +1,83 @@ +# Notes App — Kubernetes Assignment + +## What the application does + +A simple **Notes** web application where users can create, view, and delete short text notes. Notes are stored persistently in a PostgreSQL database. The app is accessible via a web browser. + +## Containers used + +| Container | Image | Description | +|---|---|---| +| `frontend` | custom (Nginx 1.27-alpine) | Serves the static HTML/JS frontend and proxies `/api/` requests to the backend | +| `backend` | custom (Python 3.12-slim / Flask) | REST API for CRUD operations on notes | +| `db` | `postgres:16-alpine` | Relational database storing notes persistently | +| `adminer` | `adminer:4` | Web UI for browsing and managing the PostgreSQL database | + +## Kubernetes objects + +| File | Object | Kind | Description | +|---|---|---|---| +| `k8s/namespace.yaml` | `notes-app` | Namespace | Isolates all app objects in one namespace | +| `k8s/statefulset.yaml` | `db` | StatefulSet | Runs the PostgreSQL pod with stable identity | +| `k8s/statefulset.yaml` | `postgres-pv` | PersistentVolume | Host-path volume providing 1Gi of storage | +| `k8s/statefulset.yaml` | `postgres-pvc` | PersistentVolumeClaim | Claims the PV for use by the StatefulSet | +| `k8s/deployment.yaml` | `backend` | Deployment | Runs the Flask API pod | +| `k8s/deployment.yaml` | `frontend` | Deployment | Runs the Nginx frontend pod | +| `k8s/deployment.yaml` | `adminer` | Deployment | Runs the Adminer pod | +| `k8s/service.yaml` | `db` | Service (ClusterIP/Headless) | Internal DNS for the database (`db:5432`) | +| `k8s/service.yaml` | `backend` | Service (ClusterIP) | Internal DNS for the API (`backend:5000`) | +| `k8s/service.yaml` | `frontend` | Service (NodePort 30080) | Exposes the frontend to the host | +| `k8s/service.yaml` | `adminer` | Service (NodePort 30081) | Exposes Adminer to the host | +| `k8s/configmap.yaml` | `db-init` | ConfigMap | Holds `init.sql` mounted into the DB container | + +## Networking and volumes + +- All objects live in the `notes-app` namespace. +- Pods communicate via Kubernetes internal DNS (service names). +- The `db` service is headless (`clusterIP: None`) — required for StatefulSets. +- The `frontend` and `adminer` services use `NodePort` to be reachable from the host. +- `postgres-pv` is a `hostPath` PersistentVolume at `/data/notes-postgres` on the node. +- `postgres-pvc` binds to `postgres-pv` and is mounted into the `db` pod. + +## Container configuration + +- **db**: configured via env vars (`POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`). The `db-init` ConfigMap is mounted into `/docker-entrypoint-initdb.d/` and runs once on first startup. +- **backend**: receives `DATABASE_URL` pointing to the `db` service. +- **frontend**: Nginx proxies `/api/` to `http://backend:5000` and serves `index.html` for all other paths. +- **adminer**: no extra configuration, exposed on NodePort 30081. + +## Instructions + +### Prepare (build and load images) +```bash +chmod +x prepare-app.sh start-app.sh stop-app.sh remove-app.sh +./prepare-app.sh +``` + +### Start +```bash +./start-app.sh +``` + +### Stop (data is preserved) +```bash +./stop-app.sh +``` + +### Remove everything +```bash +./remove-app.sh +``` + +## Viewing the application + +| URL | Description | +|---|---| +| http://localhost:30080 | Notes web application | +| http://localhost:30081 | Adminer — Server: `db`, User: `appuser`, Password: `apppassword`, Database: `appdb` | + +> If using minikube, replace `localhost` with the output of `minikube ip`. + +## Use of artificial intelligence + +This project was created with the assistance of Kiro AI (kiro-cli chat agent). The AI generated the Kubernetes manifests, shell scripts, and documentation. All generated files were reviewed and understood by the author. diff --git a/z2/docker-compose.yaml b/z2/docker-compose.yaml new file mode 100644 index 0000000..7b03672 --- /dev/null +++ b/z2/docker-compose.yaml @@ -0,0 +1,55 @@ +version: "3.9" + +networks: + frontend_net: + backend_net: + +volumes: + postgres_data: + +services: + db: + image: postgres:16-alpine + container_name: app_db + restart: on-failure + environment: + POSTGRES_DB: appdb + POSTGRES_USER: appuser + POSTGRES_PASSWORD: apppassword + volumes: + - postgres_data:/var/lib/postgresql/data + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + networks: + - backend_net + + backend: + build: ./backend + container_name: app_backend + restart: on-failure + environment: + DATABASE_URL: postgresql://appuser:apppassword@db:5432/appdb + depends_on: + - db + networks: + - backend_net + + frontend: + build: ./frontend + container_name: app_frontend + restart: on-failure + ports: + - "8080:80" + depends_on: + - backend + networks: + - frontend_net + - backend_net + + adminer: + image: adminer:4 + container_name: app_adminer + restart: on-failure + ports: + - "8081:8080" + networks: + - backend_net diff --git a/z2/prepare-app.sh b/z2/prepare-app.sh new file mode 100644 index 0000000..1612386 --- /dev/null +++ b/z2/prepare-app.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -e + +echo "Building images..." +docker build -t notes-backend:latest ./backend +docker build -t notes-frontend:latest ./frontend + +# Load images into the cluster node (minikube or kind) +if command -v minikube &>/dev/null; then + echo "Loading images into minikube..." + minikube image load notes-backend:latest + minikube image load notes-frontend:latest +elif command -v kind &>/dev/null; then + echo "Loading images into kind..." + kind load docker-image notes-backend:latest + kind load docker-image notes-frontend:latest +else + echo "Warning: neither minikube nor kind detected. Make sure the cluster can access local images." +fi + +echo "Done. Run ./start-app.sh to deploy." diff --git a/z2/remove-app.sh b/z2/remove-app.sh new file mode 100644 index 0000000..664ff0c --- /dev/null +++ b/z2/remove-app.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +echo "Removing Notes App..." + +kubectl delete namespace notes-app --ignore-not-found +kubectl delete pv postgres-pv --ignore-not-found + +echo "All Kubernetes objects removed." diff --git a/z2/start-app.sh b/z2/start-app.sh new file mode 100644 index 0000000..ee1c20d --- /dev/null +++ b/z2/start-app.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -e + +echo "Deploying Notes App to Kubernetes..." + +kubectl apply -f k8s/namespace.yaml +kubectl apply -f k8s/configmap.yaml +kubectl apply -f k8s/statefulset.yaml +kubectl apply -f k8s/service.yaml +kubectl apply -f k8s/deployment.yaml + +echo "Waiting for pods to be ready..." +kubectl wait --namespace notes-app --for=condition=ready pod --all --timeout=120s + +echo "App is running." +echo "Frontend: http://localhost:30080" +echo "Adminer: http://localhost:30081" diff --git a/z2/stop-app.sh b/z2/stop-app.sh new file mode 100644 index 0000000..6f1af85 --- /dev/null +++ b/z2/stop-app.sh @@ -0,0 +1,11 @@ +.#!/usr/bin/env bash +set -e + +echo "Stopping Notes App..." + +kubectl delete -f k8s/deployment.yaml --ignore-not-found +kubectl delete -f k8s/service.yaml --ignore-not-found +kubectl delete -f k8s/statefulset.yaml --ignore-not-found +kubectl delete -f k8s/configmap.yaml --ignore-not-found + +echo "App stopped. Namespace and volumes are preserved."