{"id":950,"date":"2024-07-23T01:49:00","date_gmt":"2024-07-23T01:49:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=950"},"modified":"2024-07-23T01:49:00","modified_gmt":"2024-07-23T01:49:00","slug":"jenkins-build-agents-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=950","title":{"rendered":"How to Setup Jenkins  Build Agents on Kubernetes Pods"},"content":{"rendered":"<p>In this Jenkins tutorial, I explained the detailed steps to set up Jenkins controller and scale Jenkins build agents on Kubernetes pods using the Jenkins Kubernetes plugin<\/p>\n<p>In another post, I&nbsp;explained how to set up&nbsp;<a href=\"https:\/\/devopscube.com\/docker-containers-as-build-slaves-jenkins\/\">Docker-based&nbsp;Jenkins agents<\/a>.<\/p>\n<p>If you have a <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\" rel=\"noreferrer noopener\">Kubernetes cluster<\/a> in your environment, running Jenkins agents on the Kubernetes pods will give you good build isolation for different application versions.<\/p>\n<p>Also, an ephemeral Kubernetes pod-based Jenkins agent is a great way to reduce the cost of the CI environment, as Jenkins agents are spun up only if there is a build request.<\/p>\n<h2 id=\"how-do-jenkins-kubernetes-pod-agents-work\">How Do Jenkins Kubernetes Pod Agents Work?<\/h2>\n<p>Before getting into the implementation, let&#8217;s understand how this setup works.<\/p>\n<p>The following image shows the high-level workflow.<\/p>\n<figure class=\"kg-card kg-image-card kg-card-hascaption\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jenkins-agent-1.gif\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"781\" height=\"939\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/jenkins-agent-1.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jenkins-agent-1.gif 781w\" sizes=\"auto, (min-width: 720px) 720px\"><figcaption><span style=\"white-space: pre-wrap;\">Click to view in HD<\/span><\/figcaption><\/figure>\n<ol>\n<li>Whenever you trigger a <a href=\"https:\/\/devopscube.com\/jenkins-architecture-explained\/\" rel=\"noreferrer noopener\">Jenkins<\/a> job, the Jenkins Kubernetes plugin will make an API call to create a Kubernetes agent pod.<\/li>\n<li>Then, the <a href=\"https:\/\/devopscube.com\/setup-slaves-on-jenkins-2\/\" rel=\"noreferrer noopener\">Jenkins agent <\/a>pod is deployed in Kubernetes with a few environment variables containing the Jenkins server details and secrets.<\/li>\n<li>When the agent pod comes up, it uses the details in its <strong>environment variables<\/strong> and talks back to Jenkins using the JNLP method. The following images show the environment variables of the agent pod.<\/li>\n<\/ol>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-20-21.png\" class=\"kg-image\" alt=\"Jenkins pod agent environment variables\" loading=\"lazy\" width=\"1306\" height=\"382\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-20-21.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-20-21.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-20-21.png 1306w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>All the build steps from the Jenkinsfile run on that pod. Once the build is complete, the pod will be terminated automatically. However, there are also options to retain the build pod.<\/p>\n<p>The Jenkins Kubernetes plugin handles all the communication from Jenkins to the Kubernetes cluster.<\/p>\n<p>Also, as long as your Kubernetes cluster scales, you can scale your Jenkins build agents without issues.<\/p>\n<h2 id=\"setting-up-jenkins-build-pods-on-kubernetes\">Setting Up Jenkins Build Pods On Kubernetes<\/h2>\n<p>To work on this setup, we need the following.<\/p>\n<ol>\n<li>A working Kubernetes cluster.<\/li>\n<li>Kubernetes admin user to <a href=\"https:\/\/devopscube.com\/kubernetes-deployment-tutorial\/\" rel=\"noreferrer noopener\">create Kubernetes deployments<\/a> and service accounts<\/li>\n<li>A running Jenkins controller<\/li>\n<\/ol>\n<p>Also, I am considering two scenarios here.<\/p>\n<ol>\n<li>Jenkins controller running inside the Kubernetes cluster.<\/li>\n<li>Jenkins controller running outside the Kubernetes cluster.<\/li>\n<\/ol>\n<p>We will look at both scenarios and their configurations.<\/p>\n<p>Overfall, here is what we are going to do.<\/p>\n<ol>\n<li>Create a namespace <code>devops-tools<\/code><\/li>\n<li>Create a Kubernetes service account named <code>jenkins-admin<\/code> with permissions to manage pods in <code>devops-tools<\/code> namespace. Jenkins will use this service account to deploy the agent pods. (Both internal &amp; external Jenkins)<\/li>\n<li>Deploy Jenkins in <code>devops-tools <\/code>namespace with the <code>jenkins-admin<\/code> service account. (If you don&#8217;t have an existing Jenkins)<\/li>\n<li>Configure the Kubernetes Jenkins Plugin so that Jenkins can interact with the Kubernetes cluster and deploy build agents.<\/li>\n<\/ol>\n<h2 id=\"setting-up-kubernetes-namespace-service-account\">Setting Up Kubernetes Namespace &amp; Service Account<\/h2>\n<p>Let&#8217;s get started with the setup.<\/p>\n<p><strong>Step 1: <\/strong>Create a namespace called <code>devops-tools<\/code><\/p>\n<pre><code>kubectl create namespace devops-tools<\/code><\/pre>\n<p><strong>Step 2: <\/strong>Save the following manifest as <code>service-account.yaml<\/code>. It contains the role and role-binding for the service account with all the permission to manage pods in the <code>devops-tools<\/code> namespace.<\/p>\n<pre><code>apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: jenkins-admin\n  namespace: devops-tools\n---\napiVersion: rbac.authorization.k8s.io\/v1\nkind: Role\nmetadata:\n  name: jenkins\n  namespace: devops-tools\n  labels:\n    \"app.kubernetes.io\/name\": 'jenkins'\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"create\",\"delete\",\"get\",\"list\",\"patch\",\"update\",\"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\/exec\"]\n  verbs: [\"create\",\"delete\",\"get\",\"list\",\"patch\",\"update\",\"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\/log\"]\n  verbs: [\"get\",\"list\",\"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io\/v1\nkind: RoleBinding\nmetadata:\n  name: jenkins-role-binding\n  namespace: devops-tools\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: jenkins\nsubjects:\n- kind: ServiceAccount\n  name: jenkins-admin\n  namespace: devops-tools<\/code><\/pre>\n<p>Create the service account.<\/p>\n<pre><code>kubectl apply -f service-account.yaml<\/code><\/pre>\n<p>The next step is to create a Secret, which creates a token for the service account.<\/p>\n<p>Create a YAML file <strong>secret.yaml<\/strong> and copy the below contents into it<\/p>\n<pre><code>apiVersion: v1\nkind: Secret\nmetadata:\n  name: sa-token-secret\n  namespace: devops-tools\n  annotations:\n    kubernetes.io\/service-account.name: jenkins-admin\ntype: kubernetes.io\/service-account-token<\/code><\/pre>\n<p>Create the secret<\/p>\n<pre><code>kubectl apply -f secret.yaml<\/code><\/pre>\n<p>This creates a secret and links it with the service account using the annotation.<\/p>\n<h2 id=\"jenkins-controller-setup-in-kubernetes\">Jenkins Controller Setup in Kubernetes<\/h2>\n<p>In this setup, we will have both the Jenkins controller and agents deploying in the same Kubernetes cluster.<\/p>\n<p>We will set up the Jenkins controller server on the Kubernetes cluster.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> If you have an existing setup, you can use that as well. Ensure it has a service account with permissions to deploy pods in the namespace where Jenkins is deployed.<\/div>\n<\/div>\n<p>Save the following manifest as <code>deployment.yaml<\/code>. This manifest contains persistent volume claim, deployment, and service definitions.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> Ensure that your Kubernetes cluster setup supports persistent volumes. If you deploy Jenkins without a persistent volume, you will lose the Jenkins data on every restart or pod deletion.<\/div>\n<\/div>\n<p>Also, if you are using a local cluster, create a persistent volume before running the below manifest.<\/p>\n<pre><code># Persistent Volume Claim\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: jenkins-pv-claim\n  namespace: devops-tools\nspec:\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 50Gi\n\n# Deployment Config\n---\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: jenkins-deployment\n  namespace: devops-tools\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: jenkins\n  template:\n    metadata:\n      labels:\n        app: jenkins\n    spec:\n      serviceAccountName: jenkins-admin\n      securityContext:\n            fsGroup: 1000 \n            runAsUser: 1000\n      containers:\n        - name: jenkins\n          image: jenkins\/jenkins:lts\n          resources:\n            limits:\n              memory: \"2Gi\"\n              cpu: \"1000m\"\n            requests:\n              memory: \"500Mi\"\n              cpu: \"500m\"\n          ports:\n            - name: httpport\n              containerPort: 8080\n            - name: jnlpport\n              containerPort: 50000\n          livenessProbe:\n            httpGet:\n              path: \"\/login\"\n              port: 8080\n            initialDelaySeconds: 90\n            periodSeconds: 10\n            timeoutSeconds: 5\n            failureThreshold: 5\n          readinessProbe:\n            httpGet:\n              path: \"\/login\"\n              port: 8080\n            initialDelaySeconds: 60\n            periodSeconds: 10\n            timeoutSeconds: 5\n            failureThreshold: 3\n          volumeMounts:\n            - name: jenkins-data\n              mountPath: \/var\/jenkins_home         \n      volumes:\n        - name: jenkins-data\n          persistentVolumeClaim:\n              claimName: jenkins-pv-claim\n\n# Service Config\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: jenkins-service\n  namespace: devops-tools\n  annotations:\n      prometheus.io\/scrape: 'true'\n      prometheus.io\/path:   \/\n      prometheus.io\/port:   '8080'\nspec:\n  selector: \n    app: jenkins\n  type: NodePort  \n  ports:\n    - name: httpport\n      port: 8080\n      targetPort: 8080\n      nodePort: 32000\n    - name: jnlpport\n      port: 50000\n      targetPort: 50000\n  <\/code><\/pre>\n<p>Create the deployment.<\/p>\n<pre><code>kubectl apply -f deployment.yaml<\/code><\/pre>\n<p>After a couple of minutes, the Jenkins deployment will be up, and you will be able to access any Kubernetes node on the port <code>32000<\/code><\/p>\n<p><strong>Step 4: <\/strong>Access the Jenkins dashboard over the node port and unlock it using the password from the pod logs. Install the suggested plugins and create a Jenkins user.<\/p>\n<p>Please follow the <a href=\"https:\/\/devopscube.com\/setup-jenkins-on-kubernetes-cluster\/\" rel=\"noreferrer noopener\">Jenkins on Kubernetes blog<\/a> if you have any doubts.<\/p>\n<h2 id=\"jenkins-kubernetes-plugin-configuration\">Jenkins Kubernetes Plugin Configuration<\/h2>\n<p><a href=\"https:\/\/plugins.jenkins.io\/kubernetes\/?ref=devopscube.com\" rel=\"noreferrer noopener\">Jenkins Kubernetes plugin <\/a>is required to set up Kubernetes-based build agents. Let&#8217;s configure the plugin.<\/p>\n<h3 id=\"step-1-install-jenkins-kubernetes-plugin\">Step 1: Install Jenkins Kubernetes Plugin<\/h3>\n<p>Go to <code>Manage Jenkins <\/code>\u2013&gt; <code>Manage Plugins<\/code>, search for the Kubernetes Plugin in the available tab, and install it. The following Gif video shows the plugin installation process.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jenkins-kubernetes-plugin-installtion-1.gif\" class=\"kg-image\" alt=\"installing Jenkins Kubernetes Plugin\" loading=\"lazy\" width=\"1728\" height=\"1080\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/jenkins-kubernetes-plugin-installtion-1.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/jenkins-kubernetes-plugin-installtion-1.gif 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/jenkins-kubernetes-plugin-installtion-1.gif 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jenkins-kubernetes-plugin-installtion-1.gif 1728w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h3 id=\"step-2-create-a-kubernetes-cloud-configuration\"><strong>Step 2: <\/strong>Create a Kubernetes Cloud Configuration<\/h3>\n<p>Once installed, go to<code> Manage Jenkins<\/code> \u2013&gt; <code>Clouds<\/code><\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-133-6.png\" class=\"kg-image\" alt=\"configure Jenkins cloud\" loading=\"lazy\" width=\"1114\" height=\"613\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-133-6.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-133-6.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-133-6.png 1114w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Click New Cloud<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-134-8.png\" class=\"kg-image\" alt=\"add a new Jenkins cloud agent\" loading=\"lazy\" width=\"775\" height=\"409\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-134-8.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-134-8.png 775w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Give a name and select Kubernetes.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/blog.techiescamp.com\/content\/images\/2024\/08\/image-33.png\" class=\"kg-image\" alt=\"create a Jenkins cloud\" loading=\"lazy\" width=\"938\" height=\"477\"><\/figure>\n<p>Click Create and move on to the next step<\/p>\n<h3 id=\"step-3-configure-jenkins-kubernetes-cloud\">Step 3: Configure Jenkins Kubernetes Cloud<\/h3>\n<p>Here we have two scenarios.<\/p>\n<ol>\n<li>Jenkins server <strong>running inside<\/strong> the same Kubernetes cluster<\/li>\n<li>The Jenkins server is <strong>running outside<\/strong> the Kubernetes cluster.<\/li>\n<\/ol>\n<p>Let&#8217;s look at configurations for both scenarios.<\/p>\n<h4 id=\"jenkins-server-running-inside-the-same-kubernetes-cluster\">Jenkins server running inside the same Kubernetes cluster<\/h4>\n<p>Since we have Jenkins inside the Kubernetes cluster with a service account to deploy the agent pods, we don&#8217;t have to mention the Kubernetes URL or certificate key.<\/p>\n<p>However, to validate the connection using the service account, use the Test Connection button as shown below. It should show a connected message if the Jenkins pod can connect to the Kubernetes API Server.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-6-19.png\" class=\"kg-image\" alt=\"Test Jenkins Kubernetes cloud connection\" loading=\"lazy\" width=\"674\" height=\"711\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-6-19.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-6-19.png 674w\"><\/figure>\n<h4 id=\"jenkins-server-running-outside-the-kubernetes-cluster\">Jenkins server running Outside the Kubernetes cluster<\/h4>\n<p>If your Jenkins server is running outside the Kubernetes cluster, you need to specify the following.<\/p>\n<ol>\n<li><strong>Kubernetes URL:<\/strong> This is the Kubernetes API Server endpoint. If it is https enabled, use the https URL.<\/li>\n<li><strong>Kubernetes<\/strong> Server Certificate key:  If you have a Kubernetes Cluster CA certificate, you can add it for secure connectivity. You can get the certificate from the pod location <code>\/var\/run\/secrets\/kubernetes.io\/serviceaccount\/ca.crt<\/code> .If you do not have the certificate, you can enable the  &#8220;<code>disable https certificate check<\/code>&#8221; option.<\/li>\n<li><strong>Credentials:<\/strong> For Jenkins to communicate with the Kubernetes cluster, we need a service account token with permission to deploy pods in the <code>devops-tools<\/code> namespace.<\/li>\n<\/ol>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> If you use managed services like <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-google-cloud\/\" rel=\"noreferrer noopener\">GKE cluster<\/a>, you can get all the cluser details from the GKE dashboard.<\/div>\n<\/div>\n<p>We have created the service account and assigned a secret token to the <code>devops-tools<\/code> namespace. We need to get the token from the secret.<\/p>\n<p>Execute the following commands to retrieve the secret name from the service account.<\/p>\n<pre><code>kubectl get secret sa-token-secret -n devops-tools -o jsonpath='{.data.token}' | base64 --decode<\/code><\/pre>\n<p>Now click the <code>Add<\/code> button under credentials and create a credential type &#8220;<code>Secret text<\/code>&#8220;. Enter the service account token in the secret box and add other details as shown below. Finally, save the credential.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-151-2.png\" class=\"kg-image\" alt=\"Add Kubernetes service account token as Jenkins credential\" loading=\"lazy\" width=\"868\" height=\"418\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-151-2.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-151-2.png 868w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-152-2.png\" class=\"kg-image\" alt=\"Add Kubernetes service account token as Jenkins credential\" loading=\"lazy\" width=\"574\" height=\"252\"><\/figure>\n<p>The Kubernetes cloud configuration would look like the following.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-23-19.png\" class=\"kg-image\" alt=\"configure Kubernetes cloud for external Jenkins server\" loading=\"lazy\" width=\"598\" height=\"626\"><\/figure>\n<p>After filling in all the details, you can test the connection to validate the Kubernetes cluster connectivity.<\/p>\n<h4 id=\"step-4-configure-the-jenkins-url-details\">Step 4: Configure the Jenkins URL Details<\/h4>\n<p>For the Jenkins Controller running inside the cluster, you can use the Kubernetes cluster&#8217;s service endpoint as the Jenkins URL because agent pods can connect to the cluster via internal service DNS.<\/p>\n<p>The URL is derived using the following syntax.<\/p>\n<pre><code>http:\/\/&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local:8080<\/code><\/pre>\n<p>In our case, the service DNS will be,<\/p>\n<pre><code>http:\/\/jenkins-service.devops-tools.svc.cluster.local:8080<\/code><\/pre>\n<p>Also, add the POD label, which can be used to group the containers that can be used for billing or custom build dashboards.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> If the Jenkins controller is outside the Kubernetes cluster, use the <b><strong style=\"white-space: pre-wrap;\">Jenkins IP or DNS <\/strong><\/b>in the Jenkins URL configuration.<\/div>\n<\/div>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-23.png\" class=\"kg-image\" alt=\"Jenkins URL configuration for Kubernetes Jenkins plugin \" loading=\"lazy\" width=\"665\" height=\"683\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-8-23.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-23.png 665w\"><\/figure>\n<h4 id=\"step-5-create-pod-and-container-template\">Step 5: Create POD and Container Template<\/h4>\n<p>Next, you must add the POD template with the details, as shown in the image below. The label kubeagent will be used as an identifier to pick this pod as the build agent. Next, we must add a container template with the Docker image details.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-9-18.png\" class=\"kg-image\" alt=\"POD template configuration for Kubernetes Jenkins plugin \" loading=\"lazy\" width=\"675\" height=\"570\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-9-18.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-9-18.png 675w\"><\/figure>\n<p>The next configuration is the container template. If you don&#8217;t add a container template, the Jenkins Kubernetes plugin will use the default JNLP image from the Docker hub to spin up the agents. ie, <a href=\"https:\/\/hub.docker.com\/r\/jenkins\/inbound-agent\/?ref=devopscube.com\" rel=\"noreferrer noopener\">jenkins\/inbound-agent<\/a><\/p>\n<p>If you are on the corporate network and don&#8217;t have access to the Docker hub, you will have to build your own <code>jnlp<\/code> image and override the default with the same name as shown below, assuming <code>jenkins\/inbound-agent:latest<\/code> is the custom jnlp image.<\/p>\n<p>Ensure that you remove the <code>sleep<\/code> and <code>9999999<\/code> default argument from the container template.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-140-5.png\" class=\"kg-image\" alt=\"overriding default JNLP agent in Kubernetes Jenkins plugin \" loading=\"lazy\" width=\"983\" height=\"698\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-140-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-140-5.png 983w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>We can add multiple container templates to the POD template and use them in the pipeline. I have explained that in the next section with <code>Jenkinsfile<\/code> examples.<\/p>\n<p>This is the base minimum configuration required for the agent to work. Later in the pipeline examples, I will explain a few use cases for volumes and other options.<\/p>\n<p>Now save all the configurations, and let&#8217;s test if we can build a job with a pod agent.<\/p>\n<p><strong>Step 6: <\/strong>Go to Jenkins home &#8211;&gt; New Item and create a freestyle project.<\/p>\n<p>In the job description, add the label <code>kubeagent<\/code> as shown below. It is the label we assigned to the pod template. This way, Jenkins knows which pod template to use for the agent container.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-154-3.png\" class=\"kg-image\" alt=\"validating docker agent build using Kubernetes Jenkins plugin \" loading=\"lazy\" width=\"830\" height=\"356\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-154-3.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-154-3.png 830w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Add a shell build step with an echo command to validate the job as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-153-2.png\" class=\"kg-image\" alt=\"Jenkins execute shell\" loading=\"lazy\" width=\"526\" height=\"323\"><\/figure>\n<p>Now, save the job configuration and click &#8220;Build Now&#8221;<\/p>\n<p>You should see a pending agent in the job build history below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-148-4.png\" class=\"kg-image\" alt=\"docker agent build history\" loading=\"lazy\" width=\"316\" height=\"167\"><\/figure>\n<p>You will see a successful build in a couple of minutes. If you check the logs, the executed shell will be shown.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-149-6.png\" class=\"kg-image\" alt=\"successful Jenkins Kubernetes agent build\" loading=\"lazy\" width=\"1077\" height=\"176\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-149-6.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-149-6.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-149-6.png 1077w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"jenkinsfile-with-pod-template\">Jenkinsfile With Pod Template<\/h2>\n<p>Whatever we have seen till now is to understand and validate the Kubernetes Jenkins plugin setup.<\/p>\n<p>When it comes to actual project pipelines, it is better to have the POD templates in the <code>Jenkinsfile<\/code><\/p>\n<p>Here is what you should know about the POD template.<\/p>\n<ol>\n<li>By default, the plugin uses a JNLP container image to connect to the Jenkins server. You can override with a custom JNLP image provided you give the name <code>jnlp<\/code> in the container template.<\/li>\n<li>You can have multiple container templates in a single pod template. Then, each container can be used in different pipeline stages.<\/li>\n<li><code>POD_LABEL<\/code> will assign a random build label to the pod when the build is triggered. You cannot give any other names other than <code>POD_LABEL<\/code><\/li>\n<\/ol>\n<p>Here is an example <code>Jenkinsfile<\/code> with a POD template.<\/p>\n<pre><code>podTemplate {\n    node(POD_LABEL) {\n        stage('Run shell') {\n            sh 'echo hello world'\n        }\n    }\n}<\/code><\/pre>\n<p>Building the above Jenkinsfile in a pipeline job will use the default JNLP image and execute the commands in the &#8220;Run Shell&#8221; stage. When I say default, the plugin will use the JNLP image from the docker hub if you don&#8217;t specify any.<\/p>\n<p>Now, you can use your own <code>jnlp<\/code> image using a <code>containerTemplate<\/code> with all necessary build tools and use them in the pipeline as given below.<\/p>\n<p>Here, instead of <code>jenkins\/inbound-agent:latest<\/code>, you will have your own image.<\/p>\n<pre><code>podTemplate(containers: [\n    containerTemplate(\n        name: 'jnlp', \n        image: 'jenkins\/inbound-agent:latest'\n        )\n  ]) {\n\n    node(POD_LABEL) {\n        stage('Get a Maven project') {\n            container('jnlp') {\n                stage('Shell Execution') {\n                    sh '''\n                    echo \"Hello! I am executing shell\"\n                    '''\n                }\n            }\n        }\n\n    }\n}<\/code><\/pre>\n<h2 id=\"multi-container-pod-template\">Multi Container Pod Template<\/h2>\n<p>You can use multiple container templates in a single POD template.<\/p>\n<p>Here is a use case of this setup.<\/p>\n<p>Let&#8217;s say you want to set up a single build pipeline that builds both Java and Python projects. In this case, you can use two container templates in the build stages.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/multi-container-build-pod-1.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"1200\" height=\"1200\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/multi-container-build-pod-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/multi-container-build-pod-1.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/multi-container-build-pod-1.png 1200w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>In the following example, in two separate stages, we are calling two different containers specified in the pod template.<\/p>\n<p>One container contains all the maven dependencies for Java build, and another contains Python build dependencies.<\/p>\n<pre><code>podTemplate(containers: [\n    containerTemplate(\n        name: 'maven', \n        image: 'maven:3.8.1-jdk-8', \n        command: 'sleep', \n        args: '30d'\n        ),\n    containerTemplate(\n        name: 'python', \n        image: 'python:latest', \n        command: 'sleep', \n        args: '30d')\n  ]) {\n\n    node(POD_LABEL) {\n        stage('Get a Maven project') {\n            git 'https:\/\/github.com\/spring-projects\/spring-petclinic.git'\n            container('maven') {\n                stage('Build a Maven project') {\n                    sh '''\n                    echo \"maven build\"\n                    '''\n                }\n            }\n        }\n\n        stage('Get a Python Project') {\n            git url: 'https:\/\/github.com\/hashicorp\/terraform.git', branch: 'main'\n            container('python') {\n                stage('Build a Go project') {\n                    sh '''\n                    echo \"Go Build\"\n                    '''\n                }\n            }\n        }\n\n    }\n}<\/code><\/pre>\n<p>You can try building the above Jenkinsfile using the pipeline job.<\/p>\n<p>While building the above pipeline, if you check the Kubernetes pods, you will see three containers in the build agent pod, as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-18-25.png\" class=\"kg-image\" alt=\"Kubernetes Jenkins plugin with multi container pod build.\" loading=\"lazy\" width=\"632\" height=\"233\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-18-25.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-18-25.png 632w\"><\/figure>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\">Note: You cannot use the Docker hub images directly due to security compliance issues in actual projects. So you have to build your own Docker images and host them in the organization-approved container registry.<\/div>\n<\/div>\n<h2 id=\"using-shared-persistent-volumes-with-jenkins-docker-agent-pods\">Using Shared Persistent Volumes With Jenkins Docker Agent Pods<\/h2>\n<p>It is better to attach a shared persistent volume to the build container to speed up the build process.<\/p>\n<p>For example, if you take a Java application, it has many Maven package dependencies.<\/p>\n<p>When you build the Java apps, it downloads dependencies added in the pom.xml from the remote maven repository the first time, and it creates a local .m2 cache directory where the dependent packages are cached.<\/p>\n<p>The <code>.m2<\/code> cache is impossible in Docker agent-based builds as it gets destroyed after the build.<\/p>\n<p>To solve this issue, we can create a persistent volume for the maven cache and attach it to the agent pod via the container template.<\/p>\n<p>To demonstrate this, first, let&#8217;s create a PVC<\/p>\n<pre><code>apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: maven-repo-storage\n  namespace: devops-tools\nspec:\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 50Gi<\/code><\/pre>\n<p>Here is an example <code>Jenkinsfile<\/code> with a POD template that uses the <code>maven-repo-storage<\/code> persistent volume.<\/p>\n<pre><code>podTemplate(containers: [\n  containerTemplate(\n      name: 'maven', \n      image: 'maven:latest', \n      command: 'sleep', \n      args: '99d'\n      )\n  ], \n  \n  volumes: [\n  persistentVolumeClaim(\n      mountPath: '\/root\/.m2\/repository', \n      claimName: 'maven-repo-storage', \n      readOnly: false\n      )\n  ]) \n\n{\n  node(POD_LABEL) {\n    stage('Build Petclinic Java App') {\n      git url: 'https:\/\/github.com\/spring-projects\/spring-petclinic.git', branch: 'main'\n      container('maven') {\n        sh 'mvn -B -ntp clean package -DskipTests'\n      }\n    }\n  }\n}<\/code><\/pre>\n<h2 id=\"building-docker-images-on-kubernetes-cluster\">Building Docker Images On Kubernetes Cluster<\/h2>\n<p>If you are using Docker to deploy applications, you can integrate your CI Docker build pipeline with Kubernetes agents.<\/p>\n<p>There are a few ways to <a href=\"https:\/\/devopscube.com\/run-docker-in-docker\/\">run Docker on Docker<\/a> for build use cases. However, since Kubernetes removed Docker&nbsp;runtimes, it is better to use alternative solutions.<\/p>\n<p>For now, the best way to build docker images on the Kubernetes cluster is using <a href=\"https:\/\/github.com\/GoogleContainerTools\/kaniko?ref=devopscube.com\" rel=\"noreferrer noopener\">Kaniko<\/a><\/p>\n<p>Refer <a href=\"https:\/\/devopscube.com\/build-docker-image-kubernetes-pod\/\" rel=\"noreferrer noopener\">building docker image using kaniko <\/a>to learn more about kaniko build pipeline using Jenkins pipeline.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>If you are using Jenkins &amp; Kubernetes, you should definitely try out the container-based agents.<\/p>\n<p>Scaling your Jenkins agents on Kubernetes helps you avoid the administrative overhead associated with static build VMs. Even though dynamic VM build options are available, each build could take a long time compared to dynamic container agents.<\/p>\n<p>You don&#8217;t have to worry about running out of resources for Jenkins builds.<\/p>\n<p>Do give it a try, and let me know if you face any issues.<\/p>\n<p>Also, check out my comprehensive list of <a href=\"https:\/\/devopscube.com\/jenkins-2-tutorials-getting-started-guide\/\" rel=\"noreferrer noopener\">Jenkins tutorials for beginners<\/a><\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/jenkins-build-agents-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Setup Jenkins  Build Agents on Kubernetes Pods \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/jenkins-build-agents-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":951,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-950","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/950","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=950"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/950\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/951"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=950"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=950"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=950"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}