Part 6 – The kubelet

How Kubernetes Brings Your Spring Boot Application to Life

In the previous article, we left our Payment Service in an interesting state.

The Scheduler had successfully completed its work.

After evaluating CPU, memory, affinity rules, taints, topology constraints, and available capacity, it selected the most suitable worker node for each Pod.

Our cluster now looked like this.

                     Control Plane

Deployment
      │
      ▼
ReplicaSet
      │
      ▼
Payment Pod-1 ─────────────► Worker-3
Payment Pod-2 ─────────────► Worker-5
Payment Pod-3 ─────────────► Worker-1

At first glance, it appears that deployment is almost complete.

But if you logged into Worker-3 at this exact moment, you would discover something surprising.

There is still no running Java application.

No Docker container.

No Spring Boot banner.

No embedded Tomcat.

No REST endpoint listening on port 8080.

The Scheduler has merely answered one question:

“Where should this Pod run?”

It has not answered the next, equally important question:

“How do we actually start it?”

That responsibility belongs to a component that runs quietly on every single worker node.

Unlike the API Server or Scheduler, which live in the control plane, this component lives where the real work happens.

Meet the kubelet.


The Most Important Kubernetes Component You’ve Probably Never Logged Into

Ask developers about Kubernetes, and you’ll often hear discussions about:

  • Deployments
  • Pods
  • Services
  • Ingress
  • Helm
  • ConfigMaps

Far fewer people talk about the kubelet.

Yet, without the kubelet, Kubernetes would simply be a distributed database storing YAML files.

Nothing would ever run.

The kubelet is the component that transforms a Pod specification into a living application.

It is responsible for:

  • Watching Pods assigned to its node
  • Pulling container images
  • Creating containers
  • Mounting volumes
  • Injecting environment variables
  • Executing health probes
  • Restarting failed containers
  • Reporting status back to the API Server

If the Scheduler is the planner, the kubelet is the engineer that builds the plan.


One kubelet Per Worker Node

Every worker node runs exactly one kubelet process.

Imagine our banking cluster.

                    Control Plane
                API Server / Scheduler

                        ▲
                        │
      ──────────────────┼──────────────────
                        │
        Reports Status  │   Receives Pod Assignments

┌──────────────────────────────────────────────────────┐
│                    Worker Node 1                     │
│                                                      │
│                  kubelet                             │
│                      │                               │
│                 containerd / CRI-O                   │
│                      │                               │
│                 Linux Containers                     │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│                    Worker Node 2                     │
│                                                      │
│                  kubelet                             │
│                      │                               │
│                 containerd / CRI-O                   │
│                      │                               │
│                 Linux Containers                     │
└──────────────────────────────────────────────────────┘

The control plane never starts containers directly.

Instead, it delegates execution to the kubelet running on the selected worker node.

This architecture allows Kubernetes clusters to scale to thousands of nodes without the control plane becoming a bottleneck.


Worker-3 Receives a New Assignment

Let’s return to Sarah’s Payment Service.

The Scheduler has bound one Pod to Worker-3.

The kubelet on Worker-3 continuously watches the API Server for changes affecting its node.

Suddenly it notices a new assignment.

Conceptually, it receives something similar to:

Pod

payment-service-a84fd

Node

worker-3

The kubelet immediately understands:

“This Pod is my responsibility.”

From this point onward, the control plane steps back.

The kubelet owns the lifecycle of this Pod.


The kubelet Reads the Pod Specification

The kubelet doesn’t receive a Docker command.

It receives a complete Pod specification.

spec:

  containers:

    - name: payment-service

      image: bank/payment-service:2.4.1

      ports:

      - containerPort: 8080

      env:

      - name: SPRING_PROFILES_ACTIVE

        value: production

Notice how much information is available.

The kubelet now knows:

  • Which image to run
  • Which environment variables to inject
  • Which ports to expose
  • Which volumes to mount
  • Which Secrets to load
  • Which ConfigMaps to inject
  • Which resource limits apply
  • Which health probes to configure

Everything needed to launch the application is already present.

The kubelet simply needs to execute the specification.


The kubelet Doesn’t Run Containers

Here’s another misconception.

Many developers assume the kubelet itself starts Docker containers.

It doesn’t.

The kubelet delegates container management to a Container Runtime.

Modern Kubernetes clusters typically use:

  • containerd
  • CRI-O

Older clusters often used Docker, but Kubernetes no longer communicates with Docker directly.

Instead, Kubernetes talks through a standard interface known as the Container Runtime Interface (CRI).

Conceptually:

        kubelet

           │

           ▼

 Container Runtime Interface (CRI)

           │

           ▼

     containerd / CRI-O

           │

           ▼

   Linux Containers

This abstraction allows Kubernetes to support multiple container runtimes without changing kubelet logic.


Step 1 – Does the Image Already Exist?

Before creating the container, the runtime checks its local image cache.

Worker-3 asks:

“Do I already have bank/payment-service:2.4.1?”

If the answer is yes, the deployment becomes significantly faster.

If not, the image must be downloaded from the enterprise container registry.

For example:

registry.bank.internal

└── payment-service

      └── 2.4.1

Only after the image has been successfully retrieved can container creation begin.

This is why the very first deployment of a new version is often slower than subsequent deployments on the same node.


Image Pull Policies

Kubernetes controls this behavior using the imagePullPolicy.

containers:

- image: bank/payment-service:2.4.1

  imagePullPolicy: IfNotPresent

Common options include:

PolicyBehaviour
AlwaysAlways download the latest image
IfNotPresentUse local cache if available
NeverExpect image to already exist

For production systems using versioned images such as:

2.4.1
2.4.2
2.5.0

IfNotPresent is usually the preferred option because it reduces deployment time while ensuring immutable image versions.

Using mutable tags like latest in production is generally discouraged because it makes deployments unpredictable and harder to audit.


What If the Image Can’t Be Downloaded?

Suppose Sarah accidentally references:

image:

bank/payment-service:99.99.99

The runtime contacts the registry.

The registry responds:

404

Image Not Found

The kubelet reports the failure back to the API Server.

The Pod status changes to:

ErrImagePull

After several retries:

ImagePullBackOff

This is one of the most common deployment failures in enterprise environments.

The kubelet keeps retrying with an exponential backoff, assuming the issue might be temporary—for example, a transient network outage or a registry becoming available again.


Step 2 – Creating the Pod Sandbox

Once the image is available, Kubernetes creates something many developers never hear about:

The Pod Sandbox.

Remember:

A Pod is not a container.

A Pod is an execution environment that can contain one or more containers.

The sandbox provides shared resources such as:

  • Network namespace
  • IP address
  • Shared storage volumes
  • IPC namespace
  • Linux hostname

Conceptually:

                Pod Sandbox

       ┌──────────────────────────┐

        Shared Network

        Shared IP Address

        Shared Volumes

        Shared Namespaces

       └──────────────────────────┘

             │             │

             ▼             ▼

      Spring Boot      Log Sidecar
       Container        Container

Even if your Pod contains only a single Spring Boot container, Kubernetes still creates this sandbox first.

It’s the foundation upon which the containers will run.


Step 3 – Creating the Container

Now the runtime creates the actual container.

At this point it:

  • Unpacks the image layers
  • Creates the writable container layer
  • Applies CPU limits
  • Applies memory limits
  • Configures Linux namespaces
  • Configures cgroups
  • Mounts volumes
  • Injects environment variables
  • Starts the container process

Notice that Kubernetes isn’t running Java directly.

It starts the container’s entrypoint.

For a Spring Boot application, that might look like:

ENTRYPOINT ["java","-jar","payment-service.jar"]

Only now does Linux finally launch the Java Virtual Machine.


The JVM Finally Starts

For the first time since Sarah executed kubectl apply, something familiar begins to happen.

Inside the container, the JVM starts.

Spring Boot begins its initialization sequence.

Loading Spring Context...

Scanning Components...

Creating Beans...

Initializing Embedded Server...

Connecting to Oracle...

Connecting to Redis...

Connecting to Solace...

Eventually, the familiar startup banner appears in the logs.

Started PaymentServiceApplication

Many developers instinctively celebrate at this point.

The application has started.

The Pod must be ready.

Not quite.

A running JVM is only one milestone in the deployment lifecycle.

The application may still be:

  • Performing database migrations
  • Warming Redis caches
  • Loading reference data
  • Registering with service discovery
  • Establishing messaging connections
  • Initializing thread pools

Sending customer traffic too early could result in failed requests.

This is why Kubernetes introduces another critical concept: health probes.

In the next article, we’ll explore Startup, Liveness, and Readiness probes in depth and answer one of the most common production questions:

Why is my Pod in the Running state, but my application still isn’t receiving any traffic?

Understanding that distinction is essential for building truly resilient Spring Boot microservices.

Coming Next

Part 7 – Health Probes: Startup, Liveness & Readiness

We’ll cover:

  • Why Running does not mean Ready
  • Startup vs Liveness vs Readiness probes
  • Probe execution lifecycle
  • Spring Boot Actuator integration
  • Common probe configuration mistakes
  • CrashLoopBackOff explained
  • Zero-downtime deployments
  • Real banking scenarios
  • Production-ready probe configurations
  • Best practices and troubleshooting

Leave a Reply

Your email address will not be published. Required fields are marked *