8 min read A. Sulaiman

Kubernetes Deployment Best Practices

A comprehensive guide to deploying applications on Kubernetes with confidence, covering resource management, health checks, and security best practices.

Kubernetes has become the de facto standard for container orchestration, but deploying applications effectively requires understanding several key concepts and best practices. In this article, we'll explore essential patterns for production-ready Kubernetes deployments.

Why Deployment Best Practices Matter

Poor deployment configurations can lead to:
- Application downtime during updates
- Resource exhaustion affecting other workloads
- Security vulnerabilities in your cluster
- Unpredictable scaling behavior

Let's dive into the critical areas you need to master.

1. Resource Requests and Limits

Always define resource requests and limits for your containers. This ensures proper scheduling and prevents resource starvation.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: app
        image: my-app:1.0.0
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Why This Matters

Resource Type Purpose Impact if Not Set
CPU Request Scheduling guarantee Poor pod placement
Memory Request Scheduling guarantee OOMKill risk
CPU Limit Throttling threshold Noisy neighbor problems
Memory Limit Hard cap Potential OOMKill

2. Health Checks: Liveness and Readiness Probes

Kubernetes needs to know when your application is ready to serve traffic and when it's unhealthy.

# Example Flask health check endpoints
from flask import Flask, jsonify
import psutil

app = Flask(__name__)

@app.route('/healthz')
def liveness():
    """Liveness probe - is the app running?"""
    return jsonify({"status": "alive"}), 200

@app.route('/ready')
def readiness():
    """Readiness probe - can the app handle requests?"""
    # Check dependencies like database, cache, etc.
    try:
        # Verify database connection
        db.session.execute('SELECT 1')
        return jsonify({"status": "ready"}), 200
    except Exception as e:
        return jsonify({"status": "not ready", "error": str(e)}), 503

Configure these probes in your deployment:

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  timeoutSeconds: 3
  failureThreshold: 2

Pro Tip: Liveness probes should check if the app is alive, not if dependencies are available. If your database is down, the app is still "alive" - it's just not "ready".

3. Rolling Update Strategy

Configure your deployment strategy to ensure zero-downtime updates:

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1        # Max pods above desired count during update
    maxUnavailable: 0  # Ensures at least N replicas always available

This configuration ensures:
1. New pods start before old ones terminate
2. Service remains available throughout the update
3. Gradual rollout reduces blast radius

4. Pod Disruption Budgets

Protect your application from voluntary disruptions like node drains:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: my-app

5. Security Context

Run containers with minimal privileges:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

Complete Example Deployment

Here's a production-ready deployment incorporating all best practices:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: production-app
  labels:
    app: production-app
    version: v1.0.0
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: production-app
  template:
    metadata:
      labels:
        app: production-app
        version: v1.0.0
    spec:
      serviceAccountName: production-app-sa
      securityContext:
        fsGroup: 1000
      containers:
      - name: app
        image: gcr.io/my-project/app:1.0.0
        ports:
        - containerPort: 8080
          name: http
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          readOnlyRootFilesystem: true
          allowPrivilegeEscalation: false
          capabilities:
            drop:
              - ALL
        env:
        - name: ENV
          value: "production"
        - name: LOG_LEVEL
          value: "info"
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /app/cache
      volumes:
      - name: tmp
        emptyDir: {}
      - name: cache
        emptyDir: {}

Deployment Checklist

Before deploying to production, verify:

  • ✅ Resource requests and limits defined
  • ✅ Liveness and readiness probes configured
  • ✅ Rolling update strategy set with maxUnavailable: 0
  • ✅ Pod Disruption Budget created
  • ✅ Security context configured (non-root, read-only filesystem)
  • ✅ Multiple replicas for high availability
  • ✅ Proper labels for observability
  • ✅ Monitoring and logging configured

Monitoring Your Deployments

Use these commands to monitor deployment progress:

# Watch deployment rollout
kubectl rollout status deployment/my-app

# Check deployment history
kubectl rollout history deployment/my-app

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

# View pod events
kubectl get events --sort-by='.lastTimestamp' | grep my-app

Common Pitfalls to Avoid

  1. No resource limits - Can cause node resource exhaustion
  2. Missing probes - Kubernetes can't manage unhealthy pods properly
  3. Aggressive probe timing - Can cause cascading failures
  4. Single replica - No high availability
  5. Running as root - Security risk
  6. No PodDisruptionBudget - Vulnerable to disruptions

Conclusion

Kubernetes deployments require thoughtful configuration to achieve reliability, security, and performance. By following these best practices, you'll build resilient applications that can handle failures gracefully and scale with confidence.

Remember: Start with good defaults, then tune based on your application's specific needs and monitoring data.


Have questions about Kubernetes deployments? Reach out to us for expert DevOps consulting.