kubectl is your primary interface to Kubernetes. In exams, speed with kubectl is the difference between finishing or not. In production, it's your debugging swiss-army knife. This lesson covers the techniques that make you fast and precise.

1. Imperative vs Declarative — Two Worlds

kubectl supports both approaches. Know when to use each:

Imperative Commands (do it now)

# Create resources directly:
kubectl run nginx --image=nginx
kubectl create deployment web --image=nginx --replicas=3
kubectl expose deployment web --port=80 --type=ClusterIP
kubectl scale deployment web --replicas=5

# Modify in place:
kubectl set image deployment/web nginx=nginx:1.25
kubectl label pod nginx-abc app=web
kubectl annotate deployment web owner=team-platform
kubectl taint node worker-1 dedicated=gpu:NoSchedule

Declarative Management (apply the desired state)

# Apply a manifest (create or update):
kubectl apply -f deployment.yaml
kubectl apply -f ./manifests/       # whole directory
kubectl apply -k ./overlays/prod/   # kustomize

# Delete what's declared:
kubectl delete -f deployment.yaml

When to Use Each

ApproachBest ForDrawbacks
ImperativeCKA/CKAD exams (speed), quick debugging, one-off tasksNot reproducible, no Git history, drift
DeclarativeProduction, GitOps, team workflowsSlower for quick experiments
In CKA/CKAD exams, use imperative commands to create the skeleton, then edit the YAML if you need to add fields that imperative doesn't support. This is the fastest workflow: kubectl create deployment web --image=nginx --dry-run=client -o yaml > dep.yaml → edit → kubectl apply -f dep.yaml.

create vs apply

kubectl createkubectl apply
If exists❌ Error✅ Updates it
If not exists✅ Creates✅ Creates
Tracks changesNoYes (via last-applied-configuration annotation)
Merge strategyN/A (full replacement)Strategic merge patch
apply uses three-way merge: It compares (1) the new manifest, (2) the last-applied-configuration annotation, and (3) the live object. This lets it detect fields that were removed from your manifest and delete them from the live object — something a simple patch can't do.

2. Contexts & kubeconfig

kubectl needs to know: which cluster, which user, and which namespace. This is configured in ~/.kube/config (or via $KUBECONFIG).

kubeconfig Structure

apiVersion: v1
kind: Config
clusters:               # ← WHERE to connect
- name: production
  cluster:
    server: https://k8s.prod.company.com:6443
    certificate-authority-data: LS0t...
- name: staging
  cluster:
    server: https://k8s.stg.company.com:6443

users:                  # ← WHO you are
- name: admin
  user:
    client-certificate-data: LS0t...
    client-key-data: LS0t...
- name: developer
  user:
    token: eyJhbG...

contexts:               # ← Combines cluster + user + namespace
- name: prod-admin
  context:
    cluster: production
    user: admin
    namespace: default
- name: stg-dev
  context:
    cluster: staging
    user: developer
    namespace: app-team

current-context: prod-admin   # ← Active context

Context Commands

# See all contexts:
kubectl config get-contexts

# Switch context:
kubectl config use-context stg-dev

# Set default namespace for current context:
kubectl config set-context --current --namespace=kube-system

# View current context:
kubectl config current-context

# Quick temporary override (doesn't change config):
kubectl get pods --context=prod-admin -n monitoring
CKA/CKAD exams have multiple clusters. Each question tells you which context to use. Run kubectl config use-context <name> at the start of each question. Forgetting this = answering against the wrong cluster.
Use tools like kubectx and kubens for fast switching. In production, protect yourself: set PS1 to show current context/namespace in your shell prompt. Accidentally running kubectl delete against prod instead of staging is a real disaster pattern.

3. Output Formats — Getting the Data You Need

The -o Flag

FormatUse CaseExample
-o wideMore columns (node, IP)kubectl get pods -o wide
-o yamlFull object as YAMLkubectl get deploy nginx -o yaml
-o jsonFull object as JSONkubectl get pod nginx -o json
-o nameJust resource nameskubectl get pods -o namepod/nginx
-o jsonpathExtract specific fieldsSee below
-o custom-columnsCustom table formatSee below

JSONPath — Surgical Data Extraction

# Get the IP of a Pod:
kubectl get pod nginx -o jsonpath='{.status.podIP}'

# Get all node names:
kubectl get nodes -o jsonpath='{.items[*].metadata.name}'

# Get image of first container:
kubectl get pod nginx -o jsonpath='{.spec.containers[0].image}'

# Multiple fields with formatting:
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.addresses[0].address}{"\n"}{end}'

# Conditional (nodes that are Ready):
kubectl get nodes -o jsonpath='{.items[?(@.status.conditions[?(@.type=="Ready")].status=="True")].metadata.name}'

Custom Columns

# Readable table with chosen fields:
kubectl get pods -o custom-columns=\
NAME:.metadata.name,\
NODE:.spec.nodeName,\
STATUS:.status.phase,\
IP:.status.podIP

# Output:
# NAME          NODE       STATUS    IP
# nginx-abc     worker-1   Running   10.244.1.5
# redis-xyz     worker-2   Running   10.244.2.3
JSONPath is tested directly in CKA exams (e.g., "write the internal IPs of all nodes to a file"). Practice: {.items[*].status.addresses[?(@.type=="InternalIP")].address}. Tip: pipe to tr ' ' '\n' to get one per line.

4. dry-run, diff, and explain

--dry-run: Generate Without Applying

Two modes:

FlagWhat It DoesUse Case
--dry-run=clientGenerates YAML locally, no server contactScaffold manifests fast (exam workflow)
--dry-run=serverSends to API server for full validation, but doesn't persistValidate that admission webhooks and RBAC allow it
# The exam power move — generate YAML skeleton:
kubectl create deployment web --image=nginx --replicas=3 \
  --dry-run=client -o yaml > deploy.yaml

# Generate a Service:
kubectl expose deployment web --port=80 --target-port=8080 \
  --dry-run=client -o yaml > svc.yaml

# Generate a Pod with resource requests:
kubectl run nginx --image=nginx \
  --dry-run=client -o yaml -- /bin/sh -c "sleep 3600" > pod.yaml

# Validate against server (catches invalid fields, denied by webhooks):
kubectl apply -f deploy.yaml --dry-run=server
This is the #1 speed technique for CKA/CKAD: kubectl create ... --dry-run=client -o yaml > file.yaml. Edit the file to add what you need, then kubectl apply -f file.yaml. Never write YAML from scratch in an exam.

kubectl diff: Preview Changes

# See what would change before applying:
kubectl diff -f deploy.yaml

# Output looks like unified diff:
# -  replicas: 3
# +  replicas: 5

This is invaluable in production — review changes before they go live, especially for GitOps workflows.

kubectl explain: Built-in Documentation

# What fields does a Deployment spec have?
kubectl explain deployment.spec

# Go deeper:
kubectl explain deployment.spec.strategy
kubectl explain deployment.spec.strategy.rollingUpdate

# See the full tree:
kubectl explain pod.spec --recursive | grep -i volume

# Check what apiVersion to use:
kubectl explain deployment | head -5
# KIND:     Deployment
# VERSION:  apps/v1
kubectl explain is available during exams and is faster than searching docs. Use it whenever you can't remember a field name: kubectl explain pod.spec.containers.livenessProbe.

5. Essential Commands Reference

Inspection

# Describe — human-readable details + events:
kubectl describe pod nginx
kubectl describe node worker-1

# Logs:
kubectl logs nginx                     # current logs
kubectl logs nginx -c sidecar          # specific container
kubectl logs nginx --previous          # previous (crashed) container
kubectl logs -l app=web --all-containers  # all pods matching label
kubectl logs nginx -f                  # stream (follow)
kubectl logs nginx --since=5m          # last 5 minutes
kubectl logs nginx --tail=100          # last 100 lines

# Events (cluster-wide, sorted by time):
kubectl get events --sort-by='.lastTimestamp'
kubectl get events -n kube-system --field-selector reason=FailedScheduling

Editing Live Objects

# Open in $EDITOR, saves on close:
kubectl edit deployment web

# Patch (strategic merge):
kubectl patch deployment web -p '{"spec":{"replicas":5}}'

# JSON patch (more precise):
kubectl patch deployment web --type=json \
  -p '[{"op":"replace","path":"/spec/replicas","value":5}]'

# Replace (full object overwrite, needs resourceVersion):
kubectl replace -f deploy.yaml

# Force replace (delete + create — causes downtime!):
kubectl replace -f deploy.yaml --force

Deletion

# Delete by name:
kubectl delete pod nginx

# Delete by file:
kubectl delete -f deploy.yaml

# Delete by label:
kubectl delete pods -l app=test

# Force immediate deletion (skip graceful period):
kubectl delete pod nginx --grace-period=0 --force

# Delete all pods in namespace:
kubectl delete pods --all -n testing

Exec and Debug

# Execute command in a running container:
kubectl exec nginx -- ls /etc/nginx
kubectl exec -it nginx -- /bin/bash    # interactive shell

# Port forward (access a Pod locally):
kubectl port-forward pod/nginx 8080:80
kubectl port-forward svc/web 8080:80

# Copy files:
kubectl cp nginx:/etc/nginx/nginx.conf ./nginx.conf
kubectl cp ./config.yaml nginx:/tmp/config.yaml

# Debug with ephemeral container (K8s 1.23+):
kubectl debug -it nginx --image=busybox --target=nginx

# Debug a node:
kubectl debug node/worker-1 -it --image=ubuntu
kubectl exec into production pods should be rare and audited. CKS topic: audit policies can log exec events, and RBAC should restrict pods/exec to break-glass roles only.

6. Speed Tips for Exams and Daily Use

Shell Setup

# Add to ~/.bashrc for exams:
alias k=kubectl
complete -o default -F __start_kubectl k   # enable completion for alias

# Shortcuts that save seconds per question:
alias kgp='kubectl get pods'
alias kgs='kubectl get svc'
alias kgd='kubectl get deploy'
alias kdn='kubectl describe node'
alias kdp='kubectl describe pod'

# Set default namespace:
export ns="-n kube-system"
kubectl get pods $ns

Imperative Generation Cheat Sheet

NeedCommand
Podk run nginx --image=nginx --dry-run=client -o yaml
Deploymentk create deploy web --image=nginx --replicas=3 --dry-run=client -o yaml
Service (ClusterIP)k expose deploy web --port=80 --dry-run=client -o yaml
Service (NodePort)k expose deploy web --port=80 --type=NodePort --dry-run=client -o yaml
Jobk create job pi --image=perl --dry-run=client -o yaml -- perl -Mbignum=bpi -wle 'print bpi(2000)'
CronJobk create cronjob backup --image=busybox --schedule="0 2 * * *" --dry-run=client -o yaml -- /bin/sh -c "echo backup"
ConfigMapk create configmap app-cfg --from-literal=KEY=value --dry-run=client -o yaml
Secretk create secret generic db-creds --from-literal=pass=s3cr3t --dry-run=client -o yaml
ServiceAccountk create sa my-sa --dry-run=client -o yaml
Rolek create role pod-reader --verb=get,list --resource=pods --dry-run=client -o yaml
RoleBindingk create rolebinding read-pods --role=pod-reader --user=jane --dry-run=client -o yaml
Ingressk create ingress web --rule="host.com/path=svc:80" --dry-run=client -o yaml

Useful Filters

# Sort by restart count (find crashlooping pods):
kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'

# Find pods on a specific node:
kubectl get pods --field-selector spec.nodeName=worker-1

# Show pods not in Running state:
kubectl get pods --field-selector status.phase!=Running

# All resources in a namespace:
kubectl get all -n my-namespace

# Watch changes live:
kubectl get pods -w

Verbosity for Debugging API Calls

# See the actual HTTP request kubectl makes:
kubectl get pods -v=6    # shows URL
kubectl get pods -v=8    # shows request/response headers
kubectl get pods -v=9    # shows request/response bodies

# Example output at -v=6:
# GET https://k8s.example.com:6443/api/v1/namespaces/default/pods?limit=500
# 200 OK in 23ms
-v=6 is your secret weapon: It shows the exact API URL kubectl uses. When you're confused about which API path a resource uses, or why RBAC denies a request, -v=6 reveals the truth. Combine with Lesson 4 (API model) and everything clicks.

Summary: The kubectl Mental Model

TaskFastest Approach
Create something quicklyImperative: kubectl create/run
Generate YAML scaffold--dry-run=client -o yaml > file.yaml
Update existing resourcekubectl apply -f (declarative) or kubectl edit (quick fix)
Validate before applyingkubectl diff -f then kubectl apply --dry-run=server
Debug/inspectdescribe, logs, events, -v=6
Look up field syntaxkubectl explain resource.field
Extract specific data-o jsonpath='{...}'

📝 Quiz: kubectl Fluency

Q1: What's the fastest way to generate a Deployment YAML without actually creating it on the cluster?

kubectl create deployment web --image=nginx --dry-run=client -o yaml > deploy.yaml
This generates the YAML locally without touching the API server.

Q2: What's the difference between --dry-run=client and --dry-run=server?

client: Generates the object locally — no API server contact, no validation beyond basic structure.
server: Sends to the API server which runs full admission (webhooks, RBAC, validation) but doesn't persist. Use server-side to verify admission policies will accept your manifest.

Q3: You applied a manifest that added a label. Now you remove that label from the YAML and apply again. Does the label get removed from the live object?

Yes (with kubectl apply). apply uses three-way merge: it compares the new manifest, the last-applied-configuration annotation, and the live object. It detects that the label was in last-applied but is now gone, so it removes it. This wouldn't work with kubectl patch (which doesn't track removed fields).

Q4: Write the jsonpath expression to get the container image of the first container in pod "web".

kubectl get pod web -o jsonpath='{.spec.containers[0].image}'

Q5: You want to see what HTTP request kubectl makes when you run kubectl get pods. What flag do you add?

-v=6 (or higher). This shows the API URL: GET https://...server.../api/v1/namespaces/default/pods. Use -v=8 for full headers, -v=9 for request/response bodies.

Q6: You're in a CKA exam. The question says "use context k8s-prod." What's the first command you run?

kubectl config use-context k8s-prod
Always do this first. Operating against the wrong cluster is an instant score of zero for that question.