CKAD Preparation: Kubernetes Fundamentals
Pods, Services, Deployments, Networking Pitfalls, and Troubleshooting Lessons from My CKAD Journey

π Hi! Iβm Dani, a passionate automation, Ansible, DevOps, and Cloud technologies enthusiast. I currently work as a Middleware Solutions Architect at Atradius, leading middleware automation and optimizing IT infrastructure. π‘ My Story: I started my career specializing in Oracle Middleware, working with technologies such as WebLogic, Oracle Database, Oracle iPlanet Web Server, and Oracle JDK. Over time, my focus shifted towards deployment automation, continuous integration, and process optimization in complex enterprise environments. π Impact & Achievements: β Direct the automation of Oracle Fusion Middleware (FMW) with Ansible, streamlining the installation, configuration, and patching processes for Oracle WebLogic, SOA Suite, and OSB. β Lead IBM WebSphere Application Server (WAS) automation with Ansible and AWX, including installation, configuration, certificates, and deployments, reducing implementation times by 70%. β Integrated Azure DevOps with AWX, eliminating manual deployment tasks and reducing human intervention to a simple approval step. β Mentor and train teams on Ansible automation, fostering continuous improvement and knowledge transfer. ππ₯ In my free time, I enjoy playing padel and basketball, always looking for new challenges and improvements, both in sports and technology. I also love building web applications with Oracle APEX, bringing ideas to life through low-code development. π₯ Letβs connect! π§ Email: dbenitez.vk@gmail.com π LinkedIn: https://www.linkedin.com/in/danielbenitezaguila π» GitHub: https://github.com/dbeniteza
Introduction
As part of my CKAD preparation, I decided to step back from advanced Kubernetes topics and revisit the fundamentals. Instead of relying solely on theory, I deployed and tested everything on a real AKS cluster, documenting the lessons learned along the way. In this article, I cover Pods, Services, Deployments, multi-container Pods, networking concepts, and common troubleshooting scenarios that helped me build a stronger Kubernetes foundation. Whether you're studying for CKAD or just starting your Kubernetes journey, these exercises provide practical insights into how Kubernetes behaves in the real world.
Environment
All exercises were performed on:
Azure Kubernetes Service (AKS)
Kubernetes v1.34
kubectl CLI
Single-node lab cluster optimized for CKAD practice A useful command when exploring Kubernetes is:
kubectl api-resources
This shows all resource types supported by the cluster, including Pods, Services, Deployments, ConfigMaps, ReplicaSets, and more.
Creating My First Pod
The simplest Kubernetes workload is a Pod. Example:
apiVersion: v1
kind: Pod
metadata:
name: demo-web
spec:
containers:
- name: web
image: nginx
Create it:
kubectl apply -f pod.yaml
pod/demo-web created
Verify it:
kubectl get pods
NAME READY STATUS RESTARTS AGE
demo-web 1/1 Running 0 64s
The two fields I check first are:
STATUS should be Running
READY should show 1/1
If READY is not equal to the number of containers in the Pod, further investigation is required.
Pod Networking: A Common Beginner Mistake
After creating the Pod, I retrieved its IP address:
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-web 1/1 Running 0 10m 10.244.0.22 aks-agentpool-38340810-vmss000001 <none> <none>
I then tried:
curl http://10.244.0.22
curl: (28) Failed to connect to 10.244.0.22 port 80 after 129171 ms: Could not connect to server
The request failed. At first I assumed nginx wasn't working. The real reason was much simpler:
Pod IPs are internal cluster addresses and are not automatically reachable from outside Kubernetes.
To verify the application, I executed a shell inside the container:
kubectl exec -it demo-web -- sh
``
# curl http://10.244.0.22
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
Lesson learned:
Pod IPs are cluster-internal.
Running means scheduled and executing.
Running does not mean externally accessible.
Introducing Services
Pods are ephemeral.
A Service provides a stable endpoint for accessing workloads.
Example:
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- port: 80
Apply it:
kubectl apply -f service.yaml
service/web-service created
Check it:
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-service ClusterIP 10.0.203.69 <none> 80/TCP 10s
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 23h
ClusterIP service receives an internal cluster address.
Applications inside Kubernetes can communicate with the service using: http://10.0.203.69
Understanding NodePort on AKS
One of my biggest learning moments came from NodePort services.
I created a service similar to:
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
type: NodePort
ports:
- port: 80
Kubernetes assigned a port in the 30000+ range.
My expectation was NodeIP:NodePort would be reachable immediately. Not on AKS.
The nodes in my cluster had:
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-service NodePort 10.0.217.113 <none> 80:32765/TCP 61s
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 23h
EXTERNAL-IP means:
NodePort existed.
The nodes only had private addresses.
Requests from outside the cluster could not reach them. The easiest workaround for testing is:
kubectl port-forward svc/web-service 8080:80
Then curl http://localhost:8080. This is a useful concept to understand before working with LoadBalancers and Ingress controllers
Multi-Container Pods
A Pod can contain more than one container.
Example structure:
apiVersion: v1
kind: Pod
metadata:
name: multicont
labels:
type: webserver
spec:
containers:
- name: web
image: nginx:alpine
ports:
- containerPort: 80
- name: cache
image: redis:alpine
ports:
- containerPort: 6379
When running:
kubectl get pods
NAME READY STATUS RESTARTS AGE
multicont 2/2 Running 0 5s
You may see READY 2/2, this means:
Both containers are running.
Both containers passed readiness checks. This is your first exposure to the sidecar pattern.
Deployments and ReplicaSets
Managing Pods directly is useful for learning but rarely used in production.
Deployments manage Pods through ReplicaSets.
Create a Deployment:
kubectl create deployment web-app --image=nginx
deployment.apps/web-app created
Inspect the hierarchy:
kubectl get deploy,rs,po
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web-app 1/1 1 1 28s
NAME DESIRED CURRENT READY AGE
replicaset.apps/web-app-5c9c5f7865 1 1 1 4m53s
NAME READY STATUS RESTARTS AGE
pod/web-app-5c9c5f7865-tx88r 1/1 Running 0 28s
Output will show:
Deployment
-> ReplicaSet
-> Pod
One interesting experiment was deleting the ReplicaSet:
kubectl delete rs web-app-5c9c5f7865
A replacement ReplicaSet was automatically created.
The Deployment reconciled the desired state and recreated the workload.
This demonstrates one of the most important Kubernetes principles:
Kubernetes continuously works to restore the declared desired state.
Troubleshooting ImagePullBackOff
One of the first troubleshooting exercises involved a failing Pod.
The image contained a typo:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mountain-region
image: nginx:stable-alpin
Notice the mistake alpin instead of alpine. Checking the Pod:
kubectl get pod mypod
NAME READY STATUS RESTARTS AGE
mypod 0/1 ErrImagePull 0 3s
kubectl describe pod mypod
Name: mypod
Namespace: default
Priority: 0
Service Account: default
Node: aks-agentpool-38340810-vmss000001/10.224.0.4
Start Time: Tue, 16 Jun 2026 15:44:06 +0000
Labels: <none>
Annotations: <none>
Status: Pending
[β¦]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 14s default-scheduler Successfully assigned default/break1 to aks-agentpool-38340810-vmss000001
Normal Pulling 14s kubelet spec.containers{mountain-region}: Pulling image "nginx:stable-alpin"
Warning Failed 13s kubelet spec.containers{mountain-region}: Failed to pull image "nginx:stable-alpin": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/nginx:stable-alpin": failed to resolve reference "docker.io/library/nginx:stable-alpin": docker.io/library/nginx:stable-alpin: not foundound
Warning Failed 13s kubelet spec.containers{mountain-region}: Error: ErrImagePull
Normal BackOff 13s kubelet spec.containers{mountain-region}: Back-off pulling image "nginx:stable-alpin"
Warning Failed 13s kubelet spec.containers{mountain-region}: Error: ImagePullBackOff
revealed:
ErrImagePull
ImagePullBackOff
The events section clearly showed the image could not be found.
After correcting the tag, the Pod started successfully.
My Kubernetes Troubleshooting Checklist
When a workload fails, I now use this quick reference:
Status | Typical Cause |
Pending | Scheduling or resource problem |
ErrImagePull | Image cannot be downloaded |
ImagePullBackOff | Repeated image pull failure |
CrashLoopBackOff | Container exits repeatedly |
Running 0/1 | Readiness issue |
Running 1/1 | Healthy container |
And the first command I run is always: kubectl describe pod <pod-name>
Most problems become obvious after reviewing the Events section.
Key Takeaways
After completing these exercises, a few concepts became much clearer:
Pods are not automatically reachable from outside the cluster.
Services provide stable network endpoints.
AKS NodePorts are not automatically exposed to the internet.
Deployments manage ReplicaSets, which manage Pods.
Kubernetes continuously reconciles desired state.
kubectl describe is one of the most valuable troubleshooting tools.
ImagePullBackOff almost always points to an image or registry issue.
These may seem like simple exercises, but they provide the foundation for more advanced CKAD topics such as ConfigMaps, Probes, Jobs, Security Contexts, and Ingress resources.
The stronger the fundamentals, the easier the troubleshooting becomes later on.



