Getting Started with Probes in Kubernetes

Rupinder SinghRupinder Singh
3 min read

Kubernetes offers three types of probes that you can configure on a container to improve application reliability and traffic management:

  • Liveness Probe: The liveness probe checks whether a container is running properly. If it fails, Kubernetes assumes the container is stuck or broken and will automatically restart it. This helps applications recover from unrecoverable failures without human intervention.

  • Readiness Probe: The readiness probe determines whether a container is ready to handle incoming requests. If the probe fails, Kubernetes will temporarily remove the container from the service's endpoints, effectively pausing traffic to it, but without stopping or restarting the container itself.

  • Startup Probe: For applications that need extra time to initialize, the startup probe offers a better alternative to the liveness probe during startup. It allows Kubernetes to give the container sufficient time to become ready, avoiding premature restarts and unnecessary failures during the boot process.

Usage by example

Golang Health App

Let's now look at an example of how you can configure HTTP health endpoints in a Golang application and how we can specify probes on a Kubernetes pod container.

./main.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync/atomic"
    "time"
)

var (
    hasStarted atomic.Bool
    isReady    atomic.Bool
)

func startApp() {
    log.Println("Initializing application")
    // simulate initialization delay
    time.Sleep(time.Second * 10)
    hasStarted.Store(true)
    isReady.Store(true)
    log.Println("Successfully initialized application, " +
        "ready to receive traffic")
}

func main() {
    // start main application
    go startApp()

    // setup liveness HTTP endpoint
    http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
        if hasStarted.Load() {
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("STATUS OK"))
        } else {
            w.WriteHeader(http.StatusServiceUnavailable)
            w.Write([]byte("APPLICATION NOT STARTED"))
        }
    })

    // setup readiness HTTP endpoint
    http.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
        if isReady.Load() {
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("STATUS OK"))
        } else {
            w.WriteHeader(http.StatusServiceUnavailable)
            w.Write([]byte("APPLICATION NOT READY"))
        }
    })

    listenPort := 8080

    log.Printf("Starting HTTP server on port: %d\n", listenPort)
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", listenPort), nil))
}

Docker image

Let’s use a simple Dockerfile to run our Go application in Kubernetes. (This is not recommended in production.)

./dockerfile

FROM golang:1.24

COPY main.go main.go

CMD ["go", "run", "main.go"]

To build Docker image run,

$ docker build -t docker.io/health_check_app:latest -f dockerfile .

Deploying in Kubernetes

./kubernetes.yaml

apiVersion: v1
kind: Pod
metadata:
  name: health-app
  labels:
    app: health-app
spec:
  containers:
  - name: main
    image: docker.io/health_check_app:latest
    ports:
    - containerPort: 8080
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 3
    readinessProbe:
      httpGet:
        path: /readyz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 2
      failureThreshold: 3

Here,

  • initialDelaySeconds is delay before the probe starts running.

  • periodSeconds is how often to perform the probe.

  • timeoutSeconds is how long to wait for a response.

  • failureThreshold is how many times to fail before marking the container as unhealthy/unready.

Bonus

The publishNotReadyAddresses field in a Kubernetes Service allows the service to include the IPs of Pods that are not yet marked as Ready, which is especially useful in StatefulSet peer discovery scenarios. In distributed systems like Cassandra or custom peer-to-peer apps, a Pod often needs to discover and connect to other peers before it can be considered Ready. By enabling this field on a headless service (clusterIP: None), all Pods—including those still starting up—are included in DNS resolution, allowing early peer communication and successful bootstrapping before the readiness condition is met.

0
Subscribe to my newsletter

Read articles from Rupinder Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rupinder Singh
Rupinder Singh