Repair a Failing Go API on Kubernetes
Diagnose a Kubernetes readiness failure and restore a Go API service.
Introduction
30 Second Summary
An app can keep running while every real request is kept away from it. During an incident, that gap can make healthy code look broken.
In this project, you will build a Go status API inside a container image. You will use a local Kubernetes cluster to diagnose an intentional readiness probe failure before restoring traffic.
What You'll Build
Your browser will show status-api is running through a Kubernetes Service once the failed Pod is ready again.
By the end of this project, you'll have:
- A working Go API where /status returns status-api is running. The /healthz endpoint provides a separate health response.
- A portable container image that runs on your workstation. You can load the same image into the sre-lab cluster.
- An incident walkthrough that captures a Pod stuck at 0/1 readiness. You can trace the evidence to /readyz. You can repair the manifest with /healthz. The recovered Service then carries the API response to your browser.
- Secret Mission: Scale the recovered Deployment to three replicas. Delete one Pod. Kubernetes restores the desired state automatically.
Are there any prerequisites?
You need a supported 64-bit Windows device with at least 8 GB RAM. You also need enabled hardware virtualization, administrator access for the initial WSL setup, and an internet connection.
Before We Start
Before the hands-on work begins, this step locks in the incident you will investigate: a Kubernetes readiness probe pointed at the nonexistent /readyz path. Pod readiness, probe events, and EndpointSlice evidence will show why the running Go process stays ineligible for Service traffic.
Prepare the Windows Workstation
A readiness incident only teaches the right lesson when the workstation underneath it is dependable. Otherwise, a missing runtime can look like a Kubernetes failure.
Windows PowerShell gives you the command line for preparing the machine. WSL 2 provides the Linux foundation used by the container runtime.
Docker Desktop supplies the local container engine. Go compiles the status API with version 1.27.1.
Visual Studio Code gives the project a dedicated editing workspace.
In this step, get ready to:
- Confirm the Windows foundation for WSL 2.
- Prepare Docker Desktop for Linux containers.
- Prepare the coding workspace.
Check Windows and WSL 2
Docker Desktop uses WSL 2 to run Linux containers on Windows. The machine also needs enough memory plus hardware virtualization for that backend.
- Confirm your device runs a supported 64-bit Windows 10 or Windows 11 release in Windows system settings.
- Confirm the machine has at least 8 GB RAM in Windows system settings.
- Confirm hardware virtualization is enabled in your firmware or Windows system information.
- Press the Windows key to open Windows search.
- Type Windows PowerShell into the search field.
- Start Windows PowerShell with administrator privileges from the search result.
- Check the installed WSL version by running this command:
wsl --version
What does this command check?
The command reports whether WSL is available. It also shows the installed WSL version.
✔️ WSL meets the required version
Continue when the WSL version is 2.1.5 or higher. Your machine has the required WSL foundation.
ⓧ WSL shows an older version
An older WSL installation needs an update before Docker Desktop can rely on it.
- Update WSL by running these commands:
wsl --update
wsl --version
What do these commands do?
The first command updates WSL. The second command displays the resulting version.
Continue when the reported WSL version is 2.1.5 or higher.
ⓧ WSL is unavailable
Installing WSL changes a Windows system feature. Windows may request administrator approval during this process.
- Install WSL by running this command:
wsl --install
What does this command do?
The command enables the required Windows components. It also installs WSL.
- Restart Windows when the installation requests it.
- Return to an administrator Windows PowerShell session through Windows search.
- Update WSL after the restart by running these commands:
wsl --update
wsl --version
What happens after the restart?
The update brings WSL to the current supported release. The version check confirms that WSL is ready for Docker Desktop.
Continue when the reported WSL version is 2.1.5 or higher.
Having trouble with WSL?
- Confirm Windows PowerShell is running with administrator privileges before retrying the installation.
- Complete any pending Windows restart before checking the version again.
Still stuck? Help me prepare WSL 2 for Docker Desktop on Windows.
Your Windows foundation can now support the container runtime used throughout the incident lab.
Install Docker Desktop
The API eventually runs inside a container. Docker Desktop provides the client plus the Linux container engine that builds and runs that image locally.
- Check whether Docker Desktop is already available by running this command:
docker version
What does this command check?
The command asks the Docker client for local client details. It also requests server details from the running container engine.
✔️ I see client and server details
- Confirm Docker Desktop uses the WSL 2 backend in its settings.
- Confirm Docker Desktop is using Linux containers.
Docker Desktop is already installed. Its running server is ready to build Linux container images.
ⓧ I see client details only
The Docker client is installed. The missing server section means the container engine still needs to start.
- Press the Windows key to open Windows search.
- Type Docker Desktop into the search field.
- Press Enter to start Docker Desktop.
The first start can sit quietly while Docker Desktop prepares its backend. Give it a few minutes before treating the pause as a problem.
- Confirm Docker Desktop uses the WSL 2 backend in its settings.
- Keep Linux containers selected.
- Check the running engine by repeating this command:
docker version
What confirms Docker is running?
A working engine returns both client details and server details. The server section proves that the Docker client can reach the local container runtime.
ⓧ The Docker command is unavailable
The installer may request permission to change your device. The subscription terms can also look like a billing step.
This project assumes personal or educational use of Docker Desktop. That use is free under the applicable subscription terms.
- Download the Windows installer from the official Docker Desktop installation page.
- Run the installer using the recommended per-user installation.
The installer displays the backend choices after it starts.
- Keep the WSL 2 backend selected.
- Complete the Docker Desktop installation.
Windows finishes the per-user installation before Docker Desktop starts.
- Start Docker Desktop through Windows search.
- Accept the applicable subscription terms.
Docker Desktop prepares the Linux container engine after you accept the terms.
- Keep Linux containers selected.
- Confirm the running engine by repeating this command:
docker version
What confirms the installation?
The command returns a client section from the installed command-line tool. It returns a server section when Docker Desktop is running successfully.
Docker server details still missing?
- Wait until Docker Desktop finishes starting before repeating the version check.
- Confirm the WSL 2 backend remains selected in Docker Desktop.
- Confirm Docker Desktop is using Linux containers.
Need a hand? Help me troubleshoot Docker Desktop on Windows.
Install Go and Visual Studio Code
The Go version must match the project toolchain so the build stays reproducible. The editor also needs to expose its folder command to new PowerShell sessions.
- Check the installed Go version by running this command:
go version
What does this command check?
The command reports the active Go toolchain. This project uses Go 1.27.1.
✔️ Go reports the required version
Continue when the output contains go1.27.1. The required Go toolchain is ready.
ⓧ Go reports a different version
A different Go release can change the build environment. Install the pinned release before creating the API.
- Open the official Go downloads page.
- Choose go1.27.1.windows-amd64.msi for an AMD64 machine.
- Choose go1.27.1.windows-arm64.msi for an ARM64 machine.
- Run the installer that matches your machine architecture.
- Close the current Windows PowerShell session after installation.
- Start a new Windows PowerShell session through Windows search.
- Verify the new Go installation by running this command:
go version
Why use a new PowerShell session?
A new session picks up the path changes made by the installer. The version check should now contain go1.27.1.
ⓧ The Go command is unavailable
Go needs the Windows installer that matches your machine architecture.
- Open the official Go downloads page.
- Choose go1.27.1.windows-amd64.msi for an AMD64 machine.
- Choose go1.27.1.windows-arm64.msi for an ARM64 machine.
- Run the installer that matches your machine architecture.
- Close the current Windows PowerShell session after installation.
- Start a new Windows PowerShell session through Windows search.
- Verify the Go installation by running this command:
go version
What confirms Go is installed?
The new PowerShell session can find the installed Go executable. Its output should contain go1.27.1.
Go still shows the wrong version?
- Confirm you installed the package that matches your machine architecture.
- Close every old PowerShell window before checking the version again.
Still stuck? Help me troubleshoot Go 1.27.1 on Windows.
Visual Studio Code User setup adds its folder command to the Windows path. A new PowerShell session can then open the current folder as an editor workspace.
- Press the Windows key to open Windows search.
- Type Visual Studio Code into the search field.
✔️ Visual Studio Code is installed
Visual Studio Code is ready. Continue to create the project folder.
ⓧ Visual Studio Code is not installed
- Open the official Visual Studio Code Windows setup page.
- Download the Visual Studio Code User setup.
- Run the downloaded setup.
- Complete the installation.
The User setup installs without requiring administrator permissions. It also adds Visual Studio Code to the Windows path.
- Close the current Windows PowerShell session.
- Start a new Windows PowerShell session through Windows search.
- Create the sre-readiness-lab folder on your Desktop by running these commands:
New-Item -Path "$HOME\Desktop\sre-readiness-lab" -ItemType Directory
Set-Location -Path "$HOME\Desktop\sre-readiness-lab" -PassThru
What do these commands do?
The first command creates the project folder on your Desktop. The second command moves PowerShell into that folder.
The location command prints the resulting path so you can confirm where the project lives.
You'll see a path ending in Desktop\sre-readiness-lab.
Project folder was not created?
- Confirm the PowerShell path includes Desktop\sre-readiness-lab exactly.
- Confirm your Windows account can create folders on the Desktop.
Need help? Help me create the project folder with PowerShell.
Before you run the final check, which tools do you expect PowerShell to find?
- Verify the complete workstation from inside sre-readiness-lab by running these commands:
docker version
go version
code .
What does the final check prove?
- The Docker command confirms that the client can reach the local server.
- The Go command confirms that PowerShell can find the pinned toolchain.
- The Visual Studio Code command opens the current project folder.
You'll see Docker client details plus server details.
You'll see go1.27.1 in the Go output.
Visual Studio Code opens the sre-readiness-lab folder.
One of the final checks failed?
- Start Docker Desktop before repeating the Docker version check.
- Open a new PowerShell session if the Go installer completed in an older session.
- Reinstall Visual Studio Code with the User setup if the folder command remains unavailable.
Still blocked? Help me diagnose my Windows workstation checks.
That's the workstation ready: Docker can serve container requests, Go reports the pinned release, and Visual Studio Code has your project folder open. Next, you'll turn this container-ready machine into a local Kubernetes cluster.
Create the Local Kubernetes Cluster
Your workstation can now build and run containers with Docker Desktop. The incident lab also needs a local Kubernetes control plane that can schedule the API and evaluate its readiness.
You'll use Windows PowerShell to add kubectl and kind. These tools provide the client and local cluster that Docker Desktop does not create on its own.
In this step, get ready to:
- Install kubectl 1.37.0 for controlling Kubernetes.
- Install kind v0.33.0 for creating the local cluster.
- Create the sre-lab cluster with a Kubernetes v1.37.0 control plane.
Install kubectl 1.37.0
The kubectl client sends instructions to a Kubernetes cluster and retrieves its current state. Matching it to the cluster version keeps the incident commands predictable.
- Check whether the required client is already available by running this command:
kubectl version --client
What does this check do?
This asks the local kubectl executable to report its client version. It does not require a running cluster.
✔️ I see version 1.37.0
Your Kubernetes client is ready. You can move directly to the kind installation.
ⓧ I see an older version
An older client can drift outside the supported version range for this cluster. Upgrade the exact kubectl package before continuing.
- Upgrade kubectl through Windows Package Manager by running this command:
winget upgrade -e --id Kubernetes.kubectl
What does this command do?
The Windows Package Manager targets the package with the exact Kubernetes.kubectl identifier. It upgrades that package to the current available release.
- Accept any package-source terms if prompted.
- Close the current PowerShell session after the upgrade completes.
- Start a fresh PowerShell session in the sre-readiness-lab folder.
- Confirm the upgraded client by running this command:
kubectl version --client
What should you see?
The client version should now report v1.37.0. The fresh PowerShell session loads the updated executable from your PATH.
Still seeing the older version?
- Close every open PowerShell session before starting another one.
- Check the official kubectl installation guide for the current Windows installation options.
Still stuck? Help me find why PowerShell still uses an older kubectl version.
ⓧ Command not found
PowerShell cannot find kubectl yet. Install the current Windows package before creating the cluster.
- Install kubectl by running this command:
winget install -e --id Kubernetes.kubectl
What does this command do?
The command selects the exact Kubernetes.kubectl package. Windows Package Manager installs the current client release.
- Accept any package-source terms if prompted.
- Close the current PowerShell session after the installation completes.
- Start a fresh PowerShell session in the sre-readiness-lab folder.
- Confirm the installation by running this command:
kubectl version --client
What should you see?
The command should report client version v1.37.0. That output proves PowerShell can find the installed client.
Kubectl still unavailable?
- Confirm the installation command completed without a package or network failure.
- Start another fresh PowerShell session so Windows reloads PATH.
- Use the official Windows installation guide if Windows Package Manager cannot install the package.
Still stuck? Help me troubleshoot a kubectl installation that PowerShell cannot find.
Install kind v0.33.0
Kind creates Kubernetes clusters whose nodes run as Docker containers. The existing Go toolchain can install the pinned kind release directly.
- Check whether kind is already available by running this command:
kind version
What does this check do?
This asks the local kind executable to report its version. The result tells you whether to continue or install the pinned release.
✔️ I see version v0.33.0
Kind is ready to create the lab cluster. Your two Kubernetes command-line tools now match the project versions.
ⓧ I see an older version
The project uses a pinned kind release so the node image and cluster behavior stay reproducible. Replace the older binary through the Go toolchain.
The download may take a minute while Go retrieves the required modules. A short pause in the terminal is expected.
- Install the pinned kind release by running this command:
go install sigs.k8s.io/kind@v0.33.0
What does this command do?
Go downloads the kind source for v0.33.0 and builds its executable. The executable is placed in the Go workspace's bin directory.
- Close the current PowerShell session after the installation completes.
- Start a fresh PowerShell session in the sre-readiness-lab folder.
- Confirm the replacement version by running this command:
kind version
What should you see?
Kind should now report v0.33.0. That version matches the cluster image used later in this step.
Still seeing the older kind version?
- Close every PowerShell session before starting a new one.
- Confirm the Go workspace's bin directory appears in your user Path.
Still stuck? Help me replace an older kind executable on Windows.
ⓧ Command not found
The kind executable is not installed or its location is missing from PATH. You'll install it first and connect its folder to PowerShell.
The first installation may take a minute while Go downloads modules. The terminal can stay quiet briefly during that work.
- Install the pinned kind release by running this command:
go install sigs.k8s.io/kind@v0.33.0
What does this command do?
Go downloads kind v0.33.0 and builds its executable. The result is stored under the Go workspace's bin directory.
- Find the Go workspace directory by running this command:
go env GOPATH
What does this command do?
The command prints the current GOPATH value. Kind is installed in the bin directory beneath that location.
- Record the returned directory here: your Go workspace path.
- Press the Windows key to open Windows search.
- Search for Edit environment variables for your account.
- Select the user Path entry.
- Add your Go workspace path followed by \bin as a new entry.
- Save the environment variable changes.
- Start a fresh PowerShell session in the sre-readiness-lab folder.
- Confirm PowerShell can find kind by running this command:
kind version
What should you see?
Kind should report v0.33.0. PowerShell can now locate Go-installed commands through your user PATH.
Kind still unavailable?
- Confirm the path entry ends with \bin.
- Confirm the path begins with the exact directory printed by go env GOPATH.
- Start another fresh PowerShell session after saving the PATH change.
Still stuck? Help me add the Go bin directory to my Windows PATH.
Create and verify the cluster
Kind runs each Kubernetes node inside a Docker container. A pinned node image gives this lab the same Kubernetes version every time.
Why kind for this lab?
Kind can create a disposable cluster and load locally built Docker images into it. That keeps the later build-to-cluster loop short.
- Confirm Docker Desktop from the previous step is still running.
The first cluster creation can take up to five minutes while Docker downloads the node image. A few quiet moments in PowerShell do not mean the command has stalled.
- Create the pinned sre-lab cluster by running this command:
kind create cluster --name sre-lab --image kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5 --wait 5m
What does this command do?
- The --name sre-lab option gives the cluster a stable project name.
- The --image value pins the control plane to Kubernetes v1.37.0 with its release digest.
- The --wait 5m option gives the control plane up to five minutes to become ready.
- Kind updates kubectl configuration so the new cluster becomes reachable.
That is the core setup complete. Your Windows machine now hosts a local Kubernetes control plane inside Docker Desktop.
Before you run the final checks, which command do you expect to prove that the control plane is reachable?
- Verify the tool versions and cluster state by running these commands:
kind version
kubectl version --client
kubectl cluster-info
kubectl get nodes
What should you see?
- Kind reports v0.33.0.
- Kubectl reports client version v1.37.0.
- The cluster information command reports a reachable Kubernetes control plane.
- The node list shows the sre-lab control-plane node with a Ready status.
Cluster checks not succeeding?
- Confirm Docker Desktop is running with Linux containers selected.
- Run the cluster creation command again if the node-image download was interrupted.
- Start a fresh PowerShell session if either installed command is unavailable.
Still stuck? Help me diagnose why my local kind cluster is unreachable.
Your local Kubernetes lab is online and taking commands. Next, you'll build the Go status API that becomes the workload for your readiness incident.
Build and Test the Go Status API
Your local Kubernetes cluster is ready. You now have a stable foundation for the incident lab.
In this step, you'll build a Go API and package it as a Docker container image. Proving both work locally isolates the later readiness failure to Kubernetes configuration.
In this step, get ready to:
- Create separate status and health endpoints.
- Package the API in a multi-stage container image.
- Run the image locally and test both endpoints.
Create the Go status API
The API exposes one HTTP endpoint for people and one endpoint for health checks. Every unregistered path receives the default not-found response.
- Switch back to the Visual Studio Code window from earlier.
- Click New File in the Explorer sidebar.
- Enter main.go in the file name field.
- Press Enter to create the file.
- Fill main.go with the API implementation by pasting this code:
package main
import (
"log"
"net/http"
)
func statusHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("status-api is running\n"))
}
func healthHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("ok\n"))
}
func main() {
http.HandleFunc("/status", statusHandler)
http.HandleFunc("/healthz", healthHandler)
log.Println("status-api listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
What does this code do?
- The statusHandler function returns a user-facing message from /status.
- The healthHandler function returns a short health response from /healthz.
- The main() function registers both routes with the HTTP server.
- The startup log gives container logs a clear signal that the server reached its listening state.
- Press Ctrl+S to save main.go.
- Confirm the Explorer sidebar lists main.go inside sre-readiness-lab.
You should see main.go in the project folder without an unsaved-change indicator on its tab.
Seeing errors in main.go?
- Check that each opening brace has a matching closing brace.
- Check that the endpoint paths and response strings use straight double quotes.
- Confirm the file is named main.go with a lowercase extension.
Still stuck? Help me compare my Go API with the expected main.go structure
✔️ Awesome, I've got everything!
Great. Double-check that you saved main.go before continuing.
ⓧ I'd like to double check the full code
package main
import (
"log"
"net/http"
)
func statusHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("status-api is running\n"))
}
func healthHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("ok\n"))
}
func main() {
http.HandleFunc("/status", statusHandler)
http.HandleFunc("/healthz", healthHandler)
log.Println("status-api listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
How to use this reference
This reference combines both handlers with the server startup in their final order. Compare it with your saved file before moving on.
Create the multi-stage Dockerfile
A multi-stage build compiles the API in one image stage. The final stage contains only the static application binary.
- Click New File in the Explorer sidebar.
- Enter Dockerfile in the file name field.
- Press Enter to create the file.
- Define the container build by pasting this code into Dockerfile:
FROM golang:1.27.1-trixie AS build
WORKDIR /src
COPY main.go .
RUN CGO_ENABLED=0 go build -o /status-api main.go
FROM scratch
COPY --from=build /status-api /status-api
EXPOSE 8080
ENTRYPOINT ["/status-api"]
How does the build work?
- The build stage uses golang:1.27.1-trixie to compile main.go.
- The CGO_ENABLED=0 setting produces a static Go binary.
- The scratch stage starts with an empty final image.
- The final instructions copy the binary into the image and start it on container launch.
- Press Ctrl+S to save Dockerfile.
- Confirm the Explorer sidebar lists Dockerfile beside main.go.
You should now see both source files inside sre-readiness-lab.
Dockerfile not recognized?
- Confirm the file name is exactly Dockerfile with no extension.
- Check that Dockerfile sits in the same folder as main.go.
- Remove any blank space before each Dockerfile instruction.
Need another pair of eyes? Help me troubleshoot my multi-stage Dockerfile
✔️ Awesome, I've got everything!
Your two source files are ready. Make sure both files are saved.
ⓧ I'd like to double check the full code
FROM golang:1.27.1-trixie AS build
WORKDIR /src
COPY main.go .
RUN CGO_ENABLED=0 go build -o /status-api main.go
FROM scratch
COPY --from=build /status-api /status-api
EXPOSE 8080
ENTRYPOINT ["/status-api"]
How to use this reference
This is the complete final Dockerfile. Compare each instruction with your saved file so the build stage and final stage stay aligned.
Build and exercise the local image
The container image freezes your source and runtime into one testable artifact. The first build can pause while Docker downloads the Go image, so a quiet wait is normal.
- Build the local image from the saved project files by running this command in PowerShell:
docker build --tag status-api:1.0.0 .
What does this command do?
- The --tag option names the image status-api:1.0.0.
- The final . sends the current sre-readiness-lab folder to the build process.
The build output should finish successfully. Docker now has the local image status-api:1.0.0.
Build did not complete?
- Confirm Docker Desktop is still running.
- Confirm PowerShell is still inside the sre-readiness-lab folder.
- Check that main.go and Dockerfile are both saved.
Still blocked? Help me diagnose why my status-api Docker image will not build
Detached mode keeps the container running while PowerShell remains available. Loopback port binding keeps the API reachable from your Windows device.
- Start a temporary container from status-api:1.0.0 by running this command:
docker run --rm --detach --name status-api --publish 127.0.0.1:8080:8080 status-api:1.0.0
How does the container run?
- The --detach option keeps the container running in the background.
- The --name status-api option gives the container a predictable name.
- The --publish 127.0.0.1:8080:8080 option connects the Windows loopback address to the API port.
- The --rm option removes the temporary container after it stops.
PowerShell should print a long container ID before returning to the prompt. That ID confirms the container started in the background.
Container did not start?
- Confirm the image name is status-api:1.0.0.
- Stop any other local process using port 8080.
- Confirm Docker Desktop still shows its Linux container engine as running.
Need help with the launch? Help me troubleshoot why my status-api container will not start
Before you test the endpoints, do you expect the same container to answer both paths?
- Enter http://localhost:8080/status in your browser address bar.
You should see status-api is running in the browser.
- Enter http://localhost:8080/healthz in your browser address bar.
You should see ok in the browser. Strong work. Both API paths now respond from the same local container image.
Browser cannot reach the API?
- Check that the browser URL uses port 8080.
- Check that the endpoint path is exactly /status or /healthz.
- Confirm the PowerShell window returned a container ID after the run command.
Still seeing no response? Help me troubleshoot the local status-api endpoints
The local test has served its purpose. Stop the temporary container so the next step can use port 8080 without a conflict.
- Stop the temporary container by running this command:
docker stop status-api
What happens when the container stops?
Docker stops the named container. The earlier --rm option then removes that temporary container while preserving the image.
PowerShell should return the container name. Your tested status-api:1.0.0 image remains available for the cluster.
Container name not found?
The container may already be stopped or removed. Help me confirm whether my temporary status-api container has already stopped
Your API works outside the cluster, so the application and image have a clean baseline. Next, you'll load that image into sre-lab and put its traffic readiness under Kubernetes control.
Deploy the Failing Workload
Your Go API already works inside a local Docker container. The next test is whether Kubernetes considers that same process safe for Service traffic.
A readiness probe gives a Deployment a repeatable health gate. You will use kind to reproduce the gap between a running process and a ready workload.
In this step, get ready to:
- Load the verified container image into the local cluster.
- Define the Deployment and ClusterIP Service in one manifest.
- Collect Pod events and EndpointSlice evidence from the workload.
Load the image into kind
The kind node has its own image store. Loading status-api:1.0.0 makes your verified local image available without publishing it to an external registry.
- Load the local image into the named cluster by running this command:
kind load docker-image status-api:1.0.0 --name sre-lab
What does this command do?
- The load operation copies status-api:1.0.0 into the cluster node's image store.
- The --name option targets the sre-lab cluster.
- The loaded image can be used with imagePullPolicy: IfNotPresent.
- Confirm the command finishes without an error.
That closes the image gap. The cluster can now start the same API image you tested locally.
Image load failed?
- Confirm Docker Desktop is still running with Linux containers selected.
- Confirm the sre-lab cluster is still reachable.
Still stuck? Help me diagnose why kind cannot load my local image
Create the Kubernetes manifest
A declarative manifest records the workload state that Kubernetes should maintain. The first document defines the API process and its readiness check.
- Switch back to Visual Studio Code from earlier.
- Click the New File button in the Explorer sidebar.
- Type kubernetes.yaml as the file name.
- Press Enter to create the file.
You should see a blank kubernetes.yaml file beside main.go and Dockerfile in the Explorer.
- Add the Deployment definition by pasting this code into kubernetes.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: status-api
labels:
app: status-api
spec:
replicas: 1
selector:
matchLabels:
app: status-api
template:
metadata:
labels:
app: status-api
spec:
containers:
- name: status-api
image: status-api:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 2
periodSeconds: 2
What does this definition do?
- The label app: status-api gives the Deployment a stable way to identify its Pod.
- The container uses the loaded status-api:1.0.0 image on port 8080.
- The pull policy keeps Kubernetes from replacing the loaded image with a registry download.
- The readiness probe requests /readyz every two seconds after an initial two-second delay.
- Save kubernetes.yaml.
- Confirm the saved definition ends with periodSeconds: 2.
Seeing YAML formatting problems?
- Keep each nested block aligned with spaces.
- Check that the container entry starts with the hyphen before name: status-api.
Need another pair of eyes? Help me check the indentation in my Deployment definition
The second document defines the stable network entry point for matching Pods. Its selector uses the same app: status-api label as the Deployment.
- Place your cursor after the final periodSeconds: 2 line.
- Add the Service definition by pasting this code:
---
apiVersion: v1
kind: Service
metadata:
name: status-api
spec:
selector:
app: status-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
How does the Service connect?
- The document separator keeps the Service definition distinct from the Deployment definition.
- The selector targets Pods carrying the app: status-api label.
- Port 80 forwards traffic to container port 8080.
- The ClusterIP type keeps the Service reachable inside the cluster.
- Save kubernetes.yaml.
- Apply the manifest from the PowerShell terminal by running this command:
kubectl apply -f kubernetes.yaml
What does this command do?
The apply operation sends both declarative definitions to the cluster. Kubernetes creates the named Deployment and Service from that source of truth.
Your declarative workload now exists in the cluster. You should see confirmation for both resources in the terminal.
Manifest rejected?
- Confirm PowerShell is still working inside the sre-readiness-lab folder.
- Check that kubernetes.yaml contains both documents.
- Check the line reported by Kubernetes for a missing space or an unexpected character.
Still blocked? Help me diagnose why Kubernetes rejected my manifest
✔️ Awesome, I've got everything!
Great. Double-check that kubernetes.yaml is saved before you inspect the workload.
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: status-api
labels:
app: status-api
spec:
replicas: 1
selector:
matchLabels:
app: status-api
template:
metadata:
labels:
app: status-api
spec:
containers:
- name: status-api
image: status-api:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 2
periodSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
name: status-api
spec:
selector:
app: status-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
What should match?
Compare the complete file with your saved manifest. The Deployment and Service should use the same app: status-api label.
Observe the rejected workload
The Deployment controller has now tried to satisfy the manifest. Before you check the Pod, do you think a started process automatically qualifies for Service traffic?
- Check the Pod state by running this command:
kubectl get pods -l app=status-api
How do the Pod columns differ?
The status column reports whether the container process has started. The READY column reports whether the readiness condition has passed.
You will see the Pod process running while the READY column remains at 0/1. This is the intended shortfall.
Pod missing from the output?
- Confirm the manifest application completed for the Deployment.
- Check that the Deployment label is exactly app: status-api.
Need help tracing the missing Pod? Help me find why the status-api Pod is not listed
Pod events explain why the readiness condition remains blocked. The description connects the probe request to the repeated failure.
- Inspect the Pod details and events by running this command:
kubectl describe pods -l app=status-api --show-events=true
What does the description prove?
- The container details show that the API process has started.
- The readiness section identifies the HTTP path being checked.
- The events show that the readiness check keeps failing.
You should see a failed HTTP readiness-probe event that names /readyz. The event explains why the running Pod remains at 0/1 readiness.
No readiness events listed?
- Wait for another probe interval before running the description command again.
- Confirm the manifest contains the readinessProbe block.
Still missing the event? Help me find the readiness-probe evidence in my Pod description
Before you inspect the Service backends, do you expect Kubernetes to treat this running Pod as ready for traffic?
- Inspect the Service backend state by running this command:
kubectl get endpointslices -l kubernetes.io/service-name=status-api
Why check the EndpointSlice?
The EndpointSlice records the backend associated with the Service. Readiness determines whether that backend is eligible for normal Service traffic.
You should find no ready backend for the status-api Service. The evidence now connects the failed probe to the missing traffic path.
EndpointSlice missing?
- Confirm the Service was created from kubernetes.yaml.
- Check that the Service name is exactly status-api.
- Check that the Service selector matches app: status-api.
Need help connecting the resources? Help me troubleshoot the status-api EndpointSlice
You now have a clean incident with three connected clues: a running process, failed readiness, and no ready Service backend. Next, you will follow that evidence to the configuration fault and restore traffic.
Repair the Readiness Probe
Your previous investigation proved that the status-api process is running. Kubernetes still keeps the Pod outside Service traffic.
The readiness probe requests /readyz. The API only serves /healthz.
Repairing kubernetes.yaml gives the Deployment a valid health endpoint. You can then watch the controller restore traffic eligibility.
In this step, get ready to:
- Trace the missing Service backend to the incorrect probe path.
- Repair the readiness probe in the declarative manifest.
- Prove the recovered API is reachable through the Service.
Trace the failed readiness path
An incident evidence chain connects the visible symptom to its configuration source. Pod events identify the failed request. The EndpointSlice shows the traffic consequence.
Before you gather the evidence again, where do you expect the incorrect path to appear?
- Check the Pod events again by running this command:
kubectl describe pods -l app=status-api --show-events=true
What does this check prove?
The Pod description combines container state with readiness events. It shows that the process remains running while requests to the configured probe path fail.
- Locate the readiness-probe event that mentions /readyz.
- Switch back to the main.go tab from earlier.
- Find http.HandleFunc("/healthz", healthHandler).
- Compare that registered route with path: /readyz in kubernetes.yaml.
The probe targets a path the application never registers. The resulting not-found response keeps the Pod unready.
- Inspect the Service backend state by running this command:
kubectl get endpointslices -l kubernetes.io/service-name=status-api
Why check the EndpointSlice?
The EndpointSlice records the backends associated with the Service. Readiness determines whether a matching Pod is eligible for normal Service traffic.
You still see no ready backend for the Service. That completes the evidence chain from the incorrect path to withheld traffic.
Unable to inspect the evidence?
- Confirm Docker Desktop is still running before retrying the command.
- Check that your current cluster context still points to the reachable sre-lab cluster.
Still stuck? Help me troubleshoot why kubectl cannot inspect the status-api Pod or EndpointSlice.
Repair the declarative manifest
The manifest is the source of desired state for this workload. Correcting the file makes the repair reproducible.
- In kubernetes.yaml, find path: /readyz.
- Replace /readyz with /healthz.
- Confirm the corrected line matches this reference:
path: /healthz
What does this change do?
The probe now requests the health route registered in main.go. A successful response allows the Pod to become ready.
- Save kubernetes.yaml.
✔️ Awesome, I've got everything!
Your repaired manifest is saved with /healthz as the readiness path.
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: status-api
labels:
app: status-api
spec:
replicas: 1
selector:
matchLabels:
app: status-api
template:
metadata:
labels:
app: status-api
spec:
containers:
- name: status-api
image: status-api:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 2
periodSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
name: status-api
spec:
selector:
app: status-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
What should you compare?
This reference preserves the aligned app: status-api selectors. The readiness path is the only repaired value.
Saving the file updates your local source of truth. Applying it sends the corrected state to the cluster.
- Apply the repaired manifest by running this command:
kubectl apply -f kubernetes.yaml
What does this command do?
The command compares the manifest with the live resources. Kubernetes updates the Deployment template to use the corrected readiness path.
The cluster accepts the updated Deployment configuration. The controller now works toward the repaired state.
Manifest not applying?
- Confirm kubernetes.yaml is saved inside the sre-readiness-lab folder.
- Compare the indentation around readinessProbe with the full-file reference.
Need help? Help me find the YAML problem preventing my repaired Kubernetes manifest from applying.
Before you watch the rollout, do you expect the corrected probe to let the Deployment complete?
- Watch the repaired rollout by running this command:
kubectl rollout status deployment/status-api
What is the controller checking?
The rollout waits for the Deployment to reach its available state. The Pod must pass the corrected readiness probe before that happens.
The rollout completes after the Pod passes /healthz. You have fixed the configuration that blocked readiness.
Rollout still waiting?
- Confirm the saved manifest contains path: /healthz.
- Inspect the latest Pod events for the path that the probe currently requests.
Need another pair of eyes? Help me diagnose why the status-api rollout does not complete after repairing its readiness path.
Verify Service recovery
A completed rollout proves the controller accepted the repaired Pod. The final checks confirm that readiness now reaches the Service layer.
Before you run these checks, which Pod readiness value do you expect to see?
- Verify the recovered workload by running these checks:
kubectl get pods -l app=status-api
kubectl get endpointslices -l kubernetes.io/service-name=status-api
What do these checks prove?
- The Pod listing shows whether the container is accepted as ready.
- The EndpointSlice shows whether the Service has an eligible backend.
You see 1/1 readiness for the running Pod. The EndpointSlice now contains a ready backend.
Backend still not ready?
- Confirm the Pod listing shows 1/1 before checking the EndpointSlice again.
- Compare the Deployment selector with the Service selector in kubernetes.yaml.
Still missing a backend? Help me trace why my ready status-api Pod is absent from the Service EndpointSlice.
Port-forwarding creates a temporary path from your computer to the internal Service. The command stays active in the terminal until you stop it.
Before you test through the Service, what response do you expect from /status now?
- Start the temporary Service connection by running this command:
kubectl port-forward service/status-api 8080:80
What does the port-forward do?
The command sends traffic from local port 8080 to port 80 on the Service. The Service forwards eligible requests to the ready Pod.
- Enter http://localhost:8080/status in your browser address bar.
You see status-api is running through the Kubernetes Service. The repaired readiness path now permits application traffic.
- Replace the browser address with http://localhost:8080/healthz.
You see ok. This is the same endpoint used by the readiness probe.
Browser response missing?
- Keep the terminal running the port-forward command while testing the browser.
- Confirm the Pod still reports 1/1 readiness.
- Check that another local process is not already using port 8080.
Still unable to reach the API? Help me troubleshoot the status-api Service port-forward and browser response.
- Return to the PowerShell terminal running the port-forward.
- Stop the temporary connection by pressing Ctrl+C.
The terminal prompt returns after the port-forward stops. That is the recovery complete: Kubernetes now treats the Pod as ready for Service traffic.
Secret mission
Prove Self-Healing at Three Replicas
Scale your repaired API from one ready replica to three. Delete one Pod to watch the Deployment controller create a replacement that restores the live desired state.
Clean Up Your Resources
Clean Up Your Resources
This local lab creates no ongoing cloud charges. Choose whether to keep the lab available, pause its cluster, or delete every project resource.
Resources you used:
- Local Kubernetes cluster named sre-lab, including the status-api Deployment, ClusterIP Service, three ready Pods, and the image loaded into the cluster.
- Local Docker image named status-api:1.0.0.
- Local project folder sre-readiness-lab containing main.go, Dockerfile, and kubernetes.yaml.
Keep everything running
Keep the lab available if you want to repeat the incident or experiment with the recovered workload.
- Leave Docker Desktop running so the sre-lab cluster stays reachable.
- Keep the live status-api Deployment at three ready replicas for further controller experiments.
- Keep the status-api:1.0.0 image available for future containers or clusters.
- Keep the sre-readiness-lab folder as a reusable readiness troubleshooting lab.
Pause - I'll come back to this later
Suspend the kind control-plane container to pause cluster activity while preserving the cluster, image, workload, and project files.
- List the running containers by running this command:
docker ps
What does this command show?
- The command lists the containers currently running through Docker Desktop.
- The output includes the control-plane container for the sre-lab cluster.
- Record the kind control-plane container name here: your kind control-plane container name.
- Pause that container by running this command:
docker pause [[CONTAINER_NAME="your kind control-plane container name"]]
What does this command do?
The command suspends every process inside the kind control-plane container. The cluster state stays stored for your return.
Your lab is safely paused. Cluster commands cannot complete until you resume the control-plane container.
Can't pause the cluster container?
Check that you copied the control-plane container name exactly from the container list. Help me identify the container that belongs to my sre-lab cluster.
- Resume the cluster when you return by running this command:
docker unpause [[CONTAINER_NAME="your kind control-plane container name"]]
What does this command do?
The command resumes the suspended processes in the same container. Your existing cluster and three-replica workload become active again.
Delete - I don't want to use this again
Remove the cluster, local image, and project folder if you want to erase the lab completely. Docker Desktop stays installed for other local projects.
- Delete the sre-lab cluster by running this command:
kind delete cluster --name sre-lab
What does this command remove?
Deleting the cluster removes its control plane, three Pods, status-api Deployment, ClusterIP Service, and the image copy loaded into the cluster.
Cluster deletion failed?
Confirm Docker Desktop is still running before trying the command again. Help me diagnose why kind cannot delete my sre-lab cluster.
- Remove the local status-api:1.0.0 image by running this command:
docker image rm status-api:1.0.0
What does this command remove?
The command removes the project image from Docker Desktop's local image store. Deleting the cluster first removes the other copy that kind loaded into its node.
Image still in use?
Check for a remaining container that still references status-api:1.0.0. Help me find what is preventing this Docker image from being removed.
- Close Visual Studio Code.
- Return to the Windows PowerShell window from earlier.
- Leave the project folder and delete sre-readiness-lab by running these commands:
Set-Location ..
Remove-Item -Recurse -Force .\sre-readiness-lab
What do these commands remove?
- The first command moves PowerShell to the folder containing sre-readiness-lab.
- The second command permanently deletes the project folder and all three source files.
Project folder won't delete?
Close any editor or terminal still using files inside sre-readiness-lab. Help me find which Windows process is keeping my project folder open.
Cleanup complete. The cluster, workload, project image, and local source files are now removed.
Nice Work!
Nice Work!
You did it! Your recovered Go status API now runs behind a healthy Kubernetes Service after a full readiness incident investigation.
You've learned how to:
- Build a Go API with separate /status and /healthz endpoints. Package it as the status-api:1.0.0 container image with a pinned multi-stage Docker build.
- Reproduce a failed readiness probe that leaves the process running while the Pod remains 0/1. Read Pod events to find the missing /readyz path. Use EndpointSlices to connect readiness with Service traffic eligibility.
- Repair kubernetes.yaml by restoring /healthz. Verify the successful Deployment rollout. Reach status-api is running through the Service in your browser.
- Secret Mission: Scale the live Deployment to three replicas. Delete one Pod. Watch the Deployment controller create a replacement to restore desired state.
Ready to quiz yourself?