Kubernetes Course
kubectl Introduction
kubectl is the tool you will use every single day as a Kubernetes engineer. You have already been using it throughout this course — now we go deep. The full command structure, every essential flag, output formats, switching between clusters, and the shortcuts that save experienced engineers hours every week. Run every command in this lesson. Do not just read.
Use Play with Kubernetes at labs.play-with-k8s.com or run minikube start. Every code block in this lesson is meant to be run. Seeing the output yourself is what makes it stick.
What kubectl Actually Is
kubectl (pronounced "kube-control") is a command-line tool that talks to your Kubernetes cluster's API Server. Every command you type becomes an HTTP request. When you type kubectl get pods, kubectl makes a GET request to the API Server and formats the JSON response into a readable table.
Anything kubectl can do, you could do with a plain curl command. kubectl is just a very convenient wrapper.
The Command Structure
Once you understand the structure, you can figure out almost any kubectl command without looking it up.
The get Command — Your Most-Used Command
The scenario: It is your first day on a new team. You need to quickly understand what is running in the cluster. These commands give you that picture in seconds.
# List all Pods in the current namespace
kubectl get pods
# List all Pods across every namespace
kubectl get pods -A
# List Pods with extra info — IP address and which node they are on
kubectl get pods -o wide
# Watch for live changes — updates every time something changes
kubectl get pods -w
# Filter Pods by label
kubectl get pods -l app=payment-api
# Get multiple resource types at once
kubectl get pods,services,deployments
# Get everything in the current namespace
kubectl get all
NAME READY STATUS RESTARTS AGE payment-api-7d9f8c6b4-x2p9k 1/1 Running 0 8m payment-api-7d9f8c6b4-mn7ql 1/1 Running 0 8m auth-service-6c5f8d9b-k8sj2 1/1 Running 2 2d NAME READY STATUS RESTARTS AGE IP NODE payment-api-7d9f8c6b4-x2p9k 1/1 Running 0 8m 10.244.0.5 worker-01 payment-api-7d9f8c6b4-mn7ql 1/1 Running 0 8m 10.244.0.6 worker-02
RESTARTS: 2 on the auth-service — that Pod has crashed and restarted twice. Worth investigating with kubectl describe and kubectl logs --previous.
-o wide adds IP and NODE columns — useful when a specific node has problems and you need to see which Pods landed there.
Output Formats — Seeing More Than the Default Table
The scenario: You need the full YAML of a running object — to understand exactly how it is configured, debug an issue, or use it as a template for a new object.
# Full YAML of a running object — includes status Kubernetes wrote
kubectl get pod payment-api-7d9f8c6b4-x2p9k -o yaml
# Extract just the Pod IP with jsonpath — great for scripting
kubectl get pod payment-api-7d9f8c6b4-x2p9k -o jsonpath='{.status.podIP}'
# Get the container image name
kubectl get pod payment-api-7d9f8c6b4-x2p9k -o jsonpath='{.spec.containers[0].image}'
# List just names — useful for piping into other commands
kubectl get pods -o name
# Build a custom table showing exactly the columns you want
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,IP:.status.podIP
10.244.0.5 nginx:1.25 pod/payment-api-7d9f8c6b4-x2p9k pod/payment-api-7d9f8c6b4-mn7ql NAME STATUS IP payment-api-7d9f8c6b4-x2p9k Running 10.244.0.5 payment-api-7d9f8c6b4-mn7ql Running 10.244.0.6
-o yaml — the most useful debugging format. You see the full object as Kubernetes knows it, including the status it wrote. Great for seeing exactly why a Pod is in a certain state.
-o jsonpath — powerful for scripting. Extract exactly the field you need. Used constantly in CI/CD pipelines to get an image tag, an IP address, or a status field without parsing everything.
Namespaces — Staying in the Right Place
The scenario: Your company runs dev, staging, and production in the same cluster. You need to check what is happening in production without switching your default context.
# See all namespaces
kubectl get namespaces
# Run any command in a specific namespace with -n
kubectl get pods -n production
kubectl get pods -n kube-system
# Run across ALL namespaces
kubectl get pods -A
# Create a namespace
kubectl create namespace staging
# Set default namespace for your session
kubectl config set-context --current --namespace=production
# Switch back to default
kubectl config set-context --current --namespace=default
# Always check which namespace you are in before any destructive command
kubectl config view --minify | grep namespace
NAME STATUS AGE
default Active 30d
kube-system Active 30d
production Active 14d
staging Active 14d
namespace/staging created
namespace: default
Running a command in the wrong namespace. You think you are deleting a test Pod in staging but you are actually in production. Before any destructive command, always confirm which namespace you are in. Many engineers install kubens (part of the kubectx package) which shows your current namespace in the terminal prompt at all times — highly recommended.
Contexts — Switching Between Clusters
In real life you will have multiple clusters — local Minikube, a dev cluster, a production cluster. A context is a named combination of cluster, user and namespace stored in your kubectl config file. Switching context switches which cluster your commands go to.
# See all available contexts
kubectl config get-contexts
# Which context are you currently using?
kubectl config current-context
# Switch to a different cluster
kubectl config use-context production-gke
# Your config file lives at ~/.kube/config
# Cloud CLIs add new contexts here automatically:
# aws eks update-kubeconfig --name my-cluster --region us-east-1
# gcloud container clusters get-credentials my-cluster --zone us-central1-a
CURRENT NAME CLUSTER NAMESPACE
dev-eks dev-eks default
* minikube minikube default
production-gke production-gke production
minikube
Switched to context "production-gke".
Every kubectl command you run goes to the cluster marked with *. Switching is instant. The kubeconfig file stores all credentials — when you configure a new EKS or GKE cluster, that cloud CLI automatically adds a new context to this file.
apply vs create — Which One to Use
Two commands create objects from YAML. They work differently in one important way.
# kubectl apply — CREATE if not exists, UPDATE if it does exist
# Use this almost always — it is the declarative approach
kubectl apply -f payment-deployment.yaml
# Apply everything in a directory
kubectl apply -f ./k8s/
# kubectl create — only CREATES. Errors if the object already exists
# Good for one-time setup like namespaces
kubectl create namespace test-ns
# Create objects from the command line without a YAML file
kubectl create deployment test-app --image=nginx:1.25 --replicas=2
# THE MOST USEFUL TRICK — generate YAML without applying anything
# Use this to bootstrap your YAML files instead of writing from scratch
kubectl create deployment test-app --image=nginx:1.25 --dry-run=client -o yaml
# Save it directly to a file, then edit and apply
kubectl create deployment test-app --image=nginx:1.25 --dry-run=client -o yaml > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: test-app
name: test-app
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- image: nginx:1.25
name: nginx
resources: {}
Run kubectl create ... --dry-run=client -o yaml, redirect the output to a file, edit it to add resource limits, env variables, or labels. This is faster than writing YAML from scratch and the structure is always correct. Every experienced Kubernetes engineer does this.
describe — Your Best Debugging Command
The scenario: A Pod is stuck in Pending. A Deployment is not rolling out. In every one of these situations, kubectl describe is your first move. It shows the Events section that tells you exactly what happened.
# Describe a Pod — always read the Events section at the very bottom
kubectl describe pod payment-api-7d9f8c6b4-x2p9k
# Describe a Deployment
kubectl describe deployment payment-api
# Describe a Service — shows Endpoints (real Pod IPs behind it)
kubectl describe service payment-service
# Describe a Node — shows conditions, capacity, running Pods, events
kubectl describe node worker-node-01
Name: payment-api-7d9f8c6b4-x2p9k
Namespace: default
Node: worker-node-01/10.0.0.4
Status: Running
IP: 10.244.0.5
Containers:
payment-api:
Image: nginx:1.25
State: Running
Ready: True
Restart Count: 0
Events:
Type Reason Age Message
---- ------ --- -------
Normal Scheduled 10m Assigned to worker-node-01
Normal Pulling 10m Pulling image "nginx:1.25"
Normal Started 9m Started container payment-api
Warning BackOff 2m Back-off restarting failed container
Warning events mean something went wrong. Normal events are informational. When a Pod is stuck in Pending, the Events section explains exactly why — "0/3 nodes available: insufficient memory" or "no matching nodeSelector." The answer is almost always right there in plain English.
logs — Seeing What Your App Is Printing
The scenario: Users are reporting payment failures. You need to see what the payment API is printing to understand what is going wrong.
# Get logs from a Pod
kubectl logs payment-api-7d9f8c6b4-x2p9k
# Stream logs live — keep watching as new lines appear
kubectl logs payment-api-7d9f8c6b4-x2p9k -f
# Only the last 50 lines
kubectl logs payment-api-7d9f8c6b4-x2p9k --tail=50
# Logs from the last 1 hour
kubectl logs payment-api-7d9f8c6b4-x2p9k --since=1h
# THE MOST IMPORTANT ONE — logs from the PREVIOUS crashed container
# When a Pod is in CrashLoopBackOff, this shows you why it crashed
kubectl logs payment-api-7d9f8c6b4-x2p9k --previous
# Get logs from ALL Pods matching a label at once — no need to check each one
kubectl logs -l app=payment-api --all-containers=true
2026/03/16 09:14:30 nginx/1.25.0 started 10.244.0.1 - - [16/Mar/2026] "GET /health HTTP/1.1" 200 0 10.244.1.3 - - [16/Mar/2026] "POST /payment HTTP/1.1" 500 142 ERROR: Database connection refused at 10.244.2.8:5432
The logs show the problem immediately: Database connection refused. Now you know exactly where to look next — is the database Pod running? Does its Service exist?
kubectl logs -l app=payment-api streams logs from all matching Pods at once — far faster than checking each Pod individually when you have a scaled Deployment.
exec — Getting Inside a Running Container
The scenario: You want to check from inside the payment container whether it can reach the database. Running the check from your laptop will not work — networking inside the cluster is different.
# Open a bash terminal inside a running container
kubectl exec -it payment-api-7d9f8c6b4-x2p9k -- /bin/bash
# If bash is not available, use sh
kubectl exec -it payment-api-7d9f8c6b4-x2p9k -- /bin/sh
# Run a single command without opening a full terminal
kubectl exec payment-api-7d9f8c6b4-x2p9k -- env
kubectl exec payment-api-7d9f8c6b4-x2p9k -- cat /etc/nginx/nginx.conf
# In a multi-container Pod, specify which container with -c
kubectl exec -it payment-api-7d9f8c6b4-x2p9k -c payment-api -- /bin/bash
root@payment-api-7d9f8c6b4-x2p9k:/# env HOSTNAME=payment-api-7d9f8c6b4-x2p9k PAYMENT_SERVICE_SERVICE_HOST=10.96.142.87 DB_URL=postgresql://postgres-service:5432/payments KUBERNETES_SERVICE_HOST=10.96.0.1 root@payment-api-7d9f8c6b4-x2p9k:/# exit
Running env shows all environment variables — including auto-injected Service host variables Kubernetes adds for every Service in the namespace. Quick way to confirm your container is receiving the config you expect.
port-forward — Testing Without Exposing
The scenario: You want to test a ClusterIP Service from your laptop without creating a LoadBalancer or NodePort. port-forward creates a secure tunnel.
# Forward local port 8080 to port 80 on a Service
kubectl port-forward service/payment-service 8080:80
# Forward to a specific Pod
kubectl port-forward pod/payment-api-7d9f8c6b4-x2p9k 8080:80
# Test it from another terminal while it is running
curl http://localhost:8080
Forwarding from 127.0.0.1:8080 -> 80 Handling connection for 8080 <!DOCTYPE html> <html><head><title>Welcome to nginx!</title></head> <body><h1>Welcome to nginx!</h1></body></html>
Brilliant for local development testing — hit an internal database UI, test a ClusterIP API, check a monitoring dashboard — all without changing any Service types or creating security risks. The tunnel only exists while the command is running.
The Complete kubectl Cheat Sheet
| Command | What it does |
|---|---|
| Reading and Inspecting | |
| kubectl get <resource> | List resources as a table |
| kubectl get <resource> -o wide | Table with extra columns — IPs, nodes |
| kubectl get <resource> -o yaml | Full YAML including Kubernetes-written status |
| kubectl get <resource> -w | Watch live — streams updates as they happen |
| kubectl describe <resource> <name> | Human-readable details and Events section |
| kubectl logs <pod> -f | Stream container logs live |
| kubectl logs <pod> --previous | Logs from the last crashed container instance |
| Creating and Changing | |
| kubectl apply -f file.yaml | Create or update from YAML — use this always |
| kubectl create ... --dry-run=client -o yaml | Generate YAML template without creating anything |
| kubectl edit <resource> <name> | Open live object in editor — applies on save |
| kubectl scale deployment <name> --replicas=5 | Change replica count immediately |
| kubectl delete <resource> <name> | Delete an object |
| Debugging | |
| kubectl exec -it <pod> -- /bin/bash | Open terminal inside container |
| kubectl port-forward svc/<name> 8080:80 | Tunnel a Service port to your laptop |
| kubectl run debug --image=busybox -it --rm -- sh | Temporary debug Pod — auto-deletes on exit |
| kubectl top pods | Live CPU and memory usage per Pod |
| Cluster and Config | |
| kubectl config get-contexts | List all clusters you can connect to |
| kubectl config use-context <name> | Switch to a different cluster |
| kubectl api-resources | List every resource type Kubernetes knows about |
Day to day, Kubernetes work comes down to three commands over and over: kubectl apply to deploy, kubectl get to check what is running, and kubectl describe or kubectl logs to debug when things go wrong. Everything else is situational.
The best way to get fast with kubectl is muscle memory from using it every day. Run every command in this lesson on your own cluster right now.
kubectl create deployment nginx-test --image=nginx:1.25 --replicas=3 --dry-run=client -o yaml. Save the output to a file, add resource requests and limits, then apply it.kubectl create deployment broken --image=nginx:this-does-not-exist. Diagnose it using only kubectl get pods and kubectl describe. Fix it with kubectl set image deployment/broken nginx=nginx:1.25.kubectl port-forward svc/nginx-test 8080:80 and open http://localhost:8080 in your browser to confirm the full chain works.Practice Questions
Type from memory.
1. A Pod is in CrashLoopBackOff. You want to see the logs from the previous crashed instance — not the one that just started. What command do you run?
2. You want to generate a Deployment YAML template without creating anything in the cluster. Which two flags do you add to kubectl create?
3. You have multiple clusters configured. You want to switch kubectl to the production GKE cluster whose context is named production-gke. What command do you run?
Knowledge Check
Pick the best answer.
1. A Pod has been in Pending for 10 minutes. What is the best first command to understand why?
2. What is the key difference between kubectl apply and kubectl create?
3. You want to test a ClusterIP Service from your laptop without changing it to NodePort or LoadBalancer. What do you do?
Up Next · Lesson 14
Kubernetes YAML Basics
Every Kubernetes object lives in a YAML file. We break down the full anatomy — apiVersion, kind, metadata, spec — and you will write real YAML for multiple object types from scratch. One lesson away from your first full end-to-end deployment.