Ingress routing • Secrets injection • Sidecar Pod pattern (2 containers/Pod) • 3 replicas • Health checks (Readiness/Liveness probes) • Reproducible
✅ FastAPI User API served by Uvicorn (port 8000)
✅ MySQL backend (initialized DB Main + table Users)
✅ 3× replicas via a Kubernetes Deployment (sidecar-style - each Pod runs 2 containers: db + api)
✅ Secrets-based configuration (DB password injected via Kubernetes Secret)
✅ Service + Ingress routing (Ingress → Service → Pods)
✅ Health checks (readiness + liveness probes on /status)
✅ Proof capture script that snapshots cluster state + endpoint responses into evidence/
☸️ Kubernetes (Deployment, Service, Ingress, Secret) | 🐳 Docker | 🐍 Python | ⚡ FastAPI | 🧰 SQLAlchemy | 🌐 Uvicorn | 🗄️ MySQL | 🔀 Traefik Ingress (cluster-specific)
(external client)
|
| HTTP :80
v
+-------------------+
| Ingress | api-ingress (ingressClassName must match cluster, e.g. traefik)
+---------+---------+
|
| routes "/" -> api-svc:8000
v
+---------------------+
| Service (ClusterIP) | api-svc :8000
+---------+-----------+
|
| load-balances across 3 replicas
v
+------------------- Deployment: api-mysql (replicas=3) -------------------+
| |
| Pod (x3) |
| +------------------+ localhost (same Pod) +------------+ |
| | db | <-------------------------------> | api | |
| | (MySQL) | | FastAPI | |
| | :3306 | | :8000 | |
| +------------------+ +------------+ |
| |
+--------------------------------------------------------------------------+
Notes:
- The password is injected via Secret `mysql-secret` (no hardcoding in code).
- API talks to MySQL via 127.0.0.1:3306 because both containers run in the same Pod.
Figure 1: K8s Deployment Architecture.
.
├── api/ # FastAPI build context (Docker image source)
│ ├── Dockerfile # Builds the API image (uvicorn on :8000)
│ ├── main.py # FastAPI app + DB connection via env vars
│ └── requirements.txt # Python dependencies (fastapi, sqlalchemy, mysqlclient, uvicorn)
├── compose.yaml # Local API↔DB integration test (2 services: api + db) before K8s
├── docs/
│ ├── IMPLEMENTATION.md # Step-by-step build diary + commands + troubleshooting
│ └── RUNBOOK.md # Quick Setup Guide (project setup, excecution + verification flow)
├── evidence/ # Captured proof bundles (one folder per run)
│ └── 2026-02-23_2108/ # Example proof run (timestamped)
│ ├── 00_meta.txt # Metadata (timestamp + base URL used for curls)
│ ├── 01_pods.txt # kubectl get pods ... (replicas + readiness)
│ ├── 02_service.txt # kubectl get svc ... (ClusterIP + ports)
│ ├── 03_endpointslice.txt # EndpointSlice (pod IPs:8000 behind the Service)
│ ├── 04_ingress.txt # kubectl get ingress ... (class + address)
│ ├── 05_curl_status.txt # curl /status response
│ ├── 06_curl_users.json # curl /users response (JSON)
│ └── 07_curl_user_1.json # curl /users/1 response (JSON)
├── Makefile # Convenience targets (build/run/checks) during development
├── my-deployment-eval.yml # Deployment (replicas=3, 2 containers per Pod: db+api)
├── my-ingress-eval.yml # Ingress (routes "/" to api-svc:8000; class must match cluster)
├── my-secret-eval.yml # Secret (MYSQL_PASSWORD) injected into db+api containers
├── my-service-eval.yml # ClusterIP Service (api-svc:8000) selecting Deployment Pods
├── README.md # Project overview + quick start (this file)
└── scripts/
└── capture-proof.sh # Captures proof bundle into evidence/<timestamp>/
kubectl apply -f my-secret-eval.yml
kubectl apply -f my-deployment-eval.yml
kubectl apply -f my-service-eval.yml
kubectl apply -f my-ingress-eval.ymlkubectl rollout status deployment/api-mysql
kubectl get pods -l app=api-mysql -o wide
kubectl get svc api-svc -o wide
kubectl get endpointslice -l kubernetes.io/service-name=api-svc -o wide
kubectl get ingress api-ingress -o wideIf your Ingress shows an address:
curl -s http://<INGRESS_ADDRESS>/status; echo
curl -s http://<INGRESS_ADDRESS>/users | jq
curl -s http://<INGRESS_ADDRESS>/users/1 | jqRun:
./scripts/capture-proof.shThis creates a timestamped folder under evidence/ containing:
- current cluster state (pods/service/endpointslice/ingress)
- API responses for
/status,/users,/users/1
Runbook
For the TL;DR command checklist and quick setup guide:
Implementation log
For the detailed build diary, decisions, and commands:
Goal: Create a commented set of Kubernetes deployment files to deploy a data API with 3 Pods
3 Pods with 2 Containers per Pod:
- 1 MySQL container (using a provided database image:
datascientest/mysql-k8s:1.0.0) - 1 FastAPI API container (to be built and pushed)
Provided API sources (to complete, refactor, bugfix + build):
Dockerfile(builds the FastAPI container)main.py(API code; DB connection fields to complete)requirements.txt(Python deps:fastapi,sqlalchemy,mysqlclient==2.1.1,uvicorn)
Kubernetes requirements:
- Create one Deployment with 3 Pods, each Pod containing:
- 1× MySQL container
- 1× FastAPI container
- Create a Service and an Ingress to enable API access.
- DB password must be:
- Must NOT be hard-coded in
main.py - Must be stored in a Kubernetes Secret and injected as env vars.
- Must NOT be hard-coded in
Expected deliverables (files):
- Reworked
api/main.py my-deployment-eval.yml(Deployment)my-service-eval.yml(Service)my-ingress-eval.yml(Ingress)my-secret-eval.yml(Secret)
Submission note: Upload the deliverables as a zip/tar archive after validating the module exercises.
- ✅ FastAPI image rebuilt locally and pushed to Docker Hub
- ✅
my-secret-eval.ymlstores DB password and is referenced by the Deployment - ✅
my-deployment-eval.ymldefinesreplicas: 3and runs 2 containers per Pod - ✅
my-service-eval.ymlexposes the API internally on port8000 - ✅
my-ingress-eval.ymlroutes HTTP traffic to the Service - ✅
docs/IMPLEMENTATION.mdcontains a reproducible build diary + proof commands - ✅
scripts/capture-proof.shproduces evidence snapshots underevidence/