Ravindra BagaleCourses & study guides मराठी

Chapter 66: Docker and Kubernetes Together: Dockerfile to Registry to Cluster, Jenkins and Kompose

66.4 Jenkins pipeline: build, test, push and deploy to Kubernetes

Why: the problem

Doing 66.1 to 66.3 by hand is fine once. On a team, every merge to main must repeat the same steps the same way: run tests, build the image, tag it with the commit, scan it, push it, update the Deployment and wait for the rollout. Jenkins is still the most common CI server in Indian enterprises, banks and IT services companies, so knowing how a Jenkinsfile drives Docker and kubectl is a very practical skill even if your next job uses GitHub Actions or GitLab CI. A declarative Jenkinsfile lives in the repository next to the Dockerfile. It has an agent, an environment block, stages and post actions. Credentials never appear in the file: they live in the Jenkins credentials store and are injected only inside withCredentials or the credentials() helper, which also masks them in the console log. The registry login uses a username and token credential; the cluster access uses a kubeconfig file credential for a ServiceAccount that can only update Deployments in one namespace, never cluster-admin. Where do builds run? The classic setup is a Jenkins agent VM with Docker installed. When Jenkins itself runs on Kubernetes, the Kubernetes plugin starts a fresh agent pod per build, with containers for python, kaniko or buildkit and kubectl. Mounting the host Docker socket into a pod gives that build root on the node, so in-cluster builds use rootless BuildKit or Kaniko instead. In this lesson we write a Jenkinsfile for the orders app, set up a least-privilege deployer ServiceAccount and kubeconfig, add credentials, and then write a Kubernetes-agent version that builds with Kaniko. Pushing to main produces a new tag, a rollout and an automatic rollback if the rollout fails.

What: every term in plain English

  • Jenkinsfile — Pipeline as code stored in the repository.
  • Declarative pipeline — pipeline { agent; environment; stages; post } syntax.
  • Agent — Machine or pod that runs the build steps.
  • Credentials store — Encrypted Jenkins store for tokens, passwords and kubeconfig files.
  • withCredentials — Injects a credential into a block as environment variables or a file, masked in logs.
  • GIT_COMMIT — Environment variable Jenkins sets to the commit being built.
  • Kubernetes plugin — Runs each build in a temporary agent pod.
  • Kaniko — Builds images inside a container without a Docker daemon.
  • Deployer ServiceAccount — Kubernetes identity allowed only to patch Deployments in one namespace.
  • post { failure } — Steps that run when the pipeline fails, such as rollback and alerts.
Create a least-privilege deployer ServiceAccount. Build a kubeconfig for it. Add Jenkins credentials. Write the Jenkinsfile stages. Add rollout wait and rollback. Run the pipeline. Switch to a Kubernetes agent with Kaniko.
Every merge becomes a tested, tagged, rolled-out image

How: step by step

  1. Create a least-privilege deployer ServiceAccount. RBAC ready.
  2. Build a kubeconfig for it. File credential.
  3. Add Jenkins credentials. registry-creds and kubeconfig-orders.
  4. Write the Jenkinsfile stages. Test, build, push, deploy.
  5. Add rollout wait and rollback. Safe deploy.
  6. Run the pipeline. Green build.
  7. Switch to a Kubernetes agent with Kaniko. No Docker socket.
  8. Review logs for masked secrets. Asterisks only.

Example 1: Least-privilege deployer and its kubeconfig

Goal. Least-privilege deployer and its kubeconfig.

deployer.yaml

apiVersion: v1
kind: ServiceAccount
metadata: {name: jenkins-deployer, namespace: orders}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: deployer, namespace: orders}
rules:
  - apiGroups: [apps]
    resources: [deployments]
    verbs: [get, list, watch, patch, update]
  - apiGroups: [apps]
    resources: [replicasets]
    verbs: [get, list, watch]
  - apiGroups: [""]
    resources: [pods]
    verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: jenkins-deployer, namespace: orders}
subjects: [{kind: ServiceAccount, name: jenkins-deployer, namespace: orders}]
roleRef: {kind: Role, name: deployer, apiGroup: rbac.authorization.k8s.io}

kubeconfig.sh

kubectl apply -f deployer.yaml
TOKEN=$(kubectl -n orders create token jenkins-deployer --duration=24h)
SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca.crt
kubectl --kubeconfig=jenkins.kubeconfig config set-cluster ch66 --server=$SERVER --certificate-authority=ca.crt --embed-certs=true
kubectl --kubeconfig=jenkins.kubeconfig config set-credentials deployer --token=$TOKEN
kubectl --kubeconfig=jenkins.kubeconfig config set-context ci --cluster=ch66 --user=deployer --namespace=orders
kubectl --kubeconfig=jenkins.kubeconfig config use-context ci
kubectl --kubeconfig=jenkins.kubeconfig auth can-i patch deployments
kubectl --kubeconfig=jenkins.kubeconfig auth can-i delete namespaces
chmod 600 jenkins.kubeconfig; rm -f ca.crt

What each line does

  • Role — Only patch Deployments and read pods in orders.
  • create token --duration — Short-lived token; rotate it, do not create long-lived Secret tokens.
  • auth can-i — Proves the limits before Jenkins uses the file.
  • chmod 600 — File readable only by you until it is uploaded as a Jenkins file credential.
  • rm — Delete local copies after upload.

Green output

$ bash kubeconfig.sh
serviceaccount/jenkins-deployer created
role.rbac.authorization.k8s.io/deployer created
rolebinding.rbac.authorization.k8s.io/jenkins-deployer created
Switched to context "ci".
yes
no

Expected result. Jenkins gets a kubeconfig that can update orders Deployments and nothing else. Upload it under Manage Jenkins, Credentials, as a Secret file with ID kubeconfig-orders.

Example 2: The Jenkinsfile

Goal. The Jenkinsfile.

Jenkinsfile

pipeline {
  agent { label 'docker' }
  options { timeout(time: 20, unit: 'MINUTES'); disableConcurrentBuilds() }
  environment {
    REGISTRY = 'registry.example.com'
    IMAGE    = "${REGISTRY}/orders"
    TAG      = "1.0.${BUILD_NUMBER}-${GIT_COMMIT.take(7)}"
  }
  stages {
    stage('Test') {
      steps { sh 'python3 -m venv .v && . .v/bin/activate && pip install -q -r requirements.txt pytest && pytest -q' }
    }
    stage('Build') {
      steps { sh 'docker build --pull -t $IMAGE:$TAG .' }
    }
    stage('Scan') {
      steps { sh 'trivy image --exit-code 1 --severity CRITICAL $IMAGE:$TAG' }
    }
    stage('Push') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'RU', passwordVariable: 'RP')]) {
          sh 'echo "$RP" | docker login $REGISTRY -u "$RU" --password-stdin && docker push $IMAGE:$TAG'
        }
      }
    }
    stage('Deploy') {
      when { branch 'main' }
      steps {
        withCredentials([file(credentialsId: 'kubeconfig-orders', variable: 'KUBECONFIG')]) {
          sh 'kubectl set image deploy/orders orders=$IMAGE:$TAG'
          sh 'kubectl annotate deploy/orders kubernetes.io/change-cause="jenkins $TAG" --overwrite'
          sh 'kubectl rollout status deploy/orders --timeout=180s'
        }
      }
    }
  }
  post {
    failure {
      withCredentials([file(credentialsId: 'kubeconfig-orders', variable: 'KUBECONFIG')]) {
        sh 'kubectl rollout undo deploy/orders || true'
      }
    }
    always { sh 'docker logout $REGISTRY || true; docker image rm $IMAGE:$TAG || true' }
  }
}

What each line does

  • TAG — Build number plus short commit: unique and traceable.
  • Single quotes in sh — The shell expands secrets, not Groovy, so they are not interpolated into the log.
  • --password-stdin — Token never appears on the command line.
  • when branch main — Feature branches test and push but do not deploy.
  • post failure — Automatic rollback if rollout status times out.

Green output

[Pipeline] stage (Test)
4 passed in 0.31s
[Pipeline] stage (Build)
naming to registry.example.com/orders:1.0.42-a1b2c3d
[Pipeline] stage (Scan)
Total: 0 (CRITICAL: 0)
[Pipeline] stage (Push)
+ docker login registry.example.com -u **** --password-stdin
Login Succeeded
[Pipeline] stage (Deploy)
deployment.apps/orders image updated
deployment "orders" successfully rolled out
Finished: SUCCESS

Expected result. A merge to main tests, builds, scans, pushes and rolls out with masked credentials and an automatic rollback path.

Example 3: Kubernetes agent with Kaniko, no Docker socket

Goal. Kubernetes agent with Kaniko, no Docker socket.

Jenkinsfile.k8s

pipeline {
  agent {
    kubernetes {
      yaml '''
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: jenkins-agent
  containers:
    - name: python
      image: python:3.12-slim
      command: [sleep]
      args: [infinity]
    - name: kaniko
      image: gcr.io/kaniko-project/executor:debug
      command: [sleep]
      args: [infinity]
      volumeMounts: [{name: regcred, mountPath: /kaniko/.docker}]
    - name: kubectl
      image: bitnami/kubectl:1.34
      command: [sleep]
      args: [infinity]
  volumes:
    - name: regcred
      secret:
        secretName: regcred
        items: [{key: .dockerconfigjson, path: config.json}]
'''
    }
  }
  environment { IMAGE = 'registry.example.com/orders'; TAG = "1.0.${BUILD_NUMBER}-${GIT_COMMIT.take(7)}" }
  stages {
    stage('Test')  { steps { container('python')  { sh 'pip install -q -r requirements.txt pytest && pytest -q' } } }
    stage('Build and push') { steps { container('kaniko') { sh '/kaniko/executor --context . --destination $IMAGE:$TAG' } } }
    stage('Deploy') { steps { container('kubectl') { sh 'kubectl -n orders set image deploy/orders orders=$IMAGE:$TAG && kubectl -n orders rollout status deploy/orders' } } }
  }
}

What each line does

  • kubernetes agent — A fresh pod per build, deleted afterwards.
  • kaniko — Builds and pushes without a Docker daemon or privileged mode.
  • regcred volume — The docker-registry Secret from 66.3 mounted as Kaniko's config.json.
  • jenkins-agent SA — Bound to the same deployer Role, so the pod needs no kubeconfig file.
  • container() — Runs each step in the right container of the agent pod.

Green output

Created Pod: jenkins/orders-main-42-xk2lp
[Pipeline] container (python)
4 passed
[Pipeline] container (kaniko)
INFO Pushing image to registry.example.com/orders:1.0.42-a1b2c3d
INFO Pushed registry.example.com/orders@sha256:3e9a...
[Pipeline] container (kubectl)
deployment "orders" successfully rolled out
Finished: SUCCESS

Expected result. The same pipeline runs inside the cluster with no Docker socket, no privileged pod and no kubeconfig file.

Every command, step by step (kind lab with kubectl)

  1. RBAC

    kubectl apply -f deployer.yaml
    

    What it does: Deployer.

    role.rbac.authorization.k8s.io/deployer created
    
  2. Token

    kubectl -n orders create token jenkins-deployer --duration=24h
    

    What it does: Short-lived.

    eyJhbGciOi... (redacted)
    
  3. can-i

    kubectl --kubeconfig=jenkins.kubeconfig auth can-i patch deployments
    

    What it does: Allowed.

    yes
    
  4. can-i no

    kubectl --kubeconfig=jenkins.kubeconfig auth can-i delete namespaces
    

    What it does: Denied.

    no
    
  5. Credential

    Manage Jenkins, Credentials, Secret file kubeconfig-orders
    

    What it does: Stored.

    Credential created
    
  6. Registry

    Username with password registry-creds
    

    What it does: Stored.

    Credential created
    
  7. Pipeline

    New Item, Multibranch Pipeline, point to repo
    

    What it does: Job.

    Scan Multibranch Pipeline Triggered
    
  8. Build

    Build Now on main
    

    What it does: Green.

    Finished: SUCCESS
    
  9. History

    kubectl -n orders rollout history deploy/orders
    

    What it does: Revision.

    3   jenkins 1.0.42-a1b2c3d
    
  10. Fail test

    Push a broken readiness path
    

    What it does: Rollback.

    error: timed out waiting ... rolled back
    

When it is red 1

docker: permission denied while trying to connect to the Docker daemon socket

Cause. Jenkins user not allowed to use Docker on the agent.

Fix, step by step

  1. Add the jenkins user to the docker group on that agent VM, or use Kaniko or BuildKit.

When it is red 2

Error from server (Forbidden): deployments.apps "orders" is forbidden

Cause. Deployer Role missing a verb or wrong namespace.

Fix, step by step

  1. kubectl auth can-i with the same kubeconfig; fix the Role.

When it is red 3

Warning: A secret was passed to "sh" using Groovy String interpolation

Cause. Double quotes put the secret into the Groovy string.

Fix, step by step

  1. Use single quotes so the shell expands it.

When it is red 4

error: timed out waiting for the condition

Cause. New pods never became ready.

Fix, step by step

  1. Check probes and logs; post failure rolls back.

When it is red 5

kaniko: UNAUTHORIZED

Cause. config.json missing or for the wrong registry.

Fix, step by step

  1. Mount the docker-registry Secret as /kaniko/.docker/config.json.

Practice tasks

Easy Task 1: RBAC

Why: Deployer.

What to do: Apply deployer.yaml.

How:
  1. Role and binding.
Solution
kubectl -n orders get role,rolebinding

Easy Task 2: Kubeconfig

Why: Build.

What to do: Run kubeconfig.sh.

How:
  1. can-i yes/no.
Solution
kubectl --kubeconfig=jenkins.kubeconfig get deploy

Easy Task 3: Credentials

Why: Store.

What to do: Add both credentials.

How:
  1. IDs match.
Solution
Manage Jenkins, Credentials

Medium Task 4: Jenkinsfile

Why: Write.

What to do: Five stages.

How:
  1. when main.
Solution
Pipeline Syntax, Validate

Medium Task 5: Run

Why: Build.

What to do: Build main.

How:
  1. Masked.
Solution
Console Output

Medium Task 6: Rollback

Why: Fail.

What to do: Break readiness.

How:
  1. post failure.
Solution
kubectl -n orders rollout history deploy/orders

Hard Task 7: Kaniko

Why: Agent pod.

What to do: Jenkinsfile.k8s.

How:
  1. regcred.
Solution
kubectl -n jenkins get pods

Hard Task 8: HDFC

Why: Main task.

What to do: statements pipeline.

How:
  1. Least privilege.
Solution
kubectl -n statements rollout history deploy/statements

Homework: try at home

For HDFC's statements service, create a deployer ServiceAccount limited to namespace statements, store its kubeconfig and the registry token as Jenkins credentials, and write a Jenkinsfile that tests, builds, scans, pushes a build-number-plus-commit tag, deploys only from main and rolls back on failure.

Recap

  • A Jenkinsfile in the repo repeats test, build, scan, push and deploy identically on every merge.
  • Credentials stay in Jenkins and are injected and masked with withCredentials.
  • The deployer identity can only update Deployments in one namespace.
  • Wait for rollout status and roll back in post failure.
  • Inside Kubernetes, build with Kaniko or BuildKit instead of the Docker socket.

FAQ

Q. Should Jenkins deploy directly or update Git for Argo CD?

Both work. Many teams let Jenkins build and push, then commit the new tag to a GitOps repo so Argo CD deploys, as in ch64.

Q. How do I trigger builds on push?

A webhook from your Git server to the Multibranch Pipeline job, or polling as a fallback.

Q. Can Jenkins use IAM instead of tokens on EKS?

Yes. Run agents on EKS with EKS Pod Identity or IRSA so they get ECR and EKS access without stored keys.

Q. How do I keep the Jenkins controller safe?

Run no builds on the controller, keep plugins updated, use RBAC in Jenkins, and back up JENKINS_HOME.

Interview questions

  1. Walk through your CI/CD pipeline for a containerised app.

    Model answer: Test, build, scan, push immutable tag, deploy with rollout wait, roll back on failure.

  2. How do you secure credentials in Jenkins?

    Model answer: Credentials store, withCredentials, single-quoted shell, least-privilege identities, short-lived tokens.

  3. Why not mount docker.sock in a build pod?

    Model answer: It gives root on the node; use Kaniko or rootless BuildKit.

  4. How do you trace a running pod to a build?

    Model answer: Tag and change-cause contain build number and commit.

Quick quiz

Q1. Where do credentials live?

Jenkins credentials store, injected with withCredentials.

Q2. Why single quotes in sh steps?

So secrets are not interpolated into Groovy and logged.

Q3. What should the deployer be allowed to do?

Patch Deployments in one namespace only.

Q4. Why Kaniko inside Kubernetes?

No Docker socket or privileged pod.

Q5. What runs in post failure?

kubectl rollout undo.

Samajla ka?