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