Skip to content

Kubernetes Deployment Strategies and CI/CD: Production-Ready Practices

Kubernetes Deployment Strategies and CI/CD: Production-Ready Practices

Deploying applications to Kubernetes in production requires sophisticated strategies to ensure zero-downtime updates, reliable rollbacks, and seamless integration with CI/CD pipelines. This comprehensive guide explores advanced deployment patterns, GitOps principles, and production-ready CI/CD implementations for Kubernetes environments.

Table of Contents

  1. Introduction to Kubernetes Deployments
  2. Core Deployment Strategies
  3. Rolling Update Strategy
  4. Blue-Green Deployments
  5. Canary Deployments
  6. Feature Flags and Progressive Delivery
  7. GitOps and Declarative Deployments
  8. CI/CD Pipeline Integration
  9. Advanced Deployment Patterns
  10. Monitoring and Rollback Strategies
  11. Security and Compliance
  12. Production Best Practices

Introduction to Kubernetes Deployments

Kubernetes provides powerful primitives for application deployment, but choosing the right strategy depends on your application’s requirements, risk tolerance, and operational maturity. Modern deployment strategies minimize risk while maximizing velocity.

Why Deployment Strategies Matter

🚀 Key Benefits:

  • Zero-Downtime Updates: Keep applications available during updates
  • Risk Mitigation: Test changes with real traffic before full rollout
  • Fast Rollbacks: Quickly revert problematic deployments
  • Progressive Delivery: Gradually roll out features to users
  • Automated Operations: Reduce manual intervention and human error

Kubernetes Deployment Architecture

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: myapp
        version: v1.0.0
    spec:
      containers:
        - name: app
          image: myapp:v1.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10

Core Deployment Strategies

Strategy Comparison Matrix

StrategyDowntimeResource CostRollback SpeedTesting CapabilityComplexity
RecreateYesLowFastNoneLow
Rolling UpdateNoLowMediumLimitedLow
Blue-GreenNoHighInstantFullMedium
CanaryNoMediumFastProgressiveHigh
A/B TestingNoMediumFastTargetedHigh

Choosing the Right Strategy

graph TD
    A[Deployment Strategy Selection] --> B{Downtime Acceptable?}
    B -->|Yes| C[Recreate Strategy]
    B -->|No| D{Need Instant Rollback?}
    D -->|Yes| E[Blue-Green]
    D -->|No| F{Progressive Testing?}
    F -->|Yes| G[Canary]
    F -->|No| H[Rolling Update]

Rolling Update Strategy

Rolling updates are Kubernetes’ default deployment strategy, gradually replacing old pods with new ones.

Basic Rolling Update

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2 # Maximum pods above desired replicas
      maxUnavailable: 1 # Maximum pods that can be unavailable
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: app
          image: web-app:v2.0.0
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi

Advanced Rolling Update Configuration

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
spec:
  replicas: 20
  progressDeadlineSeconds: 600 # Timeout for deployment
  revisionHistoryLimit: 10 # Number of old ReplicaSets to keep
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25% # Percentage-based configuration
      maxUnavailable: 25%
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
        version: v2.1.0
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9090'
    spec:
      containers:
        - name: api
          image: api-service:v2.1.0
          ports:
            - containerPort: 8080
            - containerPort: 9090 # Metrics port
          env:
            - name: VERSION
              value: 'v2.1.0'
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            successThreshold: 1
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          lifecycle:
            preStop:
              exec:
                command: ['/bin/sh', '-c', 'sleep 15']

Rolling Update with PodDisruptionBudget

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 80%
  selector:
    matchLabels:
      app: api-service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0 # Ensure high availability
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
    spec:
      containers:
        - name: api
          image: api-service:v2.0.0

Blue-Green Deployments

Blue-green deployments maintain two complete environments, switching traffic between them for instant rollbacks.

Blue-Green with Services

# Blue Deployment (Current)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-blue
  labels:
    version: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
        - name: app
          image: myapp:v1.0.0
          ports:
            - containerPort: 8080
---
# Green Deployment (New)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-green
  labels:
    version: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
        - name: app
          image: myapp:v2.0.0
          ports:
            - containerPort: 8080
---
# Service (Switch between blue/green)
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue # Change to 'green' to switch
  ports:
    - port: 80
      targetPort: 8080
  type: LoadBalancer

Blue-Green Automation Script

# @filename: .env
#!/bin/bash
# blue-green-deploy.sh

NAMESPACE="production"
APP_NAME="myapp"
NEW_VERSION="v2.0.0"

# Deploy green version
kubectl set image deployment/${APP_NAME}-green \
  app=${APP_NAME}:${NEW_VERSION} \
  -n ${NAMESPACE}

# Wait for green deployment to be ready
kubectl rollout status deployment/${APP_NAME}-green -n ${NAMESPACE}

# Run smoke tests
echo "Running smoke tests on green deployment..."
GREEN_POD=$(kubectl get pod -n ${NAMESPACE} -l version=green -o jsonpath='{.items[0].metadata.name}')
kubectl exec ${GREEN_POD} -n ${NAMESPACE} -- /app/smoke-test.sh

if [ $? -eq 0 ]; then
  echo "Smoke tests passed. Switching traffic to green..."

  # Switch service to green
  kubectl patch service ${APP_NAME}-service -n ${NAMESPACE} \
    -p '{"spec":{"selector":{"version":"green"}}}'

  echo "Traffic switched to green. Monitoring for 5 minutes..."
  sleep 300

  # If everything is stable, update blue to match green
  kubectl set image deployment/${APP_NAME}-blue \
    app=${APP_NAME}:${NEW_VERSION} \
    -n ${NAMESPACE}
else
  echo "Smoke tests failed. Keeping traffic on blue."
  exit 1
fi

Blue-Green with Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-blue # or myapp-green
                port:
                  number: 80

Canary Deployments

Canary deployments gradually roll out changes to a small subset of users before full deployment.

Basic Canary with Deployments

# Stable Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-stable
spec:
  replicas: 9 # 90% of traffic
  selector:
    matchLabels:
      app: myapp
      version: stable
  template:
    metadata:
      labels:
        app: myapp
        version: stable
    spec:
      containers:
        - name: app
          image: myapp:v1.0.0
---
# Canary Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-canary
spec:
  replicas: 1 # 10% of traffic
  selector:
    matchLabels:
      app: myapp
      version: canary
  template:
    metadata:
      labels:
        app: myapp
        version: canary
    spec:
      containers:
        - name: app
          image: myapp:v2.0.0
---
# Service routing to both
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp # Matches both stable and canary
  ports:
    - port: 80
      targetPort: 8080

Advanced Canary with Flagger

# Install Flagger
# kubectl apply -k github.com/fluxcd/flagger/kustomize/linkerd

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: myapp
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  progressDeadlineSeconds: 600
  service:
    port: 80
    targetPort: 8080
    gateways:
      - public-gateway.istio-system.svc.cluster.local
    hosts:
      - myapp.example.com
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: 'curl -s http://myapp-canary.test:80/health'
      - name: load-test
        type: rollout
        url: http://flagger-loadtester.test/
        metadata:
          cmd: 'hey -z 2m -q 10 -c 2 http://myapp-canary.test:80/'

Canary with Istio

# VirtualService for traffic splitting
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp.example.com
  http:
    - match:
        - headers:
            canary:
              exact: 'true'
      route:
        - destination:
            host: myapp-canary
            port:
              number: 80
    - route:
        - destination:
            host: myapp-stable
            port:
              number: 80
          weight: 90
        - destination:
            host: myapp-canary
            port:
              number: 80
          weight: 10
---
# DestinationRule
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: myapp-stable
spec:
  host: myapp-stable
  subsets:
    - name: v1
      labels:
        version: v1
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: myapp-canary
spec:
  host: myapp-canary
  subsets:
    - name: v2
      labels:
        version: v2

Feature Flags and Progressive Delivery

Combine deployment strategies with feature flags for fine-grained control over feature rollouts.

Feature Flag Implementation

apiVersion: v1
kind: ConfigMap
metadata:
  name: feature-flags
data:
  flags.json: |
    {
      "features": {
        "new-ui": {
          "enabled": true,
          "percentage": 10,
          "allowedUsers": ["beta-tester-1", "beta-tester-2"]
        },
        "advanced-analytics": {
          "enabled": false
        },
        "payment-v2": {
          "enabled": true,
          "percentage": 100,
          "regions": ["us-east", "eu-west"]
        }
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-with-flags
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:v2.0.0
          env:
            - name: FEATURE_FLAGS_PATH
              value: /etc/feature-flags/flags.json
          volumeMounts:
            - name: feature-flags
              mountPath: /etc/feature-flags
      volumes:
        - name: feature-flags
          configMap:
            name: feature-flags

Progressive Delivery with Argo Rollouts

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp-rollout
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: myapp-canary
      stableService: myapp-stable
      trafficRouting:
        istio:
          virtualServices:
            - name: myapp-vsvc
              routes:
                - primary
      maxSurge: '25%'
      maxUnavailable: 0
      steps:
        - setWeight: 10
        - pause: { duration: 1m }
        - setWeight: 20
        - pause: { duration: 2m }
        - setWeight: 40
        - pause: { duration: 2m }
        - setWeight: 60
        - pause: { duration: 2m }
        - setWeight: 80
        - pause: { duration: 2m }
      analysis:
        templates:
          - templateName: success-rate
        startingStep: 2
        args:
          - name: service-name
            value: myapp-canary
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:v2.0.0
          ports:
            - containerPort: 8080

GitOps and Declarative Deployments

GitOps uses Git as the single source of truth for declarative infrastructure and applications.

ArgoCD Implementation

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

Flux CD Configuration

# GitRepository source
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: myapp
  namespace: flux-system
spec:
  interval: 1m
  ref:
    branch: main
  url: https://github.com/myorg/myapp-k8s
---
# Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: myapp
  namespace: flux-system
spec:
  interval: 10m
  path: './environments/production'
  prune: true
  sourceRef:
    kind: GitRepository
    name: myapp
  validation: client
  postBuild:
    substitute:
      cluster_name: production
      region: us-east-1
    substituteFrom:
      - kind: ConfigMap
        name: cluster-config

GitOps Repository Structure

myapp-k8s/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   └── kustomization.yaml
├── environments/
│   ├── development/
│   │   ├── kustomization.yaml
│   │   ├── replica-patch.yaml
│   │   └── resource-patch.yaml
│   ├── staging/
│   │   ├── kustomization.yaml
│   │   ├── replica-patch.yaml
│   │   └── ingress.yaml
│   └── production/
│       ├── kustomization.yaml
│       ├── replica-patch.yaml
│       ├── resource-patch.yaml
│       ├── hpa.yaml
│       └── pdb.yaml
└── scripts/
    ├── promote.sh
    └── rollback.sh

CI/CD Pipeline Integration

Jenkins Pipeline for Kubernetes

// @filename: manifest.yaml
pipeline {
    agent {
        kubernetes {
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: docker
    image: docker:20.10.21-dind
    securityContext:
      privileged: true
  - name: kubectl
    image: bitnami/kubectl:latest
    command:
    - cat
    tty: true
  - name: helm
    image: alpine/helm:3.10.3
    command:
    - cat
    tty: true
"""
        }
    }

    environment {
        DOCKER_REGISTRY = 'registry.example.com'
        APP_NAME = 'myapp'
        NAMESPACE = 'production'
    }

    stages {
        stage('Build') {
            steps {
                container('docker') {
                    script {
                        def tag = "${env.BUILD_NUMBER}"
                        sh """
                            docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${tag} .
                            docker push ${DOCKER_REGISTRY}/${APP_NAME}:${tag}
                        """
                    }
                }
            }
        }

        stage('Test') {
            steps {
                container('docker') {
                    sh """
                        docker run --rm ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} npm test
                    """
                }
            }
        }

        stage('Security Scan') {
            steps {
                container('docker') {
                    sh """
                        trivy image ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}
                    """
                }
            }
        }

        stage('Deploy to Staging') {
            steps {
                container('kubectl') {
                    withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                        sh """
                            kubectl set image deployment/${APP_NAME} \
                                ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} \
                                -n staging
                            kubectl rollout status deployment/${APP_NAME} -n staging
                        """
                    }
                }
            }
        }

        stage('Integration Tests') {
            steps {
                sh """
                    npm run test:integration -- --url=https://staging.example.com
                """
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?"
                ok "Deploy"
            }
            steps {
                container('helm') {
                    withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                        sh """
                            helm upgrade --install ${APP_NAME} ./helm-chart \
                                --namespace ${NAMESPACE} \
                                --set image.tag=${BUILD_NUMBER} \
                                --set image.repository=${DOCKER_REGISTRY}/${APP_NAME} \
                                --wait
                        """
                    }
                }
            }
        }
    }

    post {
        success {
            slackSend(
                color: 'good',
                message: "Deployment successful: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
            )
        }
        failure {
            slackSend(
                color: 'danger',
                message: "Deployment failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
            )
        }
    }
}

GitLab CI/CD Pipeline

# .gitlab-ci.yml
variables:
  DOCKER_REGISTRY: registry.gitlab.com
  APP_NAME: myapp
  CONTAINER_IMAGE: $DOCKER_REGISTRY/$CI_PROJECT_PATH

stages:
  - build
  - test
  - security
  - deploy-staging
  - deploy-production

build:
  stage: build
  image: docker:20.10.21
  services:
    - docker:20.10.21-dind
  script:
    - docker build -t $CONTAINER_IMAGE:$CI_COMMIT_SHA .
    - docker tag $CONTAINER_IMAGE:$CI_COMMIT_SHA $CONTAINER_IMAGE:latest
    - docker push $CONTAINER_IMAGE:$CI_COMMIT_SHA
    - docker push $CONTAINER_IMAGE:latest

test:
  stage: test
  image: $CONTAINER_IMAGE:$CI_COMMIT_SHA
  script:
    - npm test
    - npm run test:coverage
  coverage: '/Coverage: \d+\.\d+%/'
  artifacts:
    reports:
      junit: test-results.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

security-scan:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy image --severity HIGH,CRITICAL $CONTAINER_IMAGE:$CI_COMMIT_SHA
  allow_failure: true

deploy-staging:
  stage: deploy-staging
  image: bitnami/kubectl:latest
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - kubectl config use-context staging
    - |
      kubectl set image deployment/$APP_NAME \
        $APP_NAME=$CONTAINER_IMAGE:$CI_COMMIT_SHA \
        -n staging
    - kubectl rollout status deployment/$APP_NAME -n staging
  only:
    - develop
    - main

deploy-production:
  stage: deploy-production
  image: alpine/helm:3.10.3
  environment:
    name: production
    url: https://example.com
  script:
    - helm upgrade --install $APP_NAME ./helm-chart
      --namespace production
      --set image.tag=$CI_COMMIT_SHA
      --set image.repository=$CONTAINER_IMAGE
      --atomic
      --timeout 10m
  when: manual
  only:
    - main

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=sha,prefix={{branch}}-

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  test:
    needs: build
    runs-on: ubuntu-latest
    container:
      image: ${{ needs.build.outputs.image-tag }}
    steps:
      - name: Run tests
        run: |
          npm test
          npm run test:integration

  deploy-staging:
    needs: [build, test]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Deploy to Kubernetes
        uses: azure/k8s-deploy@v4
        with:
          namespace: staging
          manifests: |
            k8s/deployment.yaml
            k8s/service.yaml
          images: ${{ needs.build.outputs.image-tag }}

  deploy-production:
    needs: [build, test]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://example.com
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Deploy with Helm
        uses: deliverybot/helm@v1
        with:
          release: myapp
          namespace: production
          chart: ./helm-chart
          values: |
            image:
              repository: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
              tag: ${{ github.sha }}
          atomic: true
        env:
          KUBECONFIG_FILE: ${{ secrets.KUBECONFIG }}

Advanced Deployment Patterns

Multi-Region Deployment

# Global Traffic Manager
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: global-ingress
  annotations:
    external-dns.alpha.kubernetes.io/hostname: app.example.com
    external-dns.alpha.kubernetes.io/ttl: '60'
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: regional-router
                port:
                  number: 80
---
# Regional Deployment Template
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-${REGION}
  labels:
    app: myapp
    region: ${REGION}
spec:
  replicas: ${REPLICAS}
  selector:
    matchLabels:
      app: myapp
      region: ${REGION}
  template:
    metadata:
      labels:
        app: myapp
        region: ${REGION}
    spec:
      nodeSelector:
        topology.kubernetes.io/region: ${REGION}
      containers:
        - name: app
          image: myapp:v2.0.0
          env:
            - name: REGION
              value: ${REGION}
            - name: DB_HOST
              value: db-${REGION}.example.com

Shadow Deployment

# Shadow deployment for testing with production traffic
apiVersion: v1
kind: Service
metadata:
  name: myapp-shadow
spec:
  selector:
    app: myapp
    version: shadow
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-shadow
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
      version: shadow
  template:
    metadata:
      labels:
        app: myapp
        version: shadow
    spec:
      containers:
        - name: app
          image: myapp:experimental
          env:
            - name: SHADOW_MODE
              value: 'true'
---
# Istio VirtualService for traffic mirroring
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp.example.com
  http:
    - match:
        - uri:
            prefix: '/'
      route:
        - destination:
            host: myapp-stable
            port:
              number: 80
      mirror:
        host: myapp-shadow
        port:
          number: 80
      mirrorPercentage:
        value: 10.0

Dark Launch Pattern

apiVersion: v1
kind: ConfigMap
metadata:
  name: dark-launch-config
data:
  features.yaml: |
    darkFeatures:
      - name: new-recommendation-engine
        enabled: true
        percentage: 0  # Feature is deployed but not active
        metrics:
          - recommendation-accuracy
          - processing-time
      - name: enhanced-search
        enabled: true
        percentage: 5  # 5% of users see this feature
        userGroups:
          - beta-testers
          - employees
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-dark-launch
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:v2.0.0-beta
          volumeMounts:
            - name: dark-launch
              mountPath: /etc/features
          env:
            - name: DARK_LAUNCH_ENABLED
              value: 'true'
      volumes:
        - name: dark-launch
          configMap:
            name: dark-launch-config

Monitoring and Rollback Strategies

Automated Rollback with Prometheus

apiVersion: v1
kind: ConfigMap
metadata:
  name: rollback-rules
data:
  rules.yaml: |
    groups:
    - name: deployment_health
      interval: 30s
      rules:
      - alert: HighErrorRate
        expr: |
          rate(http_requests_total{status=~"5.."}[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
          action: rollback
        annotations:
          summary: "High error rate detected"
          description: "Error rate is above 5% for 2 minutes"
      
      - alert: LowSuccessRate
        expr: |
          rate(http_requests_total{status="200"}[5m]) / 
          rate(http_requests_total[5m]) < 0.95
        for: 3m
        labels:
          severity: warning
          action: investigate
        annotations:
          summary: "Low success rate"
          description: "Success rate below 95%"
      
      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, http_request_duration_seconds_bucket) > 0.5
        for: 5m
        labels:
          severity: warning
          action: scale
        annotations:
          summary: "High latency detected"
          description: "95th percentile latency above 500ms"
---
# Rollback automation script
apiVersion: v1
kind: ConfigMap
metadata:
  name: rollback-script
data:
  rollback.sh: |
    #!/bin/bash
    set -e

    DEPLOYMENT=$1
    NAMESPACE=$2

    # Get current and previous revision
    CURRENT=$(kubectl get deployment $DEPLOYMENT -n $NAMESPACE -o jsonpath='{.metadata.annotations.deployment\.kubernetes\.io/revision}')
    PREVIOUS=$((CURRENT - 1))

    echo "Rolling back $DEPLOYMENT from revision $CURRENT to $PREVIOUS"

    # Perform rollback
    kubectl rollout undo deployment/$DEPLOYMENT -n $NAMESPACE --to-revision=$PREVIOUS

    # Wait for rollback to complete
    kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE

    # Verify health
    sleep 30
    kubectl get pods -n $NAMESPACE -l app=$DEPLOYMENT

    # Send notification
    curl -X POST $SLACK_WEBHOOK -d "{\"text\": \"Rollback completed for $DEPLOYMENT\"}"

Deployment Monitoring Dashboard

apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-dashboard
data:
  deployment-dashboard.json: |
    {
      "dashboard": {
        "title": "Kubernetes Deployment Monitor",
        "panels": [
          {
            "title": "Deployment Status",
            "targets": [
              {
                "expr": "kube_deployment_status_replicas{deployment=\"$deployment\"}"
              }
            ]
          },
          {
            "title": "Error Rate",
            "targets": [
              {
                "expr": "rate(http_requests_total{status=~\"5..\",deployment=\"$deployment\"}[5m])"
              }
            ]
          },
          {
            "title": "Response Time",
            "targets": [
              {
                "expr": "histogram_quantile(0.95, http_request_duration_seconds_bucket{deployment=\"$deployment\"})"
              }
            ]
          },
          {
            "title": "Pod Restarts",
            "targets": [
              {
                "expr": "rate(kube_pod_container_status_restarts_total{deployment=\"$deployment\"}[15m])"
              }
            ]
          }
        ]
      }
    }

Security and Compliance

Secure Deployment Pipeline

# Security scanning in deployment
apiVersion: batch/v1
kind: Job
metadata:
  name: security-scan-${BUILD_ID}
spec:
  template:
    spec:
      containers:
        - name: scanner
          image: aquasec/trivy:latest
          command:
            - sh
            - -c
            - |
              # Scan image for vulnerabilities
              trivy image --severity HIGH,CRITICAL ${IMAGE}

              # Check for secrets
              trivy image --security-checks secret ${IMAGE}

              # Policy compliance check
              trivy image --policy /policies ${IMAGE}

              # Generate SBOM
              trivy image --format cyclonedx ${IMAGE} > sbom.json
          volumeMounts:
            - name: policies
              mountPath: /policies
      volumes:
        - name: policies
          configMap:
            name: security-policies
      restartPolicy: Never

RBAC for Deployments

# ServiceAccount for CI/CD
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cicd-deployer
  namespace: production
---
# Role with minimal permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cicd-deployer
  namespace: production
rules:
  - apiGroups: ['apps']
    resources: ['deployments']
    verbs: ['get', 'list', 'update', 'patch']
  - apiGroups: ['apps']
    resources: ['replicasets']
    verbs: ['get', 'list']
  - apiGroups: ['']
    resources: ['pods']
    verbs: ['get', 'list']
  - apiGroups: ['']
    resources: ['pods/log']
    verbs: ['get']
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cicd-deployer
  namespace: production
subjects:
  - kind: ServiceAccount
    name: cicd-deployer
    namespace: production
roleRef:
  kind: Role
  name: cicd-deployer
  apiGroup: rbac.authorization.k8s.io

Pod Security Standards

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
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-app
  template:
    metadata:
      labels:
        app: secure-app
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 2000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: myapp:v2.0.0
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /app/cache
      volumes:
        - name: tmp
          emptyDir: {}
        - name: cache
          emptyDir: {}

Production Best Practices

Complete Production Deployment

# Production-ready deployment configuration
apiVersion: apps/v1
kind: Deployment
metadata:
  name: production-app
  labels:
    app: myapp
    env: production
spec:
  replicas: 10
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0
  selector:
    matchLabels:
      app: myapp
      env: production
  template:
    metadata:
      labels:
        app: myapp
        env: production
        version: v2.0.0
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9090'
        prometheus.io/path: '/metrics'
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - myapp
                topologyKey: kubernetes.io/hostname
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: node-type
                    operator: In
                    values:
                      - production
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: myapp
      priorityClassName: high-priority
      serviceAccountName: myapp-sa
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 2000
      containers:
        - name: app
          image: registry.example.com/myapp:v2.0.0
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
            - containerPort: 9090
              name: metrics
              protocol: TCP
          env:
            - name: APP_VERSION
              value: 'v2.0.0'
            - name: ENVIRONMENT
              value: 'production'
            - name: LOG_LEVEL
              value: 'info'
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
              httpHeaders:
                - name: X-Probe-Type
                  value: Readiness
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 3
            successThreshold: 1
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
              httpHeaders:
                - name: X-Probe-Type
                  value: Liveness
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /startup
              port: 8080
            initialDelaySeconds: 0
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 30
          lifecycle:
            preStop:
              exec:
                command:
                  ['/bin/sh', '-c', 'sleep 15 && /app/graceful-shutdown.sh']
          volumeMounts:
            - name: config
              mountPath: /etc/config
              readOnly: true
            - name: secrets
              mountPath: /etc/secrets
              readOnly: true
            - name: cache
              mountPath: /app/cache
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
      initContainers:
        - name: migration
          image: registry.example.com/myapp:v2.0.0
          command: ['/app/migrate.sh']
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
      volumes:
        - name: config
          configMap:
            name: app-config
        - name: secrets
          secret:
            secretName: app-secrets
            defaultMode: 0400
        - name: cache
          emptyDir:
            sizeLimit: 1Gi
      imagePullSecrets:
        - name: registry-credentials
      terminationGracePeriodSeconds: 60
---
# HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: production-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: production-app
  minReplicas: 10
  maxReplicas: 50
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: '1000'
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
        - type: Pods
          value: 2
          periodSeconds: 60
      selectPolicy: Min
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60
        - type: Pods
          value: 5
          periodSeconds: 60
      selectPolicy: Max
---
# PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: production-app-pdb
spec:
  minAvailable: 80%
  selector:
    matchLabels:
      app: myapp
      env: production
  unhealthyPodEvictionPolicy: AlwaysAllow

GitOps Production Workflow

# @filename: .env
#!/bin/bash
# promote-to-production.sh

set -euo pipefail

SOURCE_ENV="staging"
TARGET_ENV="production"
APP_NAME="myapp"
GIT_REPO="git@github.com:myorg/k8s-deployments.git"

# Clone GitOps repository
git clone $GIT_REPO /tmp/gitops
cd /tmp/gitops

# Get current staging version
STAGING_VERSION=$(yq eval '.spec.template.spec.containers[0].image' \
  environments/$SOURCE_ENV/$APP_NAME/deployment.yaml | cut -d: -f2)

echo "Promoting version $STAGING_VERSION from $SOURCE_ENV to $TARGET_ENV"

# Update production manifests
yq eval -i ".spec.template.spec.containers[0].image = \"myapp:$STAGING_VERSION\"" \
  environments/$TARGET_ENV/$APP_NAME/deployment.yaml

# Create pull request
git checkout -b promote-$STAGING_VERSION
git add environments/$TARGET_ENV/
git commit -m "Promote $APP_NAME $STAGING_VERSION to $TARGET_ENV

- Source: $SOURCE_ENV
- Version: $STAGING_VERSION
- Automated promotion"

git push origin promote-$STAGING_VERSION

# Create PR using GitHub CLI
gh pr create \
  --title "Promote $APP_NAME $STAGING_VERSION to $TARGET_ENV" \
  --body "Automated promotion from $SOURCE_ENV" \
  --base main \
  --label "promotion" \
  --label "production"

Conclusion

Kubernetes deployment strategies and CI/CD integration form the backbone of modern cloud-native applications. By mastering these concepts and patterns, you can:

  • ✅ Deploy applications with zero downtime
  • ✅ Implement sophisticated rollout strategies
  • ✅ Build robust CI/CD pipelines
  • ✅ Practice GitOps for declarative deployments
  • ✅ Ensure security and compliance
  • ✅ Monitor and rollback deployments automatically

Key Takeaways

  1. Choose the Right Strategy: Match deployment strategy to your risk tolerance and requirements
  2. Automate Everything: Use CI/CD pipelines and GitOps for consistent deployments
  3. Monitor Proactively: Implement comprehensive monitoring and automated rollbacks
  4. Security First: Integrate security scanning and compliance checks
  5. Practice Progressive Delivery: Use canary deployments and feature flags
  6. Embrace GitOps: Git as single source of truth simplifies operations

Next Steps

  • Implement service mesh for advanced traffic management
  • Explore progressive delivery tools like Flagger and Argo Rollouts
  • Study chaos engineering for resilience testing
  • Learn multi-cluster deployment strategies
  • Master observability with distributed tracing

Remember, successful Kubernetes deployments combine the right strategies, automation, and monitoring to deliver reliable applications at scale.

Additional Resources

Master these deployment strategies, and you’ll be ready to operate Kubernetes at scale with confidence! 🚀⚓

Kubernetes Container Orchestration DevOps Go Backend Concurrency
Share:

Continue Reading

Advanced Kubernetes Deployment Patterns: From Blue-Green to Canary

Explore advanced deployment strategies in Kubernetes that go beyond basic rolling updates. Learn how to implement Blue-Green deployments, Canary releases, and A/B testing patterns to minimize downtime and risk in your production environments. This comprehensive guide covers practical implementations, best practices, and real-world scenarios for each deployment pattern.

Read article
KubernetesContainer OrchestrationDevOps