How to Integrate Jenkins with Kubernetes for Container Orchestration

How to Integrate Jenkins with Kubernetes for Container Orchestration

Running static Jenkins agents used to mean paying for idle EC2 instances around the clock, or manually scaling a fixed pool up and down. Moving Jenkins agents onto Kubernetes solved that completely for me — agents now spin up as pods exactly when a build starts and disappear the moment it finishes. This guide covers both sides of that integration: running Jenkins agents on Kubernetes, and using Jenkins to deploy applications onto Kubernetes clusters.

Why Kubernetes and Jenkins Fit Together

Kubernetes is fundamentally about running many containerized workloads reliably; Jenkins is fundamentally about running many build workloads reliably. Combining them means every build gets a fresh, isolated pod (no more “works on this agent but not that one” drift), agent capacity scales automatically with demand, and you can define agent pod specs — CPU, memory, specific tool images — per pipeline rather than maintaining a fixed set of static agents.

Jenkins Architecture with Kubernetes

  • Controller: Can run inside the cluster (as a Deployment with persistent storage for $JENKINS_HOME) or outside it, connecting to the cluster’s API server.
  • Kubernetes Plugin: The core integration piece — it talks to the Kubernetes API to dynamically create agent pods per build and tear them down afterward.
  • Pod Templates: Define what an agent pod looks like — which container images, how much CPU/memory, what volumes are mounted.
  • Cloud config: Registered under Manage Jenkins > Clouds, pointing Jenkins at the Kubernetes API endpoint and namespace to provision agents in.

Prerequisites

  • A running Kubernetes cluster (EKS, GKE, AKS, or self-managed) with kubectl access
  • Helm (recommended for installing Jenkins) or raw manifests
  • Cluster admin or namespace-scoped permissions to create Deployments, Services, and RBAC roles

Step 1: Install Jenkins on Kubernetes via Helm

helm repo add jenkins https://charts.jenkins.io
helm repo update

helm install jenkins jenkins/jenkins \
  --namespace jenkins --create-namespace \
  --set controller.serviceType=LoadBalancer \
  --set persistence.enabled=true \
  --set persistence.size=20Gi

Retrieve the initial admin password:

kubectl exec -n jenkins -it svc/jenkins -c jenkins -- \
  /bin/cat /run/secrets/additional/chart-admin-password

Step 2: Set Up RBAC for Jenkins to Provision Agent Pods

The Helm chart creates a service account and role by default, but here’s what it looks like explicitly, useful to understand or customize:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: jenkins-agent-role
  namespace: jenkins
rules:
- apiGroups: [""]
  resources: ["pods", "pods/exec", "pods/log", "persistentvolumeclaims"]
  verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins-agent-rolebinding
  namespace: jenkins
subjects:
- kind: ServiceAccount
  name: jenkins
  namespace: jenkins
roleRef:
  kind: Role
  name: jenkins-agent-role
  apiGroup: rbac.authorization.k8s.io

Step 3: Install and Configure the Kubernetes Plugin

If you’re not using the Helm chart’s built-in configuration, install the Kubernetes Plugin manually and configure it under Manage Jenkins > Clouds > Add a new cloud > Kubernetes:

  • Kubernetes URL: usually auto-detected if Jenkins runs in-cluster
  • Kubernetes Namespace: jenkins (or a dedicated jenkins-agents namespace)
  • Jenkins URL: the internal service URL agents use to connect back

Step 4: Define a Pod Template for Agents

Pod templates can be configured in the UI or, more maintainably, directly in the Jenkinsfile using the kubernetes agent type:

pipeline {
    agent {
        kubernetes {
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.9-eclipse-temurin-17
    command:
    - sleep
    args:
    - 99d
  - name: docker
    image: docker:24-dind
    securityContext:
      privileged: true
  - name: kubectl
    image: bitnami/kubectl:latest
    command:
    - sleep
    args:
    - 99d
'''
        }
    }

    stages {
        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn clean package'
                }
            }
        }

        stage('Build and Push Image') {
            steps {
                container('docker') {
                    sh '''
                        docker build -t myrepo/myapp:$BUILD_NUMBER .
                        docker push myrepo/myapp:$BUILD_NUMBER
                    '''
                }
            }
        }

        stage('Deploy') {
            steps {
                container('kubectl') {
                    sh '''
                        kubectl set image deployment/myapp myapp=myrepo/myapp:$BUILD_NUMBER -n production
                        kubectl rollout status deployment/myapp -n production
                    '''
                }
            }
        }
    }
}

Each container() block runs its steps inside a specific container within the same pod — this multi-container pattern is the standard way to give a single build access to different toolchains (Maven, Docker, kubectl) without installing everything onto one bloated image.

Step 5: Handling Docker-in-Docker Securely

Running Docker builds inside Kubernetes pods traditionally required privileged mode (as shown above), which is a real security concern in shared clusters. Two better alternatives:

Kaniko (build images without a Docker daemon, no privileged mode needed):

container('kaniko') {
    sh '''
        /kaniko/executor \
          --context=dir://$(pwd) \
          --dockerfile=Dockerfile \
          --destination=myrepo/myapp:$BUILD_NUMBER
    '''
}

Pod template addition:

- name: kaniko
  image: gcr.io/kaniko-project/executor:debug
  command:
  - sleep
  args:
  - 99d

Buildah is another rootless alternative with similar benefits, worth considering if your organization standardizes on Red Hat tooling.

Step 6: Resource Requests and Autoscaling

Set resource requests/limits on agent pod templates so the cluster’s autoscaler can react correctly:

- name: maven
  image: maven:3.9-eclipse-temurin-17
  resources:
    requests:
      cpu: "1"
      memory: "2Gi"
    limits:
      cpu: "2"
      memory: "4Gi"

Pair this with the Cluster Autoscaler (or Karpenter on EKS) so a burst of concurrent builds automatically provisions additional nodes, then scales back down once the agent pods terminate.

Using Jenkins to Deploy Applications to Kubernetes

Beyond hosting agents, the more common use case is Jenkins deploying application workloads to a Kubernetes cluster — either the same cluster running Jenkins, or a separate target cluster:

stage('Deploy to Target Cluster') {
    steps {
        withKubeConfig([credentialsId: 'target-cluster-kubeconfig']) {
            sh '''
                kubectl apply -f k8s/deployment.yaml
                kubectl apply -f k8s/service.yaml
                kubectl rollout status deployment/myapp -n production --timeout=180s
            '''
        }
    }
}

Using withKubeConfig (from the Kubernetes CLI Plugin) with a stored kubeconfig credential lets Jenkins deploy to a completely separate cluster from the one hosting its own agents — common in setups where CI infrastructure and production infrastructure are deliberately isolated.

Using Helm from Jenkins

For applications packaged as Helm charts:

stage('Helm Upgrade') {
    steps {
        container('helm') {
            sh '''
                helm upgrade --install myapp ./charts/myapp \
                  --namespace production \
                  --set image.tag=$BUILD_NUMBER \
                  --wait --timeout 5m
            '''
        }
    }
}

The --wait flag blocks until all resources reach a ready state, giving the same fail-fast safety as kubectl rollout status.

Real-World Workflow

  1. A commit triggers a multibranch pipeline; Jenkins provisions a fresh agent pod with Maven, Kaniko, and kubectl containers.
  2. Build and test run in the Maven container; on success, Kaniko builds and pushes the image without needing privileged access.
  3. The kubectl container applies updated manifests or runs a Helm upgrade against the target cluster.
  4. --wait/rollout status blocks until the deployment is confirmed healthy.
  5. The agent pod terminates immediately after the pipeline finishes, freeing cluster resources for the next build.

Security Best Practices

  • Avoid privileged Docker-in-Docker; use Kaniko or Buildah for rootless image builds.
  • Scope the Jenkins service account’s RBAC role to only the namespace(s) it needs, never cluster-admin.
  • Use separate namespaces (or separate clusters entirely) for Jenkins infrastructure versus production workloads.
  • Store target-cluster kubeconfigs as Jenkins Credentials, never inline in pipeline scripts.
  • Enable Kubernetes NetworkPolicies to restrict what agent pods can reach on the network.

Troubleshooting

  • Agent pods stuck in “Pending”: Usually insufficient cluster resources — check kubectl describe pod for scheduling failures and confirm the cluster autoscaler is configured.
  • “Error: Jenkins agent failed to connect”: Confirm the JNLP port and Jenkins controller’s internal service URL are correctly set in the Kubernetes cloud configuration.
  • Build hangs indefinitely: Check pod template idleMinutes and podRetention settings; a hung container can block cleanup if retention policy isn’t configured properly.

FAQs

Do I need a separate Kubernetes cluster for Jenkins agents versus my production workloads? Not strictly required, but recommended for isolation — a runaway build shouldn’t be able to affect production resource availability or, worse, gain network access to production services.

Is the Kubernetes Plugin the only way to run Jenkins agents in containers? It’s the most mature option; the Kubernetes Operator for Jenkins and self-managed Jenkins Configuration as Code (JCasC) setups offer alternative approaches with similar underlying mechanics.

Can Jenkins itself run as a Kubernetes-native CRD-based pipeline instead of the traditional controller model? Not natively — Jenkins remains a traditional controller/agent model even when hosted on Kubernetes; if you want a fully Kubernetes-native pipeline model, that’s a different tool category (like Tekton), though the two can coexist.

How do I persist Jenkins configuration and job history across pod restarts? Use a PersistentVolumeClaim mounted at $JENKINS_HOME, as configured by the Helm chart’s persistence.enabled setting — without it, restarting the controller pod wipes all job history and configuration.

Summary

Integrating Jenkins with Kubernetes pays off on two fronts: dynamically provisioned, isolated build agents that scale with demand and cost nothing when idle, and a natural deployment target for the applications those pipelines build. Multi-container pod templates give each build exactly the toolchain it needs, rootless builders like Kaniko keep things secure, and kubectl/Helm deployment stages with proper wait conditions keep rollouts safe and observable.

References

Total
1
Shares

Leave a Reply

Previous Post
How to Use Jenkins with Terraform for Infrastructure as Code

How to Use Jenkins with Terraform for Infrastructure as Code

Next Post
How to Set Up Jenkins for Continuous Deployment on Google Cloud Platform (GCP)

How to Set Up Jenkins for Continuous Deployment on Google Cloud Platform (GCP)

Related Posts