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:

FieldValueWhy
disableDefaultCNItrueRemove kindnet, install Cilium manually
podSubnet10.244.0.0/16~65k addresses for Pods
serviceSubnet10.96.0.0/16Virtual IPs for Services, must not overlap podSubnet
extraPortMappings80/443Map host ports into control-plane for Day 4 Ingress
ingress-ready=true labelvia kubeadmConfigPatchesTells 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 PullingImagePullBackOff (bad image name, no registry access)
  • Crashes after StartedCrashLoopBackOff (app crashing — check logs --previous)

Other fields worth noting:

  • Controlled By: ReplicaSet/xxx — manually deleting a Pod just triggers RS to recreate it
  • QoS Class: BestEffort — no requests/limits set; first to be evicted under pressure
  • IP — assigned by Cilium from podSubnet

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)