Kubernetes Best Practices for Production Deployments

Learn essential practices for running Kubernetes in production, including resource management, security, and observability strategies.

Kubernetes Best Practices for Production Deployments

Orchestrating containers at scale with Kubernetes

Introduction

Kubernetes has become the de facto standard for container orchestration, powering everything from small startups to Fortune 500 companies. However, running Kubernetes in production requires more than just deploying applications. This comprehensive guide covers essential best practices that ensure your Kubernetes clusters are secure, reliable, scalable, and maintainable.

Resource Management

Setting Resource Requests and Limits

One of the most critical yet often overlooked aspects of Kubernetes deployments is proper resource management.

Always Set Resource Requests:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: api
        image: myapp:v1.0.0
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Best Practices:

  • Requests: What the container is guaranteed to get
  • Limits: Maximum resources the container can use
  • Start with generous limits and optimize based on metrics
  • Use Vertical Pod Autoscaler (VPA) to find optimal values

Quality of Service (QoS) Classes

Kubernetes assigns QoS classes based on resource specifications:

  1. Guaranteed: Requests equal limits for all resources
  2. Burstable: At least one resource has a request
  3. BestEffort: No resource requests or limits

Production Recommendation: Aim for Guaranteed or Burstable QoS for critical workloads.

Namespace Resource Quotas

Implement namespace-level resource controls:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: production
spec:
  hard:
    requests.cpu: "100"
    requests.memory: 200Gi
    limits.cpu: "200"
    limits.memory: 400Gi
    persistentvolumeclaims: "10"
    services.loadbalancers: "2"

Security Best Practices

Pod Security Standards

Implement Pod Security Standards (replacing Pod Security Policies in 1.25+):

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Security Contexts

Always define security contexts for your pods:

apiVersion: v1
kind: Pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL

Network Policies

Implement zero-trust networking with NetworkPolicies:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-server-netpol
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-server
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432

RBAC Configuration

Follow the principle of least privilege:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: deployment-manager
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployment-manager-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: deployment-manager
  namespace: production
roleRef:
  kind: Role
  name: deployment-manager
  apiGroup: rbac.authorization.k8s.io

Secrets Management

Never hardcode secrets. Use proper secret management:

  1. External Secret Operators:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: vault-secret
spec:
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: app-secret
  data:
  - secretKey: password
    remoteRef:
      key: secret/data/database
      property: password
  1. Sealed Secrets for GitOps:
# Encrypt secret
echo -n mypassword | kubectl create secret generic mysecret \
  --dry-run=client --from-file=password=/dev/stdin -o yaml | \
  kubeseal -o yaml > mysealedsecret.yaml

High Availability and Reliability

Multi-Zone Deployments

Spread pods across availability zones:

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: api-server
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: api-server
            topologyKey: kubernetes.io/hostname

Health Checks

Implement comprehensive health checks:

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: app
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 3
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 3
      failureThreshold: 3
    startupProbe:
      httpGet:
        path: /startup
        port: 8080
      initialDelaySeconds: 0
      periodSeconds: 10
      timeoutSeconds: 3
      failureThreshold: 30

Graceful Shutdown

Handle SIGTERM signals properly:

// Node.js example
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, starting graceful shutdown');
  
  // Stop accepting new requests
  server.close(() => {
    console.log('HTTP server closed');
  });
  
  // Close database connections
  await database.close();
  
  // Wait for ongoing requests to complete (max 30s)
  await waitForRequestsToComplete(30000);
  
  process.exit(0);
});

Pod configuration:

spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 15"]

Observability

Structured Logging

Implement structured JSON logging:

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File parsers.conf
    
    [INPUT]
        Name              tail
        Path              /var/log/containers/*.log
        Parser            docker
        Tag               kube.*
    
    [FILTER]
        Name              kubernetes
        Match             kube.*
        Kube_URL          https://kubernetes.default.svc:443
        Merge_Log         On
        Keep_Log          Off
    
    [OUTPUT]
        Name              elasticsearch
        Match             *
        Host              elasticsearch.logging.svc.cluster.local
        Port              9200
        Logstash_Format   On
        Replace_Dots      On
        Retry_Limit       False

Metrics and Monitoring

Deploy Prometheus with proper scrape configs:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: myapp
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9090"
    prometheus.io/path: "/metrics"
spec:
  ports:
  - name: metrics
    port: 9090
    targetPort: 9090

Custom application metrics:

// Go example with Prometheus
var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "endpoint", "status"},
    )
    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name: "http_request_duration_seconds",
            Help: "HTTP request latency",
        },
        []string{"method", "endpoint"},
    )
)

Distributed Tracing

Implement OpenTelemetry for tracing:

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: app
        env:
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: "http://otel-collector:4317"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "service.name=api-server,service.version=1.0.0"
        - name: OTEL_TRACES_SAMPLER
          value: "parentbased_traceidratio"
        - name: OTEL_TRACES_SAMPLER_ARG
          value: "0.1"  # Sample 10% of traces

Deployment Strategies

Blue-Green Deployments

Using Flagger for automated blue-green deployments:

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: api-server
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  service:
    port: 80
  analysis:
    interval: 1m
    threshold: 10
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 1m

Progressive Delivery

Implement feature flags with ConfigMaps:

apiVersion: v1
kind: ConfigMap
metadata:
  name: feature-flags
data:
  features.json: |
    {
      "new-checkout-flow": {
        "enabled": false,
        "rollout_percentage": 10
      },
      "enhanced-search": {
        "enabled": true,
        "rollout_percentage": 100
      }
    }

Cluster Management

GitOps with ArgoCD

Implement GitOps for declarative cluster management:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: production-apps
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/company/k8s-manifests
    targetRevision: main
    path: production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
    - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Backup and Disaster Recovery

Implement Velero for cluster backups:

# Install Velero
velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.5.0 \
  --bucket velero-backups \
  --secret-file ./credentials-velero \
  --backup-location-config region=us-west-2 \
  --snapshot-location-config region=us-west-2

# Create backup schedule
velero schedule create daily-backup \
  --schedule="0 2 * * *" \
  --ttl 720h0m0s

Cost Optimization

Cluster Autoscaling

Configure cluster autoscaler properly:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cluster-autoscaler
  namespace: kube-system
spec:
  template:
    spec:
      containers:
      - image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.27.0
        name: cluster-autoscaler
        command:
        - ./cluster-autoscaler
        - --v=4
        - --stderrthreshold=info
        - --cloud-provider=aws
        - --skip-nodes-with-local-storage=false
        - --expander=least-waste
        - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/prod-cluster
        - --balance-similar-node-groups
        - --skip-nodes-with-system-pods=false

Spot Instances

Leverage spot instances for non-critical workloads:

apiVersion: v1
kind: Node
metadata:
  labels:
    node.kubernetes.io/lifecycle: spot
    karpenter.sh/capacity-type: spot
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      tolerations:
      - key: spot
        operator: Equal
        value: "true"
        effect: NoSchedule
      nodeSelector:
        karpenter.sh/capacity-type: spot

Maintenance and Updates

Rolling Updates Strategy

Configure proper rolling update parameters:

apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 1
  minReadySeconds: 30
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

Automated Image Updates

Use Flux for automated image updates:

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
      messageTemplate: 'Update image to {{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  update:
    path: ./clusters/production
    strategy: Setters

Conclusion

Running Kubernetes in production requires attention to many details beyond basic deployment. By following these best practices—proper resource management, comprehensive security measures, high availability design, robust observability, and efficient cluster management—you can build a production-grade Kubernetes environment that is secure, reliable, and scalable.

Remember that Kubernetes is a powerful but complex system. Start with the fundamentals, gradually adopt more advanced practices, and always test changes in non-production environments first. Continuous monitoring, regular updates, and ongoing optimization are key to maintaining a healthy Kubernetes cluster.

The journey to Kubernetes excellence is ongoing, but with these best practices as your foundation, you’re well-equipped to handle the challenges of production deployments.