9.2 Core Concepts

Building Production-Ready Applications

You’ve successfully deployed your first application to Kubernetes and experienced the basic workflow. Now let’s dive deeper into the fundamental building blocks that make Kubernetes powerful: Pods, Deployments, Services, and configuration management.

Understanding these core concepts will enable you to design resilient, scalable applications and prepare you for production deployment strategies in the next chapter.

Pods: Containers++

The Atomic Unit

A Pod wraps one or more containers that share storage and network. Think of it as a “logical host” for your application.

Simple Pod:

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
spec:
  containers:
  - name: web
    image: nginx:latest
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "128Mi"
        cpu: "100m"
      limits:
        memory: "256Mi"
        cpu: "200m"
kubectl apply -f pod.yaml
kubectl get pods
kubectl port-forward pod/web-pod 8080:80

Multi-Container Pod (Sidecar Pattern):

apiVersion: v1
kind: Pod
metadata:
  name: app-with-logging
spec:
  containers:
  - name: app
    image: my-app:latest
    volumeMounts:
    - name: logs
      mountPath: /app/logs
  - name: log-shipper
    image: fluentd:latest
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
  volumes:
  - name: logs
    emptyDir: {}

Key Points: - Containers in a Pod share IP and storage - Pods are ephemeral - they come and go - Usually one container per Pod - Use multi-container for helper services (logging, monitoring)

Deployments: Managing Apps

Beyond Individual Pods

Deployments manage multiple Pod replicas, handle updates, and provide self-healing. This is what you’ll use 99% of the time.

Basic Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web
        image: nginx:latest
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"

Deployment Operations:

# Deploy
kubectl apply -f deployment.yaml

# Scale
kubectl scale deployment web-app --replicas=5

# Update
kubectl set image deployment web-app web=nginx:1.21

# Check rollout
kubectl rollout status deployment web-app

# Rollback
kubectl rollout undo deployment web-app

What Deployments Give You: - Multiple replicas for reliability - Rolling updates with zero downtime - Automatic restart of failed pods - Easy scaling up and down - Rollback to previous versions

Services: Stable Networking

Connecting to Your Apps

Pods get random IP addresses. Services provide stable endpoints and load balancing across Pod replicas.

ClusterIP Service (Internal):

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

LoadBalancer Service (External):

apiVersion: v1
kind: Service
metadata:
  name: web-service-external
spec:
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer

Service Discovery:

# Services are accessible by name
curl http://web-service
curl http://web-service.default.svc.cluster.local

Service Types: - ClusterIP: Internal access only (default) - NodePort: Access via node IP:port - LoadBalancer: Cloud provider load balancer - ExternalName: DNS alias to external service

ConfigMaps: Configuration

Separate Config from Code

ConfigMaps store configuration data that your apps can consume as environment variables or files.

Create ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  database_host: "postgres.example.com"
  log_level: "info"
  app.properties: |
    server.port=8080
    debug.enabled=false

Use in Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
      - name: web
        image: my-app:latest
        env:
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: database_host
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log_level
        volumeMounts:
        - name: config
          mountPath: /etc/config
      volumes:
      - name: config
        configMap:
          name: app-config

Secrets: Sensitive Data

Passwords, Tokens, Keys

Secrets are like ConfigMaps but for sensitive data. They’re base64 encoded and can be encrypted at rest.

Create Secret:

# From command line
kubectl create secret generic app-secrets \
  --from-literal=db_password=supersecret \
  --from-literal=api_key=abc123

Or from YAML:

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  db_password: c3VwZXJzZWNyZXQ=  # base64 encoded
  api_key: YWJjMTIz              # base64 encoded

Use in Deployment:

spec:
  containers:
  - name: web
    image: my-app:latest
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: db_password

Persistent Volumes: Storage

Data That Survives

For databases and file storage, you need persistent volumes that survive Pod restarts.

Persistent Volume Claim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-storage
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

Use in Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  template:
    spec:
      containers:
      - name: postgres
        image: postgres:13
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db_password
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: postgres-storage

Complete Example

Web App + Database

Here’s a complete application showing all concepts working together:

# Secret
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
data:
  password: cGFzc3dvcmQxMjM=

---
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  database_url: "postgres://user:password@postgres:5432/myapp"
  log_level: "info"

---
# Database PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 1Gi

---
# Database Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:13
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_USER
          value: user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: postgres-pvc

---
# Database Service
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  selector:
    app: postgres
  ports:
  - port: 5432

---
# Web App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: web
        image: my-webapp:latest
        ports:
        - containerPort: 8080
        env:
        - name: DATABASE_URL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: database_url
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log_level
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# Web App Service
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  selector:
    app: webapp
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

Deploy Everything:

# Deploy complete stack
kubectl apply -f complete-app.yaml

# Check status
kubectl get all

# View logs
kubectl logs -l app=webapp

# Access the app
kubectl port-forward service/webapp 8080:80

Essential Commands

Daily Operations:

# View resources
kubectl get pods,services,deployments
kubectl describe deployment webapp
kubectl logs deployment/webapp

# Manage apps
kubectl apply -f app.yaml
kubectl scale deployment webapp --replicas=5
kubectl set image deployment webapp web=myapp:v2

# Troubleshoot
kubectl exec -it pod-name -- bash
kubectl port-forward service/webapp 8080:80
kubectl get events --sort-by=.metadata.creationTimestamp

What’s Next?

You’re Ready for Production

You now understand:

  • Pods - Container groups with shared networking

  • Deployments - Manage app lifecycle and scaling

  • Services - Stable networking and load balancing

  • ConfigMaps - Configuration management

  • Secrets - Secure data handling

  • Persistent Volumes - Stateful storage

Next: Connect these concepts to your CI/CD pipelines for automated production deployments with rolling updates and monitoring.

Design Pattern: Pods enable the sidecar pattern, where auxiliary containers (logging, monitoring, proxies) enhance your main application without modifying its code. This separation of concerns is fundamental to microservices architecture.

Deployments: Managing Application Lifecycle

From Docker Compose Services to Kubernetes Deployments

Docker Compose services manage single instances with basic restart policies. Kubernetes Deployments manage multiple replicas with sophisticated rollout strategies, health monitoring, and automatic recovery.

Basic Deployment Pattern:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web
        image: myapp:v1.2.0
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Zero-Downtime Deployments:

# Update deployment with new image version
kubectl set image deployment/web-app web=myapp:v1.3.0

# Monitor rollout progress
kubectl rollout status deployment/web-app

# View rollout history
kubectl rollout history deployment/web-app

# Rollback if needed
kubectl rollout undo deployment/web-app

Deployment vs Docker Compose:

Feature

Docker Compose

Kubernetes Deployment

Scaling

Manual replica count

Declarative with auto-scaling

Updates

Replace entire stack

Rolling updates with rollback

Health Monitoring

Basic restart policies

Liveness & readiness probes

Resource Management

Limited controls

Requests, limits, and quotas

Self-Healing

Container-level only

Pod and node-level recovery

Services: Networking and Load Balancing

Beyond Docker Compose Networking

Docker Compose creates simple networks where services find each other by name. Kubernetes Services provide sophisticated traffic management, load balancing, and service discovery that works across multiple nodes and availability zones.

Service Types Explained:

# ClusterIP - Internal cluster access only
apiVersion: v1
kind: Service
metadata:
  name: web-app-internal
spec:
  type: ClusterIP
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 8080

---
# LoadBalancer - External access with cloud integration
apiVersion: v1
kind: Service
metadata:
  name: web-app-external
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 8080

---
# NodePort - Access via any node's IP
apiVersion: v1
kind: Service
metadata:
  name: web-app-nodeport
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30080

Advanced Service Configuration:

apiVersion: v1
kind: Service
metadata:
  name: web-app-advanced
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:..."
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: https
    port: 443
    targetPort: 8080
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

Service Discovery in Action:

# Frontend deployment accessing backend service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: frontend:latest
        env:
        - name: BACKEND_URL
          value: "http://backend:80/api"  # Service discovery by name
        - name: DATABASE_URL
          value: "postgresql://database:5432/myapp"

ConfigMaps and Secrets

Configuration Management Done Right

Docker Compose uses environment files and bind mounts for configuration. Kubernetes provides ConfigMaps for non-sensitive data and Secrets for passwords, tokens, and certificates - with encryption, rotation, and fine-grained access control.

ConfigMap Examples:

# Application configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  app.properties: |
    server.port=8080
    logging.level=INFO
    feature.enabled=true
  nginx.conf: |
    server {
      listen 80;
      location / {
        proxy_pass http://backend:8080;
      }
    }

---
# Using ConfigMap in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
      - name: web
        image: myapp:latest
        env:
        - name: SERVER_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: server.port
        volumeMounts:
        - name: config-volume
          mountPath: /etc/config
      volumes:
      - name: config-volume
        configMap:
          name: app-config

Secrets Management:

# Create secret for database credentials
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: bXl1c2Vy  # base64 encoded 'myuser'
  password: bXlwYXNz  # base64 encoded 'mypass'

---
# Use secret in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
      - name: web
        image: myapp:latest
        env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Creating Secrets from Command Line:

# Create secret from literal values
kubectl create secret generic api-keys \
  --from-literal=github-token=ghp_xxxxxxxxxxxx \
  --from-literal=stripe-key=sk_test_xxxxxxxxxxxx

# Create secret from files
kubectl create secret generic tls-certs \
  --from-file=tls.crt=./server.crt \
  --from-file=tls.key=./server.key

# Create docker registry secret for private images
kubectl create secret docker-registry my-registry \
  --docker-server=myregistry.io \
  --docker-username=myuser \
  --docker-password=mypassword \
  --docker-email=email@example.com

Persistent Volumes and Storage

Stateful Applications in Kubernetes

Docker Compose handles volumes simply - bind mounts and named volumes work locally. Kubernetes storage spans multiple nodes and cloud providers, requiring understanding of Persistent Volumes, Storage Classes, and StatefulSets.

Storage Concepts:

Concept

Purpose

Use Case

Volume

Pod-level storage

Temporary data, configuration files

Persistent Volume

Cluster-level storage resource

Databases, file uploads, logs

Storage Class

Dynamic volume provisioning

Different performance tiers

StatefulSet

Ordered, stable deployments

Databases, messaging systems

Persistent Volume Claim Example:

# Storage class for SSD volumes
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  encrypted: "true"

---
# Persistent volume claim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-storage
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: fast-ssd

---
# StatefulSet using persistent storage
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgresql
spec:
  serviceName: postgresql
  replicas: 1
  selector:
    matchLabels:
      app: postgresql
  template:
    metadata:
      labels:
        app: postgresql
    spec:
      containers:
      - name: postgresql
        image: postgres:15
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: username
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password
        volumeMounts:
        - name: postgresql-storage
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: postgresql-storage
        persistentVolumeClaim:
          claimName: database-storage

Namespaces and Resource Management

Multi-Tenancy and Environment Separation

Docker Compose projects are isolated by directory. Kubernetes namespaces provide true multi-tenancy with resource quotas, network policies, and RBAC - essential for teams, environments, and cost management.

Namespace Strategy:

# Development environment namespace
apiVersion: v1
kind: Namespace
metadata:
  name: development
  labels:
    environment: dev
    team: backend

---
# Resource quota for development
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: development
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    persistentvolumeclaims: "10"
    services.loadbalancers: "2"

---
# Network policy for namespace isolation
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: development
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Best Practices for Resource Management:

# Production deployment with proper resource management
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: production
spec:
  replicas: 5
  template:
    spec:
      containers:
      - name: web
        image: myapp:v1.2.0
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          timeoutSeconds: 10
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3

Environment-Based Deployment Patterns:

# Deploy to different environments
kubectl apply -f manifests/ -n development
kubectl apply -f manifests/ -n staging
kubectl apply -f manifests/ -n production

# Different configurations per environment
kubectl create configmap app-config \
  --from-file=config/development/ \
  -n development

kubectl create configmap app-config \
  --from-file=config/production/ \
  -n production

Putting It All Together: Complete Application

Real-World Application Deployment

Let’s deploy a complete application stack that demonstrates all the concepts we’ve covered, showing how they work together in a production-like scenario.

# Complete application manifest
# Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: webapp-prod

---
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: webapp-prod
data:
  redis.conf: |
    maxmemory 256mb
    maxmemory-policy allkeys-lru
  app.properties: |
    server.port=8080
    redis.host=redis
    database.host=postgres

---
# Secret
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: webapp-prod
type: Opaque
data:
  db-password: c3VwZXJzZWNyZXQ=
  api-key: YWJjZGVmZ2hpams=

---
# PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: webapp-prod
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        env:
        - name: POSTGRES_DB
          value: webapp
        - name: POSTGRES_USER
          value: webapp
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-password
        volumeMounts:
        - name: postgres-data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: postgres-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

---
# Redis Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: webapp-prod
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command: ["redis-server", "/etc/redis/redis.conf"]
        volumeMounts:
        - name: redis-config
          mountPath: /etc/redis
      volumes:
      - name: redis-config
        configMap:
          name: app-config

---
# Web Application Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: webapp-prod
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: mywebapp:v1.2.0
        ports:
        - containerPort: 8080
        env:
        - name: DATABASE_URL
          value: "postgresql://webapp:$(DB_PASSWORD)@postgres:5432/webapp"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-password
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: api-key
        volumeMounts:
        - name: app-config
          mountPath: /etc/webapp
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
      volumes:
      - name: app-config
        configMap:
          name: app-config

---
# Services
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: webapp-prod
spec:
  selector:
    app: postgres
  ports:
  - port: 5432

---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: webapp-prod
spec:
  selector:
    app: redis
  ports:
  - port: 6379

---
apiVersion: v1
kind: Service
metadata:
  name: webapp
  namespace: webapp-prod
spec:
  type: LoadBalancer
  selector:
    app: webapp
  ports:
  - port: 80
    targetPort: 8080

Deploy and Manage:

# Deploy the complete application
kubectl apply -f webapp-complete.yaml

# Monitor deployment progress
kubectl get all -n webapp-prod
kubectl rollout status deployment/webapp -n webapp-prod

# Check application health
kubectl logs -f deployment/webapp -n webapp-prod
kubectl get events -n webapp-prod --sort-by=.metadata.creationTimestamp

# Scale the application
kubectl scale deployment webapp --replicas=5 -n webapp-prod

# Update the application
kubectl set image deployment/webapp webapp=mywebapp:v1.3.0 -n webapp-prod