Skip to main content

Command Palette

Search for a command to run...

Day 63: Kubernetes Multi-Tier Application - Iron Gallery Deployment ๐ŸŽจ

Published
โ€ข10 min read
Day 63: Kubernetes Multi-Tier Application - Iron Gallery Deployment ๐ŸŽจ

Welcome back! ๐Ÿ‘‹ Day 63 of the 100 Days Cloud DevOps Challenge, and today we're deploying a complete multi-tier application on Kubernetes! This is real production architecture - frontend, database, services, and proper resource management. Let's architect! ๐ŸŽฏ

๐Ÿ“‹ TASK TICKET #DEV-8063 - Multi-Tier App Deployment
Priority: HIGH | Type: Application Architecture
Server: Jump Host | Cluster: Kubernetes

APPLICATION:
- Iron Gallery (Image gallery application)
- Two-tier architecture: Frontend + Database
- Custom namespace deployment
- Service exposure configuration

IMPLEMENTATION:
1. Namespace: iron-namespace-nautilus
2. Frontend Deployment: iron-gallery-deployment-nautilus
3. Database Deployment: iron-db-deployment-nautilus
4. Database Service: iron-db-service-nautilus (ClusterIP)
5. Frontend Service: iron-gallery-service-nautilus (NodePort:32678)

SUCCESS CRITERIA:
- All resources in correct namespace
- Both deployments running
- Services properly configured
- Application accessible via NodePort
- Installation page visible

This is production multi-tier architecture! ๐Ÿ—๏ธ

๐Ÿ› ๏ธ Complete Implementation

Step 1: Create Complete Deployment Manifests

# SSH to jump host
ssh user@jump_host

# Create all-in-one manifest file
cat > iron-gallery-complete.yaml << 'EOF'
---
# Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: iron-namespace-nautilus

---
# Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iron-gallery-deployment-nautilus
  namespace: iron-namespace-nautilus
  labels:
    run: iron-gallery
spec:
  replicas: 1
  selector:
    matchLabels:
      run: iron-gallery
  template:
    metadata:
      labels:
        run: iron-gallery
    spec:
      containers:
      - name: iron-gallery-container-nautilus
        image: kodekloud/irongallery:2.0
        resources:
          limits:
            memory: "100Mi"
            cpu: "50m"
        volumeMounts:
        - name: config
          mountPath: /usr/share/nginx/html/data
        - name: images
          mountPath: /usr/share/nginx/html/uploads
      volumes:
      - name: config
        emptyDir: {}
      - name: images
        emptyDir: {}

---
# Database Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iron-db-deployment-nautilus
  namespace: iron-namespace-nautilus
  labels:
    db: mariadb
spec:
  replicas: 1
  selector:
    matchLabels:
      db: mariadb
  template:
    metadata:
      labels:
        db: mariadb
    spec:
      containers:
      - name: iron-db-container-nautilus
        image: kodekloud/irondb:2.0
        env:
        - name: MYSQL_DATABASE
          value: "database_host"
        - name: MYSQL_ROOT_PASSWORD
          value: "R00tP@ssw0rd123!"
        - name: MYSQL_PASSWORD
          value: "Us3rP@ssw0rd456!"
        - name: MYSQL_USER
          value: "iron_user"
        volumeMounts:
        - name: db
          mountPath: /var/lib/mysql
      volumes:
      - name: db
        emptyDir: {}

---
# Database Service (ClusterIP)
apiVersion: v1
kind: Service
metadata:
  name: iron-db-service-nautilus
  namespace: iron-namespace-nautilus
spec:
  type: ClusterIP
  selector:
    db: mariadb
  ports:
  - protocol: TCP
    port: 3306
    targetPort: 3306

---
# Frontend Service (NodePort)
apiVersion: v1
kind: Service
metadata:
  name: iron-gallery-service-nautilus
  namespace: iron-namespace-nautilus
spec:
  type: NodePort
  selector:
    run: iron-gallery
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 32678
EOF

Step 2: Deploy All Resources

# Apply the complete manifest
kubectl apply -f iron-gallery-complete.yaml

Expected output:

namespace/iron-namespace-nautilus created
deployment.apps/iron-gallery-deployment-nautilus created
deployment.apps/iron-db-deployment-nautilus created
service/iron-db-service-nautilus created
service/iron-gallery-service-nautilus created

All resources created! โœ…

Step 3: Verify Namespace

# Check namespace
kubectl get namespace iron-namespace-nautilus

Expected output:

NAME                       STATUS   AGE
iron-namespace-nautilus    Active   10s

Step 4: Verify Deployments

# Check all deployments in namespace
kubectl get deployments -n iron-namespace-nautilus

Expected output:

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
iron-gallery-deployment-nautilus   1/1     1            1           30s
iron-db-deployment-nautilus        1/1     1            1           30s

Both deployments ready! โœ…

# Check pods
kubectl get pods -n iron-namespace-nautilus

Expected output:

NAME                                               READY   STATUS    RESTARTS   AGE
iron-gallery-deployment-nautilus-7d8f9c6b5-abc12   1/1     Running   0          45s
iron-db-deployment-nautilus-5c7d8e9f6-xyz89        1/1     Running   0          45s

Step 5: Verify Services

# Check services
kubectl get services -n iron-namespace-nautilus

Expected output:

NAME                            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
iron-db-service-nautilus        ClusterIP   10.96.123.45    <none>        3306/TCP       1m
iron-gallery-service-nautilus   NodePort    10.96.234.56    <none>        80:32678/TCP   1m

Services configured correctly! โœ…

Step 6: Verify Resource Limits

# Check frontend container resources
kubectl get deployment iron-gallery-deployment-nautilus -n iron-namespace-nautilus -o jsonpath='{.spec.template.spec.containers[0].resources}'

Expected output:

{"limits":{"cpu":"50m","memory":"100Mi"}}

Step 7: Test Application Access

# Get node IP
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
echo "Access URL: http://$NODE_IP:32678"

# Test frontend
curl -I http://$NODE_IP:32678

Expected output:

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html

Application accessible! ๐ŸŽ‰

Step 8: Complete Verification

# Comprehensive verification script
cat > verify-iron-gallery.sh << 'EOF'
#!/bin/bash
NS="iron-namespace-nautilus"
echo "=== Iron Gallery Multi-Tier Application Verification ==="
echo ""
echo "1. Namespace Status:"
kubectl get namespace $NS
echo ""
echo "2. Deployments Status:"
kubectl get deployments -n $NS
echo ""
echo "3. Pods Status:"
kubectl get pods -n $NS -o wide
echo ""
echo "4. Services Status:"
kubectl get services -n $NS
echo ""
echo "5. Frontend Deployment Details:"
echo "   Labels:"
kubectl get deployment iron-gallery-deployment-nautilus -n $NS -o jsonpath='   run={.metadata.labels.run}'
echo ""
echo "   Replicas: $(kubectl get deployment iron-gallery-deployment-nautilus -n $NS -o jsonpath='{.spec.replicas}')"
echo "   Image: $(kubectl get deployment iron-gallery-deployment-nautilus -n $NS -o jsonpath='{.spec.template.spec.containers[0].image}')"
echo "   Resources: $(kubectl get deployment iron-gallery-deployment-nautilus -n $NS -o jsonpath='{.spec.template.spec.containers[0].resources}')"
echo ""
echo "6. Frontend Volume Mounts:"
kubectl get deployment iron-gallery-deployment-nautilus -n $NS -o jsonpath='{range .spec.template.spec.containers[0].volumeMounts[*]}   - {.name}: {.mountPath}{"\n"}{end}'
echo ""
echo "7. Database Deployment Details:"
echo "   Labels:"
kubectl get deployment iron-db-deployment-nautilus -n $NS -o jsonpath='   db={.metadata.labels.db}'
echo ""
echo "   Image: $(kubectl get deployment iron-db-deployment-nautilus -n $NS -o jsonpath='{.spec.template.spec.containers[0].image}')"
echo "   Environment Variables:"
kubectl get deployment iron-db-deployment-nautilus -n $NS -o jsonpath='{range .spec.template.spec.containers[0].env[*]}   - {.name}={.value}{"\n"}{end}'
echo ""
echo "8. Database Service Details:"
echo "   Type: $(kubectl get service iron-db-service-nautilus -n $NS -o jsonpath='{.spec.type}')"
echo "   Port: $(kubectl get service iron-db-service-nautilus -n $NS -o jsonpath='{.spec.ports[0].port}')"
echo "   Selector: $(kubectl get service iron-db-service-nautilus -n $NS -o jsonpath='{.spec.selector}')"
echo ""
echo "9. Frontend Service Details:"
echo "   Type: $(kubectl get service iron-gallery-service-nautilus -n $NS -o jsonpath='{.spec.type}')"
echo "   NodePort: $(kubectl get service iron-gallery-service-nautilus -n $NS -o jsonpath='{.spec.ports[0].nodePort}')"
echo ""
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
echo "10. Access URL: http://$NODE_IP:32678"
echo ""
echo "11. Application Health Check:"
if curl -s -o /dev/null -w "%{http_code}" http://$NODE_IP:32678 | grep -q "200"; then
    echo "    โœ“ Frontend accessible and responding"
else
    echo "    โœ— Frontend not responding"
fi
echo ""
echo "12. Pod Resource Usage:"
kubectl top pods -n $NS 2>/dev/null || echo "    Metrics server not available"
echo ""
echo "โœ“ VERIFICATION COMPLETE"
EOF

chmod +x verify-iron-gallery.sh
./verify-iron-gallery.sh

Expected output:

=== Iron Gallery Multi-Tier Application Verification ===

1. Namespace Status:
NAME                       STATUS   AGE
iron-namespace-nautilus    Active   5m

2. Deployments Status:
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
iron-gallery-deployment-nautilus   1/1     1            1           5m
iron-db-deployment-nautilus        1/1     1            1           5m

3. Pods Status:
NAME                                               READY   STATUS    RESTARTS   AGE   IP           NODE
iron-gallery-deployment-nautilus-7d8f9c6b5-abc12   1/1     Running   0          5m    10.244.1.5   node01
iron-db-deployment-nautilus-5c7d8e9f6-xyz89        1/1     Running   0          5m    10.244.1.6   node01

4. Services Status:
NAME                            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
iron-db-service-nautilus        ClusterIP   10.96.123.45    <none>        3306/TCP       5m
iron-gallery-service-nautilus   NodePort    10.96.234.56    <none>        80:32678/TCP   5m

5. Frontend Deployment Details:
   Labels:
   run=iron-gallery
   Replicas: 1
   Image: kodekloud/irongallery:2.0
   Resources: {"limits":{"cpu":"50m","memory":"100Mi"}}

6. Frontend Volume Mounts:
   - config: /usr/share/nginx/html/data
   - images: /usr/share/nginx/html/uploads

7. Database Deployment Details:
   Labels:
   db=mariadb
   Image: kodekloud/irondb:2.0
   Environment Variables:
   - MYSQL_DATABASE=database_host
   - MYSQL_ROOT_PASSWORD=R00tP@ssw0rd123!
   - MYSQL_PASSWORD=Us3rP@ssw0rd456!
   - MYSQL_USER=iron_user

8. Database Service Details:
   Type: ClusterIP
   Port: 3306
   Selector: {"db":"mariadb"}

9. Frontend Service Details:
   Type: NodePort
   NodePort: 32678

10. Access URL: http://172.17.0.2:32678

11. Application Health Check:
    โœ“ Frontend accessible and responding

12. Pod Resource Usage:
NAME                                               CPU(cores)   MEMORY(bytes)
iron-gallery-deployment-nautilus-7d8f9c6b5-abc12   5m           45Mi
iron-db-deployment-nautilus-5c7d8e9f6-xyz89        25m          180Mi

โœ“ VERIFICATION COMPLETE

All components verified! ๐ŸŽŠ

๐Ÿ” Understanding Multi-Tier Architecture

Application Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚     External Users (Browser)            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
              โ”‚
              โ”‚ http://NodeIP:32678
              โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    NodePort Service (32678)             โ”‚
โ”‚    iron-gallery-service-nautilus        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
              โ”‚
              โ”‚ Port 80
              โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Frontend Pod                         โ”‚
โ”‚    iron-gallery-deployment-nautilus     โ”‚
โ”‚    - Nginx web server                   โ”‚
โ”‚    - Image gallery application          โ”‚
โ”‚    - Volumes: config, images            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
              โ”‚
              โ”‚ Port 3306
              โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    ClusterIP Service (3306)             โ”‚
โ”‚    iron-db-service-nautilus             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
              โ”‚
              โ”‚ Internal only
              โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Database Pod                         โ”‚
โ”‚    iron-db-deployment-nautilus          โ”‚
โ”‚    - MariaDB database                   โ”‚
โ”‚    - Volume: db                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Component Breakdown

1. Namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: iron-namespace-nautilus
Purpose:
- Isolates application resources
- Logical grouping
- Resource quotas possible
- Access control boundary

Benefits:
โœ“ Clean separation
โœ“ Multi-tenancy support
โœ“ Resource organization
โœ“ Security isolation

2. Frontend Deployment:

spec:
  replicas: 1
  selector:
    matchLabels:
      run: iron-gallery
  template:
    spec:
      containers:
      - name: iron-gallery-container-nautilus
        image: kodekloud/irongallery:2.0
        resources:
          limits:
            memory: "100Mi"
            cpu: "50m"
Components:
- Web server (Nginx)
- PHP application
- Image gallery frontend
- Static file serving

Resource limits:
- Memory: 100Mi (prevents OOM)
- CPU: 50m (0.05 core)
- Protects cluster resources

3. Database Deployment:

env:
- name: MYSQL_DATABASE
  value: "database_host"
- name: MYSQL_ROOT_PASSWORD
  value: "R00tP@ssw0rd123!"
- name: MYSQL_PASSWORD
  value: "Us3rP@ssw0rd456!"
- name: MYSQL_USER
  value: "iron_user"
MariaDB Configuration:
- Database: database_host
- Root password: (complex)
- User: iron_user
- User password: (complex)

Storage:
- emptyDir at /var/lib/mysql
- Temporary (pod lifetime)
- Production: Use PersistentVolume

4. Services:

Database Service (ClusterIP):
- Internal cluster access only
- No external exposure
- Port 3306 (MySQL default)
- Frontend connects via DNS

Frontend Service (NodePort):
- External access
- Port 32678 on all nodes
- Routes to frontend pods
- Users access application

Volume Strategy

emptyDir volumes used:

volumes:
- name: config
  emptyDir: {}
- name: images
  emptyDir: {}
- name: db
  emptyDir: {}
Why emptyDir:
- Quick setup (no PV needed)
- Development/testing
- Installation phase
- Data not critical yet

emptyDir characteristics:
- Created when pod assigned to node
- Deleted when pod removed
- Shared between containers in pod
- Backed by node disk

Production recommendation:
- Use PersistentVolume for db
- Use PersistentVolume for images
- Keep config as emptyDir (generated)

Service Types Explained

ClusterIP (Database):

Access: Internal only
DNS: iron-db-service-nautilus.iron-namespace-nautilus.svc.cluster.local
Port: 3306

Use case:
- Backend services
- Database access
- Internal APIs
- Not exposed externally

Security benefit:
โœ“ No external access
โœ“ Network isolation
โœ“ Protected by firewall

NodePort (Frontend):

Access: External via node IP
URL: http://NodeIP:32678
Port mapping: 32678 (external) โ†’ 80 (internal)

Use case:
- Development environments
- Testing
- Simple external access
- No load balancer available

Limitations:
- Fixed port on all nodes
- Direct node access required
- Not production standard

๐Ÿ’ก Key Takeaways

โœจ Namespaces organize and isolate resources
โœจ Labels connect deployments to services
โœจ Resource limits prevent resource exhaustion
โœจ emptyDir provides temporary storage
โœจ ClusterIP for internal service communication
โœจ NodePort for external application access
โœจ Environment variables configure applications
โœจ Multi-tier architecture separates concerns

๐ŸŽ“ Quick Interview Questions

Q: Why use separate deployments for frontend and database? A: Separation of concerns: independent scaling, different resource requirements, separate lifecycle management, easier updates. Database can scale independently from frontend. Different restart policies and update strategies.

Q: What's the difference between ClusterIP and NodePort services? A: ClusterIP: Internal cluster access only, no external exposure, uses cluster IP. NodePort: External access via node IP and port (30000-32767), includes ClusterIP functionality. NodePort = ClusterIP + external port.

Q: Why use emptyDir instead of PersistentVolume? A: emptyDir: Simpler setup, no external dependencies, good for temporary data, development/testing. PersistentVolume: Data persistence across pod restarts, production workloads, critical data. This task uses emptyDir for initial setup phase.

Q: How do frontend and database pods communicate? A: Via service DNS: iron-db-service-nautilus.iron-namespace-nautilus.svc.cluster.local:3306. Service provides stable endpoint. Even if database pod IP changes, service DNS remains constant. Frontend uses service name, not direct pod IP.

Q: What happens if you don't specify resource limits? A: Pod can consume unlimited node resources, causing: (1) Node resource exhaustion, (2) Other pods starved, (3) Node crashes, (4) Unpredictable performance. Always set limits in production to protect cluster.

Q: Why use labels and selectors? A: Labels: Identify and organize resources. Selectors: Query resources by labels. Services use selectors to find target pods. Deployments use selectors to manage pods. Enables loose coupling - change pod template without changing service.

Q: Can you access NodePort service from inside cluster? A: Yes, three ways: (1) NodeIP:NodePort from any pod, (2) ServiceName:Port using ClusterIP (preferred), (3) ClusterIP:Port directly. NodePort includes ClusterIP, so internal access works normally.

Q: What's matchLabels in deployment spec? A: Selector that defines which pods the deployment manages. Must match template.metadata.labels. Deployment creates/deletes pods based on this matching. If labels don't match, deployment won't manage the pods.

Q: Why specify exact image tags (not latest)? A: Reproducibility: exact version known, predictable behavior, easier rollback. "latest" is unpredictable: changes without warning, makes troubleshooting difficult, inconsistent across nodes. Production standard: always use specific tags.

Q: What's the purpose of environment variables in database deployment? A: Configure MariaDB at startup: create database, set passwords, create users. Alternative to manual configuration. Environment-based config enables: same image, different configs, easy customization, no image rebuild.

๐ŸŽ‰ Final Thoughts

You've successfully deployed a complete multi-tier application on Kubernetes! This is production architecture.

What you accomplished: โœ… Created isolated namespace
โœ… Deployed frontend application
โœ… Deployed database backend
โœ… Configured internal service (ClusterIP)
โœ… Exposed external service (NodePort)
โœ… Set resource limits
โœ… Configured persistent storage
โœ… Established service communication

Real-world impact:

  • Scalable architecture (independent tier scaling)

  • Resource efficiency (limits prevent waste)

  • Security (internal services isolated)

  • Maintainability (clean separation)

  • Production-ready (proper service exposure)

Skills acquired:

  • Multi-tier application deployment

  • Namespace management

  • Service type selection

  • Resource limit configuration

  • Volume mounting

  • Label-based routing

  • Environment variable injection

This is production-grade architecture! ๐Ÿ’ช


Day: 63/100
Challenge: KodeKloud Cloud DevOps
Date: January 07, 2026
Topic: Kubernetes Multi-Tier Application

How do you structure your multi-tier apps? Share your architecture patterns! ๐ŸŽจ

More from this blog

๐Ÿš€ DevOps Challenge- KodeKloud Solutions

73 posts