Deploying Applications on Kubernetes

Module 28: DevOps & Deployment - Thursday, Lecture 3

Introduction to Kubernetes Deployments

In our previous lectures, we explored container orchestration principles and Kubernetes core concepts. Now, we'll put this knowledge into practice by learning how to deploy applications to Kubernetes.

Deploying applications to Kubernetes is like moving into a new apartment building—you need to pack your belongings (containerize your application), find the right apartment (create Kubernetes resources), arrange your furniture (configure your application), connect utilities (set up networking), and establish a maintenance schedule (implement updates and scaling).

flowchart LR A[Application Code] --> B[Container Image] B --> C[Kubernetes Resources] C --> D[Deployed Application] D --> E[Maintenance & Updates] F[CI/CD Pipeline] --> B F --> C G[Configuration] --> C H[Secrets] --> C I[Monitoring] --> D J[Scaling] --> D K[Update Strategies] --> E

In this lecture, we'll explore practical approaches to deploying applications on Kubernetes, covering containerization, deployment strategies, configuration management, scaling, updates, and observability.

Preparing Applications for Kubernetes

Container Best Practices

Before deploying to Kubernetes, ensure your application follows these containerization best practices:

# Example Dockerfile with best practices
# Stage 1: Build the application
FROM node:16-alpine AS builder

WORKDIR /app

# Copy and install dependencies first (for better layer caching)
COPY package*.json ./
RUN npm ci --only=production

# Copy application code
COPY . .

# Build the application
RUN npm run build

# Stage 2: Production image
FROM node:16-alpine

# Create non-root user
RUN addgroup -g 1001 appuser && \
    adduser -u 1001 -G appuser -s /bin/sh -D appuser

WORKDIR /app

# Copy production dependencies and build from the builder stage
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

# Use non-root user
USER appuser

# Set environment variables
ENV NODE_ENV production

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# Run the application with proper signal handling
CMD ["node", "dist/server.js"]

Application Configuration

Design your application to get configuration from the environment, following the Twelve-Factor App methodology:

// Example Node.js configuration pattern
const config = {
  port: process.env.PORT || 3000,
  dbHost: process.env.DB_HOST || 'localhost',
  dbPort: process.env.DB_PORT || 5432,
  dbUser: process.env.DB_USER,
  dbPassword: process.env.DB_PASSWORD,
  dbName: process.env.DB_NAME || 'app',
  logLevel: process.env.LOG_LEVEL || 'info',
  
  // Validate required environment variables
  validate() {
    const required = ['DB_USER', 'DB_PASSWORD'];
    const missing = required.filter(key => !process.env[key]);
    
    if (missing.length > 0) {
      throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
    }
    
    return this;
  }
};

// Export validated config
module.exports = config.validate();

Health Checks

Implement health endpoints for Kubernetes probes:

// Example health check endpoints for Express.js
const express = require('express');
const app = express();

// Basic health check for liveness probe
app.get('/health', (req, res) => {
  res.status(200).send('OK');
});

// Readiness check that verifies dependencies
app.get('/ready', async (req, res) => {
  try {
    // Check database connection
    await db.ping();
    
    // Check other dependencies
    await redis.ping();
    
    res.status(200).send('Ready');
  } catch (error) {
    console.error('Readiness check failed:', error);
    res.status(503).send('Not Ready');
  }
});

// Startup check for initialization
app.get('/startup', async (req, res) => {
  if (app.isInitialized) {
    res.status(200).send('Initialized');
  } else {
    res.status(503).send('Initializing');
  }
});

Kubernetes Deployment Approaches

Imperative vs. Declarative

Kubernetes supports two approaches to resource management:

Imperative Declarative
Focus on how to achieve the result Focus on what the result should be
Direct commands to create/update resources Define desired state in YAML files
Good for quick, one-off tasks Better for production environments
Less repeatable and traceable Version-controlled, repeatable
# Imperative approach
kubectl create deployment nginx --image=nginx:1.19
kubectl expose deployment nginx --port=80 --type=ClusterIP

# Declarative approach
kubectl apply -f nginx-deployment.yaml

For production deployments, the declarative approach is strongly recommended as it enables:

Kubernetes-Native Deployment

The most direct approach is using native Kubernetes YAML manifests:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web-app
        image: my-registry/web-app:1.0.0
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: "1"
            memory: 512Mi
          requests:
            cpu: "0.2"
            memory: 256Mi
        env:
        - name: LOG_LEVEL
          value: "info"

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

This approach gives you full control but can become cumbersome as applications grow more complex.

Helm Charts

Helm is a package manager for Kubernetes that streamlines application deployment:

graph TD A[Helm Chart] --> B[Templates] A --> C[Values File] A --> D[Chart Dependencies] B & C --> E[Rendered Manifests] E --> F[Kubernetes API] D --> G[Dependent Charts] G --> E

Benefits of using Helm:

# Installing a chart from a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-release bitnami/nginx

# Creating a custom chart
helm create my-app

# Installing a local chart with custom values
helm install my-release ./my-app --values custom-values.yaml

# Upgrading a release
helm upgrade my-release ./my-app --values new-values.yaml
# Example values.yaml for Helm chart
replicaCount: 3

image:
  repository: my-registry/web-app
  tag: 1.0.0
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

resources:
  limits:
    cpu: 1
    memory: 512Mi
  requests:
    cpu: 200m
    memory: 256Mi

env:
  LOG_LEVEL: info
  FEATURE_FLAGS: "payment,notifications"

Kustomize

Kustomize is a configuration customization tool that's built into kubectl:

graph TD A[Base Configuration] --> D[Kustomize] B[Dev Overlay] --> D C[Prod Overlay] --> D D --> E[Final Manifests] E --> F[Kubernetes API]

Kustomize allows you to:

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml

commonLabels:
  app: web-app

# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

namePrefix: prod-

patchesStrategicMerge:
- deployment-patch.yaml

replicas:
- name: web-app
  count: 5

# overlays/production/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
      - name: web-app
        resources:
          limits:
            cpu: "2"
            memory: 1Gi
          requests:
            cpu: "0.5"
            memory: 512Mi
        env:
        - name: ENVIRONMENT
          value: "production"
# Applying with Kustomize
kubectl apply -k overlays/production/

Operator Pattern

For complex applications, especially stateful ones, the operator pattern provides application-specific automation:

graph TD A[Custom Resource Definition] --> B[Custom Resource] C[Operator Controller] --> B C --> D[Kubernetes API] C --> E[Application-Specific Logic] E --> F[Managed Application]

Popular operator-managed applications include:

# Example: Deploying PostgreSQL with an operator
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
  name: hippo
spec:
  image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.5-0
  postgresVersion: 14
  instances:
    - name: instance1
      replicas: 3
      dataVolumeClaimSpec:
        accessModes:
        - "ReadWriteOnce"
        resources:
          requests:
            storage: 1Gi
  backups:
    pgbackrest:
      image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-0
      repos:
      - name: repo1
        volume:
          volumeClaimSpec:
            accessModes:
            - "ReadWriteOnce"
            resources:
              requests:
                storage: 1Gi

Advanced Deployment Strategies

Rolling Updates

The default strategy in Kubernetes, which gradually replaces old pods with new ones:

graph TD subgraph "Initial State" A1[Pod V1] A2[Pod V1] A3[Pod V1] end subgraph "During Update" B1[Pod V1] B2[Pod V2] B3[Pod V1] end subgraph "Final State" C1[Pod V2] C2[Pod V2] C3[Pod V2] end A1 & A2 & A3 --> B1 & B2 & B3 B1 & B2 & B3 --> C1 & C2 & C3
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # How many pods can be created above desired number
      maxUnavailable: 1  # How many pods can be unavailable during update
  # ... rest of deployment ...

Pros:

Cons:

Blue-Green Deployment

Run two identical environments (blue and green) and switch traffic all at once:

graph TD A[Service] --> B[Blue Environment
Version 1] C[Green Environment
Version 2] D[Switch Traffic] --> E[Service] --> C B -.-> F[Decommission or Keep as Rollback]
# Blue deployment (current version)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app-blue
  labels:
    app: web-app
    version: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
      version: blue
  template:
    metadata:
      labels:
        app: web-app
        version: blue
    spec:
      containers:
      - name: web-app
        image: my-registry/web-app:1.0.0
        # ... container config ...

# Green deployment (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app-green
  labels:
    app: web-app
    version: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
      version: green
  template:
    metadata:
      labels:
        app: web-app
        version: green
    spec:
      containers:
      - name: web-app
        image: my-registry/web-app:1.1.0
        # ... container config ...
# Service initially pointing to blue
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  selector:
    app: web-app
    version: blue  # Switch to 'green' when ready
  ports:
  - port: 80
    targetPort: 80

Pros:

Cons:

Canary Deployment

Gradually shift traffic from old to new version to test in production with minimal risk:

graph TD A[Service] --> B[90% Traffic to
Stable Version] A --> C[10% Traffic to
Canary Version] D[Success] --> E[Gradually
Increase Canary] E --> F[100% Traffic to
New Version] G[Problem] --> H[Revert to
Stable Version]
# Stable deployment (majority of traffic)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app-stable
  labels:
    app: web-app
    version: stable
spec:
  replicas: 9  # 90% of traffic
  selector:
    matchLabels:
      app: web-app
      version: stable
  template:
    metadata:
      labels:
        app: web-app
        version: stable
    spec:
      containers:
      - name: web-app
        image: my-registry/web-app:1.0.0
        # ... container config ...

# Canary deployment (small portion of traffic)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app-canary
  labels:
    app: web-app
    version: canary
spec:
  replicas: 1  # 10% of traffic
  selector:
    matchLabels:
      app: web-app
      version: canary
  template:
    metadata:
      labels:
        app: web-app
        version: canary
    spec:
      containers:
      - name: web-app
        image: my-registry/web-app:1.1.0
        # ... container config ...
# Service selecting both deployments
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  selector:
    app: web-app  # Matches both stable and canary
  ports:
  - port: 80
    targetPort: 80

Pros:

Cons:

Feature Toggles

Deploy code with new features disabled, then enable them gradually:

graph TD A[Single Deployment
with All Features] --> B[Feature Flags
Config] B --> C[Feature A: ON] B --> D[Feature B: ON] B --> E[Feature C: OFF] F[Update Config] --> G[Feature C: 10% of users] G --> H[Feature C: 50% of users] H --> I[Feature C: All users]
# ConfigMap for feature flags
apiVersion: v1
kind: ConfigMap
metadata:
  name: feature-flags
data:
  flags.json: |
    {
      "paymentProcessor": "v1",
      "newCheckout": false,
      "recommendations": true,
      "darkMode": {
        "enabled": true,
        "percentage": 25
      }
    }

Pros:

Cons:

Configuration Management in Kubernetes

Environment-Specific Configuration

Different environments (dev, staging, production) often require different configurations:

Using Kustomize for Environment Configuration

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml

configMapGenerator:
- name: app-config
  literals:
  - LOG_LEVEL=info

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

namePrefix: dev-

configMapGenerator:
- name: app-config
  behavior: merge
  literals:
  - ENVIRONMENT=development
  - FEATURE_FLAGS=all
  - DEBUG=true

# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

namePrefix: prod-

configMapGenerator:
- name: app-config
  behavior: merge
  literals:
  - ENVIRONMENT=production
  - FEATURE_FLAGS=stable
  - DEBUG=false

Using Helm for Environment Configuration

# values.yaml (default)
environment: development
logLevel: info
featureFlags: all
debug: true

# production-values.yaml
environment: production
logLevel: warn
featureFlags: stable
debug: false

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  ENVIRONMENT: {{ .Values.environment }}
  LOG_LEVEL: {{ .Values.logLevel }}
  FEATURE_FLAGS: {{ .Values.featureFlags }}
  DEBUG: "{{ .Values.debug }}"

External Configuration Sources

For larger organizations, external configuration management tools can provide more sophisticated features:

graph TD A[External Config Source] --> B[Kubernetes] A --> C[HashiCorp Vault] A --> D[AWS Parameter Store] A --> E[Azure Key Vault] A --> F[Google Secret Manager] A --> G[Consul] C & D & E & F & G --> H[ConfigMaps / Secrets] H --> I[Pods]

Example: External Secrets Operator

# Define a SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secretsmanager
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key-id
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-access-key

# Define an ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  refreshInterval: "15m"
  secretStoreRef:
    name: aws-secretsmanager
    kind: SecretStore
  target:
    name: db-credentials
  data:
  - secretKey: username
    remoteRef:
      key: prod/db/credentials
      property: username
  - secretKey: password
    remoteRef:
      key: prod/db/credentials
      property: password

Secrets Management Best Practices

Scaling Applications in Kubernetes

Manual Scaling

The simplest approach is changing the replica count manually:

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

# In YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 5  # Change this value
  # ... rest of deployment ...

Horizontal Pod Autoscaler (HPA)

Automatically scales the number of pods based on observed metrics:

graph TD A[Horizontal Pod Autoscaler] --> B[Metrics Server] B --> C[Pod Metrics] A --> D[Scale Target] D --> E[Deployment/StatefulSet] E --> F[Pods]
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 60
  - type: Pods
    pods:
      metric:
        name: packets-per-second
      target:
        type: AverageValue
        averageValue: 1k
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: web-app-ingress
      target:
        type: Value
        value: 10k

Vertical Pod Autoscaler (VPA)

Automatically adjusts CPU and memory requests and limits:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-app-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: web-app
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
    - containerName: '*'
      minAllowed:
        cpu: 100m
        memory: 50Mi
      maxAllowed:
        cpu: 1
        memory: 500Mi
      controlledResources: ["cpu", "memory"]

Cluster Autoscaler

Automatically adjusts the size of the Kubernetes cluster:

graph TD A[Cluster Autoscaler] --> B[Node Groups] A --> C[Pod Scheduling Status] B --> D[Add Nodes] B --> E[Remove Nodes] F[Unschedulable Pods] --> C G[Node Utilization] --> C

Cluster Autoscaler works with most cloud providers and can be configured via flags or annotations.

Scaling Stateful Applications

Scaling stateful applications requires special consideration:

# Example: Scaling read replicas in MongoDB
apiVersion: mongodb.com/v1
kind: MongoDB
metadata:
  name: mongodb-cluster
spec:
  members: 3  # Primary + 2 secondary members
  version: 4.2.6
  type: ReplicaSet
  
  # Specify the replica set members
  replicaSetHorizons:
    - horizon: internal
    - horizon: external
    
  statefulSet:
    spec:
      selector:
        matchLabels:
          app: mongodb-svc
      serviceName: mongodb-svc
      
  # Configure auto-scaling for reads
  readPreference:
    mode: SecondaryPreferred

Monitoring and Observability

Monitoring Stack for Kubernetes

A comprehensive monitoring solution for Kubernetes typically includes:

graph TD A[Monitoring Stack] --> B[Metrics Collection] A --> C[Logging] A --> D[Tracing] A --> E[Alerting] B --> F[Prometheus] F --> G[Grafana] C --> H[Fluentd/Fluent Bit] H --> I[Elasticsearch] I --> J[Kibana] D --> K[Jaeger/Zipkin] E --> L[Alertmanager] L --> M[Notification Channels]

Prometheus and Grafana

The most popular metrics monitoring solution for Kubernetes:

# Add Prometheus annotations to a Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
        prometheus.io/path: "/metrics"
    spec:
      # ... container config ...

Logging Architecture

Centralized logging is essential for troubleshooting distributed applications:

graph TD A[Application Pods] --> B[stdout/stderr] B --> C[Node-level Agent] C --> D[Log Aggregator] D --> E[Log Storage] E --> F[Log Interface] G[Fluentd DaemonSet] --> C H[Elasticsearch] --> E I[Kibana] --> F

Tracing and Observability

Distributed tracing helps track requests across multiple services:

graph LR A[Client Request] --> B[Service A] B --> C[Service B] B --> D[Service C] D --> E[Service D] B -.-> F[Trace Collector] C -.-> F D -.-> F E -.-> F F --> G[Trace Storage] G --> H[Trace UI]

Health Probes in Kubernetes

Kubernetes offers three types of health checks:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
      - name: web-app
        image: my-registry/web-app:1.0.0
        ports:
        - containerPort: 80
        
        # Startup probe - give application time to bootstrap
        startupProbe:
          httpGet:
            path: /startup
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 2
          failureThreshold: 30
        
        # Liveness probe - detect deadlocks or frozen processes
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
        
        # Readiness probe - check if ready to receive traffic
        readinessProbe:
          httpGet:
            path: /ready
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 2
          successThreshold: 1
          failureThreshold: 3

Practical Exercise: Deploying a Three-Tier Application

Scenario

In this exercise, you'll deploy a complete three-tier application to Kubernetes with best practices:

Exercise Tasks

  1. Create Deployments for the frontend and backend
  2. Set up a StatefulSet for MongoDB
  3. Configure Services for networking
  4. Implement an Ingress resource
  5. Set up ConfigMaps and Secrets for configuration
  6. Configure health checks
  7. Implement horizontal scaling
  8. Add monitoring annotations

Solution Outline

Here's a starting point for your solution:

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: three-tier-app

# mongodb-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
  namespace: three-tier-app
spec:
  serviceName: mongodb-service
  replicas: 1
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
      - name: mongodb
        image: mongo:4.4
        ports:
        - containerPort: 27017
        env:
        - name: MONGO_INITDB_ROOT_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: username
        - name: MONGO_INITDB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: password
        volumeMounts:
        - name: mongodb-data
          mountPath: /data/db
        resources:
          limits:
            cpu: "0.5"
            memory: 512Mi
          requests:
            cpu: "0.2"
            memory: 256Mi
        livenessProbe:
          exec:
            command:
            - mongo
            - --eval
            - "db.adminCommand('ping')"
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
  volumeClaimTemplates:
  - metadata:
      name: mongodb-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

# mongodb-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mongodb-service
  namespace: three-tier-app
spec:
  clusterIP: None  # Headless service for StatefulSet
  selector:
    app: mongodb
  ports:
  - port: 27017
    targetPort: 27017

# mongodb-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mongodb-secret
  namespace: three-tier-app
type: Opaque
data:
  username: YWRtaW4=  # admin
  password: cGFzc3dvcmQxMjM=  # password123

# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: three-tier-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "3000"
        prometheus.io/path: "/metrics"
    spec:
      containers:
      - name: backend
        image: your-registry/backend:1.0.0
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: PORT
          value: "3000"
        - name: MONGODB_URI
          value: "mongodb://$(MONGODB_USERNAME):$(MONGODB_PASSWORD)@mongodb-service:27017/app?authSource=admin"
        - name: MONGODB_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: username
        - name: MONGODB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: password
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log-level
        resources:
          limits:
            cpu: "0.5"
            memory: 512Mi
          requests:
            cpu: "0.2"
            memory: 256Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: three-tier-app
spec:
  selector:
    app: backend
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

# backend-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: backend-hpa
  namespace: three-tier-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: three-tier-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: your-registry/frontend:1.0.0
        ports:
        - containerPort: 80
        env:
        - name: API_URL
          value: "http://backend-service"
        resources:
          limits:
            cpu: "0.2"
            memory: 256Mi
          requests:
            cpu: "0.1"
            memory: 128Mi
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
  namespace: three-tier-app
spec:
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

# frontend-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-hpa
  namespace: three-tier-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

# app-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: three-tier-app
data:
  log-level: "info"
  feature-flags: "payments,notifications"

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: three-tier-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 80
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls-cert

Complete this exercise by applying these manifests to a Kubernetes cluster. You may need to adjust specific details to match your application and environment.

Kubernetes Deployment Best Practices

Resource Management

High Availability

Security

Networking

Maintainability

Conclusion

Deploying applications to Kubernetes is a multi-faceted process that involves containerization, resource definition, configuration management, networking, scaling, and monitoring. By following the patterns and practices covered in this lecture, you can create reliable, scalable, and maintainable deployments that leverage Kubernetes' powerful orchestration capabilities.

Key takeaways from this lecture include:

As you continue your Kubernetes journey, remember that effective deployments are not just about technology but also about processes and people. Implement CI/CD pipelines, adopt GitOps workflows, and build team knowledge to ensure long-term success with Kubernetes.

Additional Resources