3.3 Essential Commands: get, describe, apply, delete, exec, logs
Alright, let’s get our hands dirty. Forget the theory for a moment; this is where you actually do things to your cluster. These six commands are the core of your daily kubectl life. Master them, and you’ll go from fumbling in the dark to having a firm, confident grip on your system. They are your primary interface for observing, modifying, and troubleshooting.
The get Command: Your Cluster’s Dashboard
Think of kubectl get as your ls or dir for the Kubernetes world. It’s your go-to for listing resources and getting a high-level, summarized view of what’s going on. Bored of typing kubectl get pods every five seconds? You should be. Let’s level up.
First, get works on any resource type: pods, services, deployments, nodes, secrets—you name it. The magic trick is using the -o (output) flag to stop it from being so… minimal.
# The default view. Fine, I guess, if you hate information.
kubectl get pods
# Now we're talking. Wide mode gives you the Node and IP, which is crucial.
kubectl get pods -o wide
# My personal favorite. A clean, readable, columnized view.
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName,IP:.status.podIP
# Feeling fancy? Output as JSON and pipe it to jq for some real power.
kubectl get pods -o json | jq '.items[].metadata.name'
# Watch mode. This is like `tail -f` for your cluster. Essential for watching a rollout or seeing pods come online.
kubectl get pods -w
The most common pitfall? Forgetting that get shows you what Kubernetes thinks is happening, which, thanks to its eventual consistency model, might be a few seconds behind what is actually happening. It’s a great first step, but never your only step for diagnosis.
The describe Command: The Deep Dive
When get tells you a pod is Pending and you have no idea why, you call in the heavy artillery: kubectl describe. This command gives you a sprawling, information-dense dump of everything Kubernetes knows about a specific resource—its configuration (the spec), its current state (the status), and, most importantly, a log of events related to it.
Events are the golden ticket. They are Kubernetes’s way of whispering its internal monologue to you. “I pulled the image,” “I scheduled the pod,” “I can’t mount the volume because the secret doesn’t exist.” It’s all there.
# This will give you a novel about this pod. Scroll down to the 'Events' section.
kubectl describe pod my-funky-pod
# Also works for any other resource. Why is my service not getting an IP?
kubectl describe service my-broken-service
The rough edge? The output is a wall of text. You have to learn to scan it efficiently. Your eyes should immediately go to the Status: section and then the Events: at the bottom. That’s where 90% of your answers will be hiding.
The apply Command: Declarative State FTW
This is how you change things. You don’t “create” or “update” with kubectl; you declare your desired state in a YAML file and let kubectl apply figure out how to make it happen. It’s the cornerstone of the GitOps mentality.
The -f flag is your friend. You can point it at a single file, a directory of files, or even a URL.
# The classic. Create or update everything in this file.
kubectl apply -f my-deployment.yaml
# Apply everything in a whole directory. Perfect for a project with multiple manifests.
kubectl apply -f ./my-manifests/
# Want to see what *would* happen without actually doing it? Dry runs are a gift.
kubectl apply -f my-deployment.yaml --dry-run=client
Why apply over the older create? Idempotency. You can run apply against the same file a hundred times, and if nothing has changed, nothing happens. If something has changed, it performs a patch. It’s safe, predictable, and exactly how you should operate.
The designers’ questionable choice? The kubectl.kubernetes.io/last-applied-configuration annotation it jams into your resources. It’s a blob of your entire config used for calculating diffs. It’s ugly and sometimes confusing, but necessary for the magic to work. Just accept it.
The delete Command: The Reaper
It does what it says on the tin. It deletes stuff. But it’s smarter than a simple rm.
# Delete a specific pod. Kubernetes will gracefully terminate it.
kubectl delete pod my-pod
# Delete everything defined in a manifest file. This is the perfect partner for 'apply'.
kubectl delete -f my-deployment.yaml
# Need to nuke everything of a certain type? This is powerful and terrifying.
kubectl delete all --all -n my-namespace # 'all' doesn't actually mean *all* resources, a classic gotcha.
# Sometimes, a resource is stuck in 'Terminating'. The nuclear option is to force it.
kubectl delete pod my-stuck-pod --force --grace-period=0
WARNING: There is no recycle bin. There is no “Are you sure?” (unless you use --dry-run first). A mistaken delete all --all in the wrong namespace has ended many a developer’s good day. Be paranoid. Double-check your context (kubectl config current-context).
The exec Command: Your SSH for Pods
Need to get inside a running container to poke around, check a file, or run a diagnostic tool? kubectl exec is your ticket. It’s like docker exec but for your cluster.
# Run a simple command (like 'ls') in the first container of the pod.
kubectl exec my-pod -- ls /app
# If your pod has multiple containers, you MUST specify which one.
kubectl exec my-pod -c my-specific-container -- ls /app
# The big one: Get an interactive shell. This is how you truly debug.
kubectl exec -it my-pod -- /bin/bash
# No bash? Try /bin/sh. Alpine images, I'm looking at you.
The absurd part? The mandatory -- double-dash. It’s a necessary separator to tell kubectl where your command flags end and the command inside the container begins. Forget it, and you’ll get inscrutable errors. It feels clunky, but you’ll get used to it.
The logs Command: The Truth Tells All
When your application is misbehaving and describe didn’t give you the answer, it’s time for kubectl logs. This streams the stdout/stderr from your application’s container. It is, quite simply, the most important debugging tool you have.
# Stream the logs from the first container in a pod.
kubectl logs my-pod
# Follow the logs in real-time, just like 'tail -f'.
kubectl logs -f my-pod
# If you have a multi-container pod, specify which one.
kubectl logs my-pod -c my-app-container
# Want to see the logs from a pod that already crashed and burned? Essential.
kubectl logs -p my-previously-crashed-pod
The massive, glaring pitfall? By default, Kubernetes does not manage your logs for you. If you don’t set up a cluster-wide logging solution (like Loki or Elasticsearch), those logs are stored only on the node where the pod ran. If the pod dies, the node is drained, or the cluster autoscaler removes the node, those logs are gone forever. Relying solely on kubectl logs is a recipe for disaster. It’s a fantastic tool for live debugging, but never your long-term storage strategy.