KubeMedic Kubernetes Troubleshooting

Build a Bash CLI to deploy, diagnose, and recover a local Kubernetes app.

Introduction

30 Second Summary

A broken app rarely tells you exactly what went wrong. Finding the useful clue can take longer than making the fix.

In this project, you will build KubeMedic, a reusable Bash CLI that automates the operational lifecycle of a local Kubernetes application. The same tool takes the application from a deliberate image failure to a recovered web page with evidence at every stage.

What You'll Build

At the finish line, your terminal tells the story of a failed rollout becoming two healthy Pods with an NGINX welcome page you can open in your browser.

By the end of this project, you'll have:

  • A cluster health command that proves minikube can reach the local cluster. Its output displays the node table plus the current KubeMedic Pods.
  • An automatic failure report that traces an invalid image to visible Pod status. It also prints detailed descriptions plus namespace events.
  • A repeatable application lifecycle that reaches two ready Pods. Its local Service opens the NGINX page in your browser. The CLI can remove the KubeMedic Namespace for a clean redeployment.
  • Secret Mission: Add a controlled restart command that replaces the running Pods. It monitors the Deployment rollout before printing their fresh ages.

What do I need for this project?

Bring intermediate Bash experience plus a Windows 10 or 11 machine with hardware virtualization.

The machine needs at least 8 GB of RAM plus 20 GB of free disk space. You also need internet access for Docker Desktop and Visual Studio Code.

Before We Start

Before the hands-on work begins, this is your moment to lock in what KubeMedic will automate. A clear purpose connects every later command to the rollout problems it helps you solve.

Prepare the Local Kubernetes Lab

KubeMedic can only diagnose workloads after your local Kubernetes cluster responds correctly. A missing runtime could otherwise look like an application failure.

This step proves that Bash, minikube, Docker Desktop, and Visual Studio Code are ready. You will finish with a local cluster that the future KubeMedic commands can reach.

In this step, get ready to:
  • Verify the command-line tools that KubeMedic depends on.
  • Start the local Kubernetes cluster with the Docker driver.
  • Create the kubemedic workspace in Visual Studio Code.
Verify the Windows tools

KubeMedic uses command-line tools to inspect the cluster. Checking them from the same shell confirms that each executable is available on your Windows PATH.

  • Press the Windows key to open Windows search.
  • Type Git Bash into the search field.

✔️ Git Bash appears

  • Select Git Bash from the search results.

You now have a Bash terminal that can run the KubeMedic script later.

ⓧ Git Bash is missing

Git Bash is provided by Git for Windows. Installing it adds a Bash executable that Windows applications can find.

  • Visit the official Git for Windows site.
  • Download the Windows installer from the page.
  • Run the installer with its recommended options.
  • Press the Windows key after the installation completes.
  • Type Git Bash into the search field.
  • Select Git Bash from the search results.
  • Verify Bash plus the previously confirmed Kubernetes client and editor launcher by running these commands:
bash --version
kubectl version --client
code --version

What do these checks prove?

  • The first command confirms that a Bash executable is available on PATH.
  • The second command confirms that the standalone kubectl client can start.
  • The final command confirms that Windows can launch Visual Studio Code from a terminal.

Is a confirmed tool missing?

  • Close Git Bash after installing a tool. Open it again so the terminal receives the updated PATH.
  • Confirm that Visual Studio Code was installed with command-line access enabled if code --version fails.
  • Use the installation method from the earlier prerequisite check if kubectl version --client fails.
  • Ask for help with the exact command that failed: Help me diagnose why a required command is missing from Git Bash on Windows.
  • Check the installed minikube version by running this command:
minikube version

Why check the exact version?

This project uses minikube 1.38.1. Confirming the release now keeps the local cluster behavior consistent throughout the project.

✔️ I see the required version

The output includes v1.38.1. Your minikube installation is ready for the cluster setup.

ⓧ I see an older version

PowerShell can ask Windows Package Manager to upgrade the existing minikube package.

  • Press the Windows key to open Windows search.
  • Type PowerShell into the search field.
  • Select PowerShell from the search results.
  • Upgrade minikube by running this command:
winget upgrade --id Kubernetes.minikube

What does this command do?

Windows Package Manager locates the installed minikube package by its package ID. It replaces the older release with the latest available release.

Did the upgrade fail?

  • Confirm that PowerShell has internet access to retrieve package information.
  • Install minikube with the command in the missing-version tab if Windows Package Manager cannot find an existing package.
  • Ask for help with the package output: Help me understand why the minikube winget upgrade failed.
  • Close PowerShell after the upgrade completes.
  • Return to Git Bash through Windows search.

ⓧ Command not found

Windows Package Manager can install the stable minikube package. Reopening Git Bash afterward refreshes its command lookup.

  • Press the Windows key to open Windows search.
  • Type PowerShell into the search field.
  • Select PowerShell from the search results.
  • Install minikube by running this command:
winget install Kubernetes.minikube

What does this command do?

Windows Package Manager downloads the latest stable minikube package. It installs the executable so Windows terminals can find it.

Did the installation fail?

  • Confirm that Windows Package Manager can access the internet.
  • Use the official minikube installation guide if the package cannot be retrieved.
  • Ask for help with the installer output: Help me diagnose the minikube installation failure.
  • Close PowerShell after the installation completes.
  • Return to Git Bash through Windows search.
  • Confirm the required minikube release from Git Bash by running the version check again:
minikube version

What should I see?

The output should include v1.38.1. That version is the local cluster release used throughout KubeMedic.

Good start: your Windows command-line layer now has the required minikube release.

Start Docker Desktop and minikube

The minikube Docker driver runs the cluster inside Linux containers. Docker Desktop must therefore be running with its Linux container engine before minikube starts.

  • Press the Windows key to open Windows search.
  • Type Docker Desktop into the search field.

✔️ Docker Desktop appears

  • Select Docker Desktop from the search results.
  • Wait for Docker Desktop to report that its engine is running.

ⓧ Docker Desktop is missing

The recommended Windows installation uses per-user mode with the WSL 2 backend. This provides the Linux container engine that minikube needs.

  • Visit the official Docker Desktop Windows installation page.
  • Download Docker Desktop Installer.exe from the page.
  • Run the downloaded installer.
  • Choose the recommended per-user installation mode.
  • Select the WSL 2 backend when the installer asks.
  • Complete the installation wizard.
  • Start Docker Desktop from the Windows Start menu.
  • Wait for Docker Desktop to report that its engine is running.
  • Check the active container operating system from Git Bash by running this command:
docker info --format '{{.OSType}}'

What does this check prove?

Docker reports the operating-system type used by its active container engine. Minikube needs the result to be linux on Windows.

✔️ I see linux

The linux result confirms that Docker Desktop is using the correct container engine.

ⓧ I see another result

Docker Desktop is using a different container engine. Its taskbar menu provides the control for returning to Linux containers.

  • Select the Docker menu from the Windows taskbar.
  • Select Switch to Linux containers.
  • Wait for Docker Desktop to restart its engine.
  • Confirm the new container mode by running the check again:
docker info --format '{{.OSType}}'

What should change?

The repeated check should now print linux. This confirms that the engine switch completed.

Is Docker still unavailable?

  • Confirm that Docker Desktop reports its engine as running before repeating the command.
  • Restart Docker Desktop if the engine switch does not complete.
  • Confirm that hardware virtualization is enabled if Docker Desktop cannot start its Linux backend.
  • Ask for help with the Docker state: Help me get Docker Desktop running Linux containers on Windows.

The first cluster start downloads the local cluster images. This can take several minutes, so a quiet pause during the download is expected.

  • Start minikube with the Docker driver by running this command:
minikube start --driver=docker

Why use the Docker driver?

The Docker driver keeps the local cluster inside Docker Desktop. This avoids Windows edition differences around other virtualization drivers.

Minikube also provides its own compatible kubectl client through minikube kubectl --. That keeps the client aligned with the cluster version.

You should see minikube complete the local cluster startup with Docker as its driver.

Did the cluster fail to start?

  • Confirm that Docker Desktop is still running.
  • Repeat the container operating-system check to confirm that it still returns linux.
  • Check that your machine has at least 20 GB of free disk space for the local cluster images.
  • Ask for help with the startup output: Help me diagnose why minikube cannot start with the Docker driver.
Create the project workspace

A single workspace keeps the future Bash script beside its Kubernetes manifests. The nested k8s folder will hold the declarative resource files you create later.

Visual Studio Code may display a Workspace Trust prompt after opening the folder. You created this empty workspace yourself, so you can enable its full features.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code into the search field.
  • Select Visual Studio Code from the search results.

Visual Studio Code is now ready to turn a folder into the project workspace.

  • Select File from the top menu.
  • Select Open Folder....
  • Choose Desktop in the folder picker.
  • Select New Folder.
  • Enter kubemedic as the folder name.
  • Select Select Folder.
  • Select Yes, I trust the authors if the Workspace Trust prompt appears.

The Explorer now shows kubemedic as the workspace root.

  • Right-click kubemedic in the Explorer sidebar.
  • Select New Folder.
  • Enter k8s as the nested folder name.

Your workspace now has the directory boundary that will separate Kubernetes manifests from the Bash CLI.

  • Press Ctrl+Shift+P to open the Command Palette.
  • Type Terminal: Select Default Profile into the Command Palette.
  • Select Git Bash as the default terminal profile.
  • Select Terminal from the top menu.
  • Select New Terminal.

Before you run the final checks, do you expect the cluster to show only Kubernetes system Pods or KubeMedic application Pods too?

  • Verify the control plane and list Pods across every namespace by running these commands in the integrated Bash terminal:
minikube status
minikube kubectl -- get po -A

What do these checks prove?

  • The status command confirms that the minikube control plane is running.
  • The kubectl wrapper asks minikube to supply a client that matches its cluster.
  • The Pod query includes every namespace so the cluster's system workloads are visible.

You should see minikube report a running control plane. You should also see Kubernetes system Pods listed across the cluster namespaces.

Do the system Pods look unavailable?

  • Confirm that Docker Desktop still reports a running Linux container engine.
  • Repeat the minikube start command if the status check says that the cluster is stopped.
  • Wait for system Pods to finish starting if their status shows that containers are still being created.
  • Ask for help with the cluster output: Help me diagnose my minikube status and system Pod results.

Your local lab is ready: Bash can reach minikube, Docker Desktop is running Linux containers, and the cluster is responding from the KubeMedic workspace. Next, you will build the first health command that turns those raw checks into readable operator output.

Build the Cluster Health Command

Your local Kubernetes lab is running. KubeMedic now has a real cluster to inspect.

Application failures can resemble missing-tool failures when checks are scattered across a terminal. This step gives your Bash CLI a health command that validates minikube before it reports cluster state.

In this step, get ready to:
  • Create the reusable KubeMedic script foundation.
  • Add checks that prove minikube can reach Kubernetes.
  • Run the health command to inspect cluster state.
Create the script foundation

A reusable operations script needs fixed project values. It also needs consistent logs plus a safe way to check required commands.

  • In the Visual Studio Code Explorer from earlier, create kubemedic.sh inside the kubemedic workspace folder.
  • Build the script foundation by pasting this code into kubemedic.sh:
#!/usr/bin/env bash
set -Eeuo pipefail

readonly NAMESPACE="kubemedic"
readonly APP_NAME="kubemedic-web"
readonly MANIFEST_DIR="k8s"

log() {
  printf '[KubeMedic] %s\n' "$*"
}

require_command() {
  local command_name="$1"

  if ! command -v "$command_name" >/dev/null 2>&1; then
    printf 'Missing required command: %s\n' "$command_name" >&2
    exit 1
  fi
}

kube() {
  minikube kubectl -- "$@"
}

What does this foundation do?

  • The interpreter line finds Bash through your system path.
  • Strict error behavior stops the script when an unexpected command fails.
  • The three readonly values keep the project names consistent across later functions.
  • The log() helper gives every message a recognisable prefix.
  • The require_command() helper stops execution when a required executable is missing.
  • The kube() wrapper forwards later client arguments through minikube.
  • Save kubemedic.sh.
  • Confirm kubemedic.sh appears beside the k8s folder in the Explorer sidebar.

Cannot see the script?

  • Confirm the file sits directly inside the kubemedic workspace folder.
  • Check that the filename ends with .sh.
  • Ask for help with the file location: Help me find where kubemedic.sh should be saved in my VS Code workspace.
Add preflight and health checks

A preflight check proves the local cluster is reachable before application inspection begins. This separates environment problems from workload problems.

The health check prints the node table before querying application Pods by label. An absent Namespace represents an empty starting state.

  • In kubemedic.sh, find the closing brace of kube().
  • Add the preflight workflow plus the health report below kube() by pasting this code:
preflight() {
  require_command minikube
  minikube status >/dev/null
  kube cluster-info >/dev/null
  log "minikube and kubectl are ready."
}

health() {
  preflight
  log "Cluster nodes:"
  kube get nodes -o wide
  log "Application Pods:"

  if ! kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide 2>/dev/null; then
    log "No application Pods found yet."
  fi
}

How do the checks work?

  • The preflight() function confirms that minikube is available.
  • The cluster status check verifies that the local control plane is running.
  • The cluster information check proves that the Kubernetes API responds.
  • The health() function starts by calling preflight().
  • The node query displays the machines that form the local cluster.
  • The Pod query selects only resources labelled app=kubemedic-web.
  • The conditional branch turns a missing Namespace into a readable empty-state message.
  • Save kubemedic.sh.
  • Test the preflight path in a child Bash process by running:
bash -c 'source kubemedic.sh; preflight'

What does this check prove?

The child process loads the functions from kubemedic.sh. It calls preflight() directly so you can test cluster connectivity before the command router exists.

You'll see the prefixed readiness message. That output proves the script can validate minikube plus reach the cluster.

Does the preflight check stop?

  • Confirm Docker Desktop is still running in Linux container mode.
  • Confirm the minikube cluster from the previous step is still running.
  • Ask for help with the failed check: Help me diagnose why the KubeMedic preflight function cannot reach minikube.
Route and run the health command

A command router maps the first script argument to one named operation. The default branch prints usage guidance when the argument is missing or unsupported.

  • In kubemedic.sh, add the command router below health() by pasting this code:
usage() {
  printf 'Usage: %s {health}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"

How does argument routing work?

  • The usage() function prints the supported command.
  • The case statement examines the first argument passed to the script.
  • The health pattern calls the matching function.
  • The default pattern prints usage guidance before returning a non-zero exit status.
  • The final line passes every supplied argument into main().
  • Save kubemedic.sh.

Before you run the finished command, what cluster information do you expect KubeMedic to show?

  • Run the health workflow from the integrated Bash terminal with this command:
bash kubemedic.sh health

What does this command run?

Bash reads kubemedic.sh with health as its first argument. The router calls the health workflow after matching that argument.

You'll first see a prefixed readiness message. Then you'll see the minikube node table.

Below the node table, the application check reports No application Pods found yet. That empty state is expected before the manifests exist.

Health command does not complete?

  • Check that the terminal is still inside the kubemedic workspace folder.
  • Compare each function name with the code blocks above.
  • Ask for help with the health workflow: Help me debug why bash kubemedic.sh health does not show the node table.

✔️ Awesome, I've got everything!

Great. Double check that you saved kubemedic.sh after the health command succeeded.

ⓧ I'd like to double check the full code

#!/usr/bin/env bash
set -Eeuo pipefail

readonly NAMESPACE="kubemedic"
readonly APP_NAME="kubemedic-web"
readonly MANIFEST_DIR="k8s"

log() {
  printf '[KubeMedic] %s\n' "$*"
}

require_command() {
  local command_name="$1"

  if ! command -v "$command_name" >/dev/null 2>&1; then
    printf 'Missing required command: %s\n' "$command_name" >&2
    exit 1
  fi
}

kube() {
  minikube kubectl -- "$@"
}

preflight() {
  require_command minikube
  minikube status >/dev/null
  kube cluster-info >/dev/null
  log "minikube and kubectl are ready."
}

health() {
  preflight
  log "Cluster nodes:"
  kube get nodes -o wide
  log "Application Pods:"

  if ! kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide 2>/dev/null; then
    log "No application Pods found yet."
  fi
}

usage() {
  printf 'Usage: %s {health}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"

How the full script fits together

  • The foundation defines fixed project values plus reusable helpers.
  • The health workflow validates the cluster before printing its current state.
  • The router exposes that workflow through the health argument.

Your health command now separates cluster readiness from application state. Next, you'll use this foundation to make a failed rollout explain itself.

Deploy a Broken Workload

Your Bash health command now proves that the local minikube cluster answers Kubernetes requests. A deployment tool still needs to expose application failures clearly.

This step gives KubeMedic that responsibility through an intentionally broken Deployment. The failed rollout becomes a repeatable diagnostic exercise built from Pod state plus namespace events.

In this step, get ready to:
  • Define the isolated workload with declarative Kubernetes manifests.
  • Extend KubeMedic with rollout monitoring plus diagnostics.
  • Run the broken rollout to identify image-pull evidence.
Create the Kubernetes manifests

A declarative YAML manifest records the state Kubernetes should create. The Namespace sets the project boundary.

  • Create k8s/namespace.yaml with the new-file control in the Explorer sidebar.
  • Define the project namespace by pasting this manifest:
apiVersion: v1
kind: Namespace
metadata:
  name: kubemedic

What does this manifest define?

  • The Namespace kind creates an isolated boundary named kubemedic.
  • The boundary lets KubeMedic target its own resources without affecting unrelated workloads.
  • Save k8s/namespace.yaml.
  • Confirm the saved file shows name: kubemedic under metadata.

Does the Namespace manifest look different?

  • Check that metadata starts at the left edge.
  • Check that name: kubemedic is indented beneath metadata.
  • Make sure the file is inside the existing k8s folder.
  • Help me check my Namespace YAML indentation.

The workload needs a controller that maintains two identical application Pods. Its selector must match the labels on the Pod template.

  • Create k8s/deployment.yaml with the new-file control in the Explorer sidebar.
  • Define the two-replica workload by pasting this manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubemedic-web
  namespace: kubemedic
  labels:
    app: kubemedic-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: kubemedic-web
  template:
    metadata:
      labels:
        app: kubemedic-web
    spec:
      containers:
        - name: web
          image: registry.invalid/kubemedic/web:1.0
          ports:
            - name: http
              containerPort: 80

What does this Deployment define?

  • The replicas: 2 setting asks Kubernetes to maintain two application Pods.
  • The selector matches Pods labeled app: kubemedic-web.
  • The container is named web.
  • The named http port gives the Service a stable target.
  • Save k8s/deployment.yaml.
  • Confirm matchLabels matches the label under template.

Do the Deployment labels mismatch?

  • Check that both label values are exactly kubemedic-web.
  • Check that containers is nested beneath the Pod template specification.
  • Check that containerPort: 80 is nested beneath the named port.
  • Help me compare the Deployment selector with the Pod labels.

A Service gives the selected Pods one stable network entry point. Its target uses the named container port from the Deployment.

  • Create k8s/service.yaml with the new-file control in the Explorer sidebar.
  • Expose the workload by pasting this manifest:
apiVersion: v1
kind: Service
metadata:
  name: kubemedic-web
  namespace: kubemedic
spec:
  type: NodePort
  selector:
    app: kubemedic-web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: http

What does this Service define?

  • The selector sends traffic to Pods labeled app: kubemedic-web.
  • The NodePort type exposes the selected endpoints through a node port.
  • The Service accepts TCP traffic on port 80.
  • The targetPort: http setting connects that traffic to the named container port.
  • Save k8s/service.yaml.
  • Confirm the selector matches app: kubemedic-web in the Deployment.

Does the Service target look wrong?

  • Check that targetPort: http matches the named port in k8s/deployment.yaml.
  • Check that port: 80 is nested beneath the first item in ports.
  • Help me compare the Service selector with the Deployment labels.

✔️ Awesome, I've got everything!

Your three manifests are saved in the k8s folder.

ⓧ I'd like to double check the full code

Compare each saved manifest with these complete versions.

apiVersion: v1
kind: Namespace
metadata:
  name: kubemedic
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubemedic-web
  namespace: kubemedic
  labels:
    app: kubemedic-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: kubemedic-web
  template:
    metadata:
      labels:
        app: kubemedic-web
    spec:
      containers:
        - name: web
          image: registry.invalid/kubemedic/web:1.0
          ports:
            - name: http
              containerPort: 80
apiVersion: v1
kind: Service
metadata:
  name: kubemedic-web
  namespace: kubemedic
spec:
  type: NodePort
  selector:
    app: kubemedic-web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: http
Add deployment and diagnostic automation

A rollout failure becomes useful when the script collects evidence immediately. The diagnostic function keeps each inspection running even when another inspection fails.

  • In kubemedic.sh, locate the closing brace of health().
  • Add the diagnostic function before usage() by pasting this code:
diagnose() {
  preflight
  log "Selected Pod status:"
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide || true
  log "Selected Pod details:"
  kube describe pods -n "$NAMESPACE" -l "app=$APP_NAME" || true
  log "Namespace events:"
  kube get events -n "$NAMESPACE" || true
}

What does this function do?

  • The first command prints a wide table for Pods selected by the application label.
  • The second command prints detailed Pod state that can reveal image-pull problems.
  • The final command prints events from the kubemedic namespace.
  • Each || true keeps later diagnostics available when an earlier inspection returns a non-zero status.
  • Save kubemedic.sh.
  • Confirm the existing health command still runs by using:
bash kubemedic.sh health

What should I see?

You should still see the minikube node table followed by KubeMedic's message that no application Pods exist yet.

Did the health command stop working?

  • Check that diagnose() starts after the closing brace of health().
  • Check that every command inside diagnose() is indented by two spaces.
  • Help me find where the diagnose function broke my Bash script.

The deployment function applies the project boundary first. It then applies the workload resources before monitoring their rollout for up to 90 seconds.

  • In kubemedic.sh, locate the closing brace of diagnose().
  • Add the deployment function before usage() by pasting this code:
deploy() {
  preflight
  log "Applying the project namespace."
  kube apply -f "$MANIFEST_DIR/namespace.yaml"
  log "Applying the Deployment and Service."
  kube apply -f "$MANIFEST_DIR/deployment.yaml" -f "$MANIFEST_DIR/service.yaml"
  log "Waiting for the Deployment rollout."

  if ! kube rollout status "deployment/$APP_NAME" -n "$NAMESPACE" --timeout=90s; then
    log "The rollout failed. Collecting diagnostics."
    diagnose
    return 1
  fi

  log "Deployment is available."
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide
  kube get service "$APP_NAME" -n "$NAMESPACE"
}

How does deployment monitoring work?

  • The first apply creates the project Namespace if it is absent.
  • The second apply creates or updates the Deployment plus Service from their manifests.
  • The rollout command watches deployment/kubemedic-web for up to 90 seconds.
  • A failed rollout triggers diagnose() before the function returns a non-zero status.
  • Save kubemedic.sh.
  • Confirm deploy() appears between diagnose() and usage().

Can't find the deployment function?

  • Check that deploy() begins after the closing brace of diagnose().
  • Check that the function ends before usage() begins.
  • Help me place the deploy function correctly in kubemedic.sh.

The functions now exist inside the script. The command router must expose them as CLI arguments before you can run the deployment workflow.

  • In kubemedic.sh, select the existing usage() function plus the existing main() function.
  • Replace the selection with this updated command router:
usage() {
  printf 'Usage: %s {health|deploy|diagnose}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    deploy) deploy ;;
    diagnose) diagnose ;;
    *)
      usage
      return 1
      ;;
  esac
}

What does the router change?

  • The usage message now lists health, deploy, plus diagnose as supported arguments.
  • The case statement sends each argument to its matching function.
  • An unsupported argument prints the usage text before returning a non-zero status.
  • Save kubemedic.sh.
  • Confirm the router contains one branch for each supported command.

Does the router still show only health?

  • Check that you replaced the old usage() function.
  • Check that the deploy branch calls deploy.
  • Check that the diagnose branch calls diagnose.
  • Help me correct the case statement in kubemedic.sh.

✔️ Awesome, I've got everything!

Your saved script now contains the deployment workflow plus failure diagnostics.

ⓧ I'd like to double check the full code

Compare your saved kubemedic.sh with this complete version.

#!/usr/bin/env bash
set -Eeuo pipefail

readonly NAMESPACE="kubemedic"
readonly APP_NAME="kubemedic-web"
readonly MANIFEST_DIR="k8s"

log() {
  printf '[KubeMedic] %s\n' "$*"
}

require_command() {
  local command_name="$1"

  if ! command -v "$command_name" >/dev/null 2>&1; then
    printf 'Missing required command: %s\n' "$command_name" >&2
    exit 1
  fi
}

kube() {
  minikube kubectl -- "$@"
}

preflight() {
  require_command minikube
  minikube status >/dev/null
  kube cluster-info >/dev/null
  log "minikube and kubectl are ready."
}

health() {
  preflight
  log "Cluster nodes:"
  kube get nodes -o wide
  log "Application Pods:"

  if ! kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide 2>/dev/null; then
    log "No application Pods found yet."
  fi
}

diagnose() {
  preflight
  log "Selected Pod status:"
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide || true
  log "Selected Pod details:"
  kube describe pods -n "$NAMESPACE" -l "app=$APP_NAME" || true
  log "Namespace events:"
  kube get events -n "$NAMESPACE" || true
}

deploy() {
  preflight
  log "Applying the project namespace."
  kube apply -f "$MANIFEST_DIR/namespace.yaml"
  log "Applying the Deployment and Service."
  kube apply -f "$MANIFEST_DIR/deployment.yaml" -f "$MANIFEST_DIR/service.yaml"
  log "Waiting for the Deployment rollout."

  if ! kube rollout status "deployment/$APP_NAME" -n "$NAMESPACE" --timeout=90s; then
    log "The rollout failed. Collecting diagnostics."
    diagnose
    return 1
  fi

  log "Deployment is available."
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide
  kube get service "$APP_NAME" -n "$NAMESPACE"
}

usage() {
  printf 'Usage: %s {health|deploy|diagnose}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    deploy) deploy ;;
    diagnose) diagnose ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"
Experience the designed failure

The deployment path is ready to test. This run proves whether KubeMedic can turn a failed rollout into ordered evidence.

The rollout monitor can wait for up to 90 seconds. A quiet terminal during that window is normal.

Before you run it, what state do you expect the two Pods to reach?

  • Start the deployment workflow by running:
bash kubemedic.sh deploy

The failure is expected

  • The rollout reaches its timeout before the two replicas become available.
  • The script returns a non-zero status after collecting diagnostics.
  • The Pod table shows ErrImagePull or ImagePullBackOff.
  • The Pod description plus namespace events point to registry.invalid/kubemedic/web:1.0 as the failed image reference.

This is the intended shortfall. The .invalid domain makes the image pull fail consistently.

Why does this failure help?

KubeMedic presents the rollout result first. It then narrows the investigation through Pod status, detailed descriptions, plus namespace events.

Those sources agree on the image reference as the root cause. The evidence gives you a precise recovery target.

Did you miss the image-pull diagnostics?

  • Confirm k8s/deployment.yaml contains registry.invalid/kubemedic/web:1.0.
  • Confirm all three manifest files are saved inside k8s.
  • Confirm the deploy branch calls deploy inside main().
  • Help me find why KubeMedic did not print image-pull diagnostics.

Good work. KubeMedic now turns a failed rollout into an evidence trail you can act on.

You have captured the workload failure from rollout to root cause. Next, you will use that evidence to recover the application.

Repair the Deployment and Open the App

Your diagnostics exposed a precise root cause: Kubernetes could not pull the declared container image. The Deployment stayed unavailable because its desired state referenced the intentionally invalid image.

This step turns that evidence into a recovery through Kubernetes reconciliation. After the Pods recover, you'll add a Bash command that opens the application through minikube.

In this step, get ready to:
  • Declare the verified NGINX image in k8s/deployment.yaml.
  • Reapply the manifests to recover two ready Pods.
  • Add an open command for local browser access.
Correct the declared image

The image field tells Kubernetes which container image each replica should run. Changing this value gives the Deployment a valid desired state to reconcile.

  • In the Explorer sidebar in Visual Studio Code, select k8s/deployment.yaml.
  • Find the line containing image: registry.invalid/kubemedic/web:1.0.
  • Replace that line with the corrected image reference below:
          image: nginx:1.30.4-alpine

Why does this repair the rollout?

The new tag identifies the verified NGINX image that minikube can pull. Only the image value changes.

The existing labels keep the Service connected to the replacement Pods. The replica count still asks Kubernetes to maintain two Pods.

  • Save k8s/deployment.yaml.

✔️ Awesome, I've got everything!

Your Deployment manifest now declares the verified NGINX image. The other Deployment settings remain unchanged.

ⓧ I'd like to double check the full code

  • Compare k8s/deployment.yaml with this complete reference:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubemedic-web
  namespace: kubemedic
  labels:
    app: kubemedic-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: kubemedic-web
  template:
    metadata:
      labels:
        app: kubemedic-web
    spec:
      containers:
        - name: web
          image: nginx:1.30.4-alpine
          ports:
            - name: http
              containerPort: 80
See reconciliation in action

Applying the same manifests updates the existing Deployment. Kubernetes replaces the failed Pods until the running state matches the corrected manifest.

The first successful image pull may pause for a short time. A quiet terminal during that pull does not mean the rollout is stuck.

Before you run the deployment, predict whether Kubernetes will replace the failed Pods after the image changes.

  • Reapply the corrected manifests by running:
bash kubemedic.sh deploy

What does this command prove?

The deploy command reapplies the Namespace first. It then reapplies the Deployment plus the Service.

The rollout check waits for the corrected Deployment to become available within the 90-second limit. A successful result proves that the new desired state can run.

You'll see the rollout reach success. KubeMedic then prints Deployment is available. before showing the application Pods plus the Service.

You have closed the failure loop. The corrected desired state now produces available Pods.

Is the rollout still failing?

  • Confirm that k8s/deployment.yaml contains image: nginx:1.30.4-alpine.
  • Check that you saved the manifest before running the deployment.
  • Confirm that Docker Desktop still shows its Linux container engine as running.
  • Help me diagnose why the corrected KubeMedic rollout still fails.

Before you check health, predict how many ready application Pods the Deployment should report.

  • Confirm the recovered application state by running:
bash kubemedic.sh health

What does the health check confirm?

The health command verifies cluster access before printing the node table. It then selects only the Pods labeled for KubeMedic.

The Pod output connects the successful rollout to the two replicas declared in the manifest. This is visible evidence that reconciliation completed.

You'll see two application Pod rows. Each row shows that its container is ready.

Do you see fewer than two ready Pods?

  • Return to the deployment output to confirm that the rollout reached success.
  • Check that replicas: 2 remains in k8s/deployment.yaml.
  • Verify that each Pod still uses the app=kubemedic-web label.
  • Help me understand why KubeMedic does not show two ready Pods.
Add browser access

Windows cannot reach the minikube node IP directly when the Docker driver is active. The new command uses a temporary service tunnel to provide local browser access.

  • In the Explorer sidebar in Visual Studio Code, select kubemedic.sh.
  • Find the usage() function near the bottom of the script.
  • Insert the new function immediately above usage() using this code:
open_app() {
  preflight
  log "Printing the application URL through minikube."
  minikube service "$APP_NAME" -n "$NAMESPACE" --url
}

What does the open function do?

  • The preflight call confirms that minikube can still reach the cluster.
  • The project constants identify the existing Service plus its Namespace.
  • The URL flag asks minikube to provide a local address for the Service.
  • Replace the code from usage() through main "$@" with the completed argument handling below:
usage() {
  printf 'Usage: %s {health|deploy|diagnose|open}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    deploy) deploy ;;
    diagnose) diagnose ;;
    open) open_app ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"

How does the router expose the command?

The usage message now lists open as a supported argument. This makes the command discoverable when someone enters an unsupported argument.

The new case branch routes open to open_app(). The other command routes remain unchanged.

  • Save kubemedic.sh.

✔️ Awesome, I've got everything!

Your script now defines open_app() and routes the open argument to it.

ⓧ I'd like to double check the full code

  • Compare kubemedic.sh with this complete reference:
#!/usr/bin/env bash
set -Eeuo pipefail

readonly NAMESPACE="kubemedic"
readonly APP_NAME="kubemedic-web"
readonly MANIFEST_DIR="k8s"

log() {
  printf '[KubeMedic] %s\n' "$*"
}

require_command() {
  local command_name="$1"

  if ! command -v "$command_name" >/dev/null 2>&1; then
    printf 'Missing required command: %s\n' "$command_name" >&2
    exit 1
  fi
}

kube() {
  minikube kubectl -- "$@"
}

preflight() {
  require_command minikube
  minikube status >/dev/null
  kube cluster-info >/dev/null
  log "minikube and kubectl are ready."
}

health() {
  preflight
  log "Cluster nodes:"
  kube get nodes -o wide
  log "Application Pods:"

  if ! kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide 2>/dev/null; then
    log "No application Pods found yet."
  fi
}

diagnose() {
  preflight
  log "Selected Pod status:"
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide || true
  log "Selected Pod details:"
  kube describe pods -n "$NAMESPACE" -l "app=$APP_NAME" || true
  log "Namespace events:"
  kube get events -n "$NAMESPACE" || true
}

deploy() {
  preflight
  log "Applying the project namespace."
  kube apply -f "$MANIFEST_DIR/namespace.yaml"
  log "Applying the Deployment and Service."
  kube apply -f "$MANIFEST_DIR/deployment.yaml" -f "$MANIFEST_DIR/service.yaml"
  log "Waiting for the Deployment rollout."

  if ! kube rollout status "deployment/$APP_NAME" -n "$NAMESPACE" --timeout=90s; then
    log "The rollout failed. Collecting diagnostics."
    diagnose
    return 1
  fi

  log "Deployment is available."
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide
  kube get service "$APP_NAME" -n "$NAMESPACE"
}

open_app() {
  preflight
  log "Printing the application URL through minikube."
  minikube service "$APP_NAME" -n "$NAMESPACE" --url
}

usage() {
  printf 'Usage: %s {health|deploy|diagnose|open}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    deploy) deploy ;;
    diagnose) diagnose ;;
    open) open_app ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"

Keep the tunnel terminal active

On Windows with the Docker driver, service access stays attached to the terminal process. The local access path remains available while that process keeps running.

Before you open access, predict what kind of local address minikube will provide for the NodePort Service.

  • Start local service access by running:
bash kubemedic.sh open

What should you see?

KubeMedic prints its application URL message. Minikube then prints a local service URL beneath it.

The terminal remains occupied because it is maintaining the access path. Keep this process running while you test the page.

  • Hold Ctrl while clicking the printed URL.

Your browser should display the NGINX welcome page. This confirms that traffic reaches the NodePort Service plus one of the recovered Pods.

Does the page fail to open?

  • Confirm that the service tunnel terminal is still running.
  • Return to the earlier health output to confirm that both application Pods are ready.
  • Check that the open case routes to open_app.
  • Help me troubleshoot why the KubeMedic service URL does not show the NGINX page.

Your recovered Deployment now serves a working page through one Bash command. Next up, you'll add targeted cleanup before proving that the full lifecycle is repeatable.

Finish the CLI

Your Bash CLI already deploys the Kubernetes app. It also diagnoses failures and opens the Service.

Operational automation needs a safe reverse path. This step adds cleanup for the project Namespace without touching the minikube cluster.

In this step, get ready to:
  • Add cleanup that removes only the KubeMedic Namespace.
  • Complete the command router and usage message.
  • Run the full cleanup and redeployment lifecycle.
Add targeted cleanup

A Namespace gives every project resource one cleanup boundary. Deleting it removes the Deployment, Service, ReplicaSets, and Pods inside that boundary.

  • In the VS Code Explorer sidebar, open kubemedic.sh.
  • Find the closing brace of open_app().
  • Add the cleanup function below open_app() by pasting this code:
cleanup() {
  preflight
  log "Deleting the project namespace."
  kube delete namespace "$NAMESPACE" --ignore-not-found=true
}

What does this function do?

  • The preflight call confirms that minikube is reachable before cleanup begins.
  • The delete command targets the Namespace stored in NAMESPACE.
  • The --ignore-not-found=true flag treats an already-absent Namespace as a successful result. This makes cleanup safe to repeat.
  • Save kubemedic.sh.
  • Confirm the script still parses and reaches the cluster by running this command:
bash kubemedic.sh health

What should you see?

You should see the minikube node table. You should then see the two ready application Pods from your existing deployment.

Does the health command stop early?

  • Check that cleanup() has an opening brace and a closing brace.
  • Confirm the existing open_app() function still ends before cleanup() begins.
  • Ask for help with the function placement: Help me check where cleanup() belongs in kubemedic.sh.
Complete argument handling

The help text and command router must describe the same interface. A missing argument also needs a clear usage message plus a non-zero result.

  • In kubemedic.sh, find the usage() function.
  • Replace everything from usage() through main "$@" with the completed router below:
usage() {
  printf 'Usage: %s {health|deploy|diagnose|open|cleanup}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    deploy) deploy ;;
    diagnose) diagnose ;;
    open) open_app ;;
    cleanup) cleanup ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"

How does the completed router work?

  • The usage() function now lists all five supported commands.
  • The cleanup pattern routes that argument to cleanup().
  • The ${1:-} expansion supplies an empty value when no argument exists.
  • The wildcard branch prints the usage message. It returns 1 to signal an unsupported or missing command.
  • Save kubemedic.sh.

Before you run this check, what do you expect the CLI to do when no command is supplied?

  • Test the missing-argument path by running this command:
bash kubemedic.sh

What should you see?

You should see the supported command list with cleanup included. The wildcard branch then returns a non-zero status.

Is cleanup missing from the usage message?

  • Check that the usage string ends with {health|deploy|diagnose|open|cleanup}.
  • Confirm the router includes the cleanup) cleanup ;; branch before the wildcard branch.
  • Ask for help comparing the router: Help me find why cleanup is missing from my KubeMedic command router.

✔️ Awesome, I've got everything!

Great. Double check that you saved kubemedic.sh with the new cleanup function and router.

ⓧ I'd like to double check the full code

Compare your complete kubemedic.sh file with this version:

#!/usr/bin/env bash
set -Eeuo pipefail

readonly NAMESPACE="kubemedic"
readonly APP_NAME="kubemedic-web"
readonly MANIFEST_DIR="k8s"

log() {
  printf '[KubeMedic] %s\n' "$*"
}

require_command() {
  local command_name="$1"

  if ! command -v "$command_name" >/dev/null 2>&1; then
    printf 'Missing required command: %s\n' "$command_name" >&2
    exit 1
  fi
}

kube() {
  minikube kubectl -- "$@"
}

preflight() {
  require_command minikube
  minikube status >/dev/null
  kube cluster-info >/dev/null
  log "minikube and kubectl are ready."
}

health() {
  preflight
  log "Cluster nodes:"
  kube get nodes -o wide
  log "Application Pods:"

  if ! kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide 2>/dev/null; then
    log "No application Pods found yet."
  fi
}

diagnose() {
  preflight
  log "Selected Pod status:"
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide || true
  log "Selected Pod details:"
  kube describe pods -n "$NAMESPACE" -l "app=$APP_NAME" || true
  log "Namespace events:"
  kube get events -n "$NAMESPACE" || true
}

deploy() {
  preflight
  log "Applying the project namespace."
  kube apply -f "$MANIFEST_DIR/namespace.yaml"
  log "Applying the Deployment and Service."
  kube apply -f "$MANIFEST_DIR/deployment.yaml" -f "$MANIFEST_DIR/service.yaml"
  log "Waiting for the Deployment rollout."

  if ! kube rollout status "deployment/$APP_NAME" -n "$NAMESPACE" --timeout=90s; then
    log "The rollout failed. Collecting diagnostics."
    diagnose
    return 1
  fi

  log "Deployment is available."
  kube get pods -n "$NAMESPACE" -l "app=$APP_NAME" -o wide
  kube get service "$APP_NAME" -n "$NAMESPACE"
}

open_app() {
  preflight
  log "Printing the application URL through minikube."
  minikube service "$APP_NAME" -n "$NAMESPACE" --url
}

cleanup() {
  preflight
  log "Deleting the project namespace."
  kube delete namespace "$NAMESPACE" --ignore-not-found=true
}

usage() {
  printf 'Usage: %s {health|deploy|diagnose|open|cleanup}\n' "$0"
}

main() {
  case "${1:-}" in
    health) health ;;
    deploy) deploy ;;
    diagnose) diagnose ;;
    open) open_app ;;
    cleanup) cleanup ;;
    *)
      usage
      return 1
      ;;
  esac
}

main "$@"

What should match?

Your file should contain cleanup() below open_app(). The usage message and router should both include cleanup.

Test the full lifecycle

Repeatability means the same CLI can remove its resources and rebuild them from the saved manifests. This lifecycle proves that every command works from a clean project boundary.

  • Return to the service tunnel terminal from earlier.
  • Press Ctrl+C to stop the existing tunnel.

Before you run the lifecycle, do you expect cleanup to stop minikube or only remove KubeMedic's Namespace?

  • Remove the project resources by running this command:
bash kubemedic.sh cleanup

What should you see?

You should see KubeMedic confirm that it is deleting the project Namespace. The minikube cluster remains available because cleanup targets only kubemedic.

  • Check the empty project state by running this command:
bash kubemedic.sh health

What does the empty check prove?

You should still see the minikube node table. KubeMedic should then report No application Pods found yet..

The fresh rollout can take up to 90 seconds while Kubernetes recreates the application resources. A short wait is expected.

  • Recreate the application from the manifests by running this command:
bash kubemedic.sh deploy

What does redeployment prove?

You should see the Namespace and workloads applied again. The rollout completes with two application Pods ready for traffic.

  • Confirm the recovered Pod state by running this command:
bash kubemedic.sh health

What should the health check show?

You should see the minikube node table again. The application section should list two ready Pods from the fresh deployment.

The Windows Docker driver keeps the local service tunnel attached to the terminal. Keep that process running while you test the page.

Before you start the service tunnel, what should the browser show if the redeployment restored the Service correctly?

  • Print the application URL and start the service tunnel by running this command:
bash kubemedic.sh open

What should you see?

You should see a local service URL in the terminal. The tunnel stays active while this command keeps running.

  • Ctrl+click the printed URL to open it in your browser.

You should see the NGINX welcome page. This confirms that cleanup and redeployment restored the full application path.

  • Press Ctrl+C in the tunnel terminal after you finish the check.

Does the lifecycle stop before the page opens?

  • Confirm the cleanup command completed before running the empty health check.
  • Wait for the deployment rollout to finish before running the second health check.
  • Keep the service tunnel command running while you open the printed URL.
  • Ask for help with the failing stage: Help me diagnose which KubeMedic lifecycle command is failing.

You've closed the full loop. KubeMedic can now remove its resources, rebuild them, and serve the app again.

Secret mission

Add a Controlled Restart Command

KubeMedic already handles the application's full lifecycle. Extend it with a controlled restart command that replaces the running Pods. The new command monitors the rollout before showing their fresh names or ages.

Clean Up Your Resources

Clean Up Your Resources

This local lab creates no cloud charges. Docker Desktop requires a paid subscription for larger-organization professional use or government use, while personal or educational use is free.

Resources you used:

  • The running local minikube cluster.
  • The kubemedic Namespace.
  • The kubemedic-web Deployment with two Pods.
  • The kubemedic-web NodePort Service.
  • The kubemedic workspace folder containing kubemedic.sh plus the k8s manifests.

Keep everything running

No action needed. Choose this if you plan to keep testing KubeMedic or extend its commands.

  • Leave the minikube cluster running.
  • Keep the kubemedic workspace on your machine.

Pause - I'll come back to this later

Pause the cluster to free up local resources while preserving its workloads.

  • Pause minikube by running this command:
minikube pause

What Does This Command Do?

This pauses Kubernetes without changing the deployed KubeMedic application.

  • Resume the cluster later by running this command:
minikube unpause

What Happens When You Resume?

This resumes the existing cluster so your Namespace and application resources become available again.

Delete - I don't want to use this again

Deleting the lab is permanent, but each command stays inside the resources you created for KubeMedic. The first command removes only the kubemedic Namespace.

  • Delete the project Namespace by running the cleanup command from the open Bash terminal:
bash kubemedic.sh cleanup

What Does Cleanup Remove?

The script deletes the project Namespace. Kubernetes removes the Deployment, ReplicaSets, Pods, and Service inside that boundary.

You should see the Namespace deletion complete without affecting the rest of the minikube cluster.

  • Halt the local minikube cluster by running this command:
minikube stop

What Does Stopping Change?

This halts the local cluster after the KubeMedic resources have been removed.

  • Remove the local kubemedic workspace by running these commands from its open Bash terminal:
cd ..
rm -rf kubemedic

What Do These Commands Remove?

The first line moves outside the workspace. The second line permanently removes the project folder plus every file inside it.

The kubemedic folder should no longer exist in its previous location. Your project resources are now cleaned up.

Nice Work!

Nice Work!

You did it! KubeMedic now automates a complete local Kubernetes operations workflow through one Bash CLI.

You've learned how to:

  • Build a reusable Bash CLI that turns common cluster operations into focused commands.
  • Trace an intentional image-pull failure to its root cause using Pod diagnostics backed by Namespace events.
  • Recover the NGINX Deployment to two ready Pods. Then prove the lifecycle is repeatable with namespace-scoped cleanup followed by a fresh deployment.
  • Complete the optional Secret Mission by adding a controlled Deployment restart that waits for rollout completion before listing the replacement Pods.

Ready to quiz yourself?