Fenil Sonani

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

1 min read

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

#!/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

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

#!/bin/bash
# promote-to-production.sh

set -euo pipefail

SOURCE_ENV="staging"
TARGET_ENV="production"
APP_NAME="myapp"
GIT_REPO="[email protected]: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! 🚀⚓

Share this content

Reading time: 1 minutes
Progress: 0%
#Kubernetes#Container Orchestration#DevOps#Go#Backend#Concurrency
Kubernetes Deployment Strategies and CI/CD: Production-Ready Practices - Fenil Sonani