Steven Rosales

Kubernetes Local Lab on Mac Using kind

This lab documents how to build and troubleshoot a local Kubernetes cluster on macOS using kind, Docker, and kubectl.

The purpose of this lab is to practice Kubernetes administration, application deployment, service exposure, log analysis, troubleshooting, and operational workflows in a local environment.


Table of Contents

1. Lab Objective

The objective of this lab is to create a local Kubernetes environment on macOS and practice the most common Kubernetes support and troubleshooting tasks.

By the end of this lab, I should be able to:


2. Lab Architecture

High-level architecture:

macOS Host
│
├── Docker Desktop
│   │
│   └── kind Kubernetes Cluster
│       │
│       ├── Control Plane Node
│       │
│       └── Worker Node(s)
│
└── kubectl CLI
    │
    └── Manages Kubernetes resources

Application flow:

User / Browser
│
└── localhost
    │
    └── kubectl port-forward
        │
        └── Kubernetes Service
            │
            └── Pod running containerized application

3. Technologies Used

kind is a tool for running local Kubernetes clusters using Docker container nodes. It is commonly used for local development and testing. Kubernetes also lists kind as one of the local Kubernetes options and notes that it requires Docker or Podman.
Sources: kind Quick Start, Kubernetes Install Tools


4. Prerequisites

Before starting, confirm that the Mac has:

Check Homebrew:

brew --version

Check Docker:

docker --version
docker ps

If docker ps fails, start Docker Desktop first.


5. Install Required Tools

Install kubectl:

brew install kubectl

Validate:

kubectl version --client

Install kind:

brew install kind

Validate:

kind version

Optional tools:

brew install jq
brew install watch

Useful validation:

which kubectl
which kind
which docker

6. Create a Local Kubernetes Cluster

Create a basic cluster:

kind create cluster --name devops-lab

Expected result:

Creating cluster "devops-lab" ...
Ensuring node image ...
Preparing nodes ...
Writing configuration ...
Starting control-plane ...
Installing CNI ...
Installing StorageClass ...

Check kind clusters:

kind get clusters

Check Docker containers created by kind:

docker ps

The cluster context should be created automatically.

Check current context:

kubectl config current-context

Expected context:

kind-devops-lab

7. Validate the Cluster

Check cluster information:

kubectl cluster-info

Check nodes:

kubectl get nodes

Check nodes with more details:

kubectl get nodes -o wide

Check all pods across all namespaces:

kubectl get pods -A

Check Kubernetes system components:

kubectl get pods -n kube-system

Describe the node:

kubectl describe node devops-lab-control-plane

Troubleshooting logic:


8. Configure Daily kubectl Shortcuts

Create a short alias for kubectl.

For Zsh on macOS:

echo 'alias k=kubectl' >> ~/.zshrc
source ~/.zshrc

For Bash:

echo 'alias k=kubectl' >> ~/.bashrc
source ~/.bashrc

Test:

k get nodes

Enable autocomplete for Zsh:

echo 'source <(kubectl completion zsh)' >> ~/.zshrc
echo 'alias k=kubectl' >> ~/.zshrc
echo 'compdef __start_kubectl k' >> ~/.zshrc
source ~/.zshrc

Useful daily aliases:

alias kgp='kubectl get pods'
alias kgpa='kubectl get pods -A'
alias kgn='kubectl get nodes -o wide'
alias kgs='kubectl get svc'
alias kgsa='kubectl get svc -A'
alias kge='kubectl get events --sort-by=.metadata.creationTimestamp'
alias kgea='kubectl get events -A --sort-by=.metadata.creationTimestamp'
alias kd='kubectl describe'
alias kdp='kubectl describe pod'
alias kl='kubectl logs'
alias klf='kubectl logs -f'

Check kubeconfig:

ls -l ~/.kube/config
kubectl config get-contexts
kubectl config current-context

Secure kubeconfig permissions:

chmod 600 ~/.kube/config

9. Deploy a Test Application

Create a namespace:

kubectl create namespace web-lab

Set it as the default namespace for the current context:

kubectl config set-context --current --namespace=web-lab

Deploy NGINX:

kubectl create deployment nginx-demo --image=nginx:latest

Check deployment:

kubectl get deployments

Check pods:

kubectl get pods -o wide

Describe the deployment:

kubectl describe deployment nginx-demo

Describe the pod:

kubectl describe pod <pod-name>

Check logs:

kubectl logs <pod-name>

Expected result:

Deployment exists
Pod is Running
Container image is nginx:latest

10. Expose the Application with a Service

Expose the deployment as a ClusterIP service:

kubectl expose deployment nginx-demo --port=80 --target-port=80 --name=nginx-demo-service

Check service:

kubectl get svc

Describe service:

kubectl describe svc nginx-demo-service

Check endpoints:

kubectl get endpoints nginx-demo-service

Expected result:

The service should have endpoints pointing to the NGINX pod IP.

Troubleshooting logic:


11. Test the Application with Port Forwarding

Port forward local port 8080 to service port 80:

kubectl port-forward svc/nginx-demo-service 8080:80

Open another terminal and test:

curl -I http://localhost:8080

Expected result:

HTTP/1.1 200 OK
Server: nginx

Open in browser:

http://localhost:8080

Stop port forwarding with:

CTRL + C

Troubleshooting logic:


12. Scale the Application

Scale the deployment to 3 replicas:

kubectl scale deployment nginx-demo --replicas=3

Check pods:

kubectl get pods -o wide

Check deployment:

kubectl get deployment nginx-demo

Check endpoints:

kubectl get endpoints nginx-demo-service

Expected result:

Three NGINX pods should be running.
The service should have three endpoints.

Troubleshooting logic:


13. Perform a Rolling Update

Update the image:

kubectl set image deployment/nginx-demo nginx=nginx:1.25

Check rollout status:

kubectl rollout status deployment/nginx-demo

Check rollout history:

kubectl rollout history deployment/nginx-demo

Check pods:

kubectl get pods

Describe deployment:

kubectl describe deployment nginx-demo

Troubleshooting logic:


14. Roll Back a Deployment

Roll back to the previous version:

kubectl rollout undo deployment/nginx-demo

Check rollout status:

kubectl rollout status deployment/nginx-demo

Check deployment image:

kubectl describe deployment nginx-demo

Expected result:

Deployment should return to the previous image version.

15. Collect Logs and Events

Create an evidence folder locally:

mkdir -p ~/k8s-lab-evidence

Save pod list:

kubectl get pods -o wide > ~/k8s-lab-evidence/pods.txt

Save services:

kubectl get svc -o wide > ~/k8s-lab-evidence/services.txt

Save events:

kubectl get events --sort-by=.metadata.creationTimestamp > ~/k8s-lab-evidence/events.txt

Save deployment YAML:

kubectl get deployment nginx-demo -o yaml > ~/k8s-lab-evidence/nginx-demo-deployment.yaml

Save service YAML:

kubectl get svc nginx-demo-service -o yaml > ~/k8s-lab-evidence/nginx-demo-service.yaml

Save pod logs:

POD_NAME=$(kubectl get pods -l app=nginx-demo -o jsonpath='{.items[0].metadata.name}')
kubectl logs "$POD_NAME" > ~/k8s-lab-evidence/nginx-demo-pod.log

List evidence:

ls -lah ~/k8s-lab-evidence

Important: Review files before committing them to GitHub. Do not commit secrets, tokens, private keys, or sensitive kubeconfig files.


16. Troubleshooting Scenario 1: ImagePullBackOff

Create a broken deployment with a fake image:

kubectl create deployment broken-image --image=nginx:this-tag-does-not-exist

Check pod status:

kubectl get pods

Expected issue:

ImagePullBackOff

Investigate:

kubectl describe pod <broken-pod-name>
kubectl get events --sort-by=.metadata.creationTimestamp

What to look for:

Failed to pull image
Image not found
ErrImagePull
Back-off pulling image

Fix it:

kubectl set image deployment/broken-image nginx=nginx:latest

Validate:

kubectl rollout status deployment/broken-image
kubectl get pods

Clean up:

kubectl delete deployment broken-image

Documented root cause:

The pod failed because the container image tag did not exist. Kubernetes could not pull the image from the container registry.

17. Troubleshooting Scenario 2: CrashLoopBackOff

Create a pod that exits immediately:

kubectl run crash-demo --image=busybox --restart=Never -- /bin/sh -c "exit 1"

Check pod:

kubectl get pods

Describe pod:

kubectl describe pod crash-demo

Check logs:

kubectl logs crash-demo

Note: Because this is a standalone pod with restart=Never, it may show Error instead of CrashLoopBackOff.

To create a real CrashLoopBackOff using a Deployment:

kubectl create deployment crashloop-demo --image=busybox -- /bin/sh -c "exit 1"

Check:

kubectl get pods

Investigate:

kubectl describe pod <crashloop-pod-name>
kubectl logs <crashloop-pod-name>
kubectl logs <crashloop-pod-name> --previous
kubectl get events --sort-by=.metadata.creationTimestamp

Fix it by changing the command:

kubectl delete deployment crashloop-demo
kubectl create deployment crashloop-demo --image=busybox -- /bin/sh -c "sleep 3600"

Validate:

kubectl get pods

Clean up:

kubectl delete deployment crashloop-demo
kubectl delete pod crash-demo

Documented root cause:

The container repeatedly exited with code 1, causing Kubernetes to restart it until it entered CrashLoopBackOff.

18. Troubleshooting Scenario 3: Service Has No Endpoints

Create a service with a selector that does not match any pod:

kubectl create service clusterip broken-service --tcp=80:80

Edit the service selector:

kubectl edit svc broken-service

Set selector to something that does not match existing pods:

selector:
  app: does-not-exist

Check endpoints:

kubectl get endpoints broken-service

Expected result:

No endpoints

Investigate:

kubectl describe svc broken-service
kubectl get pods --show-labels

Fix by matching the selector to the NGINX pod label.

Check labels:

kubectl get pods --show-labels

Edit service:

kubectl edit svc broken-service

Set selector:

selector:
  app: nginx-demo

Validate:

kubectl get endpoints broken-service

Clean up:

kubectl delete svc broken-service

Documented root cause:

The service had no endpoints because the service selector did not match any pod labels.

19. Troubleshooting Scenario 4: Application Not Reachable

Problem:

The application is expected to respond through localhost, but curl fails.

Commands to investigate:

kubectl get pods
kubectl get svc
kubectl get endpoints nginx-demo-service
kubectl describe svc nginx-demo-service
kubectl describe pod <pod-name>
kubectl logs <pod-name>
kubectl port-forward svc/nginx-demo-service 8080:80
curl -v http://localhost:8080

Troubleshooting logic:

1. Confirm pod is Running.
2. Confirm service exists.
3. Confirm service has endpoints.
4. Confirm targetPort matches container port.
5. Confirm port-forward is running.
6. Test localhost with curl.
7. Check logs if HTTP response is not expected.

Possible root causes:

Pod is not running
Service selector mismatch
Wrong service port
Wrong target port
Port-forward not running
Local port already in use
Application inside container is not responding

Possible fixes:

Restart or fix pod
Correct service selector
Correct targetPort
Use a different local port
Check application logs
Redeploy application

20. Clean Up the Lab

Delete the namespace and all resources inside it:

kubectl delete namespace web-lab

Delete the kind cluster:

kind delete cluster --name devops-lab

Validate:

kind get clusters
docker ps