Skip to content

Solution

This directory contains the complete Kubernetes manifests for the final lab.

Files

  1. namespace.yaml - Application namespace
  2. database-secret.yaml - Database credentials
  3. database-pvc.yaml - Persistent storage for PostgreSQL
  4. database-statefulset.yaml - PostgreSQL StatefulSet
  5. database-service.yaml - Database Service
  6. backend-configmap.yaml - Backend configuration
  7. backend-deployment.yaml - Backend API Deployment
  8. backend-service.yaml - Backend Service
  9. frontend-configmap.yaml - Frontend HTML and configuration
  10. frontend-deployment.yaml - Frontend Deployment
  11. frontend-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):

kubectl apply -f solution/

Access Application

# Frontend
http://localhost:30080

# Or using kubectl
kubectl port-forward -n myapp service/frontend 8080:80
# Then: http://localhost:8080

Cleanup

kubectl delete namespace myapp

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

11-frontend-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: myapp
  labels:
    app: frontend
    tier: web
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
    name: http