Hands-on Lesson 11 of 14

Deploy an App End-to-End

Build, containerize, and deploy a full application to Kubernetes with ConfigMaps, Secrets, Services, Ingress, and scaling.

🧒 Simple Explanation (ELI5)

Imagine you've built a lemonade stand. Now you need to set it up in a busy marketplace. You need to 1) pack all your supplies (containerize), 2) pick a spot and set up the stand (deploy), 3) put up a sign so people can find you (service/ingress), 4) hire helpers when lines get long (scaling), and 5) protect the recipe (secrets). This lesson puts it all together.

🏗️ What We're Building

Architecture Overview
Internet
Users
Ingress
NGINX Controller
myapp.local
Service
ClusterIP
webapp-svc:80
Deployment (3 replicas)
Pod 1
Pod 2
Pod 3
Data
ConfigMap
Secret

⌨️ Step 1: Create the Namespace

bash
# Create a dedicated namespace for isolation
kubectl create namespace webapp-demo

# Set it as default context (optional but convenient)
kubectl config set-context --current --namespace=webapp-demo

⌨️ Step 2: ConfigMap for App Configuration

yaml
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
  namespace: webapp-demo
data:
  APP_ENV: "production"
  APP_PORT: "8080"
  LOG_LEVEL: "info"
  WELCOME_MESSAGE: "Welcome to SKILLY Kubernetes Demo!"
bash
kubectl apply -f configmap.yaml

⌨️ Step 3: Secret for Sensitive Data

bash
# Create secret imperatively (values auto base64-encoded)
kubectl create secret generic webapp-secret \
  --from-literal=DB_PASSWORD='s3cureP@ss!' \
  --from-literal=API_KEY='sk-abc123xyz' \
  -n webapp-demo
⚠️
Production Note

In production, use external secret managers (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) integrated with Kubernetes via CSI driver or operators. Never commit secrets to version control.

⌨️ Step 4: Deployment

yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: webapp-demo
  labels:
    app: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0    # Zero-downtime deployment
  template:
    metadata:
      labels:
        app: webapp
        version: v1
    spec:
      containers:
        - name: webapp
          image: nginx:1.25-alpine
          ports:
            - containerPort: 80
          envFrom:
            - configMapRef:
                name: webapp-config
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: webapp-secret
                  key: DB_PASSWORD
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 128Mi
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 5
bash
kubectl apply -f deployment.yaml

# Watch pods come up
kubectl get pods -w -n webapp-demo

# Verify all 3 replicas are Running
kubectl get deployment webapp -n webapp-demo

⌨️ Step 5: Service

yaml
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp-svc
  namespace: webapp-demo
spec:
  type: ClusterIP
  selector:
    app: webapp
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
bash
kubectl apply -f service.yaml

# Verify endpoints exist (should show 3 pod IPs)
kubectl get endpoints webapp-svc -n webapp-demo

# Quick test via port-forward
kubectl port-forward svc/webapp-svc 8080:80 -n webapp-demo
# Visit http://localhost:8080

⌨️ Step 6: Ingress

yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: webapp-ingress
  namespace: webapp-demo
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: myapp.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: webapp-svc
                port:
                  number: 80
bash
kubectl apply -f ingress.yaml

# On local machine, add to /etc/hosts (or C:\Windows\System32\drivers\etc\hosts)
# 127.0.0.1  myapp.local

# Verify ingress
kubectl get ingress -n webapp-demo

⌨️ Step 7: Horizontal Pod Autoscaler

yaml
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: webapp-hpa
  namespace: webapp-demo
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: webapp
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
bash
kubectl apply -f hpa.yaml

# Check HPA status
kubectl get hpa -n webapp-demo

⌨️ Step 8: Rolling Update

bash
# Update the image version
kubectl set image deployment/webapp webapp=nginx:1.26-alpine -n webapp-demo

# Watch the rollout
kubectl rollout status deployment/webapp -n webapp-demo

# View rollout history
kubectl rollout history deployment/webapp -n webapp-demo

# If something goes wrong, rollback
kubectl rollout undo deployment/webapp -n webapp-demo

✅ Verification Checklist

bash
# Run through entire verification
echo "=== Namespace ==="
kubectl get ns webapp-demo

echo "=== ConfigMap ==="
kubectl get configmap webapp-config -n webapp-demo

echo "=== Secret ==="
kubectl get secret webapp-secret -n webapp-demo

echo "=== Deployment ==="
kubectl get deployment webapp -n webapp-demo

echo "=== Pods ==="
kubectl get pods -n webapp-demo -o wide

echo "=== Service ==="
kubectl get svc webapp-svc -n webapp-demo

echo "=== Endpoints ==="
kubectl get endpoints webapp-svc -n webapp-demo

echo "=== Ingress ==="
kubectl get ingress -n webapp-demo

echo "=== HPA ==="
kubectl get hpa -n webapp-demo

echo "=== Events ==="
kubectl get events -n webapp-demo --sort-by=.lastTimestamp | tail -10

🧹 Cleanup

bash
# Delete everything at once by removing the namespace
kubectl delete namespace webapp-demo

🐛 Common Issues During Deployment

IssueCheckFix
Pods stuck in Pendingkubectl describe pod → EventsInsufficient resources; reduce requests or add nodes
ImagePullBackOffkubectl describe pod → image nameFix image name/tag or add imagePullSecrets
Service has no endpointskubectl get endpointsFix selector labels to match pod labels
Ingress returns 404kubectl describe ingressCheck service name, port, and ingressClassName
HPA shows <unknown>kubectl top podsInstall metrics-server

📝 Summary

← Back to Kubernetes Course