{"id":1001,"date":"2024-07-22T04:14:38","date_gmt":"2024-07-22T04:14:38","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=1001"},"modified":"2024-07-22T04:14:38","modified_gmt":"2024-07-22T04:14:38","slug":"deploy-jenkins-as-statefulset-on-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=1001","title":{"rendered":"How To Deploy Jenkins as StatefulSet on Kubernetes"},"content":{"rendered":"<p>In this blog, I have given the step by step tutorial to deploy Jenkins as StatefulSet on the Kubernetes Cluster.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>Given below are the Prerequisites for this setup<\/p>\n<ol>\n<li><a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">Kubernetes Cluster<\/a><\/li>\n<li>kubectl installed in your system<\/li>\n<\/ol>\n<h2 id=\"deploy-jenkins-as-statefulset\">Deploy Jenkins as StatefulSet<\/h2>\n<p>The following image shows the setup architecture and workflow.<\/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-controller.gif\" class=\"kg-image\" alt=\"Jenkins deployment as statefulset on kubernetes\" loading=\"lazy\" width=\"750\" height=\"901\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/jenkins-controller.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jenkins-controller.gif 750w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Follow the below steps to deploy Jenkins as a StatefulSet.<\/p>\n<h3 id=\"step-1-create-a-namespace\">Step 1: Create a Namespace<\/h3>\n<p>The first step is to create a separate namespace to deploy Jenkins.<\/p>\n<p>Run the following command to create a namespace<\/p>\n<pre><code>kubectl create ns jenkins<\/code><\/pre>\n<h3 id=\"step-2-create-a-clusterrole-and-serviceaccount\">Step 2: Create a ClusterRole and ServiceAccount<\/h3>\n<p>After creating the namespace, the next step is to create a <strong>clusterrole<\/strong>, <strong>serviceaccount<\/strong> and bind the <strong>clusterrole<\/strong> to a service account.<\/p>\n<p>This service account grants Jenkins the necessary permissions to interact with the Kubernetes API.<\/p>\n<p>This is primarily required for CI\/CD tasks where Jenkins need to create, update, and manage resources within the cluster (e.g., pods, services, and deployments).<\/p>\n<p>Create a YAML file <strong>sa.yaml<\/strong> and copy the below contents<\/p>\n<pre><code>apiVersion: rbac.authorization.k8s.io\/v1\nkind: ClusterRole\nmetadata:\n  name: jenkins-admin\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"*\"]\n    verbs: [\"*\"]\n\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: jenkins-admin\n  namespace: jenkins\n\n---\napiVersion: rbac.authorization.k8s.io\/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: jenkins-admin\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: jenkins-admin\nsubjects:\n- kind: ServiceAccount\n  name: jenkins-admin\n  namespace: jenkins<\/code><\/pre>\n<p>This file creates a <strong>ServiceAccount<\/strong> and a <strong>ClusterRole<\/strong> with admin permission, you can limit the permissions as you want.<\/p>\n<p>Then it binds the ClusterRole with the ServiceAccount. Run the following command to create the service account<\/p>\n<pre><code>kubectl apply -f sa.yaml<\/code><\/pre>\n<h3 id=\"step-3-create-a-secret\">Step 3: Create a Secret<\/h3>\n<p>The next step is to create a Secret which creates a token for the serviceaccount.<\/p>\n<p>Create a YAML file <strong>secret.yaml<\/strong> and copy the below contents in to it<\/p>\n<pre><code>apiVersion: v1\nkind: Secret\nmetadata:\n  name: sa-token-secret\n  namespace: jenkins\n  annotations:\n    kubernetes.io\/service-account.name: jenkins-admin\ntype: kubernetes.io\/service-account-token<\/code><\/pre>\n<p>This creates a secret and links it with the service account using the annotation.<\/p>\n<p>The token in the secret is particularly useful when you use <a href=\"https:\/\/devopscube.com\/jenkins-build-agents-kubernetes\/\">Kubernetes pods and dynamic jenkins agents.<\/a><\/p>\n<h3 id=\"step-4-create-a-persistentvolumeclaim-pvc\">Step 4: Create a PersistentVolumeClaim (PVC)<\/h3>\n<p>Now, create a <strong>PersistentVolumeClaim<\/strong> for Jenkins.<\/p>\n<p>Create a YAML file <strong>pvc.yaml <\/strong>and copy the below contents<\/p>\n<pre><code>apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: jenkins-pv-claim\n  namespace: jenkins\nspec:\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 10Gi<\/code><\/pre>\n<p>This file creates a PVC which requests 1<strong>0GB<\/strong> of storage with <strong>readwriteonce<\/strong> access mode.<\/p>\n<p>Run the following command to create a PVC<\/p>\n<pre><code>kubectl apply -f pvc.yaml<\/code><\/pre>\n<h3 id=\"step-5-deploy-jenkins-as-satefulset\">Step 5: Deploy Jenkins as SatefulSet<\/h3>\n<p>Once the required resources for Jenkins are created, deploy Jenkins as a Statefulset on the cluster.<\/p>\n<p>Create a YAML file <strong>statefulset.yaml<\/strong> and copy the below contents into it<\/p>\n<pre><code>apiVersion: apps\/v1\nkind: StatefulSet\nmetadata:\n  name: jenkins\n  namespace: jenkins\nspec:\n  serviceName: \"jenkins\"\n  replicas: 1\n  selector:\n    matchLabels:\n      app: jenkins-server\n  template:\n    metadata:\n      labels:\n        app: jenkins-server\n    spec:\n      securityContext:\n        fsGroup: 1000 \n        runAsUser: 1000\n      serviceAccountName: jenkins-admin\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<\/code><\/pre>\n<p>This file deploys Jenkins on the Jenkins namespace with the following:<\/p>\n<ol>\n<li>As statefulset with one replica. We cannot run more than one replica becuase of the way Jenkins manages it data. Read the <a href=\"https:\/\/devopscube.com\/jenkins-architecture-explained\/\">Jenkins architecture<\/a> blog to know more.<\/li>\n<li>Uses a security context to run Jenkins as non-root user ID 1000. By specifying a non-root UID, you limit the potential security risk that can be caused by a compromised Jenkins container. Running as a non-root user <strong>reduces the risk of privilege escalation attacks.<\/strong><\/li>\n<li>Jenkins uses the serviceaccoun<strong>t<\/strong> <strong>jenkins-admin<\/strong> we created in <strong>step 2<\/strong>.<\/li>\n<li>Uses the latest container image<strong> jenkins\/jenkins:lts<\/strong><\/li>\n<li>Added resource limits and requests, as well as liveness and readiness probes.<\/li>\n<li>Then it exposes port <strong>8080<\/strong> for UI access and jnlp port <strong>50000<\/strong> to communicate with the agent.<\/li>\n<li>Jenkins stores its data on the mounted volume under the path <strong>\/var\/jenkins_home<\/strong> and it claims the volume using the PVC created in <strong>Step 3.<\/strong><\/li>\n<\/ol>\n<p>Run the following command to deploy Jenkins as statefulset<\/p>\n<pre><code>kubectl apply -f statefulset.yaml<\/code><\/pre>\n<h3 id=\"step-6-expose-jenkins\">Step 6: Expose Jenkins<\/h3>\n<p>The final step is to expose Jenkins as NodePort using a service so that we can access the Jenkins UI on the web.<\/p>\n<p>Create a YAML file <strong>sva.yaml <\/strong>and copy the below content into it<\/p>\n<pre><code>apiVersion: v1\nkind: Service\nmetadata:\n  name: jenkins-service\n  namespace: jenkins\nspec:\n  selector: \n    app: jenkins-server\n  type: NodePort\n  ports:\n    - name: http-port\n      port: 8080\n      targetPort: 8080\n      nodePort: 30000\n    - name: jnlp-port\n      port: 50000\n      targetPort: 50000<\/code><\/pre>\n<p>The j<strong>enkins-service<\/strong> service exposes the Jenkins port 8080 on <strong>NodePort 30000<\/strong> to access the Jenkins UI on the web.<\/p>\n<p>Run the following command to create the service<\/p>\n<pre><code>kubectl apply -f svc.yaml<\/code><\/pre>\n<blockquote><p><strong>Note:<\/strong> Exposing Jenkins as NodePort is not suitable for the production environment. In actual projects need to setup <a href=\"https:\/\/devopscube.com\/setup-ingress-kubernetes-nginx-controller\/\">Ingress<\/a> to access Jenkins.<\/p><\/blockquote>\n<p>Now access your Jenkins on the web using the following URL<\/p>\n<pre><code>http:\/\/&lt;node-ip&gt;:30000<\/code><\/pre>\n<p>After running this command Jenkins will as you to enter the <strong>initialAdminPassword<\/strong> 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-40-9.png\" class=\"kg-image\" alt=\"Jenkins initial Admin apssword\" loading=\"lazy\" width=\"1122\" height=\"568\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-40-9.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-40-9.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-40-9.png 1122w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>To get the admin password run the following command. Replace <code> jenkins-0<\/code> with your pod name.<\/p>\n<pre><code>kubectl exec -it jenkins-0 cat  \/var\/jenkins_home\/secrets\/initialAdminPassword -n jenkins<\/code><\/pre>\n<p>After entering the admin password, it will ask you to install the plugins, and create an admin user and you can see the Jenkins workspace after completing the steps.<\/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-41-8.png\" class=\"kg-image\" alt=\"Jenkins Dashboard\" loading=\"lazy\" width=\"1313\" height=\"649\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-41-8.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-41-8.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-41-8.png 1313w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>If you want to add additional plugins to your Jenkins, go to<\/p>\n<p><strong>Manage Jenkins-&gt;Plugins-&gt;Available plugins<\/strong> and search for the plugin you need.<\/p>\n<p>For example, in the below image, I am installing the Blue Ocean plugin on Jenkins, select the plugin and click the <strong>Install<\/strong> button to install it<\/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-42-10.png\" class=\"kg-image\" alt=\"Add and additional plugin on Jenkins\" loading=\"lazy\" width=\"1133\" height=\"507\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-42-10.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-42-10.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-42-10.png 1133w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>After installing the plugin you can see it on the left side of your Jenkins dashboard 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-43-11.png\" class=\"kg-image\" alt=\"Installed plugins on Jenkins\" loading=\"lazy\" width=\"965\" height=\"462\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-43-11.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-43-11.png 965w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Click the <strong>Open Blue Ocean<\/strong> button to open the Blue Ocean dashboard<\/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-44-11.png\" class=\"kg-image\" alt=\"Blue Ocean dashboard\" loading=\"lazy\" width=\"1027\" height=\"511\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-44-11.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-44-11.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-44-11.png 1027w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/deploy-jenkins-as-statefulset-on-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How To Deploy Jenkins as StatefulSet on Kubernetes \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/deploy-jenkins-as-statefulset-on-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":1002,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1001","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\/1001","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=1001"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/1001\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/1002"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1001"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1001"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1001"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}