Solution¶
This directory contains the complete Kubernetes manifests for the final lab.
Files¶
namespace.yaml- Application namespacedatabase-secret.yaml- Database credentialsdatabase-pvc.yaml- Persistent storage for PostgreSQLdatabase-statefulset.yaml- PostgreSQL StatefulSetdatabase-service.yaml- Database Servicebackend-configmap.yaml- Backend configurationbackend-deployment.yaml- Backend API Deploymentbackend-service.yaml- Backend Servicefrontend-configmap.yaml- Frontend HTML and configurationfrontend-deployment.yaml- Frontend Deploymentfrontend-service.yaml- Frontend NodePort Service
Deployment Order¶
# 1. Namespace and ConfigMaps/Secrets (independent)
kubectl apply -f namespace.yaml
kubectl apply -f database-secret.yaml
kubectl apply -f backend-configmap.yaml
kubectl apply -f frontend-configmap.yaml
# 2. Storage
kubectl apply -f database-pvc.yaml
# 3. Database layer
kubectl apply -f database-statefulset.yaml
kubectl apply -f database-service.yaml
# Wait for database to be ready
kubectl wait --for=condition=Ready pod/database-0 -n myapp --timeout=120s
# 4. Backend layer
kubectl apply -f backend-deployment.yaml
kubectl apply -f backend-service.yaml
# 5. Frontend layer
kubectl apply -f frontend-deployment.yaml
kubectl apply -f frontend-service.yaml
# Verify all resources
kubectl get all -n myapp
Quick Deploy¶
Apply all at once (dependencies will be resolved):
Access Application¶
# Frontend
http://localhost:30080
# Or using kubectl
kubectl port-forward -n myapp service/frontend 8080:80
# Then: http://localhost:8080
Cleanup¶
Manifest Files¶
01-namespace.yaml¶
apiVersion: v1
kind: Namespace
metadata:
name: myapp
labels:
name: myapp
environment: workshop
part: part-1-final-lab
annotations:
description: "Final lab application namespace"
created-by: "Compose to Kubernetes Workshop"
02-database-secret.yaml¶
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: myapp
labels:
app: database
type: Opaque
stringData:
username: appuser
password: secret123
database: appdb
03-database-pvc.yaml¶
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: myapp
labels:
app: database
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-path
04-database-statefulset.yaml¶
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
namespace: myapp
labels:
app: database
tier: data
annotations:
description: "PostgreSQL database for application"
spec:
serviceName: database
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
tier: data
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: db-credentials
key: database
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
readinessProbe:
exec:
command:
- sh
- -c
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
exec:
command:
- sh
- -c
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
05-database-service.yaml¶
apiVersion: v1
kind: Service
metadata:
name: database
namespace: myapp
labels:
app: database
tier: data
spec:
clusterIP: None # Headless service for StatefulSet
selector:
app: database
ports:
- port: 5432
targetPort: 5432
name: postgres
06-backend-configmap.yaml¶
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-config
namespace: myapp
labels:
app: backend
data:
db.host: "database.myapp.svc.cluster.local"
db.port: "5432"
log.level: "info"
app.name: "backend-api"
07-backend-deployment.yaml¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: myapp
labels:
app: backend
tier: api
annotations:
description: "Backend API server"
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
tier: api
spec:
containers:
- name: backend
image: nginx:1.25-alpine
ports:
- containerPort: 80
name: http
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: backend-config
key: db.host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: backend-config
key: db.port
- name: DB_NAME
valueFrom:
secretKeyRef:
name: db-credentials
key: database
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: backend-config
key: log.level
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
08-backend-service.yaml¶
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: myapp
labels:
app: backend
tier: api
spec:
type: ClusterIP
selector:
app: backend
ports:
- port: 8080
targetPort: 80
name: http
09-frontend-configmap.yaml¶
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
namespace: myapp
labels:
app: frontend
data:
index.html: |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Kubernetes Application</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 800px;
width: 100%;
padding: 40px;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 2.5em;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 1.1em;
}
.status-card {
background: #f8f9fa;
padding: 25px;
border-radius: 10px;
margin: 20px 0;
}
.status-card h2 {
color: #444;
margin-bottom: 15px;
font-size: 1.5em;
}
.status-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #e0e0e0;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
flex: 1;
font-weight: 600;
color: #555;
}
.status-value {
color: #28a745;
font-weight: bold;
}
.button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 30px;
font-size: 16px;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
margin-top: 20px;
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.architecture {
margin-top: 30px;
}
.architecture h2 {
color: #444;
margin-bottom: 15px;
}
.architecture ul {
list-style: none;
padding: 0;
}
.architecture li {
background: #f0f0f0;
padding: 12px 20px;
margin: 8px 0;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.success { color: #28a745; }
.error { color: #dc3545; }
.footer {
margin-top: 30px;
text-align: center;
color: #888;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Kubernetes!</h1>
<p class="subtitle">Your application is running successfully</p>
<div class="status-card">
<h2>Component Status</h2>
<div class="status-item">
<span class="status-label">Frontend:</span>
<span class="status-value success">Running (3 replicas)</span>
</div>
<div class="status-item">
<span class="status-label">Backend API:</span>
<span class="status-value" id="backend-status">Checking...</span>
</div>
<div class="status-item">
<span class="status-label">Database:</span>
<span class="status-value success">PostgreSQL (StatefulSet)</span>
</div>
<button class="button" onclick="checkBackend()">Test Backend Connection</button>
</div>
<div class="architecture">
<h2>Application Architecture</h2>
<ul>
<li><strong>Frontend:</strong> nginx:1.25-alpine (Deployment, 3 replicas)</li>
<li><strong>Backend:</strong> nginx:1.25-alpine (Deployment, 2 replicas)</li>
<li><strong>Database:</strong> postgres:16-alpine (StatefulSet, persistent storage)</li>
</ul>
</div>
<div class="footer">
<p>Part 1 Final Lab - Compose to Kubernetes Workshop</p>
<p>Congratulations on completing the workshop!</p>
</div>
</div>
<script>
function checkBackend() {
const statusElement = document.getElementById('backend-status');
statusElement.textContent = 'Connecting...';
statusElement.className = 'status-value';
setTimeout(() => {
statusElement.textContent = 'Connected (2 replicas)';
statusElement.className = 'status-value success';
}, 500);
}
// Auto-check on load
checkBackend();
</script>
</body>
</html>
10-frontend-deployment.yaml¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: myapp
labels:
app: frontend
tier: web
annotations:
description: "Frontend web application"
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
tier: web
spec:
containers:
- name: frontend
image: nginx:1.25-alpine
ports:
- containerPort: 80
name: http
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
resources:
requests:
memory: "32Mi"
cpu: "25m"
limits:
memory: "64Mi"
cpu: "50m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
volumes:
- name: html
configMap:
name: frontend-config
items:
- key: index.html
path: index.html