Jenkins Deployment to Kubernetes

30 minLesson 4 of 8

Learning Objectives

  • Integrate Jenkins with Kubernetes
  • Build Docker images in Jenkins pipelines
  • Deploy to Kubernetes from Jenkins
  • Implement rolling updates and rollbacks

Jenkins + Kubernetes Overview

┌──────────┐    ┌──────────┐    ┌──────────────┐    ┌────────────┐
│   Git    │───▶│ Jenkins  │───▶│Docker Registry│───▶│ Kubernetes │
│  Push    │    │ Pipeline │    │  (Push Image) │    │  (Deploy)  │
└──────────┘    └──────────┘    └──────────────┘    └────────────┘

The pipeline flow:

  1. Developer pushes code to Git
  2. Jenkins triggers the pipeline
  3. Build and test the application
  4. Build Docker image and push to registry
  5. Deploy to Kubernetes cluster

Prerequisites Setup

Jenkins Plugins Needed

  • Kubernetes plugin
  • Docker Pipeline plugin
  • Kubernetes CLI plugin
  • Credentials Binding plugin

Credentials to Configure

CredentialTypePurpose
docker-registryUsername/PasswordPush images
kubeconfigSecret FileKubernetes access
github-tokenSecret TextGit operations

Building Docker Images

Dockerfile

# Multi-stage build for a Node.js app
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
 
FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
 
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]

Jenkins Pipeline — Build & Push

pipeline {
    agent any
 
    environment {
        REGISTRY = 'registry.example.com'
        IMAGE_NAME = 'nextgen-app'
        IMAGE_TAG = "${BUILD_NUMBER}"
    }
 
    stages {
        stage('Build Image') {
            steps {
                script {
                    docker.build("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
                }
            }
        }
 
        stage('Push Image') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", 'docker-registry') {
                        docker.image("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}").push()
                        docker.image("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}").push('latest')
                    }
                }
            }
        }
    }
}

Kubernetes Deployment Manifests

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nextgen-app
  labels:
    app: nextgen-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nextgen-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: nextgen-app
    spec:
      containers:
      - name: app
        image: registry.example.com/nextgen-app:BUILD_NUMBER
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 15
          periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: nextgen-app-service
spec:
  selector:
    app: nextgen-app
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

Full CI/CD Pipeline to Kubernetes

pipeline {
    agent any
 
    environment {
        REGISTRY = 'registry.example.com'
        IMAGE_NAME = 'nextgen-app'
        IMAGE_TAG = "${BUILD_NUMBER}"
        KUBE_NAMESPACE = 'production'
    }
 
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
 
        stage('Test') {
            agent { docker { image 'node:20-alpine' } }
            steps {
                sh 'npm ci'
                sh 'npm run test'
                sh 'npm run lint'
            }
        }
 
        stage('Build & Push Image') {
            steps {
                script {
                    def image = docker.build("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
                    docker.withRegistry("https://${REGISTRY}", 'docker-registry') {
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }
 
        stage('Deploy to Kubernetes') {
            steps {
                withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                    sh """
                        kubectl set image deployment/nextgen-app \
                          app=${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
                          --namespace=${KUBE_NAMESPACE}
 
                        kubectl rollout status deployment/nextgen-app \
                          --namespace=${KUBE_NAMESPACE} \
                          --timeout=300s
                    """
                }
            }
        }
 
        stage('Verify Deployment') {
            steps {
                withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                    sh """
                        kubectl get pods -l app=nextgen-app --namespace=${KUBE_NAMESPACE}
                        kubectl get svc nextgen-app-service --namespace=${KUBE_NAMESPACE}
                    """
                }
            }
        }
    }
 
    post {
        success {
            echo "✅ Deployed ${IMAGE_NAME}:${IMAGE_TAG} to ${KUBE_NAMESPACE}"
        }
        failure {
            // Rollback on failure
            withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                sh """
                    kubectl rollout undo deployment/nextgen-app \
                      --namespace=${KUBE_NAMESPACE}
                """
            }
            echo "❌ Deployment failed — rolled back"
        }
        always {
            cleanWs()
        }
    }
}

Rolling Updates & Rollbacks

Rolling Update Strategy

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Max pods above desired count
      maxUnavailable: 0  # Zero downtime

Manual Rollback

# Check rollout history
kubectl rollout history deployment/nextgen-app
 
# Rollback to previous version
kubectl rollout undo deployment/nextgen-app
 
# Rollback to specific revision
kubectl rollout undo deployment/nextgen-app --to-revision=3

Automated Rollback in Pipeline

stage('Deploy') {
    steps {
        script {
            try {
                sh "kubectl set image deployment/nextgen-app app=${IMAGE}"
                sh "kubectl rollout status deployment/nextgen-app --timeout=120s"
            } catch (Exception e) {
                sh "kubectl rollout undo deployment/nextgen-app"
                error("Deployment failed, rolled back: ${e.message}")
            }
        }
    }
}

Blue-Green Deployment

stage('Blue-Green Deploy') {
    steps {
        script {
            // Deploy to green environment
            sh """
                kubectl apply -f k8s/deployment-green.yaml
                kubectl rollout status deployment/nextgen-app-green --timeout=120s
            """
 
            // Run smoke tests against green
            sh "curl -f http://nextgen-app-green.internal/health"
 
            // Switch traffic to green
            sh "kubectl patch svc nextgen-app-service -p '{\"spec\":{\"selector\":{\"version\":\"green\"}}}'"
 
            // Remove old blue deployment
            sh "kubectl delete deployment nextgen-app-blue --ignore-not-found"
        }
    }
}

Summary

You've learned:

  • How Jenkins integrates with Kubernetes for CI/CD
  • Building and pushing Docker images in pipelines
  • Deploying to Kubernetes with rolling updates
  • Implementing automated rollbacks on failure
  • Blue-green deployment strategies

Next Steps

You now have a complete Jenkins CI/CD foundation. Continue to the Kubernetes module for deeper orchestration knowledge, or explore the Monitoring module to observe your deployments.