commit
This commit is contained in:
commit
18a369f5ad
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM python:3.10
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]
|
||||||
189
README.md
Normal file
189
README.md
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Notes Application on Kubernetes
|
||||||
|
|
||||||
|
## 📌 Description
|
||||||
|
|
||||||
|
This project is a simple full-stack **Notes Application** deployed on Kubernetes.
|
||||||
|
It allows users to create and view notes through a web interface. The application demonstrates a complete **3-tier architecture**:
|
||||||
|
|
||||||
|
* Frontend (User Interface)
|
||||||
|
* Backend (API Server)
|
||||||
|
* Database (PostgreSQL)
|
||||||
|
|
||||||
|
The application is containerized using Docker and deployed using Kubernetes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧱 Application Architecture
|
||||||
|
|
||||||
|
The system consists of three main components:
|
||||||
|
|
||||||
|
### 1. Frontend
|
||||||
|
|
||||||
|
* Built using HTML and JavaScript
|
||||||
|
* Served via Nginx
|
||||||
|
* Provides UI for adding and viewing notes
|
||||||
|
* Communicates with backend using HTTP requests
|
||||||
|
|
||||||
|
### 2. Backend
|
||||||
|
|
||||||
|
* Built using FastAPI (Python)
|
||||||
|
* Provides REST API endpoints:
|
||||||
|
|
||||||
|
* `GET /notes`
|
||||||
|
* `POST /notes`
|
||||||
|
* Handles business logic and database interaction
|
||||||
|
|
||||||
|
### 3. Database
|
||||||
|
|
||||||
|
* PostgreSQL database
|
||||||
|
* Stores notes persistently
|
||||||
|
* Managed using Kubernetes StatefulSet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Containers Used
|
||||||
|
|
||||||
|
| Container | Description |
|
||||||
|
| --------- | --------------------------------------------- |
|
||||||
|
| frontend | Nginx container serving static frontend files |
|
||||||
|
| backend | FastAPI application handling API requests |
|
||||||
|
| postgres | PostgreSQL database for storing notes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ☸️ Kubernetes Objects
|
||||||
|
|
||||||
|
| Object | Description |
|
||||||
|
| --------------------------- | -------------------------------------------- |
|
||||||
|
| Namespace | `notes-app` – isolates application resources |
|
||||||
|
| Deployment | Manages frontend and backend pods |
|
||||||
|
| StatefulSet | Manages PostgreSQL with persistent storage |
|
||||||
|
| Service | Enables communication between components |
|
||||||
|
| PersistentVolume (PV) | Provides storage for database |
|
||||||
|
| PersistentVolumeClaim (PVC) | Requests storage for PostgreSQL |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Networking
|
||||||
|
|
||||||
|
* Communication is handled using Kubernetes **Services**
|
||||||
|
* Backend is accessible via:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://backend-service
|
||||||
|
```
|
||||||
|
* PostgreSQL is accessible via:
|
||||||
|
|
||||||
|
```
|
||||||
|
postgres
|
||||||
|
```
|
||||||
|
* Frontend is exposed externally using **NodePort**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 Storage
|
||||||
|
|
||||||
|
* PostgreSQL uses persistent storage via:
|
||||||
|
|
||||||
|
* PersistentVolume (PV)
|
||||||
|
* PersistentVolumeClaim (PVC)
|
||||||
|
* Ensures data is retained even if pods restart
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Container Configuration
|
||||||
|
|
||||||
|
### Backend Configuration
|
||||||
|
|
||||||
|
```text
|
||||||
|
DATABASE_URL=postgresql://user:password@postgres:5432/notesdb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Configuration
|
||||||
|
|
||||||
|
```text
|
||||||
|
const API_URL = "http://backend-service";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 How to Run the Application
|
||||||
|
|
||||||
|
### 1. Prepare the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/prepare-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds Docker images and prepares environment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Start the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/start-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploys all Kubernetes resources.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Access the Application
|
||||||
|
|
||||||
|
#### Option 1: NodePort
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:30007
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 2: Port Forward (recommended for Docker Desktop)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl port-forward service/frontend-service 8080:80 -n notes-app
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Stop the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/stop-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Deletes all Kubernetes resources.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Functionality
|
||||||
|
|
||||||
|
* Users can add notes via UI
|
||||||
|
* Notes are stored in PostgreSQL
|
||||||
|
* Data persists across restarts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Notes
|
||||||
|
|
||||||
|
* Docker images are hosted on Docker Hub
|
||||||
|
* Kubernetes cluster used: Docker Desktop Kubernetes
|
||||||
|
* Application demonstrates real-world container orchestration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 References
|
||||||
|
|
||||||
|
* OpenAI. (2026). ChatGPT (GPT-5.5). https://chat.openai.com
|
||||||
|
Used for guidance on Kubernetes deployment, debugging, and structuring the application.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👨💻 Author
|
||||||
|
|
||||||
|
Nihal Ahmed
|
||||||
BIN
configmap.yaml
Normal file
BIN
configmap.yaml
Normal file
Binary file not shown.
12
crud.py
Normal file
12
crud.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from sqlalchemy.orm import Session
|
||||||
|
import models, schemas
|
||||||
|
|
||||||
|
def create_note(db: Session, note: schemas.NoteCreate):
|
||||||
|
db_note = models.Note(content=note.content)
|
||||||
|
db.add(db_note)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_note)
|
||||||
|
return db_note
|
||||||
|
|
||||||
|
def get_notes(db: Session):
|
||||||
|
return db.query(models.Note).all()
|
||||||
10
database.py
Normal file
10
database.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
|
import os
|
||||||
|
|
||||||
|
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@postgres:5432/notesdb")
|
||||||
|
|
||||||
|
engine = create_engine(DATABASE_URL)
|
||||||
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
44
deployment.yaml
Normal file
44
deployment.yaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: backend
|
||||||
|
namespace: notes-app
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: backend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: backend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
image: nihal0314/backend:latest # ✅ FIXED
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
vvalue: postgresql://user:password@postgres:5432/notesdb
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: frontend
|
||||||
|
namespace: notes-app
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: nihal0314/frontend:latest # ✅ FIXED
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
145
index.html
Normal file
145
index.html
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Notes App</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial;
|
||||||
|
background: #f4f4f4;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 25px;
|
||||||
|
width: 420px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
background: #e9ecef;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: green;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Notes App</h1>
|
||||||
|
|
||||||
|
<div class="input-box">
|
||||||
|
<input type="text" id="noteInput" placeholder="Enter your note">
|
||||||
|
<button onclick="addNote()">Add</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="status" class="status"></div>
|
||||||
|
|
||||||
|
<ul id="notesList"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const API_URL = "http://127.0.0.1:5000";
|
||||||
|
async function fetchNotes() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(API_URL + "/notes");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const list = document.getElementById("notesList");
|
||||||
|
list.innerHTML = "";
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
list.innerHTML = "<li>No notes yet</li>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach(note => {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.textContent = "ID: " + note.id + " | " + note.content;
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addNote() {
|
||||||
|
const input = document.getElementById("noteInput");
|
||||||
|
const status = document.getElementById("status");
|
||||||
|
|
||||||
|
if (!input.value.trim()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(API_URL + "/notes", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
content: input.value
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
status.textContent = "Saved to database ✅";
|
||||||
|
|
||||||
|
input.value = "";
|
||||||
|
fetchNotes();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
status.textContent = "Error saving ❌";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load notes on page load
|
||||||
|
fetchNotes();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
4
init.sql
Normal file
4
init.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS notes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
content TEXT
|
||||||
|
);
|
||||||
36
main.py
Normal file
36
main.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from fastapi import FastAPI, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
import models
|
||||||
|
from database import engine, SessionLocal
|
||||||
|
import crud, schemas
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def root():
|
||||||
|
return {"message": "API is running"}
|
||||||
|
|
||||||
|
@app.post("/notes")
|
||||||
|
def create(note: schemas.NoteCreate, db: Session = Depends(get_db)):
|
||||||
|
return crud.create_note(db, note)
|
||||||
|
|
||||||
|
@app.get("/notes")
|
||||||
|
def read(db: Session = Depends(get_db)):
|
||||||
|
return crud.get_notes(db)
|
||||||
8
models.py
Normal file
8
models.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String
|
||||||
|
from database import Base
|
||||||
|
|
||||||
|
class Note(Base):
|
||||||
|
__tablename__ = "notes"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
content = Column(String, index=True)
|
||||||
4
namespace.yaml
Normal file
4
namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: notes-app
|
||||||
8
nginx.conf
Normal file
8
nginx.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
package.json
Normal file
4
package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "notes-frontend",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
3
prepare-app.sh
Normal file
3
prepare-app.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker-compose build
|
||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
psycopg2-binary
|
||||||
|
sqlalchemy
|
||||||
|
pydantic
|
||||||
|
|
||||||
11
schemas.py
Normal file
11
schemas.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class NoteCreate(BaseModel):
|
||||||
|
content: str
|
||||||
|
|
||||||
|
class NoteResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
content: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
BIN
secret.yaml
Normal file
BIN
secret.yaml
Normal file
Binary file not shown.
25
service.yaml
Normal file
25
service.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: backend-service
|
||||||
|
namespace: notes-app
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: backend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 5000
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: frontend-service
|
||||||
|
namespace: notes-app
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: frontend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 30007
|
||||||
3
start-app.sh
Normal file
3
start-app.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker-compose up -d
|
||||||
51
statefulset.yaml
Normal file
51
statefulset.yaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: notes-app
|
||||||
|
spec:
|
||||||
|
serviceName: postgres
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:14
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: user
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: password
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: notesdb
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
volumeMounts:
|
||||||
|
- name: postgres-storage
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: postgres-storage
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: notes-app
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
3
stop-app.sh
Normal file
3
stop-app.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker-compose down
|
||||||
1
vite.config.js
Normal file
1
vite.config.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {}
|
||||||
Loading…
Reference in New Issue
Block a user