Kubernetes 7-Day Intensive: Day 1 — kubectl Mastery
Environment Setup (Day 0) Link to heading
Cluster: kind (Kubernetes in Docker) — 1 control-plane + 2 worker nodes, Cilium CNI.
Key kind-cluster.yaml decisions:
| Field | Value | Why |
|---|---|---|
disableDefaultCNI | true | Remove kindnet, install Cilium manually |
podSubnet | 10.244.0.0/16 | ~65k addresses for Pods |
serviceSubnet | 10.96.0.0/16 | Virtual IPs for Services, must not overlap podSubnet |
extraPortMappings | 80/443 | Map host ports into control-plane for Day 4 Ingress |
ingress-ready=true label | via kubeadmConfigPatches | Tells ingress-nginx which node to schedule on |
kubeadmConfigPatches embeds a kubeadm YAML string inside kind’s YAML (using | block scalar). Two separate schemas nested together — easy to confuse.
Cilium on kind: metrics-server needs --kubelet-insecure-tls because kind nodes use self-signed certs:
kubectl patch deployment metrics-server -n kube-system --type=json \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
Day 1: kubectl Command Reference Link to heading
Cluster Exploration Link to heading
kubectl api-resources # all resource types; shows SHORTNAMES, APIVERSION, NAMESPACED, KIND
kubectl explain deployment.spec.template.spec.containers # inline docs, drill down with dots
nodes and namespaces are NAMESPACED: false — they are cluster-scoped, not bound to any namespace. Everything else (Pod, Service, ConfigMap) is true.
Output Formats — the SELECT analogy Link to heading
All three formats pull from the same underlying JSON the API returns:
kubectl get pods -o wide # extra columns: Pod IP, node name, container runtime
kubectl get pods -o jsonpath='{.items[*].metadata.name}' # extract specific fields
kubectl get pods -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName' # custom table
Useful jsonpath paths to memorize: .metadata.name, .spec.nodeName, .status.phase, .status.podIP.
Label Filtering Link to heading
Labels are how Kubernetes loosely couples resources. Deployments find Pods via labels; Services route traffic via labels.
kubectl get pods -l app=web # single label
kubectl get pods -l app=web,env=prod # AND — both must match
kubectl get pods --show-labels # inspect what labels exist
kubectl get pods --field-selector status.phase=Running # filter by resource fields, not user labels
pod-template-hash is auto-added by Deployments. It’s a hash of the Pod template — when you roll out a new image, the hash changes, letting Kubernetes distinguish old from new Pods during rollout.
describe — First Stop for Debugging Link to heading
kubectl describe pod <pod>
The Events section at the bottom is what matters. Normal startup chain:
Scheduled → Pulling → Pulled → Created → Started
Where it stalls tells you the failure type:
- Stuck before
Scheduled→ scheduling constraint (insufficient resources, taint mismatch) - Stuck at
Pulling→ImagePullBackOff(bad image name, no registry access) - Crashes after
Started→CrashLoopBackOff(app crashing — checklogs --previous)
Other fields worth noting:
Controlled By: ReplicaSet/xxx— manually deleting a Pod just triggers RS to recreate itQoS Class: BestEffort— norequests/limitsset; first to be evicted under pressureIP— assigned by Cilium frompodSubnet
Logs Link to heading
kubectl logs <pod> -c <container> # required when Pod has multiple containers
kubectl logs <pod> --previous # critical for CrashLoopBackOff — current container's log is empty after restart
kubectl logs <pod> -f # tail -f equivalent
exec — Enter a Container Link to heading
kubectl exec -it <pod> -- sh
-i keeps stdin open; -t allocates a TTY. The -- separator tells kubectl everything after it is the in-container command.
Production images strip out tools (ps, curl, etc.) to minimize attack surface. Use netshoot instead.
port-forward — Local Debug Tunnel Link to heading
kubectl expose deployment web --port=80 --name=web # create a ClusterIP Service
kubectl port-forward svc/web 8080:80 # local:8080 → Service:80
Important: port-forward bypasses Service load balancing. It’s a direct tunnel from your machine to one Pod, managed by the kubectl process. Ctrl+C kills it. Dev-only.
Modifying Resources Link to heading
kubectl edit deploy web # opens editor; changes applied on save; not tracked in Git
kubectl patch deploy web --type=json \
-p='[{"op":"replace","path":"/spec/replicas","value":3}]'
--type=json uses RFC 6902 JSON Patch: op (add/replace/remove), path (slash-separated field path), value.
Production rule: never use edit — use apply -f so changes live in Git.
Declarative apply workflow Link to heading
kubectl diff -f deploy.yaml # see what would change (like git diff)
kubectl apply -f deploy.yaml --dry-run=server # API Server validates without committing
kubectl apply -f deploy.yaml # apply for real
--dry-run=server is more accurate than client — it actually hits the API Server and catches schema/permission errors that client-side validation misses.
debug — Network Troubleshooting Link to heading
kubectl debug <pod> -it --image=nicolaka/netshoot
Injects a sidecar container into an existing Pod sharing its network namespace. From inside:
curl localhost # reaches other containers in the same Pod (shared network ns)
ip addr # shows Pod's eth0 — note eth0@ifXX is one end of a veth pair
dig web.default.svc.cluster.local # DNS resolution (Day 3)
tcpdump -i eth0 # packet capture on the Pod's interface (Day 5)
The debug container is ephemeral — cleaned up on exit, no impact on the original container.
The eth0@ifXX notation means this interface is one end of a veth pair; the other end (ifXX) lives on the node. Cilium creates these pairs to connect each Pod’s network namespace to the node — deep dive in Day 4 (CNI internals).
top — Resource Usage Link to heading
kubectl top nodes
kubectl top pods
Units: m = millicores (1000m = 1 CPU core). Data is point-in-time from metrics-server, not averaged. For production monitoring use Prometheus + Grafana.
Mental Models from Day 1 Link to heading
Everything is a REST resource. kubectl is just an HTTP client. kubectl get pods = GET /api/v1/namespaces/default/pods. Understanding this makes api-resources, explain, and jsonpath feel natural.
Labels are the glue. Kubernetes has no direct object references. Deployments “own” Pods via label selectors. Services route to Pods via label selectors. NetworkPolicies apply via label selectors. Loose coupling by design.
Three-layer ownership: Deployment → ReplicaSet → Pod. A Deployment manages ReplicaSets (one per rollout version). A ReplicaSet ensures N identical Pods exist. Deleting a Pod manually just triggers the RS to create a replacement. To actually reduce count, change spec.replicas.
control-plane has a taint. Ordinary workloads don’t schedule there by default — it’s reserved for system components. The taint mechanism (Day 2+) is how Kubernetes enforces this.
Day 2 Preview Link to heading
Workloads + Configuration Management:
- Deployment rolling update mechanics (maxSurge, maxUnavailable)
- StatefulSet / DaemonSet / Job / CronJob — when to use each
- Probe trio: liveness / readiness / startup (readiness directly controls traffic routing)
- requests/limits and QoS classes (BestEffort → Burstable → Guaranteed)
- ConfigMap + Secret injection (env vars vs volume mounts)