diff --git a/assignment1/README.md b/assignment1/README.md new file mode 100644 index 0000000..26f8774 --- /dev/null +++ b/assignment1/README.md @@ -0,0 +1,64 @@ +# Docker Web Application Deployment + +The application consists of two services: + +1. **Web Service (Nginx):** + - Uses the official Nginx image. + - Listens on port 80 inside the container and is mapped to host port 8080. +2. **Database Service (MySQL 5.7):** + - Uses the official MySQL 5.7 image. + - Configured with a root password and a default database. + - Persists its data using the named volume `mysql-data`. + +## Files Overview + +- **prepare-app.sh:** + - Creates the required Docker network (`myapp-net`) and persistent volume (`mysql-data`). +- **docker-compose.yaml:** + - Defines the two services along with their ports, environment variables, volumes, and restart policies. +- **start-app.sh:** + - Starts the services using Docker Compose in detached mode. + - Displays the URL to access the web service. +- **stop-app.sh:** + - Stops the running containers without deleting the persistent volume. +- **remove-app.sh:** + - Removes all the created resources (containers, network, and volume) from the deployment. + +## Deployment Instructions + +1. **Preparation:** + - Ensure Docker and Docker Compose are installed. + - Make the scripts executable: + ```bash + chmod +x prepare-app.sh start-app.sh stop-app.sh remove-app.sh + ``` + - Run the preparation script: + ```bash + ./prepare-app.sh + ``` + +2. **Starting the Application:** + - Launch the services by running: + ```bash + ./start-app.sh + ``` + - Open your web browser and navigate to [http://localhost:8080](http://localhost:8080) to see the Nginx welcome page. + +3. **Stopping the Application:** + - Stop the services without losing data: + ```bash + ./stop-app.sh + ``` + +4. **Removing the Application:** + - To completely remove all deployed resources, run: + ```bash + ./remove-app.sh + ``` + +## Notes + +- The application uses an external network (`myapp-net`) and a persistent volume (`mysql-data`) that are created in `prepare-app.sh`. +- The Nginx container depends on the MySQL container to demonstrate inter-service communication within the `myapp-net` network. +- Containers are configured to restart on failure. + diff --git a/assignment1/docker-compose.yaml b/assignment1/docker-compose.yaml new file mode 100644 index 0000000..09426fa --- /dev/null +++ b/assignment1/docker-compose.yaml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + web: + image: nginx:latest + ports: + - "8080:80" + networks: + - myapp-net + restart: on-failure + depends_on: + - db + + db: + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: example + MYSQL_DATABASE: appdb + volumes: + - mysql-data:/var/lib/mysql + networks: + - myapp-net + restart: on-failure + +networks: + myapp-net: + external: true + +volumes: + mysql-data: + external: true + diff --git a/assignment1/prepare-app.sh b/assignment1/prepare-app.sh new file mode 100755 index 0000000..753d306 --- /dev/null +++ b/assignment1/prepare-app.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# prepare-app.sh + +if ! docker network ls | grep -w myapp-net >/dev/null; then + docker network create myapp-net + echo "Created Docker network: myapp-net" +else + echo "Docker network myapp-net already exists." +fi + + +if ! docker volume ls | grep -w mysql-data >/dev/null; then + docker volume create mysql-data + echo "Created Docker volume: mysql-data" +else + echo "Docker volume mysql-data already exists." +fi + +echo "Preparation complete." + diff --git a/assignment1/remove-app.sh b/assignment1/remove-app.sh new file mode 100755 index 0000000..3aa3114 --- /dev/null +++ b/assignment1/remove-app.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# remove-app.sh + +docker-compose down + +docker volume rm mysql-data +docker network rm myapp-net + +echo "All application resources removed." + diff --git a/assignment1/start-app.sh b/assignment1/start-app.sh new file mode 100755 index 0000000..5be746e --- /dev/null +++ b/assignment1/start-app.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# start-app.sh + +docker-compose up -d + +sleep 5 + +echo "Application started. Access the web service at http://localhost:8080" + diff --git a/assignment1/stop-app.sh b/assignment1/stop-app.sh new file mode 100755 index 0000000..d4db2c2 --- /dev/null +++ b/assignment1/stop-app.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# stop-app.sh + +docker-compose stop +echo "Application services stopped. Persistent data remains intact." + diff --git a/assignment2/.prepare-app.sh.swp b/assignment2/.prepare-app.sh.swp new file mode 100644 index 0000000..5a49185 Binary files /dev/null and b/assignment2/.prepare-app.sh.swp differ diff --git a/assignment2/Dockerfile b/assignment2/Dockerfile new file mode 100644 index 0000000..9955ee2 --- /dev/null +++ b/assignment2/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:alpine +COPY weather.html /usr/share/nginx/html/index.html diff --git a/assignment2/Dockerfile.api b/assignment2/Dockerfile.api new file mode 100644 index 0000000..591ee9a --- /dev/null +++ b/assignment2/Dockerfile.api @@ -0,0 +1,6 @@ +FROM node:18-alpine +WORKDIR /app +COPY server.js . +RUN npm init -y && npm install express pg body-parser +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/assignment2/README.md b/assignment2/README.md new file mode 100644 index 0000000..c976af5 --- /dev/null +++ b/assignment2/README.md @@ -0,0 +1,136 @@ +# Weather Web App - Kubernetes Deployment + +This project deploys a full-stack weather web application to Kubernetes. It includes: +- A frontend website to search weather by city +- A backend PostgreSQL database for logging searches +- A Node.js API to connect the frontend to the database + +--- + +## Application Description + +- **Frontend (weather.html):** A responsive, modern web UI that shows current weather data using the OpenWeatherMap API. +- **Backend (PostgreSQL):** Stores weather search history via a `weather_log` table. +- **API Server (Node.js):** Receives weather data from the frontend and logs it to the PostgreSQL database. + +--- + +## Containers Used + +| Container | Image | Description | +|------------------|--------------------|----------------------------------------------| +| `weather-frontend` | Custom Nginx-based | Serves the `weather.html` UI | +| `postgres` | `postgres:15` | Provides relational database backend | +| `weather-api` | Custom Node.js | API that logs weather data to PostgreSQL | + +--- + +## ☸️ Kubernetes Objects + +| Object Type | File | Description | +|------------------------|----------------------|----------------------------------------------------------------------| +| `Namespace` | (inside script) | Isolates resources under `webapp-ns` | +| `Deployment` | `deployment.yaml` | Manages frontend app | +| `Deployment` | `deployment-api.yaml` | Manages the Node.js API | +| `StatefulSet` | `statefulset.yaml` | Manages PostgreSQL instance with persistent volume | +| `PersistentVolume` | `statefulset.yaml` | Host-mounted volume for PostgreSQL data | +| `PersistentVolumeClaim`| `statefulset.yaml` | Requests storage for StatefulSet | +| `Service` | `service.yaml` | Exposes frontend via NodePort | +| `Service` | `deployment-api.yaml` | Exposes API via NodePort | +| `ConfigMap` | Created by script | Stores `init-db.sql` used to create the `weather_log` table | + +--- + +## Networking & Volumes + +### Virtual Networks: +- Kubernetes handles inter-service communication via internal DNS and `ClusterIP` services. +- Frontend and backend are externally reachable using `NodePort` services. + +### Volumes: +- `PersistentVolume` and `PersistentVolumeClaim` ensure PostgreSQL data is retained. +- `ConfigMap` mounts the SQL init script for PostgreSQL setup. + +--- + +## βš™οΈ Container Configurations + +### Frontend +- Dockerfile uses `nginx:alpine` +- Serves `weather.html` (renamed as `index.html` inside container) +- Exposed on port `80` + +### Node.js API +- Built with Node.js 18 +- Listens on port `3000` +- Connects to PostgreSQL using service DNS +- Accepts POST requests at `/log` with weather data + +### PostgreSQL +- `postgres:15` with `weatheruser`, `weatherpass`, and `weatherdb` +- Init script creates a `weather_log` table with a sample row + +--- + +## How to Use the Application + +### 1. Build Docker Images + +```bash +./prepare-app.sh +``` + +### 2. Start the App (create namespace and apply all configs) + +```bash +./start-app.sh +``` + +### 3. View the App + +```bash +minikube service weather-service -n webapp-ns +``` + +Or get the NodePort manually: + +```bash +kubectl get svc -n webapp-ns +minikube ip +``` + +Then open: +``` +http://: +``` + +### 4. Use the App +- Enter a city +- Weather will be shown +- Data is logged in PostgreSQL automatically + +### 5. Stop and Clean Up + +```bash +./stop-app.sh +``` + +--- + +## API Key Setup + +- Register at [OpenWeatherMap](https://openweathermap.org/api) +- Get your free API key +- Replace the placeholder in `weather.html`: + +```js +const apiKey = "YOUR_API_KEY"; +``` + +--- + +## Conclusion + +- Frontend calls OpenWeatherMap and logs data to your backend +- PostgreSQL is prepped with a table to store queries +- You can expand the API to serve saved data or stats diff --git a/assignment2/deployment-api.yaml b/assignment2/deployment-api.yaml new file mode 100644 index 0000000..f8044d6 --- /dev/null +++ b/assignment2/deployment-api.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: weather-api + namespace: webapp-ns +spec: + replicas: 1 + selector: + matchLabels: + app: weather-api + template: + metadata: + labels: + app: weather-api + spec: + containers: + - name: weather-api + image: weather-api:latest + ports: + - containerPort: 3000 +--- +apiVersion: v1 +kind: Service +metadata: + name: weather-api-service + namespace: webapp-ns +spec: + selector: + app: weather-api + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 + nodePort: 31000 + type: NodePort + diff --git a/assignment2/deployment.yaml b/assignment2/deployment.yaml new file mode 100644 index 0000000..de11dfa --- /dev/null +++ b/assignment2/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: weather-frontend + namespace: webapp-ns +spec: + replicas: 1 + selector: + matchLabels: + app: weather-frontend + template: + metadata: + labels: + app: weather-frontend + spec: + containers: + - name: weather-frontend + image: weather-frontend:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + diff --git a/assignment2/init-db.sql b/assignment2/init-db.sql new file mode 100644 index 0000000..8835f65 --- /dev/null +++ b/assignment2/init-db.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS weather_log ( + id SERIAL PRIMARY KEY, + city VARCHAR(100), + temperature DECIMAL(5,2), + description TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO weather_log (city, temperature, description) +VALUES ('London', 15.5, 'partly cloudy'); + diff --git a/assignment2/minikube-linux-amd64 b/assignment2/minikube-linux-amd64 new file mode 100644 index 0000000..af25bb2 Binary files /dev/null and b/assignment2/minikube-linux-amd64 differ diff --git a/assignment2/prepare-app.sh b/assignment2/prepare-app.sh new file mode 100755 index 0000000..616963a --- /dev/null +++ b/assignment2/prepare-app.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +IMAGE_NAME=weather-frontend +IMAGE_TAG=latest + +echo "πŸ”§ Building Docker image: $IMAGE_NAME:$IMAGE_TAG" +docker build -t $IMAGE_NAME:$IMAGE_TAG . +docker build -t weather-api:latest -f Dockerfile.api . + +echo "βœ… Build complete." + diff --git a/assignment2/server.js b/assignment2/server.js new file mode 100644 index 0000000..c8ea8eb --- /dev/null +++ b/assignment2/server.js @@ -0,0 +1,35 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const { Pool } = require('pg'); + +const app = express(); +const port = 3000; + +app.use(bodyParser.json()); + +const pool = new Pool({ + host: 'postgres-0.postgres.webapp-ns.svc.cluster.local', + user: 'weatheruser', + password: 'weatherpass', + database: 'weatherdb', + port: 5432 +}); + +app.post('/log', async (req, res) => { + const { city, temperature, description } = req.body; + try { + await pool.query( + 'INSERT INTO weather_log (city, temperature, description) VALUES ($1, $2, $3)', + [city, temperature, description] + ); + res.status(200).send('Logged!'); + } catch (err) { + console.error(err); + res.status(500).send('Error saving data'); + } +}); + +app.listen(port, () => { + console.log(`Weather API listening on port ${port}`); +}); + diff --git a/assignment2/service.yaml b/assignment2/service.yaml new file mode 100644 index 0000000..96f3ffd --- /dev/null +++ b/assignment2/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: weather-service + namespace: webapp-ns +spec: + selector: + app: weather-frontend + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: NodePort + diff --git a/assignment2/start-app.sh b/assignment2/start-app.sh new file mode 100755 index 0000000..cf5a3b9 --- /dev/null +++ b/assignment2/start-app.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "πŸš€ Creating namespace 'webapp-ns'..." +kubectl create namespace webapp-ns + +echo "πŸ“¦ Deploying frontend app..." +kubectl apply -f deployment.yaml +kubectl apply -f service.yaml + +echo "🧾 Creating ConfigMap with init-db.sql..." +kubectl create configmap db-init-script --from-file=init-db.sql=init-db.sql -n webapp-ns + +echo "πŸ—„οΈ Deploying database backend..." +kubectl apply -f statefulset.yaml +kubectl apply -f deployment-api.yaml + +echo "βœ… All resources created." diff --git a/assignment2/statefulset.yaml b/assignment2/statefulset.yaml new file mode 100644 index 0000000..e8c9bcb --- /dev/null +++ b/assignment2/statefulset.yaml @@ -0,0 +1,84 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: postgres-pv + namespace: webapp-ns +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/postgres" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc + namespace: webapp-ns +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres + namespace: webapp-ns +spec: + serviceName: "postgres" + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + initContainers: + - name: init-db + image: postgres:15 + env: + - name: POSTGRES_USER + value: "weatheruser" + - name: POSTGRES_PASSWORD + value: "weatherpass" + - name: POSTGRES_DB + value: "weatherdb" + volumeMounts: + - name: init-script + mountPath: /docker-entrypoint-initdb.d + - name: postgres-storage + mountPath: /var/lib/postgresql/data + containers: + - name: postgres + image: postgres:15 + ports: + - containerPort: 5432 + env: + - name: POSTGRES_USER + value: "weatheruser" + - name: POSTGRES_PASSWORD + value: "weatherpass" + - name: POSTGRES_DB + value: "weatherdb" + volumeMounts: + - name: postgres-storage + mountPath: /var/lib/postgresql/data + volumes: + - name: init-script + configMap: + name: db-init-script + volumeClaimTemplates: + - metadata: + name: postgres-storage + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + diff --git a/assignment2/stop-app.sh b/assignment2/stop-app.sh new file mode 100755 index 0000000..19c58aa --- /dev/null +++ b/assignment2/stop-app.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +echo "🧼 Deleting all resources in namespace 'webapp-ns'..." +kubectl delete namespace webapp-ns + +echo "βœ… Cleanup complete." + diff --git a/assignment2/weather.html b/assignment2/weather.html new file mode 100644 index 0000000..7dd0628 --- /dev/null +++ b/assignment2/weather.html @@ -0,0 +1,122 @@ + + + + + + Weather Info + + + + +
+

🌦️ Check the Weather

+ + +
+
+ + + + +